@geoly-ai/social-hub-cli 0.3.40 → 0.3.42
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.
- package/CHANGELOG.md +19 -0
- package/dist/cmd-manifest.json +24 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +358 -1
- package/dist/index.js.map +1 -1
- package/dist/partial-list-guard.test.js +45 -1
- package/dist/partial-list-guard.test.js.map +1 -1
- package/dist/register-extensions.d.ts.map +1 -1
- package/dist/register-extensions.js +4 -0
- package/dist/register-extensions.js.map +1 -1
- package/dist/watchlists-list-filters.test.d.ts +2 -0
- package/dist/watchlists-list-filters.test.d.ts.map +1 -0
- package/dist/watchlists-list-filters.test.js +143 -0
- package/dist/watchlists-list-filters.test.js.map +1 -0
- package/package.json +2 -2
- package/skills/integrity.json +3 -3
- package/skills/manifest.json +1 -1
- package/skills/social-hub-intelligence/SKILL.md +174 -1
- package/skills/social-hub-posts/SKILL.md +3 -0
- package/dist/register-subreddit-post-style.d.ts +0 -8
- package/dist/register-subreddit-post-style.d.ts.map +0 -1
- package/dist/register-subreddit-post-style.js +0 -161
- package/dist/register-subreddit-post-style.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,25 @@ All notable changes to `@geoly-ai/social-hub-cli` are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
Release workflow: `pnpm release:cli` → `pnpm publish:cli`.
|
|
6
6
|
|
|
7
|
+
## [0.3.42] - 2026-09-10
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- watchlist 筛选补齐 CLI/skills 层 + 清两处 sticky 死代码
|
|
12
|
+
|
|
13
|
+
## [0.3.41] - 2026-09-09
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- 板块新帖监听 P2 —— 近义词扩展 + 帖子类型标注
|
|
18
|
+
- 板块新帖监听 P1 —— 版本化关键词规则 + 命中账本 + 覆盖诊断
|
|
19
|
+
- calendar list-full 补 --offset —— 服务端一直支持,只有下游没暴露
|
|
20
|
+
- 帖子表现新增「序列号范围」筛选
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- 给偶发失败的用例加取证钩子 —— 默认不激活,只有设 CLI_FLAKE_LOG 才写盘
|
|
25
|
+
|
|
7
26
|
## [0.3.40] - 2026-09-04
|
|
8
27
|
|
|
9
28
|
### Fixed
|
package/dist/cmd-manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generatedAt": "2026-09-
|
|
3
|
-
"cliVersion": "0.3.
|
|
2
|
+
"generatedAt": "2026-09-10T02:54:19.672Z",
|
|
3
|
+
"cliVersion": "0.3.42",
|
|
4
4
|
"commands": [
|
|
5
5
|
"accounts",
|
|
6
6
|
"accounts brand-bindings",
|
|
@@ -249,6 +249,28 @@
|
|
|
249
249
|
"intelligence kol-intents-create",
|
|
250
250
|
"intelligence kol-profiles-get",
|
|
251
251
|
"intelligence kol-profiles-list",
|
|
252
|
+
"intelligence monitor-brand-keyword-candidates",
|
|
253
|
+
"intelligence monitor-expansion-approve",
|
|
254
|
+
"intelligence monitor-expansion-create",
|
|
255
|
+
"intelligence monitor-expansion-delete",
|
|
256
|
+
"intelligence monitor-expansion-generate",
|
|
257
|
+
"intelligence monitor-expansion-get",
|
|
258
|
+
"intelligence monitor-expansion-retire",
|
|
259
|
+
"intelligence monitor-expansions-list",
|
|
260
|
+
"intelligence monitor-label-spec-activate",
|
|
261
|
+
"intelligence monitor-label-spec-create",
|
|
262
|
+
"intelligence monitor-label-spec-delete",
|
|
263
|
+
"intelligence monitor-label-spec-retire",
|
|
264
|
+
"intelligence monitor-label-specs-list",
|
|
265
|
+
"intelligence monitor-ruleset-activate",
|
|
266
|
+
"intelligence monitor-ruleset-apply-expansions",
|
|
267
|
+
"intelligence monitor-ruleset-create",
|
|
268
|
+
"intelligence monitor-ruleset-delete",
|
|
269
|
+
"intelligence monitor-ruleset-dry-run",
|
|
270
|
+
"intelligence monitor-ruleset-get",
|
|
271
|
+
"intelligence monitor-ruleset-retire",
|
|
272
|
+
"intelligence monitor-ruleset-rules-set",
|
|
273
|
+
"intelligence monitor-rulesets-list",
|
|
252
274
|
"intelligence monitor-runs-list",
|
|
253
275
|
"intelligence monitors-disable",
|
|
254
276
|
"intelligence monitors-enable",
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA+DpC,QAAA,MAAM,OAAO,SAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA+DpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAunF9B,8DAA8D;AAC9D,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,MAAM,GAAG,IAAI,CAOf;AAg+BD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC1D,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACjE,CAAC;AAQF,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,gBAAgB,EACxB,MAAM,EAAE;IACN,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC,GACA,OAAO,CAAC;IAAE,QAAQ,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,CAkJnE;AA0qCD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,sBAAsB,CAAC,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpE,GAAG,MAAM,CAiBT;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAyCpD;AAqzDD,OAAO,EAAE,OAAO,EAAE,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -370,6 +370,8 @@ reddit
|
|
|
370
370
|
.option("--repost-type <t>", "repostType filter (none|first|second)")
|
|
371
371
|
.option("--contains-brand-keyword <v>", "true | false | unset")
|
|
372
372
|
.option("--primary-product <text>", "primary product contains match")
|
|
373
|
+
.option("--seq-no-min <n>", "序列号下界(含);与 --seq-no-max 组成闭区间,无编号(null)的帖会落选")
|
|
374
|
+
.option("--seq-no-max <n>", "序列号上界(含)")
|
|
373
375
|
.option("--sort-by <f>", "score|comments_count|posted_at|view_count|seq_no")
|
|
374
376
|
.option("--sort-dir <d>", "asc|desc")
|
|
375
377
|
.option("-n, --limit <n>", "limit per page", "100")
|
|
@@ -389,6 +391,8 @@ reddit
|
|
|
389
391
|
repostType: opts.repostType,
|
|
390
392
|
containsBrandKeyword: opts.containsBrandKeyword,
|
|
391
393
|
primaryProduct: opts.primaryProduct,
|
|
394
|
+
seqNoMin: opts.seqNoMin !== undefined ? Number(opts.seqNoMin) : undefined,
|
|
395
|
+
seqNoMax: opts.seqNoMax !== undefined ? Number(opts.seqNoMax) : undefined,
|
|
392
396
|
sortBy: opts.sortBy,
|
|
393
397
|
sortDir: opts.sortDir,
|
|
394
398
|
...redditAggregateQueryParams(opts),
|
|
@@ -2981,12 +2985,41 @@ intel
|
|
|
2981
2985
|
.description("GET .../subreddit-intelligence/watchlists")
|
|
2982
2986
|
.option("--campaign <uuid>", "campaignId")
|
|
2983
2987
|
.option("--subreddit <name>", "按板块名精确查归属(r/ 可省)")
|
|
2988
|
+
.option("--subreddit-query <text>", "板块名**子串**搜索(大小写不敏感,r/ 可省);与 --subreddit 是两个独立谓词,后者是精确匹配")
|
|
2989
|
+
.option("--status <st>", "active|paused|archived")
|
|
2990
|
+
.option("--brand <uuid>", "brandId:只返回**显式绑定**该品牌的监控池(不含全局池)")
|
|
2991
|
+
.option("--industry-keys <list>", "industryKey 过滤(逗号分隔可多个)")
|
|
2992
|
+
.option("--tag <tag>", "标签精确包含(tags 是 jsonb 数组)")
|
|
2993
|
+
.option("--monitor-state <state>", "新帖监听状态,三值:enabled=在监听 | disabled=加过但已停(软停,配置行还在) | never=从没加过。" +
|
|
2994
|
+
"⚠️ 受限主体(client)传这个会 403")
|
|
2995
|
+
.option("--sort-by <f>", "createdAt|subreddit|monitorLastObservedAt")
|
|
2996
|
+
.option("--sort-dir <d>", "asc|desc")
|
|
2984
2997
|
.option("-n, --limit <n>", "limit", "100")
|
|
2985
2998
|
.option("--offset <n>", "offset", "0")
|
|
2986
2999
|
.action(async (opts) => {
|
|
3000
|
+
// 🔴 显式校验只给 --monitor-state 一个,不是漏了别的:本仓 CLI 的既有惯例是
|
|
3001
|
+
// 枚举透传、由服务端 zod 400(见 hot-posts / voc 的 --sort-by),只有**会被静默
|
|
3002
|
+
// 强转**的选项才本地拦(monitors-list 的 `--enabled` 是 Boolean() 强转)。
|
|
3003
|
+
// monitorState 拦下来的理由不是强转,而是**语义**:它最容易被当成布尔用
|
|
3004
|
+
// (`--monitor-state true` / `false`),而服务端那句 "invalid enum value"
|
|
3005
|
+
// 讲不清 disabled(停了的) 与 never(从没加过) 的区别 —— 而用户提这个需求
|
|
3006
|
+
// 要的正是这个区别。
|
|
3007
|
+
if (opts.monitorState !== undefined &&
|
|
3008
|
+
!["enabled", "disabled", "never"].includes(opts.monitorState))
|
|
3009
|
+
throw new Error("--monitor-state 必须是 enabled | disabled | never 之一(三值,不是布尔):" +
|
|
3010
|
+
"disabled = 加过监听后停了(软停,配置行与观察水位都还在,重新启用不会重复报旧帖);" +
|
|
3011
|
+
"never = 压根没加过监听(需要新建配置行)。想要「没在监听的全部」请分两次查。");
|
|
2987
3012
|
const res = await requireClient().listSubredditWatchlists({
|
|
2988
3013
|
campaignId: opts.campaign,
|
|
2989
3014
|
subreddit: opts.subreddit,
|
|
3015
|
+
subredditQuery: opts.subredditQuery,
|
|
3016
|
+
status: opts.status,
|
|
3017
|
+
brandId: opts.brand,
|
|
3018
|
+
industryKeys: opts.industryKeys,
|
|
3019
|
+
tag: opts.tag,
|
|
3020
|
+
monitorState: opts.monitorState,
|
|
3021
|
+
sortBy: opts.sortBy,
|
|
3022
|
+
sortDir: opts.sortDir,
|
|
2990
3023
|
limit: Number(opts.limit),
|
|
2991
3024
|
offset: Number(opts.offset),
|
|
2992
3025
|
});
|
|
@@ -3095,6 +3128,322 @@ intel
|
|
|
3095
3128
|
});
|
|
3096
3129
|
console.log(JSON.stringify(res, null, 2));
|
|
3097
3130
|
});
|
|
3131
|
+
/* ── 监听关键词规则集(0168)。P1「确定性监听骨架」 ──────────────────────────
|
|
3132
|
+
*
|
|
3133
|
+
* 🔴 版本语义:draft 可反复改 → activate 之后**不可原地修改,只能新建版本**。
|
|
3134
|
+
* 🔴 激活/停用**不会重发历史命中** —— 观察的水位在被评估的那一轮就推进了(无论命中与否)。
|
|
3135
|
+
* 这是刻意的:否则调一次关键词,负责人就被历史帖子刷一次屏。追溯需显式 replay(P3)。
|
|
3136
|
+
* ⚠️ 停用当前版本 = 回到「不设关键词门」,报告**恢复全量新帖**,不是一条都不报。
|
|
3137
|
+
* ⚠️ 本期只有 include(排除词是 P2,契约层就表达不出来)。
|
|
3138
|
+
* ────────────────────────────────────────────────────────────────────────── */
|
|
3139
|
+
intel
|
|
3140
|
+
.command("monitor-rulesets-list")
|
|
3141
|
+
.description("GET .../monitors/:monitorId/rule-sets —— 某监听的全部规则版本(version DESC)")
|
|
3142
|
+
.requiredOption("--monitor <uuid>", "Monitor UUID(不是 watchlist id)")
|
|
3143
|
+
.option("--status <status>", "draft|active|retired")
|
|
3144
|
+
.option("-n, --limit <n>", "limit", "20")
|
|
3145
|
+
.action(async (opts) => {
|
|
3146
|
+
const res = await requireClient().listSubredditMonitorRuleSets(opts.monitor, {
|
|
3147
|
+
status: opts.status,
|
|
3148
|
+
limit: Number(opts.limit),
|
|
3149
|
+
});
|
|
3150
|
+
console.log(JSON.stringify(res, null, 2));
|
|
3151
|
+
});
|
|
3152
|
+
intel
|
|
3153
|
+
.command("monitor-ruleset-get")
|
|
3154
|
+
.description("GET .../monitors/:monitorId/rule-sets/:ruleSetId —— 含全部规则")
|
|
3155
|
+
.requiredOption("--monitor <uuid>", "Monitor UUID")
|
|
3156
|
+
.requiredOption("--rule-set <uuid>", "Rule set UUID")
|
|
3157
|
+
.action(async (opts) => {
|
|
3158
|
+
const res = await requireClient().getSubredditMonitorRuleSet(opts.monitor, opts.ruleSet);
|
|
3159
|
+
console.log(JSON.stringify(res, null, 2));
|
|
3160
|
+
});
|
|
3161
|
+
intel
|
|
3162
|
+
.command("monitor-ruleset-create")
|
|
3163
|
+
.description("POST .../monitors/:monitorId/rule-sets —— 新建 **draft** 版本(不会自动激活)。" +
|
|
3164
|
+
"规则形状:[{term,scope?,matchMode?,weight?}];scope=title|body|both,matchMode=word|substring|phrase")
|
|
3165
|
+
.requiredOption("--monitor <uuid>", "Monitor UUID")
|
|
3166
|
+
.option("--lookback <minutes>", "回看窗口,只允许 720(12h)或 1440(24h);run 窗口取所有启用监听里最长的一档")
|
|
3167
|
+
.option("--note <text>", "本版说明(会随版本链留痕,作为审计的一部分)")
|
|
3168
|
+
.option("-j, --json <json>", "规则数组 JSON;省略则建空 draft 再用 rules-set 填")
|
|
3169
|
+
.action(async (opts) => {
|
|
3170
|
+
const res = await requireClient().createSubredditMonitorRuleSet(opts.monitor, {
|
|
3171
|
+
...(opts.lookback === undefined
|
|
3172
|
+
? {}
|
|
3173
|
+
: { lookbackMinutes: Number(opts.lookback) }),
|
|
3174
|
+
...(opts.note === undefined ? {} : { note: opts.note }),
|
|
3175
|
+
...(opts.json === undefined ? {} : { rules: JSON.parse(opts.json) }),
|
|
3176
|
+
});
|
|
3177
|
+
console.log(JSON.stringify(res, null, 2));
|
|
3178
|
+
});
|
|
3179
|
+
intel
|
|
3180
|
+
.command("monitor-ruleset-rules-set")
|
|
3181
|
+
.description("PUT .../rule-sets/:ruleSetId/rules —— **整批替换** draft 的规则(传什么就是最终全量)。" +
|
|
3182
|
+
"已激活的版本会被服务端 409 拒绝:改规则必须新建版本")
|
|
3183
|
+
.requiredOption("--monitor <uuid>", "Monitor UUID")
|
|
3184
|
+
.requiredOption("--rule-set <uuid>", "Rule set UUID(必须是 draft)")
|
|
3185
|
+
.requiredOption("-j, --json <json>", "规则数组 JSON(全量)")
|
|
3186
|
+
.option("--lookback <minutes>", "顺带改回看窗口(720|1440)")
|
|
3187
|
+
.action(async (opts) => {
|
|
3188
|
+
const res = await requireClient().replaceSubredditMonitorRules(opts.monitor, opts.ruleSet, {
|
|
3189
|
+
rules: JSON.parse(opts.json),
|
|
3190
|
+
...(opts.lookback === undefined
|
|
3191
|
+
? {}
|
|
3192
|
+
: { lookbackMinutes: Number(opts.lookback) }),
|
|
3193
|
+
});
|
|
3194
|
+
console.log(JSON.stringify(res, null, 2));
|
|
3195
|
+
});
|
|
3196
|
+
intel
|
|
3197
|
+
.command("monitor-ruleset-activate")
|
|
3198
|
+
.description("POST .../rule-sets/:ruleSetId/activate —— 激活该版本,当前 active 自动 retire。" +
|
|
3199
|
+
"🔴 不会重发历史命中;空规则集会被拒(那等于把整个板块静音)")
|
|
3200
|
+
.requiredOption("--monitor <uuid>", "Monitor UUID")
|
|
3201
|
+
.requiredOption("--rule-set <uuid>", "Rule set UUID(必须是 draft)")
|
|
3202
|
+
.action(async (opts) => {
|
|
3203
|
+
const res = await requireClient().activateSubredditMonitorRuleSet(opts.monitor, opts.ruleSet);
|
|
3204
|
+
console.log(JSON.stringify(res, null, 2));
|
|
3205
|
+
});
|
|
3206
|
+
intel
|
|
3207
|
+
.command("monitor-ruleset-retire")
|
|
3208
|
+
.description("POST .../rule-sets/:ruleSetId/retire —— 停用。" +
|
|
3209
|
+
"⚠️ 停用后该监听回到「不设关键词门」,报告**恢复全量新帖**,不是一条都不报")
|
|
3210
|
+
.requiredOption("--monitor <uuid>", "Monitor UUID")
|
|
3211
|
+
.requiredOption("--rule-set <uuid>", "Rule set UUID")
|
|
3212
|
+
.action(async (opts) => {
|
|
3213
|
+
const res = await requireClient().retireSubredditMonitorRuleSet(opts.monitor, opts.ruleSet);
|
|
3214
|
+
console.log(JSON.stringify(res, null, 2));
|
|
3215
|
+
});
|
|
3216
|
+
intel
|
|
3217
|
+
.command("monitor-ruleset-delete")
|
|
3218
|
+
.description("DELETE .../rule-sets/:ruleSetId —— 只能删 draft。已激活/已停用的版本必须留痕(服务端 409)")
|
|
3219
|
+
.requiredOption("--monitor <uuid>", "Monitor UUID")
|
|
3220
|
+
.requiredOption("--rule-set <uuid>", "Rule set UUID(必须是 draft)")
|
|
3221
|
+
.action(async (opts) => {
|
|
3222
|
+
const res = await requireClient().deleteSubredditMonitorRuleSetDraft(opts.monitor, opts.ruleSet);
|
|
3223
|
+
console.log(JSON.stringify(res, null, 2));
|
|
3224
|
+
});
|
|
3225
|
+
intel
|
|
3226
|
+
.command("monitor-ruleset-dry-run")
|
|
3227
|
+
.description("POST .../rule-sets/:ruleSetId/dry-run —— 🔴 **试跑**:拿过去 12h/24h 的历史帖子跑一遍," +
|
|
3228
|
+
"看会命中什么。**只读:不写命中账本、不推进水位、不发任何通知**。" +
|
|
3229
|
+
"先看 sampled:它是 0 时「0 命中」说明的是没有样本,不是规则太窄。" +
|
|
3230
|
+
"termHitCounts 看哪个词过宽,unusedTerms 看哪个词拼错了")
|
|
3231
|
+
.requiredOption("--monitor <uuid>", "Monitor UUID")
|
|
3232
|
+
.requiredOption("--rule-set <uuid>", "Rule set UUID(draft 或 active 都可以)")
|
|
3233
|
+
.option("--lookback <minutes>", "720|1440;缺省用规则集自己的 lookback")
|
|
3234
|
+
.option("-n, --limit <n>", "取样上限", "200")
|
|
3235
|
+
.option("--matched-only", "只回命中的。⚠️ 默认给全量是刻意的 —— 要判断词是不是配窄了,必须看到没命中的长什么样")
|
|
3236
|
+
.action(async (opts) => {
|
|
3237
|
+
const res = await requireClient().dryRunSubredditMonitorRules(opts.monitor, opts.ruleSet, {
|
|
3238
|
+
...(opts.lookback === undefined
|
|
3239
|
+
? {}
|
|
3240
|
+
: { lookbackMinutes: Number(opts.lookback) }),
|
|
3241
|
+
limit: Number(opts.limit),
|
|
3242
|
+
...(opts.matchedOnly ? { matchedOnly: true } : {}),
|
|
3243
|
+
});
|
|
3244
|
+
console.log(JSON.stringify(res, null, 2));
|
|
3245
|
+
});
|
|
3246
|
+
intel
|
|
3247
|
+
.command("monitor-brand-keyword-candidates")
|
|
3248
|
+
.description("GET .../monitors/:monitorId/brand-keyword-candidates —— 从品牌词取导入候选(只读)。" +
|
|
3249
|
+
"⚠️ 语义是**快照复制**不是订阅:导入后品牌词再变,已激活的规则集不会跟着变 —— " +
|
|
3250
|
+
"否则给某品牌加一个词、或把品牌绑到这个 watchlist,都会让监听范围悄然扩大")
|
|
3251
|
+
.requiredOption("--monitor <uuid>", "Monitor UUID")
|
|
3252
|
+
.option("--brands <ids>", "逗号分隔的品牌 id;缺省取该 watchlist 已绑定的全部品牌")
|
|
3253
|
+
.option("-n, --limit <n>", "limit", "200")
|
|
3254
|
+
.action(async (opts) => {
|
|
3255
|
+
const res = await requireClient().listSubredditMonitorBrandKeywordCandidates(opts.monitor, {
|
|
3256
|
+
...(opts.brands ? { brandIds: String(opts.brands).split(",") } : {}),
|
|
3257
|
+
limit: Number(opts.limit),
|
|
3258
|
+
});
|
|
3259
|
+
console.log(JSON.stringify(res, null, 2));
|
|
3260
|
+
});
|
|
3261
|
+
/* ── 近义词扩展集(0169)· 板块新帖监听 P2 ──────────────────────────────────
|
|
3262
|
+
*
|
|
3263
|
+
* 🔴 链路是 **离线生成 → 审核 → 固化 → 物化进 draft → 激活**,四步缺一不可:
|
|
3264
|
+
* 1. expansion-generate(或 expansion-create 手工录词)→ 产出 **proposed**
|
|
3265
|
+
* 2. expansion-approve → 内容**就此冻结**
|
|
3266
|
+
* 3. ruleset-apply-expansions → **复制**进某个 draft
|
|
3267
|
+
* 4. monitor-ruleset-activate → 才真正生效
|
|
3268
|
+
*
|
|
3269
|
+
* ⛔ **生产匹配路径上没有任何即时展开**。理由:同一原词在不同时间可能得到不同词集
|
|
3270
|
+
* (模型会漂、prompt 会改),即时展开会让「昨天没命中、今天命中了」**无法解释** ——
|
|
3271
|
+
* 而这个监听的全部价值就建立在「能说清为什么命中」上。
|
|
3272
|
+
*
|
|
3273
|
+
* ⚠️ 否决(proposed→retired)与下架(approved→retired)是**两件事**:
|
|
3274
|
+
* 被否决过的词**永远**进不了 active 规则集(DB 激活闸拦);而下架只是不再供新的物化使用,
|
|
3275
|
+
* 已经复制进规则集的词不受影响 —— 它们拿的是拷贝。
|
|
3276
|
+
* ────────────────────────────────────────────────────────────────────────── */
|
|
3277
|
+
intel
|
|
3278
|
+
.command("monitor-expansions-list")
|
|
3279
|
+
.description("GET .../monitors/:monitorId/expansion-sets —— 某监听的近义词扩展集(created_at DESC)。" +
|
|
3280
|
+
"--status proposed 就是「还有哪些等我审」")
|
|
3281
|
+
.requiredOption("--monitor <uuid>", "Monitor UUID")
|
|
3282
|
+
.option("--status <status>", "proposed|approved|retired")
|
|
3283
|
+
.option("-n, --limit <n>", "limit", "20")
|
|
3284
|
+
.action(async (opts) => {
|
|
3285
|
+
const res = await requireClient().listSubredditMonitorExpansionSets(opts.monitor, {
|
|
3286
|
+
status: opts.status,
|
|
3287
|
+
limit: Number(opts.limit),
|
|
3288
|
+
});
|
|
3289
|
+
console.log(JSON.stringify(res, null, 2));
|
|
3290
|
+
});
|
|
3291
|
+
intel
|
|
3292
|
+
.command("monitor-expansion-get")
|
|
3293
|
+
.description("GET .../expansion-sets/:expansionSetId —— 含固化的完整词集")
|
|
3294
|
+
.requiredOption("--monitor <uuid>", "Monitor UUID")
|
|
3295
|
+
.requiredOption("--expansion-set <uuid>", "Expansion set UUID")
|
|
3296
|
+
.action(async (opts) => {
|
|
3297
|
+
const res = await requireClient().getSubredditMonitorExpansionSet(opts.monitor, opts.expansionSet);
|
|
3298
|
+
console.log(JSON.stringify(res, null, 2));
|
|
3299
|
+
});
|
|
3300
|
+
intel
|
|
3301
|
+
.command("monitor-expansion-generate")
|
|
3302
|
+
.description("POST .../expansion-sets/generate —— 🔴 **离线**用 LLM 生成近义词候选。" +
|
|
3303
|
+
"产出恒为 **proposed**,进不了任何 active 规则集,必须再 approve + apply。" +
|
|
3304
|
+
"provider 没配 ⇒ 409(功能未启用,可改用 monitor-expansion-create 手工录词)")
|
|
3305
|
+
.requiredOption("--monitor <uuid>", "Monitor UUID")
|
|
3306
|
+
.requiredOption("--term <text>", "原词(必须是规则集里已有/将有的那个词)")
|
|
3307
|
+
.option("--language <tag>", "语言标记;und=未指定。同一原词不同语言各批一套", "und")
|
|
3308
|
+
.action(async (opts) => {
|
|
3309
|
+
const res = await requireClient().generateSubredditMonitorExpansion(opts.monitor, { baseTerm: opts.term, language: opts.language });
|
|
3310
|
+
console.log(JSON.stringify(res, null, 2));
|
|
3311
|
+
});
|
|
3312
|
+
intel
|
|
3313
|
+
.command("monitor-expansion-create")
|
|
3314
|
+
.description("POST .../expansion-sets —— 人工录入候选词表(provider 没配时的路径)。产出恒为 proposed")
|
|
3315
|
+
.requiredOption("--monitor <uuid>", "Monitor UUID")
|
|
3316
|
+
.requiredOption("--term <text>", "原词")
|
|
3317
|
+
.requiredOption("-j, --json <json>", '候选词数组 JSON,如 ["latte","espresso shot"]')
|
|
3318
|
+
.option("--language <tag>", "语言标记", "und")
|
|
3319
|
+
.action(async (opts) => {
|
|
3320
|
+
const res = await requireClient().createSubredditMonitorExpansionSet(opts.monitor, {
|
|
3321
|
+
baseTerm: opts.term,
|
|
3322
|
+
language: opts.language,
|
|
3323
|
+
terms: JSON.parse(opts.json),
|
|
3324
|
+
});
|
|
3325
|
+
console.log(JSON.stringify(res, null, 2));
|
|
3326
|
+
});
|
|
3327
|
+
intel
|
|
3328
|
+
.command("monitor-expansion-approve")
|
|
3329
|
+
.description("POST .../expansion-sets/:id/approve —— 批准。" +
|
|
3330
|
+
"🔴 批准之后**内容就冻住了**(DB 焊死,只能另提一份新候选)。" +
|
|
3331
|
+
"批准 ≠ 生效:还要 ruleset-apply-expansions 物化 + activate")
|
|
3332
|
+
.requiredOption("--monitor <uuid>", "Monitor UUID")
|
|
3333
|
+
.requiredOption("--expansion-set <uuid>", "Expansion set UUID(必须是 proposed)")
|
|
3334
|
+
.option("--note <text>", "审核意见(写进版本链,是审计的一部分)")
|
|
3335
|
+
.action(async (opts) => {
|
|
3336
|
+
const res = await requireClient().approveSubredditMonitorExpansionSet(opts.monitor, opts.expansionSet, { ...(opts.note === undefined ? {} : { reviewNote: opts.note }) });
|
|
3337
|
+
console.log(JSON.stringify(res, null, 2));
|
|
3338
|
+
});
|
|
3339
|
+
intel
|
|
3340
|
+
.command("monitor-expansion-retire")
|
|
3341
|
+
.description("POST .../expansion-sets/:id/retire —— 否决(从 proposed)或下架(从 approved)。" +
|
|
3342
|
+
"⚠️ **被否决的永远进不了 active 规则集**;而下架只是不再供新的物化使用," +
|
|
3343
|
+
"已经复制进规则集的词不受影响(它们拿的是拷贝)")
|
|
3344
|
+
.requiredOption("--monitor <uuid>", "Monitor UUID")
|
|
3345
|
+
.requiredOption("--expansion-set <uuid>", "Expansion set UUID")
|
|
3346
|
+
.option("--note <text>", "审核意见")
|
|
3347
|
+
.action(async (opts) => {
|
|
3348
|
+
const res = await requireClient().retireSubredditMonitorExpansionSet(opts.monitor, opts.expansionSet, { ...(opts.note === undefined ? {} : { reviewNote: opts.note }) });
|
|
3349
|
+
console.log(JSON.stringify(res, null, 2));
|
|
3350
|
+
});
|
|
3351
|
+
intel
|
|
3352
|
+
.command("monitor-expansion-delete")
|
|
3353
|
+
.description("DELETE .../expansion-sets/:id —— 只能删 proposed。审核过的必须留痕(改用 retire)")
|
|
3354
|
+
.requiredOption("--monitor <uuid>", "Monitor UUID")
|
|
3355
|
+
.requiredOption("--expansion-set <uuid>", "Expansion set UUID")
|
|
3356
|
+
.action(async (opts) => {
|
|
3357
|
+
const res = await requireClient().deleteSubredditMonitorExpansionSet(opts.monitor, opts.expansionSet);
|
|
3358
|
+
console.log(JSON.stringify(res, null, 2));
|
|
3359
|
+
});
|
|
3360
|
+
intel
|
|
3361
|
+
.command("monitor-ruleset-apply-expansions")
|
|
3362
|
+
.description("POST .../rule-sets/:ruleSetId/apply-expansions —— 🔴 **物化**:把 approved 的近义词" +
|
|
3363
|
+
"**复制**成 draft 规则集里的真实规则行(与品牌词导入同一手法)。" +
|
|
3364
|
+
"⚠️ 物化 ≠ 生效,之后还要 monitor-ruleset-activate。" +
|
|
3365
|
+
"⚠️ 点名的扩展集里有未批准的 ⇒ 409(⛔ 不静默少物化几条 —— 那会让你以为词已经生效了)")
|
|
3366
|
+
.requiredOption("--monitor <uuid>", "Monitor UUID")
|
|
3367
|
+
.requiredOption("--rule-set <uuid>", "Rule set UUID(必须是 draft)")
|
|
3368
|
+
.option("-j, --json <json>", "限定扩展集 id 数组 JSON;省略 = 该监听全部 approved 的")
|
|
3369
|
+
.action(async (opts) => {
|
|
3370
|
+
const res = await requireClient().applyExpansionsToRuleSet(opts.monitor, opts.ruleSet, {
|
|
3371
|
+
...(opts.json === undefined
|
|
3372
|
+
? {}
|
|
3373
|
+
: { expansionSetIds: JSON.parse(opts.json) }),
|
|
3374
|
+
});
|
|
3375
|
+
console.log(JSON.stringify(res, null, 2));
|
|
3376
|
+
});
|
|
3377
|
+
/* ── 标签 spec / taxonomy(0169)· 系统级 ─────────────────────────────────
|
|
3378
|
+
* ⛔ 刻意**不走**四眼审批(需求明说太重、会拖慢提醒),但发布本身是不可变版本链。
|
|
3379
|
+
* ⚠️ taxonomyVersion **全局唯一**:换 prompt/换模型/加 key 都要起新值,
|
|
3380
|
+
* 否则标签行会互相覆盖,「这个标签是哪版规则打的」失去答案。
|
|
3381
|
+
* ⚠️ 停用当前 spec = **标签功能整体关闭**(卡片不再显示标签行)。
|
|
3382
|
+
* ─────────────────────────────────────────────────────────────────────── */
|
|
3383
|
+
intel
|
|
3384
|
+
.command("monitor-label-specs-list")
|
|
3385
|
+
.description("GET .../label-specs —— 标签 spec 版本链(version DESC)")
|
|
3386
|
+
.option("--status <status>", "draft|active|retired")
|
|
3387
|
+
.option("-n, --limit <n>", "limit", "20")
|
|
3388
|
+
.action(async (opts) => {
|
|
3389
|
+
const res = await requireClient().listSubredditMonitorLabelSpecs({
|
|
3390
|
+
status: opts.status,
|
|
3391
|
+
limit: Number(opts.limit),
|
|
3392
|
+
});
|
|
3393
|
+
console.log(JSON.stringify(res, null, 2));
|
|
3394
|
+
});
|
|
3395
|
+
intel
|
|
3396
|
+
.command("monitor-label-spec-create")
|
|
3397
|
+
.description("POST .../label-specs —— 新建 **draft** spec(不会自动激活)。" +
|
|
3398
|
+
"--keys 是稳定 key 白名单,模型只能从里面选,自创的一律丢弃")
|
|
3399
|
+
.requiredOption("--taxonomy <version>", "🔴 全局唯一。换 prompt/换模型/加 key 都要起新值")
|
|
3400
|
+
.requiredOption("--keys <keys>", "逗号分隔,如 recommendation_request,comparison,review")
|
|
3401
|
+
.requiredOption("--prompt-version <v>", "prompt 版本(审计的一部分)")
|
|
3402
|
+
.option("--model <name>", "指定模型;省略则用 env 里那一档")
|
|
3403
|
+
.option("--temperature <n>", "温度")
|
|
3404
|
+
.option("--note <text>", "本版说明")
|
|
3405
|
+
.action(async (opts) => {
|
|
3406
|
+
const res = await requireClient().createSubredditMonitorLabelSpec({
|
|
3407
|
+
taxonomyVersion: opts.taxonomy,
|
|
3408
|
+
labelKeys: String(opts.keys)
|
|
3409
|
+
.split(",")
|
|
3410
|
+
.map((k) => k.trim())
|
|
3411
|
+
.filter(Boolean),
|
|
3412
|
+
promptVersion: opts.promptVersion,
|
|
3413
|
+
...(opts.model === undefined ? {} : { model: opts.model }),
|
|
3414
|
+
...(opts.temperature === undefined
|
|
3415
|
+
? {}
|
|
3416
|
+
: { temperature: Number(opts.temperature) }),
|
|
3417
|
+
...(opts.note === undefined ? {} : { note: opts.note }),
|
|
3418
|
+
});
|
|
3419
|
+
console.log(JSON.stringify(res, null, 2));
|
|
3420
|
+
});
|
|
3421
|
+
intel
|
|
3422
|
+
.command("monitor-label-spec-activate")
|
|
3423
|
+
.description("POST .../label-specs/:specId/activate —— 激活(当前 active 自动 retire)。" +
|
|
3424
|
+
"激活后 active 版本**不可原地改**,只能新建版本")
|
|
3425
|
+
.requiredOption("--spec <uuid>", "Label spec UUID(必须是 draft)")
|
|
3426
|
+
.action(async (opts) => {
|
|
3427
|
+
const res = await requireClient().activateSubredditMonitorLabelSpec(opts.spec);
|
|
3428
|
+
console.log(JSON.stringify(res, null, 2));
|
|
3429
|
+
});
|
|
3430
|
+
intel
|
|
3431
|
+
.command("monitor-label-spec-retire")
|
|
3432
|
+
.description("POST .../label-specs/:specId/retire —— ⚠️ 停用 = **标签功能整体关闭**" +
|
|
3433
|
+
"(卡片不再显示标签行);新帖提醒本身不受影响")
|
|
3434
|
+
.requiredOption("--spec <uuid>", "Label spec UUID(必须是 active)")
|
|
3435
|
+
.action(async (opts) => {
|
|
3436
|
+
const res = await requireClient().retireSubredditMonitorLabelSpec(opts.spec);
|
|
3437
|
+
console.log(JSON.stringify(res, null, 2));
|
|
3438
|
+
});
|
|
3439
|
+
intel
|
|
3440
|
+
.command("monitor-label-spec-delete")
|
|
3441
|
+
.description("DELETE .../label-specs/:specId —— 只能删 draft")
|
|
3442
|
+
.requiredOption("--spec <uuid>", "Label spec UUID")
|
|
3443
|
+
.action(async (opts) => {
|
|
3444
|
+
const res = await requireClient().deleteSubredditMonitorLabelSpecDraft(opts.spec);
|
|
3445
|
+
console.log(JSON.stringify(res, null, 2));
|
|
3446
|
+
});
|
|
3098
3447
|
intel
|
|
3099
3448
|
.command("hot-posts")
|
|
3100
3449
|
.description("GET .../subreddit-intelligence/hot-posts")
|
|
@@ -4064,7 +4413,10 @@ calendar
|
|
|
4064
4413
|
.option("--status <st>", "scheduled|running|succeeded|…")
|
|
4065
4414
|
.option("--from <iso>", "from ISO 时间")
|
|
4066
4415
|
.option("--to <iso>", "to ISO 时间")
|
|
4067
|
-
.option("-n, --limit <n>", "limit", "50")
|
|
4416
|
+
.option("-n, --limit <n>", "limit(服务端上限 200)", "50")
|
|
4417
|
+
// 🔴 服务端一直支持 offset,此前只有 CLI 没暴露 —— 调用方要么撞上限,
|
|
4418
|
+
// 要么自己拿时间窗二分绕(矩阵侧实测:那样会静默漏掉更早的条目且不报错)。
|
|
4419
|
+
.option("--offset <n>", "offset(翻页;默认 0)")
|
|
4068
4420
|
.action(async (opts) => {
|
|
4069
4421
|
const res = await requireClient().listCalendarEntries(resolveTeamId(opts.team), {
|
|
4070
4422
|
campaignId: opts.campaign,
|
|
@@ -4074,6 +4426,7 @@ calendar
|
|
|
4074
4426
|
from: opts.from,
|
|
4075
4427
|
to: opts.to,
|
|
4076
4428
|
limit: Number(opts.limit),
|
|
4429
|
+
...(opts.offset === undefined ? {} : { offset: Number(opts.offset) }),
|
|
4077
4430
|
});
|
|
4078
4431
|
console.log(JSON.stringify(res, null, 2));
|
|
4079
4432
|
});
|
|
@@ -4109,6 +4462,8 @@ reddit
|
|
|
4109
4462
|
.option("--repost-type <t>", "repostType filter (none|first|second)")
|
|
4110
4463
|
.option("--contains-brand-keyword <v>", "true | false | unset")
|
|
4111
4464
|
.option("--primary-product <text>", "primary product contains match")
|
|
4465
|
+
.option("--seq-no-min <n>", "序列号下界(含);与 --seq-no-max 组成闭区间,无编号(null)的帖会落选")
|
|
4466
|
+
.option("--seq-no-max <n>", "序列号上界(含)")
|
|
4112
4467
|
.option("--sort-by <f>", "score|comments_count|posted_at|view_count|seq_no")
|
|
4113
4468
|
.option("--sort-dir <d>", "asc|desc")
|
|
4114
4469
|
.option("-n, --limit <n>", "limit per page", "100")
|
|
@@ -4127,6 +4482,8 @@ reddit
|
|
|
4127
4482
|
repostType: opts.repostType,
|
|
4128
4483
|
containsBrandKeyword: opts.containsBrandKeyword,
|
|
4129
4484
|
primaryProduct: opts.primaryProduct,
|
|
4485
|
+
seqNoMin: opts.seqNoMin !== undefined ? Number(opts.seqNoMin) : undefined,
|
|
4486
|
+
seqNoMax: opts.seqNoMax !== undefined ? Number(opts.seqNoMax) : undefined,
|
|
4130
4487
|
sortBy: opts.sortBy,
|
|
4131
4488
|
sortDir: opts.sortDir,
|
|
4132
4489
|
limit: Number(opts.limit),
|