@geoly-ai/social-hub-cli 0.3.24 → 0.3.26
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 +13 -0
- package/dist/cli-error-channels.test.d.ts +2 -0
- package/dist/cli-error-channels.test.d.ts.map +1 -0
- package/dist/cli-error-channels.test.js +216 -0
- package/dist/cli-error-channels.test.js.map +1 -0
- package/dist/cmd-manifest.json +15 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -7
- package/dist/index.js.map +1 -1
- package/dist/output.d.ts +32 -0
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +122 -0
- package/dist/output.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,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.26",
|
|
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-
|
|
26
|
-
"@geoly-ai/social-hub-
|
|
25
|
+
"@geoly-ai/social-hub-sdk": "0.1.2",
|
|
26
|
+
"@geoly-ai/social-hub-authz": "0.0.19"
|
|
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**。
|