@geoly-ai/social-hub-cli 0.3.12 → 0.3.14

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 CHANGED
@@ -4,6 +4,23 @@ 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.14] - 2026-08-05
8
+
9
+ ### Added
10
+
11
+ - reddit-\* 改为本仓库直接维护 —— 补 --relock 模式,恢复被误还原的作者关系轴说明
12
+
13
+ ### Fixed
14
+
15
+ - 还原 4 个 vendored reddit skill —— 冻结区不接受本地修改
16
+
17
+ ## [0.3.13] - 2026-08-05
18
+
19
+ ### Added
20
+
21
+ - permissionMatrix 收敛为「不受限的交互式 admin」专属治理面
22
+ - 作者关系轴的外部调用面 —— 三层接线,且刻意不把它写成加分项
23
+
7
24
  ## [0.3.12] - 2026-08-04
8
25
 
9
26
  ### Added
@@ -1,6 +1,6 @@
1
1
  {
2
- "generatedAt": "2026-08-04T16:23:11.683Z",
3
- "cliVersion": "0.3.12",
2
+ "generatedAt": "2026-08-05T06:01:06.323Z",
3
+ "cliVersion": "0.3.14",
4
4
  "commands": [
5
5
  "accounts",
6
6
  "accounts brand-bindings",
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=content-review.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-review.test.d.ts","sourceRoot":"","sources":["../src/content-review.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Integration test: `content review` 的作者关系轴调用面。
3
+ *
4
+ * 三条断言是本次接线的核心不变量:
5
+ * 1. 便利 flag 合并进请求体(并覆盖 -j 里的同名值);
6
+ * 2. **输出只展示服务端派生的 authorRelation/Evidence,绝不回显调用方的 hint** ——
7
+ * 回显会让「自称 OP」看起来像被系统承认;
8
+ * 3. 422 AUTHOR_RELATION_MISMATCH 有可行动提示 + 非零退出 + stdout 为空。
9
+ */
10
+ import { beforeAll, afterEach, describe, expect, it, vi } from "vitest";
11
+ import { writeFileSync, mkdtempSync } from "node:fs";
12
+ import { tmpdir } from "node:os";
13
+ import { join } from "node:path";
14
+ import { SocialHubHttpError } from "@geoly-ai/social-hub-sdk";
15
+ const contentReview = vi.fn();
16
+ vi.mock("./client.js", () => ({
17
+ requireClient: () => ({ contentReview }),
18
+ resolveTeamId: (t) => t ?? "team-1",
19
+ getBaseUrl: () => "http://localhost",
20
+ }));
21
+ let program;
22
+ const stdout = [];
23
+ const stderr = [];
24
+ let exitCode;
25
+ /** 退出所有权只归最外层 boundary:内层再 exit 会造出生产中不存在的二次捕获路径。 */
26
+ let exitCalls = 0;
27
+ beforeAll(async () => {
28
+ program = (await import("./index.js")).program;
29
+ vi.spyOn(console, "log").mockImplementation((...a) => {
30
+ stdout.push(a.map(String).join(" "));
31
+ });
32
+ vi.spyOn(console, "error").mockImplementation((...a) => {
33
+ stderr.push(a.map(String).join(" "));
34
+ });
35
+ vi.spyOn(process, "exit").mockImplementation(((code) => {
36
+ exitCode = code;
37
+ exitCalls += 1;
38
+ throw new Error(`__exit__${code}`);
39
+ }));
40
+ }, 30000);
41
+ function reviewCmd() {
42
+ const content = program.commands.find((c) => c.name() === "content");
43
+ return content?.commands.find((c) => c.name() === "review");
44
+ }
45
+ afterEach(() => {
46
+ contentReview.mockReset();
47
+ stdout.length = 0;
48
+ stderr.length = 0;
49
+ exitCode = undefined;
50
+ exitCalls = 0;
51
+ // Commander 把值挂在单例 command 上;不清会串场。
52
+ const cmd = reviewCmd();
53
+ for (const key of [
54
+ "json",
55
+ "jsonFile",
56
+ "brandContext",
57
+ "threadPermalink",
58
+ "authorRelationHint",
59
+ ]) {
60
+ cmd?.setOptionValue(key, undefined);
61
+ }
62
+ });
63
+ const okResult = {
64
+ reviewId: "11111111-1111-1111-1111-111111111111",
65
+ executionStatus: "completed",
66
+ verdict: "pass",
67
+ // 服务端派生:与下面请求里的 hint 刻意不一致,用来钉死「不回显 hint」。
68
+ authorRelation: "other",
69
+ authorRelationEvidence: "verified_snapshot",
70
+ score: 89,
71
+ modelScore: 88,
72
+ dimensions: null,
73
+ optimized: null,
74
+ rationale: null,
75
+ reviewModel: "m",
76
+ rubricVersion: "content-review.v4",
77
+ thresholdSnapshot: {},
78
+ latencyMs: 12,
79
+ degradedReason: null,
80
+ };
81
+ const body = {
82
+ draft: "Update: it was the PSU cable.",
83
+ commentType: "top_level",
84
+ thread: { subreddit: "buildapc", postTitle: "PC reboots under load" },
85
+ };
86
+ async function run(...args) {
87
+ await program.parseAsync([
88
+ "node",
89
+ "social-hub",
90
+ "content",
91
+ "review",
92
+ ...args,
93
+ ]);
94
+ }
95
+ describe("content review 作者关系轴 flags", () => {
96
+ it("--thread-permalink / --author-relation-hint 合并进请求体", async () => {
97
+ contentReview.mockResolvedValue(okResult);
98
+ await run("-j", JSON.stringify(body), "--thread-permalink", "https://www.reddit.com/r/buildapc/comments/abc/x/", "--author-relation-hint", "post_author");
99
+ expect(contentReview).toHaveBeenCalledWith("team-1", {
100
+ ...body,
101
+ authorRelationHint: "post_author",
102
+ thread: {
103
+ subreddit: "buildapc",
104
+ postTitle: "PC reboots under load",
105
+ threadPermalink: "https://www.reddit.com/r/buildapc/comments/abc/x/",
106
+ },
107
+ });
108
+ });
109
+ it("从 --json-file 读 body 时 flag 同样生效,并覆盖文件里的同名值", async () => {
110
+ contentReview.mockResolvedValue(okResult);
111
+ const dir = mkdtempSync(join(tmpdir(), "cr-args-"));
112
+ const file = join(dir, "body.json");
113
+ writeFileSync(file, JSON.stringify({
114
+ ...body,
115
+ authorRelationHint: "other",
116
+ thread: { ...body.thread, threadPermalink: "https://old/" },
117
+ }), "utf8");
118
+ await run("--json-file", file, "--thread-permalink", "https://new/", "--author-relation-hint", "post_author");
119
+ const sent = contentReview.mock.calls[0]?.[1];
120
+ expect(sent["authorRelationHint"]).toBe("post_author");
121
+ expect(sent["thread"]["threadPermalink"]).toBe("https://new/");
122
+ });
123
+ it("非法 hint 本地拒绝:不发请求、非零退出", async () => {
124
+ await expect(run("-j", JSON.stringify(body), "--author-relation-hint", "OP")).rejects.toThrow(/__exit__1/);
125
+ expect(contentReview).not.toHaveBeenCalled();
126
+ expect(exitCode).toBe(1);
127
+ // 只退出一次:参数错误经 throw 交给最外层 boundary,而不是内层自己 exit。
128
+ expect(exitCalls).toBe(1);
129
+ expect(stderr.join("\n")).toMatch(/post_author \| other/);
130
+ });
131
+ it.each([
132
+ ["空串", ""],
133
+ ["纯空白", " "],
134
+ ])("--thread-permalink %s 被本地拒绝(不会静默覆盖掉 -j 里的有效值)", async (_l, raw) => {
135
+ await expect(run("-j", JSON.stringify({
136
+ ...body,
137
+ thread: { ...body.thread, threadPermalink: "https://real/" },
138
+ }), "--thread-permalink", raw)).rejects.toThrow(/__exit__1/);
139
+ expect(contentReview).not.toHaveBeenCalled();
140
+ });
141
+ it("--thread-permalink 超过 2048 字符本地拒绝", async () => {
142
+ await expect(run("-j", JSON.stringify(body), "--thread-permalink", `https://x/${"a".repeat(2049)}`)).rejects.toThrow(/__exit__1/);
143
+ expect(contentReview).not.toHaveBeenCalled();
144
+ });
145
+ it("--author-relation-hint 不带值由 commander 拒绝,不发请求", async () => {
146
+ await expect(run("-j", JSON.stringify(body), "--author-relation-hint")).rejects.toThrow();
147
+ expect(contentReview).not.toHaveBeenCalled();
148
+ });
149
+ // 本轮把 readJsonArgOrExit / 内层 process.exit 全改成 throw 交给最外层 boundary,
150
+ // 这两条正是被改造的控制路径:必须仍然只退出一次。
151
+ it("-j 不是合法 JSON → 单次退出、不发请求", async () => {
152
+ await expect(run("-j", "{not json")).rejects.toThrow(/__exit__1/);
153
+ expect(contentReview).not.toHaveBeenCalled();
154
+ expect(exitCalls).toBe(1);
155
+ expect(stderr.join("\n")).toMatch(/JSON/);
156
+ });
157
+ it("--brand-context 不是合法 JSON → 单次退出、不发请求", async () => {
158
+ await expect(run("-j", JSON.stringify(body), "--brand-context", "{oops")).rejects.toThrow(/__exit__1/);
159
+ expect(contentReview).not.toHaveBeenCalled();
160
+ expect(exitCalls).toBe(1);
161
+ expect(stderr.join("\n")).toContain("--brand-context");
162
+ });
163
+ it("body 里没有 thread 时用 --thread-permalink → 本地拒绝,不凭空造 thread", async () => {
164
+ await expect(run("-j", JSON.stringify({ draft: "x", commentType: "top_level" }), "--thread-permalink", "https://x/")).rejects.toThrow(/__exit__1/);
165
+ expect(contentReview).not.toHaveBeenCalled();
166
+ });
167
+ });
168
+ describe("content review 输出", () => {
169
+ it("展示服务端派生的 authorRelation/Evidence,绝不回显调用方 hint", async () => {
170
+ contentReview.mockResolvedValue(okResult);
171
+ await run("-j", JSON.stringify(body),
172
+ // 声称 post_author,但服务端派生的是 other。
173
+ "--author-relation-hint", "post_author");
174
+ const out = stdout.join("\n");
175
+ const parsed = JSON.parse(out);
176
+ expect(parsed["authorRelation"]).toBe("other");
177
+ expect(parsed["authorRelationEvidence"]).toBe("verified_snapshot");
178
+ // 输出里不得出现 hint 字段(哪怕作为「你声称了什么」的回显)。
179
+ // 只查精确 key:服务端 rationale/issues 里合法出现 "hint" 一词不该让本例变红。
180
+ expect(parsed).not.toHaveProperty("authorRelationHint");
181
+ });
182
+ it("成功路径 stderr 保持安静(脚本把非空 stderr 当异常)", async () => {
183
+ contentReview.mockResolvedValue(okResult);
184
+ await run("-j", JSON.stringify(body));
185
+ expect(stderr).toEqual([]);
186
+ });
187
+ });
188
+ describe("content review 422 AUTHOR_RELATION_MISMATCH", () => {
189
+ it("给可行动提示、stdout 为空、非零退出,且不打印服务端 details", async () => {
190
+ contentReview.mockRejectedValue(new SocialHubHttpError({
191
+ status: 422,
192
+ message: "HTTP 422: authorRelationHint=post_author contradicts evidence",
193
+ code: "AUTHOR_RELATION_MISMATCH",
194
+ details: {
195
+ authorRelation: {
196
+ snapshotAuthor: "u/someone-else",
197
+ evidence: "mismatch",
198
+ },
199
+ },
200
+ }));
201
+ await expect(run("-j", JSON.stringify(body), "--author-relation-hint", "post_author")).rejects.toThrow(/__exit__1/);
202
+ expect(exitCode).toBe(1);
203
+ expect(stdout).toEqual([]);
204
+ const err = stderr.join("\n");
205
+ expect(err).toContain("硬失败");
206
+ // details 里的快照作者不得外泄到终端。
207
+ expect(err).not.toContain("someone-else");
208
+ expect(exitCalls).toBe(1);
209
+ });
210
+ it("其它 HTTP 错误走通用错误边界,不带 mismatch 提示", async () => {
211
+ contentReview.mockRejectedValue(new SocialHubHttpError({
212
+ status: 422,
213
+ message: "HTTP 422: brand context required",
214
+ code: "BRAND_CONTEXT_REQUIRED",
215
+ }));
216
+ await expect(run("-j", JSON.stringify(body))).rejects.toThrow(/__exit__1/);
217
+ const err = stderr.join("\n");
218
+ expect(err).toContain("brand context required");
219
+ expect(err).not.toContain("硬失败");
220
+ });
221
+ });
222
+ //# sourceMappingURL=content-review.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-review.test.js","sourceRoot":"","sources":["../src/content-review.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACxE,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAE9D,MAAM,aAAa,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;AAE9B,EAAE,CAAC,IAAI,CAAC,aAAa,EAAE,GAAG,EAAE,CAAC,CAAC;IAC5B,aAAa,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC;IACxC,aAAa,EAAE,CAAC,CAAU,EAAE,EAAE,CAAC,CAAC,IAAI,QAAQ;IAC5C,UAAU,EAAE,GAAG,EAAE,CAAC,kBAAkB;CACrC,CAAC,CAAC,CAAC;AAEJ,IAAI,OAAwD,CAAC;AAC7D,MAAM,MAAM,GAAa,EAAE,CAAC;AAC5B,MAAM,MAAM,GAAa,EAAE,CAAC;AAC5B,IAAI,QAA4B,CAAC;AACjC,qDAAqD;AACrD,IAAI,SAAS,GAAG,CAAC,CAAC;AAElB,SAAS,CAAC,KAAK,IAAI,EAAE;IACnB,OAAO,GAAG,CAAC,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC;IAC/C,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,kBAAkB,CAAC,CAAC,GAAG,CAAY,EAAE,EAAE;QAC9D,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,CAAC,GAAG,CAAY,EAAE,EAAE;QAChE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAa,EAAE,EAAE;QAC9D,QAAQ,GAAG,IAAI,CAAC;QAChB,SAAS,IAAI,CAAC,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;IACrC,CAAC,CAAU,CAAC,CAAC;AACf,CAAC,EAAE,KAAK,CAAC,CAAC;AAEV,SAAS,SAAS;IAChB,MAAM,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,SAAS,CAAC,CAAC;IACrE,OAAO,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,QAAQ,CAAC,CAAC;AAC9D,CAAC;AAED,SAAS,CAAC,GAAG,EAAE;IACb,aAAa,CAAC,SAAS,EAAE,CAAC;IAC1B,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;IAClB,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;IAClB,QAAQ,GAAG,SAAS,CAAC;IACrB,SAAS,GAAG,CAAC,CAAC;IACd,oCAAoC;IACpC,MAAM,GAAG,GAAG,SAAS,EAAE,CAAC;IACxB,KAAK,MAAM,GAAG,IAAI;QAChB,MAAM;QACN,UAAU;QACV,cAAc;QACd,iBAAiB;QACjB,oBAAoB;KACrB,EAAE,CAAC;QACF,GAAG,EAAE,cAAc,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IACtC,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,MAAM,QAAQ,GAAG;IACf,QAAQ,EAAE,sCAAsC;IAChD,eAAe,EAAE,WAAW;IAC5B,OAAO,EAAE,MAAM;IACf,2CAA2C;IAC3C,cAAc,EAAE,OAAO;IACvB,sBAAsB,EAAE,mBAAmB;IAC3C,KAAK,EAAE,EAAE;IACT,UAAU,EAAE,EAAE;IACd,UAAU,EAAE,IAAI;IAChB,SAAS,EAAE,IAAI;IACf,SAAS,EAAE,IAAI;IACf,WAAW,EAAE,GAAG;IAChB,aAAa,EAAE,mBAAmB;IAClC,iBAAiB,EAAE,EAAE;IACrB,SAAS,EAAE,EAAE;IACb,cAAc,EAAE,IAAI;CACrB,CAAC;AAEF,MAAM,IAAI,GAAG;IACX,KAAK,EAAE,+BAA+B;IACtC,WAAW,EAAE,WAAW;IACxB,MAAM,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,uBAAuB,EAAE;CACtE,CAAC;AAEF,KAAK,UAAU,GAAG,CAAC,GAAG,IAAc;IAClC,MAAM,OAAO,CAAC,UAAU,CAAC;QACvB,MAAM;QACN,YAAY;QACZ,SAAS;QACT,QAAQ;QACR,GAAG,IAAI;KACR,CAAC,CAAC;AACL,CAAC;AAED,QAAQ,CAAC,4BAA4B,EAAE,GAAG,EAAE;IAC1C,EAAE,CAAC,oDAAoD,EAAE,KAAK,IAAI,EAAE;QAClE,aAAa,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;QAC1C,MAAM,GAAG,CACP,IAAI,EACJ,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EACpB,oBAAoB,EACpB,mDAAmD,EACnD,wBAAwB,EACxB,aAAa,CACd,CAAC;QACF,MAAM,CAAC,aAAa,CAAC,CAAC,oBAAoB,CAAC,QAAQ,EAAE;YACnD,GAAG,IAAI;YACP,kBAAkB,EAAE,aAAa;YACjC,MAAM,EAAE;gBACN,SAAS,EAAE,UAAU;gBACrB,SAAS,EAAE,uBAAuB;gBAClC,eAAe,EAAE,mDAAmD;aACrE;SACF,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;QAC3D,aAAa,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;QAC1C,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,UAAU,CAAC,CAAC,CAAC;QACpD,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;QACpC,aAAa,CACX,IAAI,EACJ,IAAI,CAAC,SAAS,CAAC;YACb,GAAG,IAAI;YACP,kBAAkB,EAAE,OAAO;YAC3B,MAAM,EAAE,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,eAAe,EAAE,cAAc,EAAE;SAC5D,CAAC,EACF,MAAM,CACP,CAAC;QACF,MAAM,GAAG,CACP,aAAa,EACb,IAAI,EACJ,oBAAoB,EACpB,cAAc,EACd,wBAAwB,EACxB,aAAa,CACd,CAAC;QACF,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAA4B,CAAC;QACzE,MAAM,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACvD,MAAM,CAAE,IAAI,CAAC,QAAQ,CAA6B,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CACzE,cAAc,CACf,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wBAAwB,EAAE,KAAK,IAAI,EAAE;QACtC,MAAM,MAAM,CACV,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,wBAAwB,EAAE,IAAI,CAAC,CAChE,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QAC/B,MAAM,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;QAC7C,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACzB,iDAAiD;QACjD,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC1B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,IAAI,CAAC;QACN,CAAC,IAAI,EAAE,EAAE,CAAC;QACV,CAAC,KAAK,EAAE,KAAK,CAAC;KACf,CAAC,CACA,+CAA+C,EAC/C,KAAK,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE;QAChB,MAAM,MAAM,CACV,GAAG,CACD,IAAI,EACJ,IAAI,CAAC,SAAS,CAAC;YACb,GAAG,IAAI;YACP,MAAM,EAAE,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,eAAe,EAAE,eAAe,EAAE;SAC7D,CAAC,EACF,oBAAoB,EACpB,GAAG,CACJ,CACF,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QAC/B,MAAM,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IAC/C,CAAC,CACF,CAAC;IAEF,EAAE,CAAC,mCAAmC,EAAE,KAAK,IAAI,EAAE;QACjD,MAAM,MAAM,CACV,GAAG,CACD,IAAI,EACJ,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EACpB,oBAAoB,EACpB,aAAa,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAChC,CACF,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QAC/B,MAAM,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,KAAK,IAAI,EAAE;QAC7D,MAAM,MAAM,CACV,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,wBAAwB,CAAC,CAC1D,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,oEAAoE;IACpE,2BAA2B;IAC3B,EAAE,CAAC,0BAA0B,EAAE,KAAK,IAAI,EAAE;QACxC,MAAM,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QAClE,MAAM,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;QAC7C,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC1B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;QACrD,MAAM,MAAM,CACV,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,iBAAiB,EAAE,OAAO,CAAC,CAC5D,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QAC/B,MAAM,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;QAC7C,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC1B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0DAA0D,EAAE,KAAK,IAAI,EAAE;QACxE,MAAM,MAAM,CACV,GAAG,CACD,IAAI,EACJ,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC,EACxD,oBAAoB,EACpB,YAAY,CACb,CACF,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QAC/B,MAAM,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IAC/C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,EAAE,CAAC,+CAA+C,EAAE,KAAK,IAAI,EAAE;QAC7D,aAAa,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;QAC1C,MAAM,GAAG,CACP,IAAI,EACJ,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;QACpB,iCAAiC;QACjC,wBAAwB,EACxB,aAAa,CACd,CAAC;QACF,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9B,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA4B,CAAC;QAC1D,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC/C,MAAM,CAAC,MAAM,CAAC,wBAAwB,CAAC,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;QACnE,oCAAoC;QACpC,wDAAwD;QACxD,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,oBAAoB,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,KAAK,IAAI,EAAE;QAClD,aAAa,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;QAC1C,MAAM,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;QACtC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,6CAA6C,EAAE,GAAG,EAAE;IAC3D,EAAE,CAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;QACrD,aAAa,CAAC,iBAAiB,CAC7B,IAAI,kBAAkB,CAAC;YACrB,MAAM,EAAE,GAAG;YACX,OAAO,EACL,+DAA+D;YACjE,IAAI,EAAE,0BAA0B;YAChC,OAAO,EAAE;gBACP,cAAc,EAAE;oBACd,cAAc,EAAE,gBAAgB;oBAChC,QAAQ,EAAE,UAAU;iBACrB;aACF;SACF,CAAC,CACH,CAAC;QACF,MAAM,MAAM,CACV,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,wBAAwB,EAAE,aAAa,CAAC,CACzE,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QAC/B,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACzB,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC3B,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9B,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAC7B,yBAAyB;QACzB,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;QAC1C,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kCAAkC,EAAE,KAAK,IAAI,EAAE;QAChD,aAAa,CAAC,iBAAiB,CAC7B,IAAI,kBAAkB,CAAC;YACrB,MAAM,EAAE,GAAG;YACX,OAAO,EAAE,kCAAkC;YAC3C,IAAI,EAAE,wBAAwB;SAC/B,CAAC,CACH,CAAC;QACF,MAAM,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QAC3E,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9B,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,wBAAwB,CAAC,CAAC;QAChD,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgDpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAk9E9B,8DAA8D;AAC9D,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,MAAM,GAAG,IAAI,CAOf;AAqgCD,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;AA+1ED,OAAO,EAAE,OAAO,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAuDpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AA6+E9B,8DAA8D;AAC9D,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,MAAM,GAAG,IAAI,CAOf;AAqgCD,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;AA+1ED,OAAO,EAAE,OAAO,EAAE,CAAC"}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from "commander";
3
- import { getSocialHubHealth, } from "@geoly-ai/social-hub-sdk";
3
+ import { getSocialHubHealth, isAuthorRelationMismatchError, } from "@geoly-ai/social-hub-sdk";
4
4
  import { getBaseUrl, requireClient, resolveTeamId } from "./client.js";
5
5
  import { registerAccountsExtensions, registerAuthAndConfig, registerAuthzEvaluate, registerPermissionsExplain, registerRedditExtensions, listRedditSnapshotsPaginated, redditAggregateQueryParams, } from "./register-extensions.js";
6
6
  import { registerAdminCommands } from "./register-admin.js";
@@ -13,7 +13,9 @@ import { registerBatchAndExport } from "./register-batch.js";
13
13
  import { registerAgentContext, registerOpsRuntime } from "./register-ops.js";
14
14
  import { registerDoctorAndVersion } from "./register-shared.js";
15
15
  import { registerSkillsCommands } from "./register-skills.js";
16
- import { readJsonArgOrExit } from "./lib/json-arg.js";
16
+ import { readJsonArgOrExit, resolveJsonArg } from "./lib/json-arg.js";
17
+ import { applyContentReviewFlags, AUTHOR_RELATION_MISMATCH_HINT, ContentReviewArgError, } from "./lib/content-review-args.js";
18
+ import { withCliErrorBoundary } from "./output.js";
17
19
  import { maybeNotifyUpdate } from "./update-check.js";
18
20
  import { maybeNotifySkillUpdate } from "./skill-update-check.js";
19
21
  import { getCliVersion } from "./version-info.js";
@@ -475,22 +477,44 @@ content
475
477
  .option("-j, --json <json>", "{ accountId?, draft, commentType, thread:{subreddit,postTitle,...}, brandContext?:{brandKey|campaignId}, rewrite?, stage?, attempt? } (omit accountId for persona-less ContentOnly review)。也可 -j @<path> 读文件 / -j - 读 stdin")
476
478
  .option("--json-file <path>", "从文件读 body JSON(路径,或 - 表示 stdin);与 -j 二选一。Windows 建议用此绕开 shell 引号问题")
477
479
  .option("--brand-context <json>", '品牌上下文 { "brandKey":"..." } 或 { "campaignId":"..." }(§0.3;便捷参数,覆盖 -j 里的 brandContext)')
478
- .action(async (opts) => {
479
- const body = readJsonArgOrExit({
480
- inline: opts.json,
481
- file: opts.jsonFile,
482
- });
483
- if (opts.brandContext) {
480
+ .option("--thread-permalink <url>", "本次所审原帖的 permalink,写进 thread.threadPermalink(覆盖 -j 里的同名值)。供 Hub 独立核验作者关系;不保证 Hub 侧有该帖快照,也不改变评分标准")
481
+ .option("--author-relation-hint <post_author|other>", "非权威提示,不是身份声明:Hub 始终按发布账号 × permalink × 帖子快照派生权威 authorRelation,证据永远覆盖 hint;声称 post_author 而证据证伪 → 422 硬失败")
482
+ .action(async (opts) => {
483
+ // 退出所有权只归最外层 boundary:内层再 `process.exit` 会让「参数错误」和
484
+ // 「HTTP 错误」走两套退出路径,且测试里 mock 掉 exit 后异常会被同一 action
485
+ // 二次捕获 —— 那是生产中不存在的路径(假绿)。所以这里一律 throw。
486
+ await withCliErrorBoundary(async () => {
487
+ const raw = resolveJsonArg({
488
+ inline: opts.json,
489
+ file: opts.jsonFile,
490
+ required: true,
491
+ });
492
+ const body = applyContentReviewFlags(raw, {
493
+ threadPermalink: opts.threadPermalink,
494
+ authorRelationHint: opts.authorRelationHint,
495
+ });
496
+ if (opts.brandContext) {
497
+ try {
498
+ body["brandContext"] = JSON.parse(opts.brandContext);
499
+ }
500
+ catch {
501
+ throw new ContentReviewArgError("--brand-context 不是合法 JSON");
502
+ }
503
+ }
484
504
  try {
485
- body.brandContext = JSON.parse(opts.brandContext);
505
+ // 输出即服务端响应:其中的 authorRelation/authorRelationEvidence 是**服务端派生**的。
506
+ // 刻意不额外回显调用方的 --author-relation-hint —— 把 hint 和结论并排展示,
507
+ // 会让「自称 OP」看起来像被系统承认。
508
+ const res = await requireClient().contentReview(resolveTeamId(opts.team), body);
509
+ console.log(JSON.stringify(res, null, 2));
486
510
  }
487
- catch {
488
- console.error("Invalid --brand-context JSON");
489
- process.exit(1);
511
+ catch (e) {
512
+ if (isAuthorRelationMismatchError(e)) {
513
+ console.error(AUTHOR_RELATION_MISMATCH_HINT);
514
+ }
515
+ throw e;
490
516
  }
491
- }
492
- const res = await requireClient().contentReview(resolveTeamId(opts.team), body);
493
- console.log(JSON.stringify(res, null, 2));
517
+ });
494
518
  });
495
519
  content
496
520
  .command("post-review")