@geoly-ai/social-hub-cli 0.3.24 → 0.3.25
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 +7 -0
- package/dist/cmd-manifest.json +15 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/register-subreddit-assets.d.ts +5 -0
- package/dist/register-subreddit-assets.d.ts.map +1 -0
- package/dist/register-subreddit-assets.js +347 -0
- package/dist/register-subreddit-assets.js.map +1 -0
- package/dist/register-subreddit-assets.test.d.ts +2 -0
- package/dist/register-subreddit-assets.test.d.ts.map +1 -0
- package/dist/register-subreddit-assets.test.js +124 -0
- package/dist/register-subreddit-assets.test.js.map +1 -0
- package/package.json +3 -3
- package/skills/manifest.json +1 -1
- package/skills/social-hub-content-review/SKILL.md +10 -0
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
/** 逗号分隔的权限位 → 数组。空串/未给 → undefined(= 不声明,不是"声明为空")。 */
|
|
3
|
+
export declare function parseDeclaredPermissions(raw: string | undefined): string[] | undefined;
|
|
4
|
+
export declare function registerSubredditAssetCommands(program: Command): void;
|
|
5
|
+
//# sourceMappingURL=register-subreddit-assets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register-subreddit-assets.d.ts","sourceRoot":"","sources":["../src/register-subreddit-assets.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA0BzC,uDAAuD;AACvD,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,MAAM,GAAG,SAAS,GACtB,MAAM,EAAE,GAAG,SAAS,CAOtB;AAED,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA+arE"}
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { hubRequest } from "./client.js";
|
|
2
|
+
import { assertApplyOrDryRun } from "./dry-run.js";
|
|
3
|
+
import { printResult, withCliErrorBoundary } from "./output.js";
|
|
4
|
+
import { withPermissionGates } from "./permission-runner.js";
|
|
5
|
+
/**
|
|
6
|
+
* 自建板块资产与版主绑定(P1A)—— `social-hub subreddit-asset ...`。
|
|
7
|
+
*
|
|
8
|
+
* ## 为什么必须有这层
|
|
9
|
+
* 需求明确「存量 20 板块 + 20 条版主绑定靠这组接口人工录入,**不做飞书导入代码**」。
|
|
10
|
+
* 没有 CLI,那 20 条就只能靠手写 curl —— 而其中每一条都要带 `rowVersion` 做 CAS、
|
|
11
|
+
* 要能当场看出「这个版主号是不是已经被封了」。
|
|
12
|
+
*
|
|
13
|
+
* ## 三个必须知道的语义
|
|
14
|
+
* 1. **写端点在 API 侧另有 `isUnrestrictedAdminSubject` 硬门**:连 role=admin 的
|
|
15
|
+
* API key 都会被拒。所以这里所有写命令都带 `userOnly: true`,本地先拒掉 api_key,
|
|
16
|
+
* 省掉一次必然 403 的往返。
|
|
17
|
+
* 2. **建一条绑定 = 把一个账号的全部自动化永久停掉**(enforce 档下,无个体 override)。
|
|
18
|
+
* 因此写命令一律走 `--dry-run` / `--apply` 双闸。
|
|
19
|
+
* 3. `protection set --mode enforce` 是**全局**动作,一次影响全部 20 个账号。
|
|
20
|
+
*/
|
|
21
|
+
const BASE = "/v1/system/subreddit-assets";
|
|
22
|
+
const PROTECTION = "/v1/system/moderator-protection";
|
|
23
|
+
/** 逗号分隔的权限位 → 数组。空串/未给 → undefined(= 不声明,不是"声明为空")。 */
|
|
24
|
+
export function parseDeclaredPermissions(raw) {
|
|
25
|
+
if (raw === undefined)
|
|
26
|
+
return undefined;
|
|
27
|
+
const parts = raw
|
|
28
|
+
.split(",")
|
|
29
|
+
.map((part) => part.trim())
|
|
30
|
+
.filter((part) => part.length > 0);
|
|
31
|
+
return parts.length > 0 ? parts : [];
|
|
32
|
+
}
|
|
33
|
+
export function registerSubredditAssetCommands(program) {
|
|
34
|
+
const asset = program
|
|
35
|
+
.command("subreddit-asset")
|
|
36
|
+
.description("自建板块资产与版主绑定(系统级;读非 client 可用,写仅真实登录 admin 且拒 API Key)");
|
|
37
|
+
/* ── 读 ──────────────────────────────────────────────────────────────── */
|
|
38
|
+
asset
|
|
39
|
+
.command("list")
|
|
40
|
+
.description("GET /v1/system/subreddit-assets — 列出自建板块(含版主绑定与只读风险投影)")
|
|
41
|
+
.option("--asset-status <s>", "active | archived | lost")
|
|
42
|
+
.option("--moderator-risk <r>", "zero | single | redundant —— 零/单版主的发现入口(只发现,不做写入硬拒绝)")
|
|
43
|
+
.option("--brand-id <uuid>", "只看挂在该品牌下的资产")
|
|
44
|
+
.option("--limit <n>", "最多返回条数(1-200)")
|
|
45
|
+
.option("--offset <n>", "偏移")
|
|
46
|
+
.action(async (opts) => {
|
|
47
|
+
await withCliErrorBoundary(async () => {
|
|
48
|
+
await withPermissionGates({
|
|
49
|
+
command: "subreddit-asset list",
|
|
50
|
+
gates: [{ resource: "subredditAsset", action: "list" }],
|
|
51
|
+
}, async () => {
|
|
52
|
+
const params = new URLSearchParams();
|
|
53
|
+
if (opts.assetStatus)
|
|
54
|
+
params.set("assetStatus", opts.assetStatus);
|
|
55
|
+
if (opts.moderatorRisk)
|
|
56
|
+
params.set("moderatorRisk", opts.moderatorRisk);
|
|
57
|
+
if (opts.brandId)
|
|
58
|
+
params.set("brandId", opts.brandId);
|
|
59
|
+
if (opts.limit)
|
|
60
|
+
params.set("limit", opts.limit);
|
|
61
|
+
if (opts.offset)
|
|
62
|
+
params.set("offset", opts.offset);
|
|
63
|
+
const qs = params.toString();
|
|
64
|
+
printResult(await hubRequest(`${BASE}${qs ? `?${qs}` : ""}`));
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
asset
|
|
69
|
+
.command("get")
|
|
70
|
+
.description("GET /v1/system/subreddit-assets/:id — 单条资产详情")
|
|
71
|
+
.requiredOption("--id <uuid>", "owned_subreddits.id")
|
|
72
|
+
.action(async (opts) => {
|
|
73
|
+
await withCliErrorBoundary(async () => {
|
|
74
|
+
await withPermissionGates({
|
|
75
|
+
command: "subreddit-asset get",
|
|
76
|
+
gates: [{ resource: "subredditAsset", action: "read" }],
|
|
77
|
+
}, async () => {
|
|
78
|
+
printResult(await hubRequest(`${BASE}/${encodeURIComponent(opts.id)}`));
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
/* ── 写:板块身份 ────────────────────────────────────────────────────── */
|
|
83
|
+
asset
|
|
84
|
+
.command("register-subreddit")
|
|
85
|
+
.description("POST .../subreddits — 只登记 canonical 板块身份,**不建资产**(幂等)。先录名字核对拼写,再逐个确认所有权")
|
|
86
|
+
.requiredOption("--name <subreddit>", "板块名(带不带 r/ 都行,服务端归一)")
|
|
87
|
+
.option("--display-name <s>", "展示用原样名")
|
|
88
|
+
.option("--dry-run", "预览", false)
|
|
89
|
+
.option("--apply", "执行写入", false)
|
|
90
|
+
.action(async (opts) => {
|
|
91
|
+
await withCliErrorBoundary(async () => {
|
|
92
|
+
await withPermissionGates({
|
|
93
|
+
command: "subreddit-asset register-subreddit",
|
|
94
|
+
gates: [{ resource: "subredditAsset", action: "create" }],
|
|
95
|
+
userOnly: true,
|
|
96
|
+
}, async () => {
|
|
97
|
+
const body = { name: opts.name };
|
|
98
|
+
if (opts.displayName)
|
|
99
|
+
body.displayName = opts.displayName;
|
|
100
|
+
if (!assertApplyOrDryRun(opts, {
|
|
101
|
+
command: "subreddit-asset register-subreddit",
|
|
102
|
+
danger: "write",
|
|
103
|
+
summary: body,
|
|
104
|
+
})) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
printResult(await hubRequest(`${BASE}/subreddits`, { method: "POST", body }));
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
/* ── 写:资产 ────────────────────────────────────────────────────────── */
|
|
112
|
+
asset
|
|
113
|
+
.command("register")
|
|
114
|
+
.description("POST /v1/system/subreddit-assets — 登记一条自建板块资产(幂等;已存在返回 created=false)")
|
|
115
|
+
.requiredOption("--name <subreddit>", "板块名(带不带 r/ 都行,服务端归一)")
|
|
116
|
+
.option("--display-name <s>", "展示用原样名")
|
|
117
|
+
.option("--brand-id <uuid>", "可选:板块是公司资产,不挂品牌也行(不挂则品牌受限主体看不到它)")
|
|
118
|
+
.option("--asset-status <s>", "active(默认)| archived | lost")
|
|
119
|
+
.option("--notes <s>", "备注")
|
|
120
|
+
.option("--dry-run", "预览", false)
|
|
121
|
+
.option("--apply", "执行写入", false)
|
|
122
|
+
.action(async (opts) => {
|
|
123
|
+
await withCliErrorBoundary(async () => {
|
|
124
|
+
await withPermissionGates({
|
|
125
|
+
command: "subreddit-asset register",
|
|
126
|
+
gates: [{ resource: "subredditAsset", action: "create" }],
|
|
127
|
+
userOnly: true,
|
|
128
|
+
}, async () => {
|
|
129
|
+
const body = { name: opts.name };
|
|
130
|
+
if (opts.displayName)
|
|
131
|
+
body.displayName = opts.displayName;
|
|
132
|
+
if (opts.brandId)
|
|
133
|
+
body.brandId = opts.brandId;
|
|
134
|
+
if (opts.assetStatus)
|
|
135
|
+
body.assetStatus = opts.assetStatus;
|
|
136
|
+
if (opts.notes)
|
|
137
|
+
body.notes = opts.notes;
|
|
138
|
+
if (!assertApplyOrDryRun(opts, {
|
|
139
|
+
command: "subreddit-asset register",
|
|
140
|
+
danger: "write",
|
|
141
|
+
summary: body,
|
|
142
|
+
})) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
printResult(await hubRequest(BASE, { method: "POST", body }));
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
asset
|
|
150
|
+
.command("set-status")
|
|
151
|
+
.description("PATCH .../:id/status — 改资产状态(CAS)。⚠️ lost 解除该板块所有绑定账号的阻断并停止核验;archived 保留阻断")
|
|
152
|
+
.requiredOption("--id <uuid>", "owned_subreddits.id")
|
|
153
|
+
.requiredOption("--asset-status <s>", "active | archived | lost")
|
|
154
|
+
.requiredOption("--row-version <n>", "乐观锁;漂移返 409")
|
|
155
|
+
.option("--reason <s>", "变更原因(进审计)")
|
|
156
|
+
.option("--dry-run", "预览", false)
|
|
157
|
+
.option("--apply", "执行写入", false)
|
|
158
|
+
.action(async (opts) => {
|
|
159
|
+
await withCliErrorBoundary(async () => {
|
|
160
|
+
await withPermissionGates({
|
|
161
|
+
command: "subreddit-asset set-status",
|
|
162
|
+
gates: [{ resource: "subredditAsset", action: "update" }],
|
|
163
|
+
userOnly: true,
|
|
164
|
+
}, async () => {
|
|
165
|
+
const body = {
|
|
166
|
+
assetStatus: opts.assetStatus,
|
|
167
|
+
rowVersion: Number(opts.rowVersion),
|
|
168
|
+
};
|
|
169
|
+
if (opts.reason)
|
|
170
|
+
body.reason = opts.reason;
|
|
171
|
+
if (!assertApplyOrDryRun(opts, {
|
|
172
|
+
command: "subreddit-asset set-status",
|
|
173
|
+
danger: "write",
|
|
174
|
+
summary: body,
|
|
175
|
+
})) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
printResult(await hubRequest(`${BASE}/${encodeURIComponent(opts.id)}/status`, { method: "PATCH", body }));
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
/* ── 写:版主绑定 ────────────────────────────────────────────────────── */
|
|
183
|
+
const mod = asset
|
|
184
|
+
.command("moderator")
|
|
185
|
+
.description("版主绑定:加 / 撤销 / 更替(更替 = 关旧建新,不就地改账号)");
|
|
186
|
+
mod
|
|
187
|
+
.command("add")
|
|
188
|
+
.description("POST .../:id/moderators — 加一条版主绑定。⚠️ 返回 accountAtRisk=true 表示该账号已 banned/restricted")
|
|
189
|
+
.requiredOption("--owned-subreddit-id <uuid>", "owned_subreddits.id")
|
|
190
|
+
.requiredOption("--social-account-id <uuid>", "我方账号 id(可跨 team)")
|
|
191
|
+
.option("--not-primary", "不作为该板块的责任主号", false)
|
|
192
|
+
.option("--declared-permissions <list>", "人工声明的权限位,逗号分隔(如 all,posts,flair)")
|
|
193
|
+
.option("--notes <s>", "备注")
|
|
194
|
+
.option("--dry-run", "预览", false)
|
|
195
|
+
.option("--apply", "执行写入", false)
|
|
196
|
+
.action(async (opts) => {
|
|
197
|
+
await withCliErrorBoundary(async () => {
|
|
198
|
+
await withPermissionGates({
|
|
199
|
+
command: "subreddit-asset moderator add",
|
|
200
|
+
gates: [{ resource: "subredditAsset", action: "update" }],
|
|
201
|
+
userOnly: true,
|
|
202
|
+
}, async () => {
|
|
203
|
+
const body = {
|
|
204
|
+
socialAccountId: opts.socialAccountId,
|
|
205
|
+
};
|
|
206
|
+
if (opts.notPrimary)
|
|
207
|
+
body.isPrimary = false;
|
|
208
|
+
const perms = parseDeclaredPermissions(opts.declaredPermissions);
|
|
209
|
+
if (perms !== undefined)
|
|
210
|
+
body.declaredPermissions = perms;
|
|
211
|
+
if (opts.notes)
|
|
212
|
+
body.notes = opts.notes;
|
|
213
|
+
if (!assertApplyOrDryRun(opts, {
|
|
214
|
+
command: "subreddit-asset moderator add",
|
|
215
|
+
danger: "write",
|
|
216
|
+
summary: body,
|
|
217
|
+
})) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
printResult(await hubRequest(`${BASE}/${encodeURIComponent(opts.ownedSubredditId)}/moderators`, { method: "POST", body }));
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
mod
|
|
225
|
+
.command("revoke")
|
|
226
|
+
.description("POST .../moderators/:bindingId/revoke — 撤销绑定(CAS)。行永久保留,历史不可抹")
|
|
227
|
+
.requiredOption("--binding-id <uuid>", "subreddit_moderator_bindings.id")
|
|
228
|
+
.requiredOption("--row-version <n>", "乐观锁;漂移返 409")
|
|
229
|
+
.option("--reason <s>", "撤销原因(进审计)")
|
|
230
|
+
.option("--dry-run", "预览", false)
|
|
231
|
+
.option("--apply", "执行写入", false)
|
|
232
|
+
.action(async (opts) => {
|
|
233
|
+
await withCliErrorBoundary(async () => {
|
|
234
|
+
await withPermissionGates({
|
|
235
|
+
command: "subreddit-asset moderator revoke",
|
|
236
|
+
gates: [{ resource: "subredditAsset", action: "update" }],
|
|
237
|
+
userOnly: true,
|
|
238
|
+
}, async () => {
|
|
239
|
+
const body = {
|
|
240
|
+
rowVersion: Number(opts.rowVersion),
|
|
241
|
+
};
|
|
242
|
+
if (opts.reason)
|
|
243
|
+
body.reason = opts.reason;
|
|
244
|
+
if (!assertApplyOrDryRun(opts, {
|
|
245
|
+
command: "subreddit-asset moderator revoke",
|
|
246
|
+
danger: "write",
|
|
247
|
+
summary: body,
|
|
248
|
+
})) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
printResult(await hubRequest(`${BASE}/moderators/${encodeURIComponent(opts.bindingId)}/revoke`, { method: "POST", body }));
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
mod
|
|
256
|
+
.command("replace")
|
|
257
|
+
.description("POST .../moderators/replace — 更替版主(关旧建新;就地改 accountId 会永久丢失「历史上是谁」)")
|
|
258
|
+
.requiredOption("--binding-id <uuid>", "被替换的绑定(必须仍 active)")
|
|
259
|
+
.requiredOption("--row-version <n>", "乐观锁;漂移返 409")
|
|
260
|
+
.requiredOption("--next-social-account-id <uuid>", "接任的账号 id")
|
|
261
|
+
.option("--declared-permissions <list>", "新绑定的权限位,逗号分隔")
|
|
262
|
+
.option("--reason <s>", "更替原因(进审计)")
|
|
263
|
+
.option("--dry-run", "预览", false)
|
|
264
|
+
.option("--apply", "执行写入", false)
|
|
265
|
+
.action(async (opts) => {
|
|
266
|
+
await withCliErrorBoundary(async () => {
|
|
267
|
+
await withPermissionGates({
|
|
268
|
+
command: "subreddit-asset moderator replace",
|
|
269
|
+
gates: [{ resource: "subredditAsset", action: "update" }],
|
|
270
|
+
userOnly: true,
|
|
271
|
+
}, async () => {
|
|
272
|
+
const body = {
|
|
273
|
+
bindingId: opts.bindingId,
|
|
274
|
+
rowVersion: Number(opts.rowVersion),
|
|
275
|
+
nextSocialAccountId: opts.nextSocialAccountId,
|
|
276
|
+
};
|
|
277
|
+
const perms = parseDeclaredPermissions(opts.declaredPermissions);
|
|
278
|
+
if (perms !== undefined)
|
|
279
|
+
body.declaredPermissions = perms;
|
|
280
|
+
if (opts.reason)
|
|
281
|
+
body.reason = opts.reason;
|
|
282
|
+
if (!assertApplyOrDryRun(opts, {
|
|
283
|
+
command: "subreddit-asset moderator replace",
|
|
284
|
+
danger: "write",
|
|
285
|
+
summary: body,
|
|
286
|
+
})) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
printResult(await hubRequest(`${BASE}/moderators/replace`, {
|
|
290
|
+
method: "POST",
|
|
291
|
+
body,
|
|
292
|
+
}));
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
/* ── rollout 档位 ────────────────────────────────────────────────────── */
|
|
297
|
+
const protection = asset
|
|
298
|
+
.command("protection")
|
|
299
|
+
.description("全局版主保护档位(off → shadow → enforce)。无个体 override,只保留受审计的全局回退");
|
|
300
|
+
protection
|
|
301
|
+
.command("get")
|
|
302
|
+
.description("GET /v1/system/moderator-protection — 读当前档位与 rowVersion")
|
|
303
|
+
.action(async () => {
|
|
304
|
+
await withCliErrorBoundary(async () => {
|
|
305
|
+
await withPermissionGates({
|
|
306
|
+
command: "subreddit-asset protection get",
|
|
307
|
+
gates: [{ resource: "subredditAsset", action: "read" }],
|
|
308
|
+
}, async () => {
|
|
309
|
+
printResult(await hubRequest(PROTECTION));
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
protection
|
|
314
|
+
.command("set")
|
|
315
|
+
.description("PATCH /v1/system/moderator-protection — 改全局档位(CAS + 审计)。⚠️ enforce 一次影响全部持有绑定的账号")
|
|
316
|
+
.requiredOption("--mode <m>", "off | shadow | enforce")
|
|
317
|
+
.requiredOption("--row-version <n>", "乐观锁;漂移返 409(不接受 last-writer-wins)")
|
|
318
|
+
.option("--note <s>", "变更说明(进审计)")
|
|
319
|
+
.option("--dry-run", "预览", false)
|
|
320
|
+
.option("--apply", "执行写入", false)
|
|
321
|
+
.action(async (opts) => {
|
|
322
|
+
await withCliErrorBoundary(async () => {
|
|
323
|
+
await withPermissionGates({
|
|
324
|
+
command: "subreddit-asset protection set",
|
|
325
|
+
gates: [{ resource: "subredditAsset", action: "update" }],
|
|
326
|
+
userOnly: true,
|
|
327
|
+
}, async () => {
|
|
328
|
+
const body = {
|
|
329
|
+
mode: opts.mode,
|
|
330
|
+
rowVersion: Number(opts.rowVersion),
|
|
331
|
+
};
|
|
332
|
+
if (opts.note)
|
|
333
|
+
body.note = opts.note;
|
|
334
|
+
if (!assertApplyOrDryRun(opts, {
|
|
335
|
+
command: "subreddit-asset protection set",
|
|
336
|
+
// 全局阻断开关:切 enforce 会一次停掉全部版主账号的自动化。
|
|
337
|
+
danger: "write",
|
|
338
|
+
summary: body,
|
|
339
|
+
})) {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
printResult(await hubRequest(PROTECTION, { method: "PATCH", body }));
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
//# sourceMappingURL=register-subreddit-assets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register-subreddit-assets.js","sourceRoot":"","sources":["../src/register-subreddit-assets.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register-subreddit-assets.test.d.ts","sourceRoot":"","sources":["../src/register-subreddit-assets.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { parseDeclaredPermissions, registerSubredditAssetCommands, } from "./register-subreddit-assets.js";
|
|
4
|
+
import { collectCommandPaths } from "./cmd-manifest.js";
|
|
5
|
+
describe("parseDeclaredPermissions", () => {
|
|
6
|
+
it("逗号分隔 → 数组,去空白", () => {
|
|
7
|
+
expect(parseDeclaredPermissions("all, posts ,flair")).toEqual([
|
|
8
|
+
"all",
|
|
9
|
+
"posts",
|
|
10
|
+
"flair",
|
|
11
|
+
]);
|
|
12
|
+
});
|
|
13
|
+
/**
|
|
14
|
+
* 🔴 「没给这个选项」与「显式给了空」必须可区分。
|
|
15
|
+
* 前者 → `undefined`(不写这个字段,服务端保留既有声明);
|
|
16
|
+
* 后者 → `[]`(显式声明"没有任何权限位")。
|
|
17
|
+
* 混成一个值的话,一次漏传选项就会把人工声明的权限位清空 —— 而 declaredPermissions
|
|
18
|
+
* 是「我们应该拥有什么权限」的人工记录,被静默清掉就再也对不出来了。
|
|
19
|
+
*/
|
|
20
|
+
it("未给选项 → undefined(不是空数组)", () => {
|
|
21
|
+
expect(parseDeclaredPermissions(undefined)).toBeUndefined();
|
|
22
|
+
});
|
|
23
|
+
it("给了空串 / 全是分隔符 → 空数组(显式声明为空)", () => {
|
|
24
|
+
expect(parseDeclaredPermissions("")).toEqual([]);
|
|
25
|
+
expect(parseDeclaredPermissions(" , , ")).toEqual([]);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
describe("命令树", () => {
|
|
29
|
+
it("命令树完整(读 2 / 身份写 1 / 资产写 2 / 绑定写 3 / 档位 2 + 两个分组)", () => {
|
|
30
|
+
const program = new Command();
|
|
31
|
+
registerSubredditAssetCommands(program);
|
|
32
|
+
const paths = collectCommandPaths(program).map((entry) => entry.path);
|
|
33
|
+
expect(paths).toEqual(expect.arrayContaining([
|
|
34
|
+
"subreddit-asset",
|
|
35
|
+
"subreddit-asset list",
|
|
36
|
+
"subreddit-asset get",
|
|
37
|
+
"subreddit-asset register-subreddit",
|
|
38
|
+
"subreddit-asset register",
|
|
39
|
+
"subreddit-asset set-status",
|
|
40
|
+
"subreddit-asset moderator",
|
|
41
|
+
"subreddit-asset moderator add",
|
|
42
|
+
"subreddit-asset moderator revoke",
|
|
43
|
+
"subreddit-asset moderator replace",
|
|
44
|
+
"subreddit-asset protection",
|
|
45
|
+
"subreddit-asset protection get",
|
|
46
|
+
"subreddit-asset protection set",
|
|
47
|
+
]));
|
|
48
|
+
});
|
|
49
|
+
/**
|
|
50
|
+
* 🔴 每一个**写**命令都必须有 `--dry-run` / `--apply` 双闸。
|
|
51
|
+
* 建一条绑定 = enforce 档下把一个账号的全部自动化永久停掉(无个体 override);
|
|
52
|
+
* `protection set --mode enforce` 更是一次影响全部 20 个账号。
|
|
53
|
+
* 少一个闸就意味着有条路径能不经确认做出这种事。
|
|
54
|
+
*/
|
|
55
|
+
it("所有写命令都带 --dry-run / --apply", () => {
|
|
56
|
+
const program = new Command();
|
|
57
|
+
registerSubredditAssetCommands(program);
|
|
58
|
+
const asset = program.commands.find((cmd) => cmd.name() === "subreddit-asset");
|
|
59
|
+
const writeLeaves = [];
|
|
60
|
+
const walk = (cmd, prefix) => {
|
|
61
|
+
for (const sub of cmd.commands) {
|
|
62
|
+
const next = [...prefix, sub.name()];
|
|
63
|
+
if (sub.commands.length > 0)
|
|
64
|
+
walk(sub, next);
|
|
65
|
+
else
|
|
66
|
+
writeLeaves.push(next.join(" "));
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
walk(asset, []);
|
|
70
|
+
const writes = [
|
|
71
|
+
"register-subreddit",
|
|
72
|
+
"register",
|
|
73
|
+
"set-status",
|
|
74
|
+
"moderator add",
|
|
75
|
+
"moderator revoke",
|
|
76
|
+
"moderator replace",
|
|
77
|
+
"protection set",
|
|
78
|
+
];
|
|
79
|
+
for (const path of writes) {
|
|
80
|
+
expect(writeLeaves).toContain(path);
|
|
81
|
+
const parts = path.split(" ");
|
|
82
|
+
let cursor = asset;
|
|
83
|
+
for (const part of parts) {
|
|
84
|
+
cursor = cursor.commands.find((cmd) => cmd.name() === part);
|
|
85
|
+
expect(cursor).toBeDefined();
|
|
86
|
+
}
|
|
87
|
+
const flags = cursor.options.map((opt) => opt.long);
|
|
88
|
+
expect(flags).toContain("--dry-run");
|
|
89
|
+
expect(flags).toContain("--apply");
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
/** CAS 是这套接口的硬要求:改状态/撤销/更替/改档位都必须带 rowVersion。 */
|
|
93
|
+
it("所有 CAS 命令都把 --row-version 设为必填", () => {
|
|
94
|
+
const program = new Command();
|
|
95
|
+
registerSubredditAssetCommands(program);
|
|
96
|
+
const asset = program.commands.find((cmd) => cmd.name() === "subreddit-asset");
|
|
97
|
+
const casPaths = [
|
|
98
|
+
["set-status"],
|
|
99
|
+
["moderator", "revoke"],
|
|
100
|
+
["moderator", "replace"],
|
|
101
|
+
["protection", "set"],
|
|
102
|
+
];
|
|
103
|
+
for (const parts of casPaths) {
|
|
104
|
+
let cursor = asset;
|
|
105
|
+
for (const part of parts) {
|
|
106
|
+
cursor = cursor.commands.find((cmd) => cmd.name() === part);
|
|
107
|
+
}
|
|
108
|
+
const rowVersion = cursor.options.find((opt) => opt.long === "--row-version");
|
|
109
|
+
expect(rowVersion, parts.join(" ")).toBeDefined();
|
|
110
|
+
expect(rowVersion.required || rowVersion.mandatory).toBe(true);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
/** 读命令不该有写闸(否则 `list --apply` 这种误用会读起来像在写)。 */
|
|
114
|
+
it("读命令不带 --apply", () => {
|
|
115
|
+
const program = new Command();
|
|
116
|
+
registerSubredditAssetCommands(program);
|
|
117
|
+
const asset = program.commands.find((cmd) => cmd.name() === "subreddit-asset");
|
|
118
|
+
for (const name of ["list", "get"]) {
|
|
119
|
+
const cmd = asset.commands.find((c) => c.name() === name);
|
|
120
|
+
expect(cmd.options.map((opt) => opt.long)).not.toContain("--apply");
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
//# sourceMappingURL=register-subreddit-assets.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register-subreddit-assets.test.js","sourceRoot":"","sources":["../src/register-subreddit-assets.test.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geoly-ai/social-hub-cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.25",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "social-hub CLI for Social Ops Hub",
|
|
6
6
|
"repository": {
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"commander": "^12.1.0",
|
|
25
|
-
"@geoly-ai/social-hub-authz": "0.0.
|
|
26
|
-
"@geoly-ai/social-hub-sdk": "0.1.
|
|
25
|
+
"@geoly-ai/social-hub-authz": "0.0.19",
|
|
26
|
+
"@geoly-ai/social-hub-sdk": "0.1.2"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/node": "^22.10.2",
|
package/skills/manifest.json
CHANGED
|
@@ -176,6 +176,9 @@ social-hub comment-drafts get -t <team-id> --draft <uuid>
|
|
|
176
176
|
# 排期前 threadPermalink 必填(可 create 就带,或后续 update 补):
|
|
177
177
|
social-hub comment-drafts create -t <team-id> \
|
|
178
178
|
-j '{ "brandId":"<uuid>", "body":"<评论>", "subreddit":"buildapc", "threadPermalink":"https://reddit.com/r/buildapc/comments/xxx" }'
|
|
179
|
+
# 无品牌评论:省略 brandId 必须配显式 brandMention("忘了传" ≠ "明确没有品牌意图")
|
|
180
|
+
social-hub comment-drafts create -t <team-id> \
|
|
181
|
+
-j '{ "brandMention":"none", "body":"<评论>", "subreddit":"buildapc" }'
|
|
179
182
|
social-hub comment-drafts update -t <team-id> --draft <uuid> -j '{ "body":"...", "expectedVersion": 2 }'
|
|
180
183
|
social-hub comment-drafts delete -t <team-id> --draft <uuid>
|
|
181
184
|
social-hub comment-drafts schedule -t <team-id> --draft <uuid> \
|
|
@@ -190,6 +193,13 @@ social-hub comment-drafts schedule -t <team-id> --draft <uuid> \
|
|
|
190
193
|
complete 系统驱动,不可客户端直设(直设会撞 DB CHECK 或与真实 job 漂移)。
|
|
191
194
|
- **排期前 `threadPermalink` 必填**:草稿没选帖(无 threadPermalink)直接 `schedule` 会被拒——
|
|
192
195
|
先在 `create` 带上或用 `update` 补 `threadPermalink` 再排期。
|
|
196
|
+
- **无品牌评论草稿**(与帖子侧同一套语义):省略 `brandId` **必须**同时传 `brandMention: "none"`,
|
|
197
|
+
两者同现(或与 `campaignId` 同现)一律 400 ——「忘了传」不等于「明确没有品牌意图」。
|
|
198
|
+
服务端据此落 `brand_intent = 'declared_none'`,把它与**存量 / 历史 bug 落成的 NULL** 区分开;
|
|
199
|
+
**存量 `brand_intent IS NULL` 的行对品牌受限主体一律不可见**。品牌受限主体读/建/改/排期
|
|
200
|
+
这类草稿分别要 `noBrandContent.read/create/update` 与 `noBrandContentSchedule.create`
|
|
201
|
+
四格(删除**没有**对应格子,照旧 404);不受限主体行为不变。声明了无品牌的正文还会在
|
|
202
|
+
**创建、改 body、排期**三处过一次「正文里不得出现已登记品牌」的确定性核验。
|
|
193
203
|
- **`comment-drafts update` 的 `expectedVersion`** 开乐观并发:版本不匹配 → 409;version 只在
|
|
194
204
|
内容字段编辑时 +1——`body/subreddit/threadPermalink/parentCommentId/threadContext` 任一变更都
|
|
195
205
|
bump,**只有纯 `status` 更新不 bump**。
|