wjs-syndicating-articles

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

wjs-syndicating-articles

wjs-syndicating-articles

每天把最新一篇还没分发过的公众号文章,扇出(syndicate)到各社交平台。一套文案走天下,有 API 的真发,没 API 的备好待发件箱让你手动粘。
每天把最新一篇还没分发过的公众号文章,分发(syndicate)到各社交平台。一套文案走天下,有API的平台自动发布,没有API的平台备好待发件箱供你手动粘贴。

Core Principles

核心原则

  • 稳定第一:每个平台是独立步骤,一个失败绝不影响其它。
  • 幂等去重
    state/history.jsonl
    (slug, platform)
    记录;重复跑只补发没成功的,永不重复发。
  • 凭证降级:API 平台缺/过期凭证 → 自动转 outbox(手动),不报错。
  • 署名 / CTA 用「王建硕」(用户全局偏好),不写营销腔、不堆 hashtag/@/emoji(除非原文有)。
  • 稳定第一:每个平台的操作是独立步骤,一个平台失败绝不影响其他平台。
  • 幂等去重
    state/history.jsonl
    (slug, platform)
    记录分发情况;重复运行只会补发未成功的内容,绝不会重复发布。
  • 凭证降级:API平台缺少/过期凭证时→自动转为待发件箱(手动发布),不会报错。
  • 署名 / CTA 使用「王建硕」(用户全局偏好),不写营销话术、不堆砌 hashtag/@/emoji(除非原文已有)。

Inputs

调用指令

/wjs-syndicating-articles                 # 选最新未分发文章,走完整流程(默认/定时用)
/wjs-syndicating-articles <article-folder># 显式指定文章
/wjs-syndicating-articles --open          # 交互模式:打开手动平台 web 页 + 文案进剪贴板
/wjs-syndicating-articles --dry-run       # 只草拟,不发、不写 history
/wjs-syndicating-articles --mark <slug> <platform>  # 手动标记某平台已发
SKILL_DIR = ~/.claude/skills/wjs-syndicating-articles
/wjs-syndicating-articles                 # 选择最新未分发文章,执行完整流程(默认/定时任务使用)
/wjs-syndicating-articles <article-folder># 显式指定要分发的文章文件夹
/wjs-syndicating-articles --open          # 交互模式:打开手动平台的网页发布界面 + 将文案复制到剪贴板
/wjs-syndicating-articles --dry-run       # 仅生成草稿,不发布、不记录分发历史
/wjs-syndicating-articles --mark <slug> <platform>  # 手动标记某篇文章已在指定平台发布
SKILL_DIR = ~/.claude/skills/wjs-syndicating-articles

Workflow (default / scheduled run)

工作流程(默认/定时运行)

Step 0: --mark short-circuit

步骤0:--mark 短路逻辑

若调用是
--mark <slug> <platform>
bash $SKILL_DIR/scripts/history.sh record <slug> <platform> posted
然后告诉用户已标记,结束。
若调用指令为
--mark <slug> <platform>
:执行
bash $SKILL_DIR/scripts/history.sh record <slug> <platform> posted
后告知用户已标记完成,流程结束。

Step 1: 选文章

步骤1:选择文章

bash
bash $SKILL_DIR/scripts/pick-next-article.sh
  • 显式指定了
    <article-folder>
    则跳过此脚本,直接用它。
  • 输出为空 → 最近文章都分发完了,今天 rest day,结束。
  • FOLDER
    SLUG=$(basename "$FOLDER")
bash
bash $SKILL_DIR/scripts/pick-next-article.sh
  • 若显式指定了
    <article-folder>
    ,则跳过此脚本,直接使用指定的文件夹。
  • 若脚本输出为空→最近的文章已全部分发完成,今日无需操作,流程结束。
  • 记录
    FOLDER
    SLUG=$(basename "$FOLDER")

Step 2: 抽一套核心文案(你来做,不是脚本)

步骤2:提取核心文案(由你执行,而非脚本)

$FOLDER/article.md
$FOLDER/meta.json
。抽出一段最 quotable 的核心句/小段,≤120 字(保证塞进 X 的 280 字符;中文每字算 2),保留王建硕语气。再加一行软 CTA + 文章链接(公众号链接,没有就用
meta.json
里信息+
article_url_base
)。
把最终文案写进
$SKILL_DIR/outbox/<date>-<SLUG>/post.txt
(先
mkdir -p
)。
<date>=$(date +%F)
--dry-run
时:打印 post.txt 内容 + 下面每个平台「将发什么」,继续 Step 3+,结束。
读取
$FOLDER/article.md
$FOLDER/meta.json
。提取一段最适合引用的核心语句/小段,≤120字(确保能容纳进X的280字符限制;中文每个字按2字符计算),保留王建硕的语气风格。再添加一行软CTA + 文章链接(优先使用公众号链接,若无则使用
meta.json
中的信息 +
article_url_base
)。
将最终文案写入
$SKILL_DIR/outbox/<date>-<SLUG>/post.txt
(需先执行
mkdir -p
创建目录)。
<date>=$(date +%F)
若使用
--dry-run
参数:打印post.txt内容 + 各平台的“待发布内容”,继续执行步骤3及以后,流程结束。

Step 3: API 平台(逐个 try/catch,真发)

步骤3:API平台发布(逐个尝试捕获异常,真实发布)

x bluesky threads linkedin
各跑一次(按 config 里 mode==api 的):
bash
undefined
x bluesky threads linkedin
逐个执行(仅处理config中mode==api的平台):
bash
undefined

先去重:tweeting skill 也可能发过 X

先去重:tweeting skill 可能已发布过X平台内容

if [[ "$P" == "x" ]]; then TW_HIST="$HOME/.claude/skills/wjs-tweeting-from-articles/state/history.jsonl" if [[ -f "$TW_HIST" ]] && jq -e --arg s "$SLUG" 'select(.slug==$s and .status=="posted")' "$TW_HIST" >/dev/null 2>&1; then bash $SKILL_DIR/scripts/history.sh record "$SLUG" x skipped; continue fi fi if bash $SKILL_DIR/scripts/history.sh has "$SLUG" "$P"; then continue; fi # already done
OUT="$(bash $SKILL_DIR/scripts/post-$P.sh "$POST_TXT")"; CODE=$? case $CODE in 0) URL="$(echo "$OUT" | sed -n 's/^url=//p')"; PID="$(echo "$OUT" | sed -n 's/^post_id=//p')" bash $SKILL_DIR/scripts/history.sh record "$SLUG" "$P" posted "$URL" "$PID" ;; 3) bash $SKILL_DIR/scripts/history.sh record "$SLUG" "$P" queued "" "" no_creds ;; # degrade -> outbox *) bash $SKILL_DIR/scripts/history.sh record "$SLUG" "$P" failed ;; # retry next run esac
undefined
if [[ "$P" == "x" ]]; then TW_HIST="$HOME/.claude/skills/wjs-tweeting-from-articles/state/history.jsonl" if [[ -f "$TW_HIST" ]] && jq -e --arg s "$SLUG" 'select(.slug==$s and .status=="posted")' "$TW_HIST" >/dev/null 2>&1; then bash $SKILL_DIR/scripts/history.sh record "$SLUG" x skipped; continue fi fi if bash $SKILL_DIR/scripts/history.sh has "$SLUG" "$P"; then continue; fi # 已发布过,跳过
OUT="$(bash $SKILL_DIR/scripts/post-$P.sh "$POST_TXT")"; CODE=$? case $CODE in 0) URL="$(echo "$OUT" | sed -n 's/^url=//p')"; PID="$(echo "$OUT" | sed -n 's/^post_id=//p')" bash $SKILL_DIR/scripts/history.sh record "$SLUG" "$P" posted "$URL" "$PID" ;; 3) bash $SKILL_DIR/scripts/history.sh record "$SLUG" "$P" queued "" "" no_creds ;; # 降级为待发件箱 *) bash $SKILL_DIR/scripts/history.sh record "$SLUG" "$P" failed ;; # 下次运行时重试 esac
undefined

Step 4: 手动平台 → 待发件箱

步骤4:手动平台→待发件箱

bash
OUTBOX="$SKILL_DIR/outbox/$(date +%F)-$SLUG"
bash $SKILL_DIR/scripts/build-outbox.sh "$FOLDER" "$POST_TXT" "$OUTBOX"
for P in facebook xiaohongshu jike zhihu; do
  bash $SKILL_DIR/scripts/history.sh has "$SLUG" "$P" || bash $SKILL_DIR/scripts/history.sh record "$SLUG" "$P" queued
done
(degrade 到 outbox 的 API 平台同理已在 history 记为 queued;它们的文案就在同一个 OPEN.md 里。)
bash
OUTBOX="$SKILL_DIR/outbox/$(date +%F)-$SLUG"
bash $SKILL_DIR/scripts/build-outbox.sh "$FOLDER" "$POST_TXT" "$OUTBOX"
for P in facebook xiaohongshu jike zhihu; do
  bash $SKILL_DIR/scripts/history.sh has "$SLUG" "$P" || bash $SKILL_DIR/scripts/history.sh record "$SLUG" "$P" queued
done
(降级到待发件箱的API平台同样会在历史记录中标记为queued;它们的文案会放在同一个OPEN.md文件中。)

Step 5: 通知 + 汇总

步骤5:通知 + 汇总

打印一张表:每个平台 status(posted+URL / queued(outbox) / failed / skipped)。 无人值守(定时)跑:发一条 PushNotification,例:「✅ X、Bluesky 已发;📋 Facebook/小红书/即刻/知乎 在 outbox 待粘:$OUTBOX」。 不要在 Step 5 自动开浏览器——那是
--open
的事。
打印一张状态表:每个平台的分发状态(已发布+链接 / 待发(待发件箱) / 发布失败 / 已跳过)。 无人值守(定时任务)运行时:发送一条PushNotification,示例:「✅ X、Bluesky 已发布;📋 Facebook/小红书/即刻/知乎 待粘贴发布,待发件箱路径:$OUTBOX」。 不要在步骤5自动打开浏览器——这是
--open
模式的功能。

--open mode(交互,发手动平台时)

--open模式(交互模式,用于手动发布平台)

  1. 找到今天的 outbox:
    $SKILL_DIR/outbox/$(date +%F)-<SLUG>
    (或最新一个)。
  2. cat OUTBOX/post.txt | pbcopy
    (文案进剪贴板)。
  3. /browse
    skill 打开 config 里 facebook、jike、zhihu 的
    web_compose
  4. 小红书:
    open "$OUTBOX/image.png"
    (Finder 弹出),提示用户 AirDrop 到手机、文案已在剪贴板。
  5. 逐个提示:粘贴 → 发布。用户发完某个可
    --mark <slug> <platform>
  1. 找到今日的待发件箱:
    $SKILL_DIR/outbox/$(date +%F)-<SLUG>
    (或最新的待发件箱)。
  2. 执行
    cat OUTBOX/post.txt | pbcopy
    (将文案复制到剪贴板)。
  3. 使用
    /browse
    技能打开config中facebook、jike、zhihu的
    web_compose
    页面。
  4. 小红书:执行
    open "$OUTBOX/image.png"
    (弹出Finder窗口),提示用户通过AirDrop传到手机、文案已在剪贴板。
  5. 逐个提示:粘贴文案 → 发布。用户发布完成某个平台后可使用
    --mark <slug> <platform>
    标记已发布。

File Layout

文件结构

$SKILL_DIR/
├── SKILL.md  config.json  secrets.json(gitignored)
├── scripts/  lib.sh history.sh pick-next-article.sh post-*.sh build-outbox.sh
├── outbox/<date>-<slug>/  post.txt image.png OPEN.md
└── state/history.jsonl
$SKILL_DIR/
├── SKILL.md  config.json  secrets.json(gitignored)
├── scripts/  lib.sh history.sh pick-next-article.sh post-*.sh build-outbox.sh
├── outbox/<date>-<slug>/  post.txt image.png OPEN.md
└── state/history.jsonl

配置 API 平台(可选,配了才真发)

配置API平台(可选,配置后才会自动发布)

secrets.json.example
secrets.json
,按需填 bluesky / threads / linkedin。不填的平台自动走 outbox。
复制
secrets.json.example
secrets.json
,按需填写bluesky / threads / linkedin的凭证。未配置的平台自动转为待发件箱模式。

Daily 自动化

每日自动化设置

/schedule daily 10:00 /wjs-syndicating-articles
/schedule daily 10:00 /wjs-syndicating-articles