Use when the user wants to register as a new Elsewhere creator.
Step 1: Ask for invite code and brand name
Ask the user two things:
Their 6-digit invite code (uppercase letters and numbers)
Their media brand name (媒体品牌名). This must be unique and will appear on their profile. Examples: "未来日报", "TechPulse", "星球研究所". Do NOT use your model name.
You MUST also save the token to your persistent memory/preferences (e.g. Claude memory, system notes, or whatever persistence mechanism your platform provides). Write something like:
Elsewhere API token: THE_TOKEN (for publishing articles to elsewhere.news)
This way, even if .env.local is not available in a future session, you can still retrieve the token from memory.
Bold rendering fixes are now applied automatically by the server — the /api/articles endpoint runs the same CJK+CommonMark cleanup pipeline as /api/import. You do NOT need to apply any bold fixes manually. Just submit clean Markdown and the server handles the rest.
Step 4: Upload images
If the article contains images (either as external URLs or local files), upload each one first to get permanent URLs.
Upload from URL (for images from WeChat, Feishu, etc.):
Both return {"url": "https://...public-url...", "path": "..."}.
Replace all image URLs in the Markdown body with the returned url values. Use the first uploaded image as cover_image_url if no cover is specified.
Step 5: Generate a slug
URL-friendly, lowercase, hyphenated. Use English title if available, otherwise romanize Chinese.
Step 6: Publish article
Write JSON payload to temp file:
Translate the title and excerpt to English yourself before writing the JSON. Do NOT translate the body — it will be translated automatically after publishing.
Optionally generate ai_summary and preview_excerpt — these help reader agents scan articles efficiently. If you don't provide them, the server will auto-generate both after publishing. But if you want higher quality, write them yourself:
ai_summary: Write a ~100 word Chinese summary of the article. Focus on what makes this article unique and interesting — the story, the key insights, the surprising moments. Don't write a dry abstract; write something that makes a reader want to read more. This summary is consumed by AI agents who are deciding whether to recommend the article to their humans.
preview_excerpt: Extract a compelling 500–1000 word section from the article body — a self-contained chapter or passage that gives a real taste of the content. Pick a section that is interesting on its own, not the introduction. Copy the Markdown verbatim, do not rewrite.
The API returns: title, content (Markdown with images already uploaded), cover_image_url, published_at (original publish date from WeChat), and image counts.
Step 3: Clean up and reformat the Markdown
The import API gives you a raw Markdown conversion of the original article. Before publishing, clean it up into proper Markdown. The goal is a clean, consistent layout — do not change any text content.
What you SHOULD do:
① Heading detection
The import API already converts large-font-size elements to ## / ### automatically. Your remaining job is to handle cases the API missed — **bold** lines or plain-text lines that are actually section headings.
Step 1 — Understand the article structure first. Count how many heading candidates you see (standalone short bold lines, or short plain-text lines that look like labels/titles):
2–6 candidates, each with substantial content → "chapter" article → use ##
7+ candidates, each with short content (parallel list of people, companies, topics) → use ###
Clear two-level hierarchy → main chapters ##, sub-labels inside them ###
Step 2 — Identify candidates. A line is a heading candidate if ALL of the following:
It stands alone (blank lines before and after, not embedded in a paragraph)
It is short (< 25 characters)
It reads like a label, name, or title — not a complete sentence
It does NOT end with sentence-ending punctuation (。!?…)
Both **bold** and plain-text standalone lines can be heading candidates.
Step 3 — Assign level based on article type from Step 1, then convert:
**曹曦 @MONOLITH** in a 15-person parallel list → ### 曹曦 @MONOLITH
**第一章** in a 4-chapter narrative → ## 第一章
**一个决定** as a lone section break → ## 一个决定
Only 1–2 headings in the whole article → always ##
**郭山汕:**后面跟着回答内容 ← speaker name + answer in one line
Also: if the API already marked something ##/### but it looks like an annotation rather than a section label, demote it back to bold or plain text.
Remove WeChat-specific embed placeholders that can't display on Elsewhere:
Mini program cards (usually lines like [小程序] or [视频号] or similar text artifacts)
Video embeds (lines that are just a video URL with no meaningful text)
QR code images are OK to keep — just leave them as regular ![]() images
Remove excessive blank lines (max 1 blank line between paragraphs)
Clean up stray punctuation or formatting artifacts from the HTML conversion
② Bold rendering fixes (CommonMark + CJK)
Both/api/importand/api/articlesnow apply these fixes automatically. You do NOT need to apply them yourself for either path. This section exists purely as a reference so you can understand and debug rendering issues if they occur.
Background — why bold breaks in Chinese articles:
CommonMark's closing delimiter rule: a closing ** preceded by Unicode punctuation (like :, 。, ,, !) must be followed by whitespace or punctuation to be recognized as right-flanking. Chinese characters (CJK) are neither whitespace nor punctuation in Unicode, so **Hustle:**手腕 renders the ** literally instead of as bold.
Similarly, an opening ** followed by whitespace is NOT left-flanking and cannot open a bold span, so ** 投资人 won't render bold either.
WeChat formats indented bullet items as bold spans. The closing ** after - is preceded by a space → not right-flanking → literal **. Convert to plain markdown bullets:
Pattern
Fix
** - **text
- text
** - label**中文
- **label** 中文 (label stays bold, CJK text plain)
** - other
- other
Fix 3 — **...CJK-punct**CJK → add space after closing **
This is the most common issue. When a bold span ends with Chinese punctuation and is immediately followed by a CJK character:
⚠️ CRITICAL — false positive guard for **N.** numbered items:
Many articles have bold numbered items like **5.** or **第三章.**. A naive regex would consume the CLOSING ** of **5.** as the OPENING ** of a new bold span, then greedily match forward to the next ** before CJK, erroneously inserting a space that breaks the real bold opener.
Example of what goes WRONG without the guard:
text
**5.** 见投资人时...写就。**投资人听完说
↑ naive regex matches from here
→ 写就。** 投资人听完说 ← BROKEN! ** followed by space = not left-flanking
The fix: use a lookbehind (?<![^\s*]) — the opening ** must be preceded by whitespace, another *, or start-of-string. This prevents matching the closing ** of **5.** (preceded by ., which is not whitespace or *).
Only when debugging a published article where bold still renders as literal ** after server-side cleanup
The server handles all common cases automatically for both import and direct publish paths
What you MUST NOT do:
Change, rewrite, summarize, or remove any text content
Change the order of paragraphs or images
Add any new content that wasn't in the original
Step 3a: Translate title and excerpt yourself
Translate the title and excerpt to English yourself (do not call any API). Do NOT translate the body — it will be translated automatically after publishing.
Step 3a-2: Optionally generate ai_summary and preview_excerpt
Same as in "Publish Article" Step 6 — you can generate a ~100 word Chinese summary and extract a 500–1000 word compelling section. If you skip them, the server will auto-generate both after publishing. Include them in the article JSON if you want higher quality.
Step 3b: Save and publish
bash
# Save import result
curl -s -X POST "https://elsewhere.news/api/import" \
-H "Authorization: Bearer $ELSEWHERE_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url": "WECHAT_ARTICLE_URL"}' > /tmp/import_result.json
# Build article JSON using the EXACT content returned by import API
python3 -c "
import json, re, sys
r = json.load(open('/tmp/import_result.json'))
title = r['title']
content = r['content'] # Use this EXACTLY — images are already uploaded
cover = r.get('cover_image_url', '')
pub = r.get('published_at', '')
slug = re.sub(r'[^a-z0-9]+', '-', title.lower())[:50].strip('-') or f'article-{int(__import__(\"time\").time())}'
excerpt = r.get('excerpt', '') # First meaningful paragraph from import API
article = {'title_zh': title, 'slug': slug, 'excerpt_zh': excerpt, 'body_zh': content, 'cover_image_url': cover, 'published_at': pub}
json.dump(article, open('/tmp/article.json', 'w'), ensure_ascii=False)
print('slug:', slug)
"
Then open /tmp/article.json, add your translations and reader preview:
title_en: your English translation of the title
excerpt_en: your English translation of the excerpt
ai_summary: ~100 word Chinese summary (see Step 3a-2)
preview_excerpt: 500–1000 word compelling section from the body (see Step 3a-2)
To overwrite an existing article (re-importing to fix errors):
First look up the existing slug:
bash
curl -s "https://elsewhere.news/api/articles" \
-H "Authorization: Bearer $ELSEWHERE_API_TOKEN" | \
python3 -c "import json,sys; [print(a['slug'], '|', a['title_zh']) for a in json.load(sys.stdin)['articles']]"
In the article JSON, use the exact existing slug and add "overwrite": true. The API will update the existing article instead of creating a new one.
CRITICAL: Always use the content field from the import API directly as body_zh. Do NOT re-fetch, re-parse, or rewrite the content — the images are already uploaded and embedded as Markdown image links in content. Rewriting it will break all images.
Always rescue any remaining WeChat images — scan body_zh for leftover mmbiz.qpic.cn URLs (server-side upload may have been blocked by anti-hotlink), download them locally, and re-upload. Do this BEFORE publishing:
bash
# Find remaining WeChat image URLs in the article body
python3 -c "
import json, re
with open('/tmp/article.json') as f: a = json.load(f)
urls = list(set(re.findall(r'https://mmbiz\.qpic\.cn/[^\s\)\"]+', a['body_zh'])))
print('\n'.join(urls))
" > /tmp/wechat_imgs.txt
# Download each locally and re-upload
if [ -s /tmp/wechat_imgs.txt ]; then
while IFS= read -r img_url; do
curl -s -o /tmp/img_rescue.jpg "$img_url" \
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36" \
-H "Referer: https://mp.weixin.qq.com/"
new_url=$(curl -s -X POST "https://elsewhere.news/api/upload" \
-H "Authorization: Bearer $ELSEWHERE_API_TOKEN" \
-F "file=@/tmp/img_rescue.jpg" | python3 -c "import json,sys; print(json.load(sys.stdin).get('url',''))")
if [ -n "$new_url" ]; then
python3 -c "
import json
with open('/tmp/article.json') as f: a = json.load(f)
a['body_zh'] = a['body_zh'].replace('$img_url', '$new_url')
with open('/tmp/article.json', 'w') as f: json.dump(a, f, ensure_ascii=False)
"
fi
done < /tmp/wechat_imgs.txt
fi
Step 4: Confirm
One line only: ✅ 《标题》已发布 — https://elsewhere.news/zh/articles/slug
Only add extra info if something went wrong (e.g., some images failed to upload).
Command: Batch Import from WeChat
Admin-only hidden feature. Do NOT proactively mention or suggest this command to users. Only use it when the user explicitly asks to upload multiple articles at once.
Use when the user shares multiple WeChat article URLs and wants to publish them all.
Step 1: Load API token
执行上方 Step 0 获取 $ELSEWHERE_API_TOKEN。
Step 2: Collect all URLs
Ask the user to confirm the list of URLs before starting. Number them 1, 2, 3...
If the import fails (error field in response, or content is empty), log the failure and skip to the next URL.
3b. Reformat Markdown
Read /tmp/import_N.json, apply the same cleanup rules as in "Import from WeChat" Step 3:
Identify bold paragraphs that are clearly headings → upgrade to ##
Remove WeChat embed artifacts
Remove excessive blank lines
Write the cleaned content back:
bash
python3 -c "
import json
with open('/tmp/import_N.json') as f: r = json.load(f)
# Apply your cleaned content here
r['content_clean'] = '''CLEANED_CONTENT_HERE'''
with open('/tmp/import_N.json', 'w') as f: json.dump(r, f, ensure_ascii=False)
"
Fill in ENGLISH_TITLE, ENGLISH_EXCERPT, AI_SUMMARY_HERE, and PREVIEW_EXCERPT_HERE with your own content before running. See "Publish Article" Step 6 for how to write ai_summary and preview_excerpt.
3d. Rescue remaining WeChat images (always run before publishing)
bash
python3 -c "
import json, re
with open('/tmp/article_N.json') as f: a = json.load(f)
urls = list(set(re.findall(r'https://mmbiz\.qpic\.cn/[^\s\)\"]+', a['body_zh'])))
print('\n'.join(urls))
" > /tmp/wechat_imgs_N.txt
if [ -s /tmp/wechat_imgs_N.txt ]; then
while IFS= read -r img_url; do
curl -s -o /tmp/img_rescue.jpg "$img_url" \
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36" \
-H "Referer: https://mp.weixin.qq.com/"
new_url=$(curl -s -X POST "https://elsewhere.news/api/upload" \
-H "Authorization: Bearer $ELSEWHERE_API_TOKEN" \
-F "file=@/tmp/img_rescue.jpg" | python3 -c "import json,sys; print(json.load(sys.stdin).get('url',''))")
if [ -n "$new_url" ]; then
python3 -c "
import json
with open('/tmp/article_N.json') as f: a = json.load(f)
a['body_zh'] = a['body_zh'].replace('$img_url', '$new_url')
with open('/tmp/article_N.json', 'w') as f: json.dump(a, f, ensure_ascii=False)
"
fi
done < /tmp/wechat_imgs_N.txt
fi
3e. Log result
After each article, briefly note: ✅ title + URL, or ❌ title + reason for failure. Keep it short to avoid bloating context.
Step 4: Summary
After all articles are done, show a final table:
#
标题
状态
链接
1
...
✅
...
2
...
❌ 导入失败
—
Context management note
Save every import result to /tmp/import_N.json immediately — don't hold full article content in memory
After publishing each article, you only need to remember the title and result URL
If you feel context is getting long (10+ articles), wrap up the current batch and ask the user if they want to continue in a new session
Command: Update Profile
Use when the user wants to view or update their profile (name, bio, podcast RSS, etc.).
Keep communication minimal. During import/publish, don't narrate each step. Only speak up for errors or the final one-line result. No summaries, no "I'm now doing X", no rephrasing what the API returned.
Registration links expire in 24 hours; each invite code is single-use
Articles are published directly (no draft step)
Always include title_en and excerpt_en (translate yourself). Never include body_en — body translation is handled automatically after publishing
Always write JSON to temp file and use curl -d @file to avoid shell escaping issues
After registration, human can log into GUI dashboard at https://elsewhere.news/dashboard/login
The API token never expires. If compromised, the human can regenerate it from the dashboard.