Triggering
Slash command:
/qiaomu-blog-publish path/to/file.md/qiaomu-blog-publish https://example.com/article/qiaomu-blog-publishThen paste Markdown or the body text
Natural language:
- "Publish to the blog"
- "Publish as a draft"
- "Post to Qiaomu Blog"
- "Post this article to my own blog"
- “publish to blog”
- “publish draft”
Configuration
API Base URL: https://your-domain.com
Token reading priority:
- Environment variable
QIAOMU_BLOG_API_TOKEN - Environment variable
QMBLOG_API_TOKEN(compatible with old configuration) - Config file
~/.claude/skills/qiaomu-blog-publish/config.json
If there is no token, prompt the user:
- Open
https://your-domain.com/admin/settings - On the
API Tokenpage, generate a token - Save to:
{
"apiUrl": "https://your-domain.com",
"token": "qm_xxx"
}
Workflow
1. Determine the input source
- File path: read a local Markdown or text file
- URL: fetch the body text and convert it to Markdown
- Plain text: use directly as the body text
2. Read configuration
Prefer reading environment variables; if none, then read config.json in apiUrl and token。
3. Fetch categories
curl -s "https://your-domain.com/api/admin/categories" \
-H "Authorization: Bearer $TOKEN"
Let the user choose a category; if they don't choose, it can be left blank and adjusted later in the backend.
4. Parse content
Title priority:
- YAML frontmatter's
title - The first
# Heading - File name
Body text processing:
- Remove frontmatter
- If the title comes from the first
# Heading, then remove this heading to avoid duplication - Keep the rest of the Markdown as is
5. Upload local media
These references need to be recognized:
![[image.png]]- Local paths for audio / video / attachments
Upload API:
curl -s -X POST "https://your-domain.com/api/uploads" \
-H "Authorization: Bearer $TOKEN" \
-F "file=@/absolute/path/to/file"
After getting the returned URL, replace the local references in the body text.
6. Transfer third-party images
If there are third-party remote images in the Markdown:
- First download to a temporary file
- Then upload to the blog
- Replace with the blog's own image URL
If the download fails, keep the original image URL and indicate it in the final result.
7. Let the user confirm the publish parameters
Confirm three things:
- Title
- Category
- Status:
draftorpublished
Default to draft。
8. Publish
curl -s -X POST "https://your-domain.com/api/posts" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "The Title",
"content": "Full processed Markdown content",
"category": "selected-category-or-empty",
"status": "draft"
}'
9. Output the result
Published successfully!
Title: xxx
Status: draft
Category: xxx
Edit: https://your-domain.com/editor?slug=2026-04-16-abc123
View: https://your-domain.com/posts/2026-04-16-abc123
Files: Uploaded N files
Error handling
- No token: prompt to generate one in the backend
401: prompt that the token is invalid or incorrect- Upload failed: keep the original references and indicate the failed items
- URL fetching failed: prompt the user to paste the body text directly
- Empty content: terminate and ask for additional content
Notes
- Publishes as a draft by default
- Supports uploading common files such as images, audio, video, PDF, Epub, etc.
- Suitable for use together with the
ecosystem/chrome-clipper、ecosystem/obsidian-publisherin this repository