@geoly-ai/social-hub-cli 0.3.16 → 0.3.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/cmd-manifest.json +6 -2
  3. package/dist/cmd-manifest.test.js +4 -0
  4. package/dist/cmd-manifest.test.js.map +1 -1
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +5 -2
  7. package/dist/index.js.map +1 -1
  8. package/dist/permissions-gates-admin.d.ts.map +1 -1
  9. package/dist/permissions-gates-admin.js +12 -0
  10. package/dist/permissions-gates-admin.js.map +1 -1
  11. package/dist/permissions-gates-notify.d.ts.map +1 -1
  12. package/dist/permissions-gates-notify.js +6 -0
  13. package/dist/permissions-gates-notify.js.map +1 -1
  14. package/dist/register-extensions.d.ts.map +1 -1
  15. package/dist/register-extensions.js +15 -1
  16. package/dist/register-extensions.js.map +1 -1
  17. package/dist/register-feishu-identities.d.ts +47 -0
  18. package/dist/register-feishu-identities.d.ts.map +1 -0
  19. package/dist/register-feishu-identities.js +346 -0
  20. package/dist/register-feishu-identities.js.map +1 -0
  21. package/dist/register-feishu-identities.test.d.ts +2 -0
  22. package/dist/register-feishu-identities.test.d.ts.map +1 -0
  23. package/dist/register-feishu-identities.test.js +124 -0
  24. package/dist/register-feishu-identities.test.js.map +1 -0
  25. package/dist/register-notify.js +27 -0
  26. package/dist/register-notify.js.map +1 -1
  27. package/package.json +3 -3
  28. package/skills/manifest.json +1 -1
  29. package/skills/reddit-matrix.lock.json +1 -1
  30. package/skills/reddit-voc-volume/SKILL.md +2 -7
  31. package/skills/reddit-voc-volume/matrix-contract.md +4 -3
  32. package/skills/reddit-voc-volume/references/arctic-shift.md +2 -1
  33. package/skills/reddit-voc-volume/scripts/reddit_voc_volume.py +157 -69
  34. package/skills/reddit-voc-volume/tests/test_archived_comments.py +104 -0
  35. package/skills/social-hub-admin/SKILL.md +47 -1
  36. package/skills/social-hub-cli/SKILL.md +1 -0
  37. package/skills/social-hub-notifications/SKILL.md +35 -20
  38. package/skills/social-hub-posts/SKILL.md +14 -1
@@ -9,7 +9,7 @@ description: >-
9
9
  不用于 OpenClaw 任务领取(那是 social-hub-ops-runtime 的 claim/complete,有租约与终态)、
10
10
  也不用于飞书审核通知渠道(social-hub-admin)。
11
11
  metadata:
12
- cliVersion: ">=0.3.9"
12
+ cliVersion: ">=0.3.17"
13
13
  ---
14
14
 
15
15
  # social-hub-notifications
@@ -372,25 +372,26 @@ reset 会 **bump `receiptEpoch`**,作废全部在途 receipt。**绝不要**
372
372
 
373
373
  ## 常见故障对照
374
374
 
375
- | 现象 | 原因 | 处理 |
376
- | ---------------------------------------------------- | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
377
- | `--follow` 一直不动、心跳报同一个 `pendingSeq` | 你没 ack(多半是未知 schema 被隔离了) | 看 stderr 的 eventId;升级 handler 后重跑 |
378
- | 游标永远卡在某条 | 把 tombstone 当业务事件处理失败了 | `payloadStatus: "expired"` **必须** ack |
379
- | ack 一直 409 | 在重放旧 receipt | 重新 pull 拿新 receipt;409 是 CAS 语义,不做幂等成功 |
380
- | pull 403 且 details 有 `missingPermissions` | 字段权限不足 | **整个订阅**被拒(不做字段级脱敏):减少 `--require-fields` 或补权限 |
381
- | 某字段突然不见了 | 后台改了 `selectedFields`(`fieldSetVersion` 会变) | 按 envelope 版本分支;**不要**自己补默认值 |
382
- | 一整段时间没有事件 | source 被 kill switch 关了 | 先 `notify schedules summary`(**不要**从事件流反推:kill switch 的 missed 不产生事件),再 `notify source-configs list`;disabled 期间到点的节点标 missed,**重新启用不补发** |
383
- | 某个帖子一条通知都没收到 | 主体被删/未发布/无锚点 → schedule 被 cancel | `notify schedules list --subject <snapshotId> --status cancelled`,看 `cancelReason` |
384
- | 老 schedule 该被清理却一直在 | 保留期 GC cron 没跑 / 追不上 | `notify schedules purge-status`:`hasRunSinceRegistration: false` = **自本次 worker 注册以来**没观测到运行(**不是**「跑了零删除」;刚重启完看到 false 是正常的);连续多轮 `backlog: true` = 真的追不上,调预算/频率。`protectedGroups` 非零是**正常**的 sentinel 保护,别当积压 |
385
- | 建订阅被拒,文案带 `template_disabled` | 系统级模板做好了但**自动派生总闸没开** | 运维 `notify templates set-auto-derivation --source <id> --enabled true --apply` |
386
- | 建订阅被拒,文案带 `template_missing` | 该 source 根本没有系统级模板,该 team 也没人工 config | 运维二选一:建模板(`notify templates create/activate` + 开总闸)或人工建 team config |
387
- | pull 403 `SUBSCRIPTION_SHARED_SENSITIVITY_FORBIDDEN` | 跨团队/桶订阅碰到目标团队 config 里的 `restricted` 字段 | 看 `details.restrictedFields`;**补权限没用**,请该团队把这几个字段从 `selectedFields` 里去掉 |
388
- | 跨团队 pull 409,文案提 `requiredFields` | 这条共享订阅带着非空 `requiredFields`(多半是早期建的) | PATCH 清空它的 `requiredFields` 后再消费 |
389
- | 跨团队 `notify events list` / `schedules` 403 | 诊断面**不随**聚合订阅开放(设计如此) | `notify pull` 消费;确需诊断请在该团队补 membership |
390
- | 建订阅 400 `SUBSCRIPTION_NAME_PREFIX_REQUIRED` | 你不是目标团队成员,名字没落在保留命名空间里 | 用 `details.requiredPrefix` 拼名字重试(`xsub/<你的 userId>/<name>`)。**换普通名字重试没用**——这与 409 `SUBSCRIPTION_NAME_TAKEN`(名字被占,换名即可)是两回事 |
391
- | 建订阅 400 `SUBSCRIPTION_NAME_PREFIX_RESERVED` | 你**是**本团队成员却用了 `xsub/` 前缀 | 换一个不以 `xsub/` 开头的名字(大小写/全角/前导空格都算命中) |
392
- | 建订阅 409 `SUBSCRIPTION_QUOTA_EXCEEDED` | 订阅配额满了,看 `details.dimension` / `basis` | `basis=live`:删/复用一条已有订阅。⚠️ `basis=total`:**删订阅不归还额度**(名字永久唯一),只能复用已有订阅或找运维物理清理 / 调高 `NOTIFICATION_*_SUBSCRIPTION_*_LIMIT`。`dimension=target_team` 是**目标团队**总量满了,不是你的问题 |
393
- | 激活 409 `SOURCE_CONFIG_ACTIVE_STATE_CONFLICT` | 你的 `--expect-active` 与服务端实际 active 不符(别人抢先改了) | `details.actualActiveConfigId` / `actualActiveVersion`,确认对方的改动后再决定是否重试(**无需**再 GET 一次) |
375
+ | 现象 | 原因 | 处理 |
376
+ | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
377
+ | `--follow` 一直不动、心跳报同一个 `pendingSeq` | 你没 ack(多半是未知 schema 被隔离了) | 看 stderr 的 eventId;升级 handler 后重跑 |
378
+ | 游标永远卡在某条 | 把 tombstone 当业务事件处理失败了 | `payloadStatus: "expired"` **必须** ack |
379
+ | ack 一直 409 | 在重放旧 receipt | 重新 pull 拿新 receipt;409 是 CAS 语义,不做幂等成功 |
380
+ | pull 403 且 details 有 `missingPermissions` | 字段权限不足 | **整个订阅**被拒(不做字段级脱敏):减少 `--require-fields` 或补权限 |
381
+ | 某字段突然不见了 | 后台改了 `selectedFields`(`fieldSetVersion` 会变) | 按 envelope 版本分支;**不要**自己补默认值 |
382
+ | 一整段时间没有事件 | source 被 kill switch 关了 | 先 `notify schedules summary`(**不要**从事件流反推:kill switch 的 missed 不产生事件),再 `notify source-configs list`;disabled 期间到点的节点标 missed,**重新启用不补发** |
383
+ | 某个帖子一条通知都没收到 | 主体被删/未发布/无锚点 → schedule 被 cancel | `notify schedules list --subject <snapshotId> --status cancelled`,看 `cancelReason` |
384
+ | 老 schedule 该被清理却一直在 | 保留期 GC cron 没跑 / 追不上 | `notify schedules purge-status`:`hasRunSinceRegistration: false` = **自本次 worker 注册以来**没观测到运行(**不是**「跑了零删除」;刚重启完看到 false 是正常的);连续多轮 `backlog: true` = 真的追不上,调预算/频率。`protectedGroups` 非零是**正常**的 sentinel 保护,别当积压 |
385
+ | 建订阅被拒,文案带 `template_disabled` | 系统级模板做好了但**自动派生总闸没开** | 运维 `notify templates set-auto-derivation --source <id> --enabled true --apply` |
386
+ | 建订阅被拒,文案带 `template_missing` | 该 source 根本没有系统级模板,该 team 也没人工 config | 运维二选一:建模板(`notify templates create/activate` + 开总闸)或人工建 team config |
387
+ | pull 403 `SUBSCRIPTION_SHARED_SENSITIVITY_FORBIDDEN` | 跨团队/桶订阅碰到目标团队 config 里的 `restricted` 字段 | 看 `details.restrictedFields`;**补权限没用**,请该团队把这几个字段从 `selectedFields` 里去掉 |
388
+ | active config 已无 restricted,但同一 403 仍卡在旧 seq | 历史事件行冻结的 `maxSensitivity` 仍是 restricted,或旧 config 的 scheduled 行还会继续发 restricted 事件 | **不要 ACK/reset/删订阅**。由目标 team 的不受限 admin 先跑 `notify source-configs narrow-history` dry-run,再携 selectionHash `--apply`;操作保留 event seq、dedupe 和订阅游标 |
389
+ | 跨团队 pull 409,文案提 `requiredFields` | 这条共享订阅带着非空 `requiredFields`(多半是早期建的) | PATCH 清空它的 `requiredFields` 后再消费 |
390
+ | 跨团队 `notify events list` / `schedules` 403 | 诊断面**不随**聚合订阅开放(设计如此) | 用 `notify pull` 消费;确需诊断请在该团队补 membership |
391
+ | 建订阅 400 `SUBSCRIPTION_NAME_PREFIX_REQUIRED` | 你不是目标团队成员,名字没落在保留命名空间里 | `details.requiredPrefix` 拼名字重试(`xsub/<你的 userId>/<name>`)。**换普通名字重试没用**——这与 409 `SUBSCRIPTION_NAME_TAKEN`(名字被占,换名即可)是两回事 |
392
+ | 建订阅 400 `SUBSCRIPTION_NAME_PREFIX_RESERVED` | 你**是**本团队成员却用了 `xsub/` 前缀 | 换一个不以 `xsub/` 开头的名字(大小写/全角/前导空格都算命中) |
393
+ | 建订阅 409 `SUBSCRIPTION_QUOTA_EXCEEDED` | 订阅配额满了,看 `details.dimension` / `basis` | `basis=live`:删/复用一条已有订阅。⚠️ `basis=total`:**删订阅不归还额度**(名字永久唯一),只能复用已有订阅或找运维物理清理 / 调高 `NOTIFICATION_*_SUBSCRIPTION_*_LIMIT`。`dimension=target_team` 是**目标团队**总量满了,不是你的问题 |
394
+ | 激活 409 `SOURCE_CONFIG_ACTIVE_STATE_CONFLICT` | 你的 `--expect-active` 与服务端实际 active 不符(别人抢先改了) | 读 `details.actualActiveConfigId` / `actualActiveVersion`,确认对方的改动后再决定是否重试(**无需**再 GET 一次) |
394
395
 
395
396
  ## 运维查看面(`notify events` / `notify schedules`)
396
397
 
@@ -588,6 +589,20 @@ social-hub notify source-configs activate ... --expect-active none --apply
588
589
  # 创建即激活也支持同一套(否则「新建并激活」就是绕过 CAS 的后门):
589
590
  social-hub notify source-configs create ... --expect-active <当前 activeConfig.id> --apply
590
591
 
592
+ # 只有「active config 已收窄、历史 restricted 行仍卡住共享订阅」时使用(不受限 admin only)。
593
+ # 第一步是真实服务端 dry-run:会锁内校验旧/新 nodes、filters、retention、事件与未触发 schedule,
594
+ # 但不写数据;保存返回的 selectionHash。
595
+ social-hub notify source-configs narrow-history -t <team> \
596
+ --source post-performance-node.v1 \
597
+ --from-config <旧-config-id> --target-config <当前-active-config-id>
598
+ # 第二步显式执行;选择集有任何漂移都会 409,必须重新 dry-run,绝不沿用旧 hash:
599
+ social-hub notify source-configs narrow-history -t <team> \
600
+ --source post-performance-node.v1 \
601
+ --from-config <旧-config-id> --target-config <当前-active-config-id> \
602
+ --expect-selection-hash <dry-run.selectionHash> --apply
603
+ # 本操作只做 payload 字段单调删除 + scheduled 行 config 迁移;不会 ACK、reset、删除/重建订阅,
604
+ # 也不会推进 cursorSeq / bump receiptEpoch。订阅机器保持原订阅,从原 cursor 自动重试即可。
605
+
591
606
  # 实时 kill switch —— 立刻影响【全部在途 schedule】,且不 bump sourceConfigVersion
592
607
  social-hub notify source-configs set-enabled -t <team> \
593
608
  --source post-performance-node.v1 --enabled false --apply
@@ -50,7 +50,7 @@ social-hub reddit refresh-logs -t <team-id> -n 20
50
50
 
51
51
  筛选帖表现时优先 **`--brand`**,勿把 `--campaign` 当作品牌 ID。
52
52
 
53
- **`opsStatus` 约定(canonical,勿传中文或 `banned`):** `normal` | `risk` | `deleted` | `abnormal` | `unknown`。列表与 stats 均支持 `--ops-status`;stats 同样支持 `--contains-brand-keyword` 与 `--primary-product`,筛选口径与 Web 帖子表现页一致。
53
+ **`opsStatus` 约定(canonical,勿传中文):** `normal` | `risk` | `deleted` | `abnormal` | `banned` | `unknown`。⚠️ 把它改成 `deleted` / `banned` 时**必须**同时带必选项 `repostRequired`(见下方「需补发」),否则 400。列表与 stats 均支持 `--ops-status`;stats 同样支持 `--contains-brand-keyword` 与 `--primary-product`,筛选口径与 Web 帖子表现页一致。
54
54
 
55
55
  ## 单条写入与更新
56
56
 
@@ -73,6 +73,9 @@ social-hub reddit update -t <team-id> --snapshot <uuid> -j '{
73
73
  "secondRepostLink": "https://www.reddit.com/r/example/comments/repost2/"
74
74
  }'
75
75
  # 改/取消关联账号(socialAccountId):account-uuid 须属该快照所在团队;null=取消关联;换团队(teamId)时后端会自动清空它
76
+ # 改成「已删除」/「账号被封」:必须带 repostRequired(是否需补发)——不带一律 400
77
+ social-hub reddit update -t <team-id> --snapshot <uuid> -j '{"opsStatus":"deleted","repostRequired":true}'
78
+ social-hub reddit update -t <team-id> --snapshot <uuid> --repost-required no -j '{"opsStatus":"banned"}'
76
79
  social-hub reddit update -t <team-id> --snapshot <uuid> -j '{"socialAccountId":"<account-uuid>"}'
77
80
  social-hub reddit update -t <team-id> --snapshot <uuid> -j '{"socialAccountId":null}'
78
81
  # 改/取消品牌关联(brandId,系统级):品牌跨团队有效,换团队不清;null=取消;client 角色只能设其可见品牌
@@ -120,6 +123,16 @@ social-hub reddit batch-upsert -t <team-id> -j '{
120
123
  - **`brandNote`**:运营侧品牌备注(nullable string,仅内部可见)。
121
124
  - **`negativeFeedback`**:帖子负面互动反馈(nullable string,仅内部可见),如被举报/删评/封号等运营记录。与 `brandNote` 同类型;`create-snapshot`、`update`、batch item 均支持;Web 帖子表现编辑弹窗可改。
122
125
  - **`repostType`**:补发类型,canonical `none`(非补发)/`first`(补发一次)/`second`(补发二次),null=none。列表 `--repost-type` 可筛选。**自动拆分**:对一个 `repostType=none` 的帖 `update` 时,若本次**新填/改了** `firstRepostLink`(或 `secondRepostLink`),后端会自动以该链接为 permalink **新建一个补发帖**(`repostType=first`/`second`),拷贝原帖归因+状态、互动清零、不带补发链接、时间=当下;`(teamId,permalink)` 已存在或链接没变则不重复创建。只 `update`(PUT)触发,`create-snapshot`/`batch-upsert` 不触发。
126
+ - **`repostRequired`(必选项「是否需补发」)**:布尔。把 `opsStatus` 改成 `deleted` / `banned` 时**必须带**,否则服务端 400(fail-closed,不会默认成「不需补发」)。原帖 `repostType` 已是 `second`(补发二次)时**不问也不写**,带了也无效。
127
+ - **`create-snapshot` 同样受这道闸门**:新建时 `opsStatus` 直接写 `deleted`/`banned`(且 `repostType` 不是 `second`)也必须在 body 里带 `repostRequired`,否则 400。新建行没有「改前状态」,轮次按本次提交的 `repostType` 派生。
128
+ - **在两个 terminal 之间切换**(`deleted` ↔ `banned`)同样要带;`pending` 的行再保存也要带。
129
+ - **不能在同一次请求里既改 `repostType` 又把状态迁入/切换 terminal**(服务端 400):「需补发第几次」按**改前**的补发类型判,这种请求下「原数据」二义。先单独保存补发类型,再改状态。
130
+ - **不要传数字**:「需补发1 / 需补发2」的轮次由服务端按**原帖**的 `repostType` 派生(首发/none → 需补发1,`first` → 需补发2),并在决策那一刻冻结进 `repostRequirement` 列;事后再改 `repostType` 不会改写历史决策(**唯一例外**:改成 `second` 会清空该列——补发二次的帖按需求就不该带「需补发」)。
131
+ - 读侧字段 `repostRequirement`:`null`(不适用)/ `pending`(自动检测删的帖,等人决定)/ `not_required` / `required_1` / `required_2`。帖子表现列表在「运营状态」一格里把 `required_1/2` 渲染成「需补发1 / 需补发2」,`pending` 渲染成「待定补发」。
132
+ - **自动路径只落 `pending`**:worker 删除检测、存活检测、刷新、飞书/批量导入(`batch-upsert`)把帖子判成已删除时没有人可以回答这个问题,一律落 `pending` 进待决队列,绝不替运营决定。
133
+ - **批量(bulk-patch)**:整批设成 `deleted`/`banned` 时必须带 `repostRequired`;但如果选中的行**全部**是 `second`,服务端不会要求作答(需求:二次不弹)。轮次由服务端**逐行**按各自 `repostType` 派生。
134
+ - 状态从 `deleted`/`banned` 改回其他值时,这一列会被**清空**(决策作废)。
135
+ - 同一次请求里既改 `repostType` 又把状态迁入 `deleted`/`banned` 会被拒绝(「原数据」二义):先改类型,再改状态。
123
136
  - **`feishuRecordId`**:飞书记录 ID。当输入来自飞书时,Hub 优先按 `(teamId, feishuRecordId)` 幂等;同一 permalink 对应不同飞书记录时,后续记录可能获得 `feishu.local` 占位 permalink
124
137
  - **无 permalink**:可只带 `feishuRecordId`(及 `baseToken`/`tableId`),Hub 会生成稳定占位 URL
125
138