@zyfasos/workbench 1.0.3

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 (3) hide show
  1. package/README.md +135 -0
  2. package/package.json +28 -0
  3. package/workbench.mjs +824 -0
package/README.md ADDED
@@ -0,0 +1,135 @@
1
+ # Lingee Workbench
2
+
3
+ 1.0.2支持会议草稿、发布、归档和未结接续。默认连接共享CloudBase;仅本地验收时使用`--local`,计划不能跨环境应用。
4
+
5
+ 专题 Owner 和 Agent 的周会协作 CLI:维护周报链接、更新路线里程碑、提报与处理议题。保存后由工作台读取同一份 CloudBase 数据,无需重新发布网页。
6
+
7
+ 需要 Node.js 20 或以上(含 npm)。无需 CloudBase CLI 或管理密钥。
8
+
9
+ ## 安装
10
+
11
+ ```sh
12
+ npm install -g @zyfasos/workbench
13
+ workbench --version
14
+ workbench --help
15
+ ```
16
+
17
+ 无需全局安装也可执行:
18
+
19
+ ```sh
20
+ npm exec --package=@zyfasos/workbench -- workbench --help
21
+ ```
22
+
23
+ 更新:`npm install -g @zyfasos/workbench@latest`。固定版本可使用 `@zyfasos/workbench@latest`。也可使用工作台提供的本地 tarball 安装。
24
+
25
+ ## 内置业务上下文
26
+
27
+ ```sh
28
+ workbench context
29
+ workbench context --topic ppt --json
30
+ ```
31
+
32
+ 随安装包提供,离线可读。默认展示全部小组和专题;`--topic`筛选,不依赖init默认值。含业务小组、组长、专题负责人、职责、已记录协作说明与周会规则,标明资料日期、核对日期及CLI版本。只代表工作台收录口径,不代表完整公司组织或权限。组织资料来自独立稳定业务底册,与每周材料分开维护,快照随版本更新;每周实际链接、路线、议题、会议仍须在线读取。建议Agent先读context,再按任务读取共享数据。
33
+
34
+ ## 开始使用
35
+
36
+ ```sh
37
+ workbench topics
38
+ workbench init --topic ppt
39
+ workbench status
40
+ workbench links show --week 2026-W45
41
+ workbench roadmap list
42
+ ```
43
+
44
+ `ppt` 仅为示例,请选择自己负责的专题。`init` 只保存本机默认专题;`--topic` 可临时覆盖。周报正文必须明确周次,不按当前日期猜测。
45
+
46
+ ## 更新一个链接
47
+
48
+ ```sh
49
+ workbench links set --dashboard https://example.com/dashboard --dry-run
50
+ # 检查输出的变化;获得本次修改确认后执行:
51
+ workbench apply <plan-id> --yes
52
+ workbench links show --week 2026-W45
53
+ ```
54
+
55
+ 示例地址不是业务材料。`--report-hub` 设置长期目录;`--weekly-report URL --week YYYY-Www` 设置指定周正文。未指定字段不改变。清除使用 `links clear --field dashboard`,也可选 `report-hub` 或 `weekly-report`。
56
+
57
+ ## 调整路线
58
+
59
+ 安装后运行 `workbench roadmap guide` 查看完整字段释义、示例、批量导入和冲突处理。说明随 CLI 打包,无需联网;可用 `--out guide.md` 保存或 `--json` 供 Agent 读取。模板生成也无需联网;读取和修改共享路线需要网络。
60
+
61
+ ```sh
62
+ workbench roadmap list
63
+ workbench roadmap update <id> --month 2026-12 --reason "调整交付窗口" --dry-run
64
+ workbench apply <plan-id> --yes
65
+ workbench roadmap show <id>
66
+ workbench roadmap history
67
+ ```
68
+
69
+ 新增 ID 已存在时会拒绝,修改已有事项用 `update`。支持 `add/update/archive/restore`;各命令用 `--help` 查看字段。新增已交付记录需真实交付日期和验收链接。归档后可恢复,历史保留。批量修改可 `roadmap export --out route.jsonc`,修改后 `roadmap import route.jsonc --dry-run`;未列出的记录不会删除。
70
+
71
+ ## 提报与处理议题
72
+
73
+ ```sh
74
+ workbench agenda add --topic ppt --week 2026-W46 --submitter "负责人" --title "讨论事项" --question "需要决定什么" --dry-run
75
+ workbench apply <plan-id> --yes
76
+ workbench agenda list --week 2026-W46
77
+ workbench agenda show <id>
78
+ workbench agenda update <id> --question "补充背景与需要的决定" --dry-run
79
+ workbench agenda withdraw <id> --dry-run
80
+ ```
81
+
82
+ 材料用 `--material-url`;空字符串可清除。跨专题用 `--topic cross`。原周次保留,已撤回或纳入的议题不能再编辑;补充请新建。
83
+ 正式议程已经发布后才执行 `agenda include <id> --formal-url <已发布地址> --dry-run`,确认后用计划ID保存;CLI本身不发布正式议程。
84
+
85
+ ## Agent 使用约定
86
+
87
+ - 加 `--json` 获取机器可读输出;错误写 stderr。不要解析人读表格。
88
+ - `--dry-run` 访问云端做预览,仅在本机保存计划,不写业务数据。
89
+ - 默认交互执行显示变化并询问;非交互未确认不写入。只有已获得该变更授权才加 `--yes`。
90
+ - 网络结果未知时保留原 plan-id,再次 `apply`;不要重新生成请求或篡改计划文件。
91
+ - 冲突时读回最新值,合并后重新预览;不要强改 revision。
92
+ - `agenda list --week YYYY-Www` 读取全部状态议题;待整理项为 submitted。议题使用持久化请求回执;同一计划迟到重试也不会回退后续更新,保存后独立读回。
93
+ - 本机状态位于 `WORKBENCH_STATE_DIR`,或 `$XDG_STATE_HOME/lingee-workbench`,默认 `~/.local/state/lingee-workbench`。不存管理密钥。
94
+ - 退出码:0 成功/预览,1 运行错误,2 参数错误/未确认,3 冲突,4 网络结果未知。
95
+
96
+ ## 工作台与数据边界
97
+
98
+ [打开工作台](https://site.lingee.com/sites/KUeKpIJOTkiNd_GWtg7H2Q9c7c5952/)。当前 CLI 指向该共享工作台,不提供多环境切换。所有人可读写,专题 ID 是定位字段而非权限隔离;只修改获得授权的内容。网页没有实时推送,保存后重新打开或刷新对应页面读回。
99
+
100
+ 本包无需登录,但联网读写会访问工作台的公共 CloudBase HTTP API。包内不包含 CloudBase 管理密钥或成员记录。无安装脚本、无运行时 npm 依赖。
101
+
102
+ ## 许可
103
+
104
+ 当前包标记为 `UNLICENSED`,未授予开源许可。公开分发不代表按 MIT、Apache 等开源许可证授权;许可调整须由权利人另行确认。
105
+
106
+ ## 补充周会纪要
107
+
108
+ ```sh
109
+ workbench meetings list
110
+ workbench meetings show --week 2026-W45
111
+ workbench meetings set --week 2026-W45 --record-url 'https://example.com/minutes' --dry-run
112
+ workbench apply <plan-id> --yes
113
+ workbench meetings show --week 2026-W45
114
+ ```
115
+
116
+ 先核对会议日期:2026-W45对应11月9日召开的周会(回顾11月2日至8日),不是开会日的日历周。链接为纪要与录音分享页,含 & 等字符时加引号;不上传文件或解析纪要正文。保存到CloudBase后在“周会回看”展示;网页重新发布不覆盖。移除用 meetings clear --week 2026-W45 --dry-run。支持版本冲突检查和原计划重试,不需要专题默认配置。
117
+
118
+ ## 周会滚动与冻结回看
119
+
120
+ `meetings cycles/detail`读取当前会议及快照;`template/export`提供议程/结论模板;`draft/publish/close/carry/pending`完成草稿、显式切周、归档和未结接续。每个子命令均有--help,写入先--dry-run,确认后apply。
121
+
122
+ ```sh
123
+ workbench meetings cycles
124
+ workbench meetings export --week 2026-W45 --results --out results.json
125
+ workbench meetings close --file results.json --dry-run
126
+ workbench apply <plan-id> --yes
127
+ workbench meetings carry --from-week 2026-W45 --week 2026-W46 --item <id> --dry-run
128
+ workbench meetings publish --week 2026-W46 --dry-run
129
+ ```
130
+
131
+ 目标周应先用template/draft建草稿。归档后不可覆盖,原计划重试不切回旧周;未结行动的ID和来源保留,后续必须明确完成/取消/继续,不能静默删除。材料冻结标题和URL,不复制外部网页正文。
132
+
133
+ ## 1.0 命令边界
134
+
135
+ 仅维护当前原子命令与统一 `apply <plan-id>`;已移除0.x的links list/export/plan/apply及roadmap plan/apply兼容入口。文件导入使用roadmap export/template → roadmap import --dry-run → apply。旧版本不再维护,不迁移旧计划;读取最新数据重新生成计划。
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@zyfasos/workbench",
3
+ "version": "1.0.3",
4
+ "description": "Lingee Work topic collaboration CLI",
5
+ "type": "module",
6
+ "bin": {
7
+ "workbench": "workbench.mjs"
8
+ },
9
+ "engines": {
10
+ "node": ">=20"
11
+ },
12
+ "files": [
13
+ "workbench.mjs",
14
+ "README.md"
15
+ ],
16
+ "license": "UNLICENSED",
17
+ "publishConfig": {
18
+ "access": "public",
19
+ "registry": "https://registry.npmjs.org/"
20
+ },
21
+ "homepage": "https://site.lingee.com/sites/KUeKpIJOTkiNd_GWtg7H2Q9c7c5952/",
22
+ "keywords": [
23
+ "lingee",
24
+ "workbench",
25
+ "cli",
26
+ "roadmap"
27
+ ]
28
+ }
package/workbench.mjs ADDED
@@ -0,0 +1,824 @@
1
+ #!/usr/bin/env node
2
+
3
+ // scripts/workbench.mjs
4
+ import { parseArgs } from "node:util";
5
+ import { realpathSync } from "node:fs";
6
+ import { readFile, writeFile, mkdir } from "node:fs/promises";
7
+ import { createHash, randomUUID } from "node:crypto";
8
+ import { homedir } from "node:os";
9
+ import { join, resolve } from "node:path";
10
+ import { pathToFileURL } from "node:url";
11
+ import { createInterface } from "node:readline/promises";
12
+
13
+ // prototype/roadmap-local/model.mjs
14
+ var fields = ["title", "month", "owner", "state", "goal", "progress", "url", "delivered"];
15
+ function normalize(input) {
16
+ return Object.fromEntries([...fields, "reason"].map((key) => [key, String(input[key] ?? "").trim()]));
17
+ }
18
+
19
+ // prototype/roadmap-local/import.mjs
20
+ var allowed = /* @__PURE__ */ new Set(["id", "revision", "action", ...fields, "reason"]);
21
+ function records(base, store) {
22
+ return [...new Map([...base, ...Object.entries(store.entries)]).values()];
23
+ }
24
+ var comments = {
25
+ schemaVersion: "\u683C\u5F0F\u7248\u672C\uFF0C\u8BF7\u4FDD\u7559",
26
+ topicId: "\u4E13\u9898\u6807\u8BC6\uFF0C\u8BF7\u4FDD\u7559",
27
+ items: "\u9700\u8981\u65B0\u589E\u6216\u4FEE\u6539\u7684\u4E8B\u9879\uFF1B\u672A\u5217\u51FA\u7684\u65E7\u4E8B\u9879\u4FDD\u6301\u4E0D\u53D8",
28
+ id: "\u7A33\u5B9A\u82F1\u6587\u6807\u8BC6\uFF1B\u65B0\u589E\u65F6\u547D\u540D\uFF0C\u540E\u7EED\u66F4\u65B0\u4E0D\u6539",
29
+ revision: "\u81EA\u52A8\u7248\u672C\u53F7\uFF1A\u65B0\u589E\u586B 0\uFF0C\u66F4\u65B0\u4FDD\u7559\u5BFC\u51FA\u503C",
30
+ action: "upsert \u65B0\u589E/\u4FEE\u6539\uFF1Barchive \u5F52\u6863\uFF1Brestore \u6062\u590D",
31
+ title: "\u5FC5\u586B\uFF0C\u4F8B\u5982\uFF1A\u79DF\u6237\u7EA7\u6A21\u677F\u914D\u7F6E",
32
+ month: "\u5FC5\u586B\uFF0C\u8BA1\u5212\u4EA4\u4ED8\u6708\u4EFD\uFF0C\u4F8B\u5982\uFF1A2026-12",
33
+ owner: "\u5FC5\u586B\uFF0C\u8D1F\u8D23\u4EBA\u59D3\u540D",
34
+ state: "planned \u8BA1\u5212\u4E2D / progress \u8FDB\u884C\u4E2D / acceptance \u5F85\u9A8C\u6536 / release \u5DF2\u4EA4\u4ED8 / delayed \u5EF6\u671F / paused \u6682\u505C",
35
+ goal: "\u5FC5\u586B\uFF0C\u4F8B\u5982\uFF1A\u4F01\u4E1A\u7EDF\u4E00\u914D\u7F6E\u548C\u590D\u7528 PPT \u6A21\u677F",
36
+ progress: "\u9009\u586B\uFF0C\u4F8B\u5982\uFF1A\u5DF2\u5B8C\u6210\u65B9\u6848\uFF0C\u7B49\u5F85\u8BC4\u5BA1",
37
+ url: "\u9009\u586B\uFF0C\u6750\u6599\u94FE\u63A5\uFF1B\u6807\u8BB0\u5DF2\u4EA4\u4ED8\u65F6\u9700\u63D0\u4F9B\u771F\u5B9E\u9A8C\u6536\u4F9D\u636E",
38
+ delivered: "\u672A\u4EA4\u4ED8\u7559\u7A7A\uFF1B\u5DF2\u4EA4\u4ED8\u586B\u5B9E\u9645\u65E5\u671F\uFF0C\u4F8B\u5982\uFF1A2026-12-18",
39
+ reason: "\u8C03\u6574\u6708\u4EFD\u6216\u8F6C\u4E3A\u5EF6\u671F/\u6682\u505C\u65F6\u5FC5\u586B\uFF0C\u4F8B\u5982\uFF1A\u7B49\u5F85\u4E1A\u52A1\u65B9\u8BD5\u7528\u7ED3\u679C"
40
+ };
41
+ function toJsonc(document) {
42
+ const { _guide, ...data } = document;
43
+ return "// \u4EA4\u7ED9 Agent \u586B\u5199\u771F\u5B9E\u5185\u5BB9\uFF1B\u7A7A\u767D\u5FC5\u586B\u9879\u9700\u8865\u9F50\u3002\u8BF4\u660E\u662F\u6CE8\u91CA\uFF0C\u4E0D\u4F1A\u5BFC\u5165\u3002\n" + JSON.stringify(data, null, 2).split("\n").map((line) => {
44
+ const field = line.match(/^\s*"([A-Za-z]+)":/);
45
+ return field && comments[field[1]] ? line + " // " + comments[field[1]] : line;
46
+ }).join("\n") + "\n";
47
+ }
48
+ function stripJsonc(text2) {
49
+ let output = "", quoted = false;
50
+ for (let i = 0; i < text2.length; i++) {
51
+ const c = text2[i], n = text2[i + 1];
52
+ if (quoted) {
53
+ output += c;
54
+ if (c === "\\") {
55
+ output += text2[++i] || "";
56
+ } else if (c === '"') quoted = false;
57
+ continue;
58
+ }
59
+ if (c === '"') {
60
+ quoted = true;
61
+ output += c;
62
+ continue;
63
+ }
64
+ if (c === "/" && n === "/") {
65
+ output += " ";
66
+ i++;
67
+ while (i + 1 < text2.length && !["\n", "\r"].includes(text2[i + 1])) {
68
+ output += " ";
69
+ i++;
70
+ }
71
+ continue;
72
+ }
73
+ if (c === "/" && n === "*") {
74
+ output += " ";
75
+ i++;
76
+ let closed = false;
77
+ while (++i < text2.length) {
78
+ if (text2[i] === "*" && text2[i + 1] === "/") {
79
+ output += " ";
80
+ i++;
81
+ closed = true;
82
+ break;
83
+ }
84
+ output += ["\n", "\r"].includes(text2[i]) ? text2[i] : " ";
85
+ }
86
+ if (!closed) throw Error("\u5757\u6CE8\u91CA\u6CA1\u6709\u7ED3\u675F");
87
+ continue;
88
+ }
89
+ output += c;
90
+ }
91
+ let cleaned = "";
92
+ quoted = false;
93
+ for (let i = 0; i < output.length; i++) {
94
+ const c = output[i];
95
+ if (quoted) {
96
+ cleaned += c;
97
+ if (c === "\\") cleaned += output[++i] || "";
98
+ else if (c === '"') quoted = false;
99
+ continue;
100
+ }
101
+ if (c === '"') quoted = true;
102
+ if (c === ",") {
103
+ let j = i + 1;
104
+ while (/\s/.test(output[j] || "") && j < output.length) j++;
105
+ if (output[j] === "]" || output[j] === "}") {
106
+ cleaned += " ";
107
+ continue;
108
+ }
109
+ }
110
+ cleaned += c;
111
+ }
112
+ return cleaned;
113
+ }
114
+ function template(topic) {
115
+ return { schemaVersion: 1, topicId: topic.id, items: [{ id: topic.id + "-replace-with-stable-id", revision: 0, action: "upsert", title: "", month: "", owner: (topic.owners || []).join("\u3001"), state: "planned", goal: "", progress: "", url: "", delivered: "", reason: "" }] };
116
+ }
117
+ function exportTopic(topicId, base, store, onlyId) {
118
+ return { schemaVersion: 1, topicId, items: records(base, store).filter((row) => row.topic === topicId && (!onlyId || row.id === onlyId)).map((row) => row.archived ? { id: row.id, revision: row.revision, action: "archive" } : { id: row.id, revision: row.revision, action: "upsert", ...normalize(row), reason: "" }) };
119
+ }
120
+ function decode(text2) {
121
+ if (typeof text2 !== "string" || new TextEncoder().encode(text2).length > 1024 * 1024) throw Error("\u6587\u4EF6\u4E0D\u80FD\u8D85\u8FC7 1 MB\u3002");
122
+ try {
123
+ return JSON.parse(stripJsonc(text2.replace(/^\uFEFF/, "")));
124
+ } catch {
125
+ throw Error("JSON / JSONC \u683C\u5F0F\u4E0D\u6B63\u786E\uFF0C\u8BF7\u8BA9 Agent \u68C0\u67E5\u5F15\u53F7\u3001\u9017\u53F7\u548C\u6CE8\u91CA\u540E\u91CD\u65B0\u5BFC\u5165\u3002");
126
+ }
127
+ }
128
+
129
+ // scripts/business-context.json
130
+ var business_context_default = {
131
+ schemaVersion: 1,
132
+ reviewedAt: "2026-11-12",
133
+ scope: "\u5468\u4F1A\u5DE5\u4F5C\u53F0\u5DF2\u6536\u5F55\u7684Work\u4E1A\u52A1\u5C0F\u7EC4\u4E0E\u4E13\u9898\uFF0C\u4E0D\u4EE3\u8868\u5B8C\u6574\u516C\u53F8\u7EC4\u7EC7\u6216\u6B63\u5F0F\u4EBA\u4E8B\u6C47\u62A5\u7EBF",
134
+ source: "business-catalog.json: workGroups \u4E0E modules \u7684\u7A33\u5B9A\u5B57\u6BB5\uFF1B\u5468\u4F1A\u64CD\u4F5C\u89C4\u5219\u6309\u5F53\u524D\u7528\u6237\u7EA6\u5B9A\u548CCLI\u5951\u7EA6",
135
+ rules: [
136
+ "\u4E3BAgent\u52A0\u5DE5\u548C\u53D1\u5E03\u5468\u4F1A\u4E3B\u4F53\u5185\u5BB9\uFF1BOwner Agent\u7EF4\u62A4\u6388\u6743\u4E13\u9898\u7684\u5171\u4EAB\u94FE\u63A5\u3001\u8DEF\u7EBF\u4E0E\u8BAE\u9898\uFF1B\u7F51\u9875\u5C55\u793A\u7ED3\u679C\u5E76\u4FDD\u7559\u94FE\u63A5\u914D\u7F6E\u515C\u5E95\u3002",
137
+ "\u5468\u62A5\u63CF\u8FF0\u5B9E\u9645\u4EA4\u4ED8\u3001\u4EFB\u52A1\u8FDB\u5C55\u3001\u8FD0\u8425\u6307\u6807\u3001\u5BA2\u6237\u95EE\u9898\u548C\u5F85\u8BA8\u8BBA\u4E8B\u9879\uFF1B\u4E0D\u5F97\u5C06\u89C4\u5212\u3001\u6F14\u793A\u6216\u5F85\u9A8C\u6536\u5199\u6210\u5DF2\u4E0A\u7EBF\u3002",
138
+ "\u8BAE\u9898\u63D0\u62A5\u4E0D\u7B49\u4E8E\u6B63\u5F0F\u8BAE\u7A0B\uFF1B\u53EA\u6709\u6B63\u5F0F\u8BAE\u7A0B\u5B9E\u9645\u53D1\u5E03\u540E\u624D\u80FDinclude\u5E76\u63D0\u4F9B\u56DE\u94FE\u3002",
139
+ "\u6C47\u62A5\u5468\u6B21\u5BF9\u5E94\u88AB\u56DE\u987E\u7684\u5DE5\u4F5C\u5468\uFF0C\u4E0D\u6309\u5F00\u4F1A\u65E5\u671F\u731C\u6D4B\uFF1B\u5177\u4F53\u4F1A\u8BAE\u7528meetings list/show\u8BFB\u53D6\u6838\u5BF9\u3002",
140
+ "\u5468\u62A5\u76EE\u5F55\u4E0E\u770B\u677F\u662F\u4E13\u9898\u957F\u671F\u5165\u53E3\uFF0C\u5F53\u671F\u5468\u62A5\u6309\u5468\u4FDD\u5B58\uFF1B\u4E09\u4E2A\u94FE\u63A5\u72EC\u7ACB\uFF0C\u4E0D\u4E92\u76F8\u66FF\u4EE3\u3002",
141
+ "\u8DEF\u7EBF\u8C03\u6574\u9700\u4FDD\u7559\u7A33\u5B9AID\u548C\u7248\u672C\uFF1B\u8C03\u671F\u3001\u5EF6\u671F\u6216\u6682\u505C\u8BF4\u660E\u539F\u56E0\uFF0C\u5DF2\u4EA4\u4ED8\u63D0\u4F9B\u5B9E\u9645\u65E5\u671F\u4E0E\u9A8C\u6536\u4F9D\u636E\u3002",
142
+ "\u8D1F\u8D23\u4EBA\u548C\u7EC4\u957F\u4FE1\u606F\u5E2E\u52A9\u5B9A\u4F4D\u534F\u4F5C\uFF0C\u4E0D\u6388\u4E88\u64CD\u4F5C\u6743\u9650\uFF1B\u8DE8\u4E13\u9898\u6216\u804C\u8D23\u4E0D\u6E05\u65F6\u5148\u5411\u7528\u6237\u786E\u8BA4\u3002",
143
+ "\u64CD\u4F5C\u524D\u8BFB\u6700\u65B0\u5171\u4EAB\u6570\u636E\uFF0C\u4EC5\u4FEE\u6539\u7528\u6237\u6388\u6743\u5B57\u6BB5\uFF1Bdry-run\u9884\u89C8\u3001\u786E\u8BA4\u540Eapply\uFF0C\u4FDD\u5B58\u540E\u72EC\u7ACB\u8BFB\u56DE\u3002\u7ED3\u679C\u672A\u77E5\u7528\u539F\u8BA1\u5212ID\u91CD\u8BD5\u3002",
144
+ "\u672C\u4E0A\u4E0B\u6587\u968FCLI\u56FA\u5B9A\u53D1\u5E03\uFF1B\u5B83\u4E0D\u662F\u5B9E\u65F6\u7EC4\u7EC7\u76EE\u5F55\u3002\u6BCF\u5468\u8BAE\u9898\u3001\u8DEF\u7EBF\u72B6\u6001\u3001\u94FE\u63A5\u548C\u7EAA\u8981\u5FC5\u987B\u8054\u7F51\u8BFB\u53D6\uFF0C\u4E0D\u4ECE\u5185\u7F6E\u77E5\u8BC6\u63A8\u65AD\u3002"
145
+ ],
146
+ sourceAsOf: "2026-11-10",
147
+ groups: [
148
+ {
149
+ id: "foundation",
150
+ name: "\u57FA\u7840\u67B6\u6784\u7EC4",
151
+ leader: "\u9093\u5C55\u9E3F"
152
+ },
153
+ {
154
+ id: "general",
155
+ name: "\u901A\u7528\u80FD\u529B\u7EC4",
156
+ leader: "\u5F20\u4E91\u98DE"
157
+ },
158
+ {
159
+ id: "collaboration",
160
+ name: "\u534F\u540C\u529E\u516C\u7EC4",
161
+ leader: "\u519C\u4F73\u6377"
162
+ },
163
+ {
164
+ id: "knowledge",
165
+ name: "\u77E5\u8BC6\u4E0E\u4E0A\u4E0B\u6587\u7EC4",
166
+ leader: "\u738B\u831C"
167
+ }
168
+ ],
169
+ topics: [
170
+ {
171
+ id: "ppt",
172
+ name: "\u4F01\u4E1A\u7EA7 PPT",
173
+ groupId: "general",
174
+ owners: [
175
+ "\u5F20\u4E91\u98DE"
176
+ ],
177
+ scope: "\u628A\u4F01\u4E1A\u6C47\u62A5\u505A\u6210\u53EF\u7F16\u8F91\u3001\u53EF\u5BFC\u51FA\u3001\u53EF\u6301\u7EED\u590D\u7528\u7684\u6F14\u793A\u6587\u7A3F\u3002",
178
+ collaborationNote: null
179
+ },
180
+ {
181
+ id: "sites",
182
+ name: "\u4F01\u4E1A\u7EA7\u7AD9\u70B9",
183
+ groupId: "general",
184
+ owners: [
185
+ "\u5F20\u4E91\u98DE"
186
+ ],
187
+ scope: "\u53D1\u5E03\u548C\u5206\u4EAB\u4F01\u4E1A\u62A5\u544A\u4E0E\u770B\u677F\uFF0C\u652F\u6301\u7F51\u9875\u6210\u679C\u6301\u7EED\u66F4\u65B0\u548C\u4E1A\u52A1\u6570\u636E\u8054\u52A8\u3002",
188
+ collaborationNote: null
189
+ },
190
+ {
191
+ id: "mywork",
192
+ name: "\u6211\u7684\u5DE5\u4F5C\u4E0E\u4EFB\u52A1\u63A8\u8FDB",
193
+ groupId: "collaboration",
194
+ owners: [
195
+ "\u51AF\u80DC\u9F99"
196
+ ],
197
+ scope: "\u56F4\u7ED5\u6B63\u5728\u63A8\u8FDB\u7684\u5DE5\u4F5C\uFF0C\u8FDE\u63A5\u65E5\u5386\u3001\u4F1A\u8BAE\u3001\u8D23\u4EFB\u3001\u4EFB\u52A1\u548C\u6210\u679C\u3002",
198
+ collaborationNote: null
199
+ },
200
+ {
201
+ id: "minutes",
202
+ name: "\u4F1A\u8BAE\u4E0E AI \u901F\u8BB0",
203
+ groupId: "collaboration",
204
+ owners: [
205
+ "\u4EC7\u52C9"
206
+ ],
207
+ scope: "\u4EE5\u65E5\u5386\u7BA1\u65F6\u95F4\u3001\u4F1A\u8BAE\u505A\u534F\u540C\u3001\u901F\u8BB0\u7559\u4E8B\u5B9E\u3001\u5F55\u97F3\u5361\u8865\u91C7\u96C6\u3002",
208
+ collaborationNote: null
209
+ },
210
+ {
211
+ id: "knowledge",
212
+ name: "\u4E2A\u4EBA\u77E5\u8BC6\u5E93",
213
+ groupId: "knowledge",
214
+ owners: [
215
+ "\u738B\u831C"
216
+ ],
217
+ scope: "\u56F4\u7ED5\u4E2A\u4EBA\u8D44\u6599\u548C\u77E5\u8BC6\u7684\u6574\u7406\u3001\u6C89\u6DC0\u3001\u68C0\u7D22\u4E0E\u590D\u7528\u3002",
218
+ collaborationNote: null
219
+ },
220
+ {
221
+ id: "context",
222
+ name: "Work \u4E0A\u4E0B\u6587",
223
+ groupId: "knowledge",
224
+ owners: [
225
+ "\u738B\u831C",
226
+ "\u5F20\u99A8\u6728"
227
+ ],
228
+ scope: "\u8FDE\u63A5\u76EE\u6807\u3001\u6C9F\u901A\u3001\u8D44\u6599\u3001\u4EFB\u52A1\u4E0E\u6210\u679C\uFF0C\u4E3A Agent \u63D0\u4F9B\u5F53\u524D\u5DE5\u4F5C\u80CC\u666F\u3002",
229
+ collaborationNote: "\u8DE8\u7EC4\u534F\u4F5C"
230
+ },
231
+ {
232
+ id: "im",
233
+ name: "\u7075\u57FA IM",
234
+ groupId: "foundation",
235
+ owners: [
236
+ "\u90B5\u5609\u6167"
237
+ ],
238
+ scope: "\u8BA9\u5458\u5DE5\u4E0E Agent \u5728\u4F1A\u8BDD\u4E2D\u534F\u4F5C\u3001\u4EA4\u4ED8\u5E76\u63A5\u7EED\u5DE5\u4F5C\u3002",
239
+ collaborationNote: null
240
+ },
241
+ {
242
+ id: "research",
243
+ name: "\u91D1\u94A5\u8D22\u62A5\u4E0E\u884C\u4E1A\u7814\u7A76",
244
+ groupId: "collaboration",
245
+ owners: [
246
+ "\u5ED6\u884D\u6E05"
247
+ ],
248
+ scope: "\u56F4\u7ED5\u516C\u53F8\u7ECF\u8425\u3001\u8D22\u62A5\u4E0E\u884C\u4E1A\u5F62\u6210\u53EF\u6838\u5BF9\u3001\u53EF\u6301\u7EED\u8DDF\u8E2A\u7684\u7814\u7A76\u3002",
249
+ collaborationNote: null
250
+ },
251
+ {
252
+ id: "yangming",
253
+ name: "\u95EE\u9053\u9633\u660E",
254
+ groupId: "foundation",
255
+ owners: [
256
+ "\u5B81\u53EF"
257
+ ],
258
+ scope: "\u805A\u7126\u95EE\u9053\u9633\u660E\u7684\u79FB\u52A8\u7AEF\u4F53\u9A8C\u4E0E\u5B9E\u9645\u4EA4\u4ED8\u3002",
259
+ collaborationNote: null
260
+ }
261
+ ]
262
+ };
263
+
264
+ // scripts/business-context.mjs
265
+ function getContext(topicId) {
266
+ if (topicId !== void 0 && !business_context_default.topics.some((t) => t.id === topicId)) throw Error("\u672A\u77E5\u4E13\u9898\uFF1A" + topicId + "\uFF1B\u8FD0\u884C workbench topics \u67E5\u770B\u4E13\u9898ID\u3002");
267
+ const topics2 = topicId ? business_context_default.topics.filter((t) => t.id === topicId) : business_context_default.topics;
268
+ return { ...structuredClone(business_context_default), groups: business_context_default.groups.filter((g) => !topicId || topics2.some((t) => t.groupId === g.id)).map((g) => ({ ...g })), topics: structuredClone(topics2), liveCommands: topicId ? [`workbench links show --topic ${topicId} --week <YYYY-Www>`, `workbench roadmap list --topic ${topicId}`, "workbench agenda list --week <YYYY-Www>", "workbench meetings list"] : ["workbench topics", "workbench meetings list", "workbench agenda list --week <YYYY-Www>"] };
269
+ }
270
+ function formatContext(context) {
271
+ return `Lingee Workbench \xB7 \u4E1A\u52A1\u4E0A\u4E0B\u6587
272
+ \u8D44\u6599\u65E5\u671F ${context.sourceAsOf} \xB7 \u6838\u5BF9\u65E5\u671F ${context.reviewedAt}
273
+ ${context.scope}
274
+
275
+ ` + context.groups.map((g) => `${g.name} (${g.id}) \xB7 \u7EC4\u957F\uFF1A${g.leader || "\u672A\u786E\u8BA4"}
276
+ ` + context.topics.filter((t) => t.groupId === g.id).map((t) => ` ${t.id} \xB7 ${t.name} \xB7 \u4E13\u9898\u8D1F\u8D23\u4EBA\uFF1A${t.owners.join("\u3001") || "\u672A\u786E\u8BA4"}
277
+ ${t.scope || "\u804C\u8D23\u5F85\u786E\u8BA4"}${t.collaborationNote ? "\n \u534F\u4F5C\u8BF4\u660E\uFF1A" + t.collaborationNote : ""}`).join("\n")).join("\n\n") + "\n\n\u5468\u4F1A\u89C4\u5219\n" + context.rules.map((r, i) => `${i + 1}. ${r}`).join("\n") + "\n\n\u8BFB\u53D6\u6700\u65B0\u534F\u4F5C\u6570\u636E\uFF08\u9700\u8054\u7F51\uFF09\n" + context.liveCommands.join("\n") + "\n";
278
+ }
279
+
280
+ // scripts/agenda-model.mjs
281
+ var topics = /* @__PURE__ */ new Set([...getContext().topics.map((t) => t.id), "cross"]);
282
+ var text = (value, max) => typeof value === "string" && value.trim().length > 0 && value.length <= max;
283
+ function validateAgenda(payload) {
284
+ if (!/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/.test(payload.id || "")) throw Error("\u8BAE\u9898 ID \u5FC5\u987B\u662F\u6709\u6548 UUID\u3002");
285
+ if (!Number.isSafeInteger(payload.revision) || payload.revision < 0) throw Error("\u8BAE\u9898\u7248\u672C\u65E0\u6548\uFF0C\u8BF7\u91CD\u65B0\u8BFB\u53D6\u3002");
286
+ const url = (value) => {
287
+ if (typeof value !== "string" || value.length > 2048) return false;
288
+ if (!value) return true;
289
+ try {
290
+ const u = new URL(value);
291
+ return ["http:", "https:"].includes(u.protocol) && !u.username && !u.password;
292
+ } catch {
293
+ return false;
294
+ }
295
+ };
296
+ if (payload.action === "proposalSave") {
297
+ if (!/^20\d{2}-W(0[1-9]|[1-4]\d|5[0-3])$/.test(payload.week || "") || !topics.has(payload.topicId) || !text(payload.submitter, 50) || !text(payload.title, 120) || !text(payload.question, 1500) || !url(payload.materialUrl)) throw Error("\u8BF7\u63D0\u4F9B\u6709\u6548\u5468\u6B21\u3001\u4E13\u9898\u3001\u63D0\u4EA4\u4EBA(\u226450)\u3001\u6807\u9898(\u2264120)\u3001\u95EE\u9898(\u22641500)\u53CA HTTP/HTTPS \u6750\u6599\u94FE\u63A5\u3002");
298
+ } else if (payload.action === "proposalLink") {
299
+ if (!payload.formalUrl || !url(payload.formalUrl)) throw Error("\u7EB3\u5165\u8BAE\u7A0B\u9700\u8981\u5DF2\u53D1\u5E03\u7684\u6B63\u5F0F\u8BAE\u7A0B HTTP/HTTPS \u94FE\u63A5\u3002");
300
+ } else if (payload.action !== "proposalWithdraw") throw Error("\u4E0D\u652F\u6301\u7684\u8BAE\u9898\u64CD\u4F5C\u3002");
301
+ }
302
+ function agendaChanges(payload, previous) {
303
+ const after = payload.action === "proposalSave" ? { week: payload.week, topicId: payload.topicId, submitter: payload.submitter, title: payload.title, question: payload.question, materialUrl: payload.materialUrl } : payload.action === "proposalLink" ? { status: "included", formalUrl: payload.formalUrl } : { status: "withdrawn" };
304
+ return Object.entries(after).filter(([key, value]) => (previous?.[key] ?? "") !== value).map(([field, after2]) => ({ field, before: previous?.[field] ?? "", after: after2 }));
305
+ }
306
+
307
+ // scripts/roadmap-guide.mjs
308
+ var example = template({ id: "ppt" });
309
+ Object.assign(example.items[0], { id: "ppt-tenant-template", title: "\u79DF\u6237\u7EA7\u6A21\u677F\u914D\u7F6E", month: "2026-12", owner: "\u4E13\u9898\u8D1F\u8D23\u4EBA", goal: "\u4F01\u4E1A\u7EDF\u4E00\u914D\u7F6E\u548C\u590D\u7528 PPT \u6A21\u677F" });
310
+ var roadmapGuide = `# \u8DEF\u7EBF\u586B\u62A5\u6307\u5357
311
+
312
+ \u672C\u8BF4\u660E\u968F CLI \u5B89\u88C5\uFF0C\u4E0D\u8054\u7F51\u3002template \u4E5F\u53EF\u672C\u5730\u751F\u6210\uFF1B\u8BFB\u53D6\u5F53\u524D\u8DEF\u7EBF\u3001\u5BFC\u51FA\u3001\u9884\u89C8\u548C\u4FDD\u5B58\u5747\u9700\u8981\u8FDE\u63A5 CloudBase\u3002\u6CA1\u6709\u79BB\u7EBF\u4FDD\u5B58\u540E\u81EA\u52A8\u540C\u6B65\u673A\u5236\u3002
313
+
314
+ ## \u5355\u9879\u8C03\u6574
315
+
316
+ workbench topics
317
+ workbench roadmap list --topic ppt
318
+ workbench roadmap update <id> --topic ppt --month 2026-12 --reason "\u8C03\u6574\u4EA4\u4ED8\u7A97\u53E3" --dry-run
319
+ workbench apply <plan-id> --yes
320
+ workbench roadmap show <id> --topic ppt
321
+
322
+ ppt \u662F\u793A\u4F8B\uFF0C\u8BF7\u9009\u62E9\u5B9E\u9645\u8D1F\u8D23\u7684\u4E13\u9898\u3002\u5148\u8BFB\u53D6\uFF0C\u518D\u4FEE\u6539\u7528\u6237\u6388\u6743\u7684\u5B57\u6BB5\uFF1B--dry-run \u5C55\u793A\u5DEE\u5F02\uFF0C\u786E\u8BA4\u540E\u4F7F\u7528\u8F93\u51FA\u7684\u8BA1\u5212 ID \u4FDD\u5B58\u3002\u4FDD\u5B58\u540E\u72EC\u7ACB\u8BFB\u56DE\uFF1B\u7F51\u9875\u5237\u65B0\u8BFB\u53D6\u540C\u4E00\u4EFD\u6570\u636E\uFF0C\u65E0\u9700\u91CD\u65B0\u53D1\u5E03\u3002
323
+
324
+ ## \u6279\u91CF\u586B\u62A5
325
+
326
+ \u5DF2\u6709\u8DEF\u7EBF\uFF1Aworkbench roadmap export --topic ppt --out route.jsonc
327
+ \u7A7A\u767D\u4E13\u9898\uFF1Aworkbench roadmap template --topic ppt --out route.jsonc
328
+ \u7F16\u8F91\u6587\u4EF6\u540E\uFF1Aworkbench roadmap import route.jsonc --dry-run
329
+ \u786E\u8BA4\u540E\uFF1Aworkbench apply <plan-id> --yes
330
+ \u8BFB\u56DE\uFF1Aworkbench roadmap list --topic ppt
331
+
332
+ \u5BFC\u51FA\u6587\u4EF6\u5305\u542B\u5B57\u6BB5\u6CE8\u91CA\u3002\u652F\u6301 JSON / JSONC\u3001\u6CE8\u91CA\u548C\u5C3E\u9017\u53F7\uFF1B--out \u4E0D\u8986\u76D6\u5DF2\u6709\u6587\u4EF6\u3002
333
+ \u6BCF\u4EFD\u4E00\u4E2A\u4E13\u9898\u3001\u6700\u591A 200 \u9879\u3001\u6587\u4EF6\u4E0D\u8D85\u8FC7 1 MiB\u3002\u672A\u5217\u51FA\u7684\u4E8B\u9879\u4FDD\u7559\uFF1B\u4EFB\u4E00\u9879\u9519\u8BEF\u5219\u6574\u6279\u4E0D\u4FDD\u5B58\u3002
334
+
335
+ ## \u5B57\u6BB5\u4E0E\u793A\u4F8B
336
+
337
+ \u4EE5\u4E0B\u4EC5\u793A\u4F8B\uFF0C\u4E0D\u4EE3\u8868\u771F\u5B9E\u4EA4\u4ED8\u627F\u8BFA\uFF1A
338
+
339
+ ${toJsonc(example)}
340
+ schemaVersion \u56FA\u5B9A 1\uFF1BtopicId \u4F7F\u7528 topics \u4E2D\u7684 ID\u3002
341
+ \u65B0\u589E id \u4EE5\u4E13\u9898 ID \u52A0\u8FDE\u5B57\u7B26\u6216\u659C\u6760\u5F00\u5934\uFF0C\u4F8B\u5982 ppt-tenant-template\uFF1B1\u2013120 \u4F4D\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u8FDE\u5B57\u7B26\u3001\u4E0B\u5212\u7EBF\u6216\u659C\u6760\u3002\u540E\u7EED\u4FDD\u7559\u539F id\uFF1B\u65B0\u589E revision \u586B 0\uFF0C\u66F4\u65B0\u4FDD\u7559\u6700\u65B0\u5BFC\u51FA\u503C\uFF0C\u4E0D\u81EA\u884C\u9012\u589E\u3002
342
+ upsert \u5FC5\u987B\u4FDD\u7559 title\u3001month\u3001owner\u3001state\u3001goal\u3001progress\u3001url\u3001delivered \u516B\u4E2A\u5B57\u7B26\u4E32\u5B57\u6BB5\uFF1B\u9009\u586B\u503C\u7528\u7A7A\u5B57\u7B26\u4E32\u3002\u6279\u91CF upsert \u662F\u5B8C\u6574\u5B57\u6BB5\u503C\uFF0C\u5C40\u90E8\u4FEE\u6539\u5EFA\u8BAE\u7528 update\u3002
343
+ title\u3001owner \u5FC5\u586B\u4E14\u5404\u4E0D\u8D85\u8FC7 160 \u5B57\uFF1Bgoal \u5FC5\u586B\u4E14\u4E0D\u8D85\u8FC7 2000 \u5B57\uFF1Bprogress \u548C reason \u5404\u4E0D\u8D85\u8FC7 2000 \u5B57\u3002
344
+ month \u4E3A 20xx-MM\uFF1Burl \u4E3A HTTP/HTTPS \u5730\u5740\uFF0C\u4E0D\u5E26\u7528\u6237\u540D\u6216\u5BC6\u7801\u3002delivered \u4E3A\u5B9E\u9645\u4EA4\u4ED8\u65E5\u671F YYYY-MM-DD\uFF0C\u672A\u4EA4\u4ED8\u7559\u7A7A\u3002
345
+ state \u5E38\u7528 planned / progress / acceptance / release / delayed / paused\uFF1B\u5BFC\u51FA\u4E2D\u7684\u5386\u53F2 tracking / pilot / verified \u53EF\u4FDD\u7559\u3002
346
+ \u8C03\u6574\u6708\u4EFD\u3001\u8FDB\u5165 delayed \u6216 paused \u65F6\u5FC5\u987B\u8BF4\u660E reason\uFF1Brelease \u9700\u8981\u771F\u5B9E\u4EA4\u4ED8\u65E5\u671F\u548C\u9A8C\u6536\u94FE\u63A5\u3002\u5386\u53F2\u5DF2\u4EA4\u4ED8\u8BB0\u5F55\u7684\u65E2\u6709\u4F9D\u636E\u6309\u670D\u52A1\u7AEF\u517C\u5BB9\u89C4\u5219\u4FDD\u7559\uFF0C\u4E0D\u51ED\u7A7A\u8865\u9020\u3002
347
+
348
+ ## \u5F52\u6863\u4E0E\u6062\u590D
349
+
350
+ workbench roadmap archive <id> --topic ppt --reason "\u4E0D\u518D\u63A8\u8FDB" --dry-run
351
+ workbench roadmap restore <id> --topic ppt --reason "\u6062\u590D\u63A8\u8FDB" --dry-run
352
+
353
+ \u6BCF\u6B21\u9884\u89C8\u540E\u786E\u8BA4\uFF0C\u518D apply \u5BF9\u5E94\u8BA1\u5212\u3002\u6279\u91CF\u5F52\u6863/\u6062\u590D\u9879\u53EA\u4FDD\u7559 id\u3001revision\u3001action \u548C\u53EF\u9009 reason\uFF0Caction \u4E3A archive \u6216 restore\uFF1B\u4E0D\u8981\u9644\u5E26\u5185\u5BB9\u5B57\u6BB5\u3002\u5DF2\u5F52\u6863\u4E8B\u9879\u5148\u6062\u590D\uFF0C\u518D\u8BFB\u53D6\u65B0\u7248\u672C\u7F16\u8F91\u3002\u5220\u9664\u6587\u4EF6\u91CC\u7684\u884C\u4E0D\u4F1A\u5F52\u6863\u4E91\u7AEF\u4E8B\u9879\u3002
354
+
355
+ ## \u51B2\u7A81\u3001\u91CD\u8BD5\u4E0E\u53D8\u66F4\u8BB0\u5F55
356
+
357
+ \u7248\u672C\u51B2\u7A81\uFF1A\u91CD\u65B0 export \u6216 show\uFF0C\u5408\u5E76\u6700\u65B0\u503C\u540E\u751F\u6210\u65B0\u9884\u89C8\uFF1B\u4E0D\u8981\u5F3A\u6539 revision \u7ED5\u8FC7\u6821\u9A8C\u3002
358
+ \u4FDD\u5B58\u7ED3\u679C\u672A\u77E5\uFF1A\u7528\u539F\u8BA1\u5212 ID \u91CD\u8BD5 apply\uFF0C\u4E0D\u91CD\u65B0\u751F\u6210\u8BA1\u5212\u3002\u8BA1\u5212\u5B58\u4E8E\u672C\u673A\uFF0C\u6362\u7535\u8111\u9700\u91CD\u65B0\u8BFB\u53D6\u5E76\u6838\u5BF9\u5B9E\u9645\u7ED3\u679C\u3002
359
+ workbench roadmap history --topic ppt
360
+ Agent \u4F7F\u7528 --json \u83B7\u53D6\u7ED3\u6784\u5316\u7ED3\u679C\uFF1B\u9519\u8BEF\u5199 stderr\u3002\u9000\u51FA\u7801 0 \u6210\u529F/\u9884\u89C8\u30011 \u8FD0\u884C\u9519\u8BEF\u30012 \u7528\u6CD5\u6216\u7F3A\u5C11\u786E\u8BA4\u30013 \u51B2\u7A81\u30014 \u7F51\u7EDC\u7ED3\u679C\u672A\u77E5\u3002
361
+ `;
362
+
363
+ // scripts/workbench.mjs
364
+ var VERSION = "1.0.3";
365
+ var ENDPOINT = "https://lingee-sites-dev-d0eqwaj6ca39fde-1251890496.ap-shanghai.app.tcloudbase.com/weekly-config";
366
+ var SITE = "https://site.lingee.com/sites/KUeKpIJOTkiNd_GWtg7H2Q9c7c5952/";
367
+ var TOPICS = Object.fromEntries(getContext().topics.map((t) => [t.id, t.name]));
368
+ var hash = (x) => createHash("sha256").update(JSON.stringify(x)).digest("hex");
369
+ var clean = (x) => String(x ?? "").replace(/[\x00-\x1f\x7f-\x9f]/g, " ");
370
+ var flags = { local: { type: "boolean" }, file: { type: "string" }, date: { type: "string" }, "from-week": { type: "string" }, item: { type: "string" }, results: { type: "boolean" }, "record-url": { type: "string" }, help: { type: "boolean", short: "h" }, version: { type: "boolean", short: "V" }, json: { type: "boolean" }, yes: { type: "boolean", short: "y" }, "dry-run": { type: "boolean" }, topic: { type: "string" }, week: { type: "string" }, out: { type: "string", short: "o" }, field: { type: "string" }, "report-hub": { type: "string" }, "weekly-report": { type: "string" }, dashboard: { type: "string" }, id: { type: "string" }, title: { type: "string" }, month: { type: "string" }, owner: { type: "string" }, state: { type: "string" }, goal: { type: "string" }, progress: { type: "string" }, url: { type: "string" }, delivered: { type: "string" }, reason: { type: "string" }, submitter: { type: "string" }, question: { type: "string" }, "material-url": { type: "string" }, "formal-url": { type: "string" } };
371
+ var linkFields = { "report-hub": "reportHub", "weekly-report": "weeklyReport", dashboard: "dashboardUrl" };
372
+ var flagDescriptions = { file: "JSON/JSONC\u6587\u4EF6\uFF0C\u5148\u5BFC\u51FA\u518D\u7F16\u8F91", date: "\u4F1A\u8BAE\u65E5\u671F YYYY-MM-DD", "from-week": "\u5DF2\u5F52\u6863\u7684\u6765\u6E90\u6C47\u62A5\u5468", item: "\u6765\u6E90\u8BAE\u7A0B\u9879ID", results: "\u5BFC\u51FA\u4F1A\u540E\u7ED3\u8BBA\u586B\u5199\u6A21\u677F", local: "\u4EC5\u4F7F\u7528127.0.0.1:8798\u672C\u5730\u9884\u89C8\u540E\u7AEF", "record-url": "HTTPS\u7EAA\u8981\u4E0E\u5F55\u97F3\u5206\u4EAB\u94FE\u63A5", submitter: "\u63D0\u4EA4\u4EBA\uFF08\u586B\u5199\u8005\uFF0C\u4E0D\u662F\u767B\u5F55\u8EAB\u4EFD\uFF09", question: "\u5E0C\u671B\u4F1A\u4E0A\u89E3\u51B3\u7684\u95EE\u9898", "material-url": "\u6750\u6599 HTTP/HTTPS \u5730\u5740\uFF0C\u53EF\u7A7A\u4E32\u6E05\u9664", "formal-url": "\u5DF2\u7ECF\u53D1\u5E03\u7684\u6B63\u5F0F\u8BAE\u7A0B\u5730\u5740", topic: "\u4E13\u9898 ID\uFF0C\u9ED8\u8BA4\u4F7F\u7528 init \u8BBE\u7F6E", week: "\u6B63\u6587\u6240\u5C5E\u5468\u6B21 YYYY-Www\uFF0C\u4E0D\u9690\u5F0F\u731C\u6D4B", out: "\u8F93\u51FA\u6587\u4EF6\uFF1B\u5DF2\u5B58\u5728\u65F6\u62D2\u7EDD\u8986\u76D6", field: "report-hub | weekly-report | dashboard", "report-hub": "\u5468\u62A5\u76EE\u5F55 URL", "weekly-report": "\u6307\u5B9A\u5468\u6B63\u6587 URL", dashboard: "\u8FD0\u8425\u770B\u677F URL", id: "\u53EF\u9009\u7A33\u5B9A ID\uFF08\u9ED8\u8BA4\u81EA\u52A8\u751F\u6210\uFF09", title: "\u91CC\u7A0B\u7891\u540D\u79F0", month: "\u4EA4\u4ED8\u6708\u4EFD YYYY-MM", owner: "\u8D1F\u8D23\u4EBA", state: "planned | progress | acceptance | release | delayed | paused", goal: "\u660E\u786E\u4EA4\u4ED8\u76EE\u6807", progress: "\u8FDB\u5C55\u6216\u963B\u585E", url: "\u6750\u6599\u6216\u9A8C\u6536\u4F9D\u636E URL", delivered: "\u5B9E\u9645\u4EA4\u4ED8\u65E5\u671F YYYY-MM-DD", reason: "\u53D8\u66F4\u539F\u56E0", "dry-run": "\u4EC5\u9884\u89C8\uFF0C\u7EDD\u4E0D\u5199\u4E91\u7AEF", yes: "\u5DF2\u83B7\u786E\u8BA4\uFF0C\u63D0\u4EA4\u672C\u6B21\u53D8\u66F4" };
373
+ var mutations = ["meetings draft", "meetings publish", "meetings close", "meetings carry", "meetings set", "meetings clear", "agenda add", "agenda update", "agenda withdraw", "agenda include", "links set", "links clear", "roadmap add", "roadmap update", "roadmap archive", "roadmap restore", "roadmap import"];
374
+ var specs = {
375
+ "meetings cycles": { usage: "meetings cycles", text: "\u5217\u51FA\u5F53\u524D\u4F1A\u8BAE\u3001\u8349\u7A3F\u53CA\u5F52\u6863\u4F1A\u8BAE\uFF0C\u4E0D\u6309\u65E5\u5386\u81EA\u52A8\u5207\u5468", options: [] },
376
+ "meetings detail": { usage: "meetings detail --week <YYYY-Www>", text: "\u8BFB\u53D6\u5B8C\u6574\u4F1A\u8BAE\u3001\u51BB\u7ED3\u5FEB\u7167\u548C\u5F53\u524D\u5468", options: ["week"] },
377
+ "meetings template": { usage: "meetings template --week <YYYY-Www> --date <YYYY-MM-DD> --title <text> [--out <file.json>]", text: "\u672C\u5730\u751F\u6210\u7A7A\u8BAE\u7A0B\u6A21\u677F\uFF1B\u6BCF\u9879\u542Bid/topicId/title/owner/question/materials\uFF0C\u53D1\u5E03\u524D\u81F3\u5C11\u4E00\u9879", options: ["week", "date", "title", "out"] },
378
+ "meetings export": { usage: "meetings export --week <YYYY-Www> [--results] [--out <file.json>]", text: "\u5BFC\u51FA\u8BAE\u7A0B\u6216\u4F1A\u540E\u7ED3\u8BBA\u6A21\u677F\uFF1Bresults\u9010\u9879\u586Bdecision/disposition(resolved|deferred|cancelled)\u53CAactions\uFF0C\u884C\u52A8status\u4E3Aopen|done|cancelled", options: ["week", "results", "out"] },
379
+ "meetings draft": { usage: "meetings draft --file <agenda.json> [--dry-run]", text: "\u521B\u5EFA/\u66F4\u65B0\u8349\u7A3F\uFF1BschemaVersion=1\uFF0C\u4FDD\u7559week/revision\u53CA\u7A33\u5B9A\u4E8B\u9879ID\uFF1B\u53EA\u6709\u8349\u7A3F\u53EF\u7F16\u8F91\uFF0C\u4E0D\u5207\u5F53\u524D\u5468", options: ["file", "dry-run", "yes"] },
380
+ "meetings publish": { usage: "meetings publish --week <YYYY-Www> [--dry-run]", text: "\u51BB\u7ED3\u8BAE\u7A0B\u5E76\u5207\u4E3A\u5F53\u524D\u4F1A\u8BAE\uFF1B\u4E0A\u4E00\u573A\u5FC5\u987B\u5148\u5F52\u6863\u3002\u6765\u6E90proposalId/proposalRevision\u5728\u540C\u4E00\u4E8B\u52A1\u6838\u5BF9\u5E76\u7EB3\u5165", options: ["week", "dry-run", "yes"] },
381
+ "meetings close": { usage: "meetings close --file <results.json> [--dry-run]", text: "\u9010\u9879\u586B\u5199\u7ED3\u8BBA\u548C\u884C\u52A8\u540E\u5F52\u6863\uFF1B\u51BB\u7ED3\u8D1F\u8D23\u4EBA\u3001\u6750\u6599\u3001\u7EAA\u8981\u548C\u7ED3\u679C\uFF0C\u4E4B\u540E\u4E0D\u53EF\u8986\u76D6", options: ["file", "dry-run", "yes"] },
382
+ "meetings pending": { usage: "meetings pending --week <YYYY-Www>", text: "\u8BFB\u53D6\u5F52\u6863\u4F1A\u8BAE\u7684\u5EF6\u671F\u9879\u548C\u672A\u7ED3\u884C\u52A8\uFF0C\u5386\u53F2\u72B6\u6001\u56FA\u5B9A", options: ["week"] },
383
+ "meetings carry": { usage: "meetings carry --from-week <YYYY-Www> --week <YYYY-Www> --item <id> [--dry-run]", text: "\u5C06\u5F52\u6863\u672A\u7ED3\u9879\u63A5\u7EED\u5230\u5DF2\u5EFA\u8349\u7A3F\uFF1B\u4FDD\u7559\u6765\u6E90\u54C8\u5E0C/\u7ED3\u8BBA/\u884C\u52A8\uFF0C\u6BCF\u9879\u53EA\u63A5\u7EED\u4E00\u6B21\uFF0C\u4E0D\u6539\u5386\u53F2", options: ["from-week", "week", "item", "dry-run", "yes"] },
384
+ "context": { usage: "context [--topic <id>]", text: "\u79BB\u7EBF\u8BFB\u53D6\u4E1A\u52A1\u5C0F\u7EC4\u3001\u4E13\u9898\u8D1F\u8D23\u4EBA\u3001\u804C\u8D23\u548C\u5468\u4F1A\u89C4\u5219\uFF1B\u4E0D\u6307\u5B9A\u4E13\u9898\u65F6\u5C55\u793A\u5168\u90E8", options: ["topic"], example: "workbench context --topic ppt --json" },
385
+ "meetings list": { usage: "meetings list", text: "\u5217\u51FA\u5DF2\u6536\u5F55\u5468\u4F1A\u53CA\u7EAA\u8981\u94FE\u63A5", options: [] },
386
+ "meetings show": { usage: "meetings show --week <YYYY-Www>", text: "\u8BFB\u53D6\u6C47\u62A5\u5468\u5BF9\u5E94\u4F1A\u8BAE\u65E5\u671F\u3001\u6807\u9898\u548C\u7EAA\u8981\u94FE\u63A5", options: ["week"] },
387
+ "meetings set": { usage: "meetings set --week <YYYY-Www> --record-url <HTTPS URL>", text: "\u8BBE\u7F6E\u7EAA\u8981\u4E0E\u5F55\u97F3\u5206\u4EAB\u94FE\u63A5\uFF1B\u5148show\u6838\u5BF9\u4F1A\u8BAE\u65E5\u671F\uFF0C\u5468\u6B21\u4E0D\u662F\u5F00\u4F1A\u65E5\u671F\u6240\u5728\u5468", options: ["week", "record-url", "dry-run", "yes"], example: "workbench meetings set --week 2026-W45 --record-url https://example.com/minutes --dry-run" },
388
+ "meetings clear": { usage: "meetings clear --week <YYYY-Www>", text: "\u6E05\u9664\u7EAA\u8981\u94FE\u63A5\uFF0C\u4FDD\u7559\u5468\u4F1A\u8BAE\u7A0B\uFF1B\u5148\u9884\u89C8\u786E\u8BA4", options: ["week", "dry-run", "yes"] },
389
+ "topics": { usage: "topics", text: "\u5217\u51FA\u4E13\u9898 ID \u4E0E\u540D\u79F0", options: [] },
390
+ "init": { usage: "init --topic <id>", text: "\u8BBE\u7F6E\u5F53\u524D\u7535\u8111\u7684\u9ED8\u8BA4\u4E13\u9898\uFF1B\u4E0D\u5199\u4E91\u7AEF", options: ["topic"] },
391
+ "status": { usage: "status", text: "\u68C0\u67E5\u7248\u672C\u3001\u9ED8\u8BA4\u4E13\u9898\u548C\u4E91\u7AEF\u8FDE\u901A\u6027", options: [] },
392
+ "links show": { usage: "links show [topic] --week <YYYY-Www>", text: "\u67E5\u770B\u6307\u5B9A\u5468\u6B63\u6587\u3001\u76EE\u5F55\u548C\u770B\u677F", options: ["topic", "week"], example: "workbench links show ppt --week 2026-W45" },
393
+ "links set": { usage: "links set [topic] [--week <YYYY-Www>] <field flags>", text: "\u53EA\u4FEE\u6539\u660E\u786E\u6307\u5B9A\u7684\u94FE\u63A5\u5B57\u6BB5\uFF0C\u4FDD\u7559\u5176\u4ED6\u914D\u7F6E", options: ["topic", "week", ...Object.keys(linkFields), "dry-run", "yes"], example: "workbench links set ppt --week 2026-W45 --weekly-report https://example.com/report --dry-run" },
394
+ "links clear": { usage: "links clear [topic] --field <report-hub|weekly-report|dashboard> [--week <YYYY-Www>]", text: "\u660E\u786E\u6E05\u7A7A\u4E00\u4E2A\u94FE\u63A5\u5B57\u6BB5", options: ["topic", "week", "field", "dry-run", "yes"], example: "workbench links clear ppt --field weekly-report --week 2026-W45 --dry-run" },
395
+ "roadmap list": { usage: "roadmap list [topic]", text: "\u5217\u51FA\u5F53\u524D\u4E13\u9898\u8DEF\u7EBF\uFF08\u542B\u5F52\u6863\u6807\u8BB0\uFF09", options: ["topic"] },
396
+ "roadmap guide": { usage: "roadmap guide [--out <file.md>]", text: "\u79BB\u7EBF\u67E5\u770B\u586B\u5199\u89C4\u5219\u3001\u5B57\u6BB5\u8BF4\u660E\u3001\u793A\u4F8B\u4E0E\u5BFC\u5165\u6D41\u7A0B", options: ["out"], example: "workbench roadmap guide" },
397
+ "roadmap show": { usage: "roadmap show <id> [--topic <topic>]", text: "\u67E5\u770B\u4E00\u9879\u91CC\u7A0B\u7891\u7684\u5B8C\u6574\u5185\u5BB9\u4E0E\u7248\u672C", options: ["topic"] },
398
+ "roadmap add": { usage: "roadmap add --topic <topic> --title <text> --month <YYYY-MM> --owner <name> --goal <text>", text: "\u65B0\u589E\u4E00\u9879\u91CC\u7A0B\u7891\uFF0C\u81EA\u52A8\u751F\u6210\u7A33\u5B9A ID", options: ["topic", "id", ...fields, "reason", "dry-run", "yes"], example: 'workbench roadmap add --topic ppt --title "\u79DF\u6237\u6A21\u677F\u914D\u7F6E" --month 2026-12 --owner "\u5F20\u4E91\u98DE" --goal "\u4F01\u4E1A\u7EDF\u4E00\u7EF4\u62A4\u6A21\u677F" --dry-run' },
399
+ "roadmap update": { usage: "roadmap update <id> [--topic <topic>] <field flags>", text: "\u53EA\u66F4\u65B0\u6307\u5B9A\u5B57\u6BB5\uFF1B\u8C03\u6574\u6708\u4EFD\u6216\u5EF6\u671F/\u6682\u505C\u9700 --reason", options: ["topic", ...fields, "reason", "dry-run", "yes"], example: 'workbench roadmap update ppt/tenant-template-config/0 --topic ppt --month 2026-12 --reason "\u6309\u4EA4\u4ED8\u7A97\u53E3\u8C03\u6574" --dry-run' },
400
+ "roadmap archive": { usage: "roadmap archive <id> [--topic <topic>] [--reason <text>]", text: "\u5F52\u6863\u4E00\u9879\u91CC\u7A0B\u7891\uFF0C\u4FDD\u7559\u5386\u53F2", options: ["topic", "reason", "dry-run", "yes"] },
401
+ "roadmap restore": { usage: "roadmap restore <id> [--topic <topic>] [--reason <text>]", text: "\u6062\u590D\u4E00\u9879\u5DF2\u5F52\u6863\u91CC\u7A0B\u7891", options: ["topic", "reason", "dry-run", "yes"] },
402
+ "roadmap history": { usage: "roadmap history [topic]", text: "\u67E5\u770B\u4E91\u7AEF\u53D8\u66F4\u5386\u53F2", options: ["topic"] },
403
+ "roadmap export": { usage: "roadmap export [topic] [--out <file.jsonc>]", text: "\u5BFC\u51FA\u8DEF\u7EBF\u4F9B\u6279\u91CF\u7F16\u8F91\uFF1B\u4E0D\u6307\u5B9A --out \u65F6\u5199\u6807\u51C6\u8F93\u51FA", options: ["topic", "out"] },
404
+ "roadmap template": { usage: "roadmap template [topic] [--out <file.jsonc>]", text: "\u751F\u6210\u5E26\u5B57\u6BB5\u6CE8\u91CA\u7684\u7A7A\u767D\u6A21\u677F", options: ["topic", "out"] },
405
+ "roadmap import": { usage: "roadmap import <file.jsonc> [--dry-run|--yes]", text: "\u9884\u89C8\u5E76\u6574\u6279\u5BFC\u5165\uFF0C\u4EFB\u4F55\u9519\u8BEF\u5747\u4E0D\u4FDD\u5B58", options: ["dry-run", "yes"] },
406
+ "apply": { usage: "apply <plan-id> [--yes]", text: "\u63D0\u4EA4\u5DF2\u9884\u89C8\u8BA1\u5212\uFF1B\u7F51\u7EDC\u7ED3\u679C\u672A\u77E5\u65F6\u7528\u540C\u4E00 ID \u91CD\u8BD5", options: ["yes"] },
407
+ "agenda show": { usage: "agenda show <id>", text: "\u6309\u7A33\u5B9AID\u67E5\u770B\u8BAE\u9898\u4E0E\u72B6\u6001", options: [] },
408
+ "agenda add": { usage: "agenda add --topic <topic|cross> --week <YYYY-Www> --submitter <name> --title <text> --question <text>", text: "\u63D0\u62A5\u8BAE\u9898\uFF1B\u9884\u89C8\u786E\u8BA4\u540E\u4FDD\u5B58\uFF0C\u751F\u6210\u7A33\u5B9AID", example: 'workbench agenda add --topic ppt --week 2026-W45 --submitter "\u8D1F\u8D23\u4EBA" --title "\u6A21\u677F\u65B9\u6848\u8BC4\u5BA1" --question "\u786E\u8BA4\u8BD5\u7528\u8303\u56F4" --dry-run', options: ["topic", "week", "submitter", "title", "question", "material-url", "dry-run", "yes"] },
409
+ "agenda update": { usage: "agenda update <id> <field flags>", text: "\u66F4\u65B0\u5F85\u6574\u7406\u8BAE\u9898\uFF0C\u53EA\u4FEE\u6539\u6307\u5B9A\u5B57\u6BB5\uFF0C\u4FDD\u7559\u539F\u5468\u6B21\uFF1B\u5DF2\u64A4\u56DE\u6216\u7EB3\u5165\u7684\u8BAE\u9898\u9700\u65B0\u589E\u8865\u5145", example: 'workbench agenda update <id> --question "\u8865\u5145\u9700\u8981\u51B3\u5B9A\u7684\u95EE\u9898" --dry-run', options: ["topic", "submitter", "title", "question", "material-url", "dry-run", "yes"] },
410
+ "agenda withdraw": { usage: "agenda withdraw <id>", text: "\u64A4\u56DE\u5F85\u6574\u7406\u8BAE\u9898\uFF0C\u4FDD\u7559\u8BB0\u5F55", options: ["dry-run", "yes"] },
411
+ "agenda include": { usage: "agenda include <id> --formal-url <URL>", text: "\u6B63\u5F0F\u8BAE\u7A0B\u5B9E\u9645\u53D1\u5E03\u540E\uFF0C\u6807\u8BB0\u5DF2\u7EB3\u5165\u5E76\u56DE\u94FE\uFF1B\u4E0D\u53D1\u5E03\u7F51\u9875", example: "workbench agenda include <id> --formal-url https://example.com/agenda --dry-run", options: ["formal-url", "dry-run", "yes"] },
412
+ "agenda list": { usage: "agenda list --week <YYYY-Www>", text: "\u8BFB\u53D6\u6307\u5B9A\u5468\u6536\u96C6\u7684\u8BAE\u9898", options: ["week"] }
413
+ };
414
+ var CliError = class extends Error {
415
+ constructor(code, message, hint = "", exitCode = 1) {
416
+ super(message);
417
+ this.code = code;
418
+ this.hint = hint;
419
+ this.exitCode = exitCode;
420
+ }
421
+ };
422
+ var usage = (message) => {
423
+ throw new CliError("USAGE", message, "\u8FD0\u884C workbench <command> --help \u67E5\u770B\u7528\u6CD5\u3002", 2);
424
+ };
425
+ function helpText(key) {
426
+ if (specs[key]) {
427
+ const s = specs[key];
428
+ return `workbench ${s.usage}
429
+
430
+ ${s.text}
431
+
432
+ \u9009\u9879
433
+ ${s.options.map((x) => (" --" + x + (flags[x].type === "string" ? " <value>" : "")).padEnd(28) + (key.startsWith("meetings ") && x === "title" ? "\u4F1A\u8BAE\u6807\u9898" : key.startsWith("meetings ") && x === "week" ? "\u4F1A\u8BAE\u5BF9\u5E94\u6C47\u62A5\u5468\u6B21 YYYY-Www" : key.startsWith("agenda ") && x === "title" ? "\u8BAE\u9898\u6807\u9898" : key.startsWith("agenda ") && x === "week" ? "\u8BAE\u9898\u6240\u5C5E\u6C47\u62A5\u5468\u6B21 YYYY-Www" : flagDescriptions[x])).join("\n")}
434
+ -h, --help
435
+ --local \u4EC5\u8FDE\u63A5\u672C\u5730\u5468\u671F\u9884\u89C8\uFF0C\u4E0D\u8BBF\u95EE\u4E91\u7AEF
436
+ --json \u8F93\u51FA\u7ED3\u6784\u5316 JSON
437
+ ${s.example ? "\n\u793A\u4F8B\n " + s.example + "\n" : ""}${mutations.includes(key) ? "\n\u9ED8\u8BA4\u5C55\u793A\u5DEE\u5F02\u5E76\u8BF7\u6C42\u786E\u8BA4\uFF1B\u975E\u4EA4\u4E92\u73AF\u5883\u4E0D\u81EA\u52A8\u5199\u5165\u3002\n--dry-run \u4FDD\u5B58\u9884\u89C8\u8BA1\u5212\uFF1B\u786E\u8BA4\u540E workbench apply <plan-id> --yes\u3002\n" : ""}`;
438
+ }
439
+ const subset = key ? Object.entries(specs).filter(([k]) => k.startsWith(key + " ")) : [["context", specs.context], ["topics", specs.topics], ["init", specs.init], ["status", specs.status], ["links", { text: "\u67E5\u770B\u3001\u8BBE\u7F6E\u6216\u6E05\u7A7A\u4E13\u9898\u94FE\u63A5" }], ["roadmap", { text: "\u67E5\u8BE2\u3001\u65B0\u589E\u3001\u66F4\u65B0\u3001\u5F52\u6863\u4E0E\u6279\u91CF\u5BFC\u5165" }], ["agenda", { text: "\u63D0\u62A5\u3001\u66F4\u65B0\u3001\u64A4\u56DE\u4E0E\u7EB3\u5165\u5468\u4F1A\u8BAE\u9898" }], ["meetings", { text: "\u67E5\u8BE2\u5468\u4F1A\u3001\u8BBE\u7F6E\u6216\u6E05\u9664\u7EAA\u8981\u94FE\u63A5" }], ["apply", specs.apply]];
440
+ if (key && !["links", "roadmap", "agenda", "meetings"].includes(key)) usage("\u672A\u77E5\u5E2E\u52A9\u4E3B\u9898\uFF1A" + key);
441
+ return `Lingee Workbench ${VERSION}
442
+ \u4E13\u9898\u534F\u4F5C \xB7 \u94FE\u63A5\u4E0E\u8DEF\u7EBF
443
+
444
+ \u7528\u6CD5
445
+ workbench ${key ? key + " " : ""}<command> [flags]
446
+
447
+ \u547D\u4EE4
448
+ ${subset.map(([k, s]) => " " + k.padEnd(20) + s.text).join("\n")}
449
+
450
+ \u5168\u5C40\u9009\u9879
451
+ -h, --help \u67E5\u770B\u5F53\u524D\u547D\u4EE4\u5E2E\u52A9
452
+ -V, --version \u663E\u793A CLI \u7248\u672C
453
+ --local \u4EC5\u8FDE\u63A5127.0.0.1\u672C\u5730\u9884\u89C8\uFF0C\u8BA1\u5212\u9694\u79BB
454
+ --json \u673A\u5668\u53EF\u8BFB\u8F93\u51FA\uFF0C\u9519\u8BEF\u5199 stderr
455
+
456
+ \u5F00\u59CB
457
+ workbench init --topic ppt
458
+ workbench links show --week 2026-W45
459
+ workbench roadmap list
460
+
461
+ \u9000\u51FA\u7801 0 \u6210\u529F/\u9884\u89C8 \xB7 1 \u8FD0\u884C\u9519\u8BEF \xB7 2 \u7528\u6CD5/\u7F3A\u5C11\u786E\u8BA4 \xB7 3 \u7248\u672C\u51B2\u7A81 \xB7 4 \u7F51\u7EDC\u7ED3\u679C\u672A\u77E5
462
+ `;
463
+ }
464
+ function human(value) {
465
+ if (value.planId) {
466
+ return `\u53D8\u66F4\u9884\u89C8 \xB7 ${clean(value.topicId)}
467
+ ${value.changes.map((c) => c.field ? ` ${c.field}: ${clean(typeof c.before === "object" ? JSON.stringify(c.before) : c.before) || "(\u7A7A)"} \u2192 ${clean(typeof c.after === "object" ? JSON.stringify(c.after) : c.after) || "(\u7A7A)"}` : ` ${clean(c.kind)} ${clean(c.title)}${c.before && c.after ? "\n" + fields.filter((k) => c.before[k] !== c.after[k]).map((k) => ` ${k}: ${clean(c.before[k])} \u2192 ${clean(c.after[k])}`).join("\n") : ""}`).join("\n") || " \u65E0\u53D8\u5316"}
468
+
469
+ \u8BA1\u5212 ${value.planId}
470
+ ${value.changed ? "\u4E0B\u4E00\u6B65 workbench " + (value.local ? "--local " : "") + "apply " + value.planId + " --yes" : "\u65E0\u9700\u63D0\u4EA4\u3002"}
471
+ `;
472
+ }
473
+ if (value.siteUrl) return `\u5DF2\u4FDD\u5B58 \xB7 ${clean(value.topicId)} \xB7 revision ${value.revision}${value.replay ? "\uFF08\u91CD\u590D\u8BF7\u6C42\u5DF2\u53BB\u91CD\uFF09" : ""}
474
+ ${value.siteUrl}
475
+ `;
476
+ if (Array.isArray(value)) return value.map((row) => Object.entries(row).map(([k, v]) => `${k}: ${clean(typeof v === "object" ? JSON.stringify(v) : v)}`).join(" ")).join("\n") + "\n";
477
+ return Object.entries(value).map(([k, v]) => `${k.padEnd(16)} ${clean(typeof v === "object" ? JSON.stringify(v) : v)}`).join("\n") + "\n";
478
+ }
479
+ async function main(args, { fetcher = fetch, stdout = (x) => process.stdout.write(x), stderr = (x) => process.stderr.write(x), stateDir = process.env.WORKBENCH_STATE_DIR || join(process.env.XDG_STATE_HOME || join(homedir(), ".local", "state"), "lingee-workbench"), interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY), confirm } = {}) {
480
+ let json = args.includes("--json");
481
+ let endpoint = ENDPOINT, site = SITE;
482
+ const emit = (value) => stdout(json ? JSON.stringify(value, null, 2) + "\n" : human(value));
483
+ try {
484
+ let parsed;
485
+ try {
486
+ parsed = parseArgs({ args, options: flags, allowPositionals: true, strict: true });
487
+ } catch (e) {
488
+ usage(e.message);
489
+ }
490
+ const o = parsed.values, p = parsed.positionals;
491
+ json = !!o.json;
492
+ if (o.local) {
493
+ endpoint = "http://127.0.0.1:8798/api/weekly-config";
494
+ site = "http://127.0.0.1:8798/";
495
+ stateDir = join(stateDir, "local-preview");
496
+ }
497
+ if (o.version) {
498
+ stdout(json ? JSON.stringify({ version: VERSION }) + "\n" : `workbench ${VERSION}
499
+ `);
500
+ return 0;
501
+ }
502
+ if (p[0] === "help") {
503
+ stdout(helpText(p.slice(1).join(" ")));
504
+ return 0;
505
+ }
506
+ const grouped = ["links", "roadmap", "agenda", "meetings"].includes(p[0]);
507
+ const key = grouped ? p.slice(0, 2).join(" ") : p[0] || "";
508
+ const rest = p.slice(grouped ? 2 : 1);
509
+ if (o.help || !p.length || grouped && p.length === 1) {
510
+ stdout(helpText(key));
511
+ return 0;
512
+ }
513
+ const spec = specs[key];
514
+ if (!spec) usage("\u672A\u77E5\u547D\u4EE4\uFF1A" + key);
515
+ for (const f of Object.keys(o)) if (!["help", "json", "local"].includes(f) && !spec.options.includes(f)) usage(`${key} \u4E0D\u652F\u6301 --${f}`);
516
+ if (o.yes && o["dry-run"]) usage("--yes \u4E0E --dry-run \u4E0D\u80FD\u540C\u65F6\u4F7F\u7528\u3002");
517
+ if (rest.length > 1) usage("\u4F4D\u7F6E\u53C2\u6570\u8FC7\u591A\u3002");
518
+ if (key === "context") {
519
+ if (rest.length) usage("context \u4E0D\u63A5\u53D7\u4F4D\u7F6E\u53C2\u6570\uFF0C\u8BF7\u4F7F\u7528 --topic\u3002");
520
+ let context;
521
+ try {
522
+ context = getContext(o.topic);
523
+ } catch (e) {
524
+ usage(e.message);
525
+ }
526
+ context.cliVersion = VERSION;
527
+ if (json) emit(context);
528
+ else stdout(formatContext(context));
529
+ return 0;
530
+ }
531
+ if (key === "roadmap guide") {
532
+ if (rest.length) usage("roadmap guide \u4E0D\u63A5\u53D7\u4F4D\u7F6E\u53C2\u6570\u3002");
533
+ if (o.out) {
534
+ await writeFile(o.out, roadmapGuide, { flag: "wx", mode: 384 });
535
+ emit({ file: resolve(o.out) });
536
+ } else if (json) emit({ format: "markdown", content: roadmapGuide });
537
+ else stdout(roadmapGuide);
538
+ return 0;
539
+ }
540
+ let config = {};
541
+ try {
542
+ config = JSON.parse(await readFile(join(stateDir, "config.json"), "utf8"));
543
+ } catch (e) {
544
+ if (e.code !== "ENOENT") throw new CliError("CONFIG_INVALID", "\u672C\u5730\u914D\u7F6E\u65E0\u6CD5\u8BFB\u53D6\u3002", "\u8BF7\u68C0\u67E5 " + join(stateDir, "config.json"));
545
+ }
546
+ const topic = (explicit) => {
547
+ const t = explicit || o.topic || config.topic;
548
+ if (!Object.hasOwn(TOPICS, t)) usage("\u8BF7\u901A\u8FC7 --topic \u6307\u5B9A\u6709\u6548\u4E13\u9898\uFF0C\u6216\u5148 workbench init --topic <id>\u3002");
549
+ if (explicit && o.topic && explicit !== o.topic) usage("\u4F4D\u7F6E\u4E13\u9898\u4E0E --topic \u4E0D\u4E00\u81F4\u3002");
550
+ return t;
551
+ };
552
+ const week = () => {
553
+ if (!/^20\d{2}-W(0[1-9]|[1-4]\d|5[0-3])$/.test(o.week || "")) usage("\u8BF7\u7528 --week \u660E\u786E\u6307\u5B9A YYYY-Www\u3002");
554
+ return o.week;
555
+ };
556
+ const exact = (n) => {
557
+ if (rest.length !== n) usage("\u4F4D\u7F6E\u53C2\u6570\u4E0D\u6B63\u786E\u3002");
558
+ };
559
+ async function request(query = "", body) {
560
+ let response, data2;
561
+ try {
562
+ response = await fetcher(endpoint + query, { method: body ? "POST" : "GET", cache: "no-store", headers: body ? { "Content-Type": "application/json" } : void 0, body: body ? JSON.stringify(body) : void 0, signal: AbortSignal.timeout(25e3) });
563
+ data2 = await response.json();
564
+ } catch {
565
+ throw new CliError("NETWORK_UNKNOWN", "\u7F51\u7EDC\u54CD\u5E94\u672A\u786E\u8BA4\u3002", "\u4FDD\u5B58\u8BF7\u7528\u76F8\u540C\u8BA1\u5212 ID \u91CD\u8BD5\uFF1B\u4E0D\u8981\u91CD\u65B0\u751F\u6210\u8BA1\u5212\u3002", 4);
566
+ }
567
+ if (!response.ok || !data2.ok) throw new CliError(response.status === 409 ? "CONFLICT" : "API_ERROR", data2.error || "\u4E91\u7AEF\u8FD4\u56DE\u5F02\u5E38\u3002", response.status === 409 ? "\u91CD\u65B0\u8BFB\u53D6\u5E76\u5408\u5E76\u4FEE\u6539\uFF0C\u518D\u751F\u6210\u65B0\u9884\u89C8\u3002" : "\u8BF7\u6838\u5BF9\u8F93\u5165\u540E\u91CD\u8BD5\u3002", response.status === 409 ? 3 : 1);
568
+ return data2;
569
+ }
570
+ async function outputFile(content) {
571
+ if (o.out) {
572
+ await writeFile(o.out, content, { flag: "wx", mode: 384 });
573
+ emit({ file: resolve(o.out) });
574
+ } else stdout(content);
575
+ }
576
+ async function approval() {
577
+ if (o.yes) return true;
578
+ if (!interactive || o["dry-run"]) return false;
579
+ if (confirm) return confirm();
580
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
581
+ try {
582
+ return /^y(es)?$/i.test((await rl.question("\u786E\u8BA4\u4FDD\u5B58\u4EE5\u4E0A\u53D8\u66F4\uFF1F[y/N] ")).trim());
583
+ } finally {
584
+ rl.close();
585
+ }
586
+ }
587
+ async function confirmPreview(preview) {
588
+ const separatePreview = json && interactive;
589
+ if (separatePreview) stderr(human(preview));
590
+ else emit(preview);
591
+ const accepted = await approval();
592
+ if (!accepted && separatePreview) emit(preview);
593
+ return accepted;
594
+ }
595
+ async function applyPlan(saved) {
596
+ if (saved.schemaVersion !== 1 || saved.endpoint !== endpoint || !["linksApply", "roadmapImport", "proposalSave", "proposalWithdraw", "proposalLink", "meetingApply", "cycleApply"].includes(saved.payload?.action) || saved.sha256 !== hash(saved.payload)) throw new CliError("PLAN_INVALID", "\u8BA1\u5212\u6587\u4EF6\u65E0\u6548\u6216\u88AB\u4FEE\u6539\u3002", "\u91CD\u65B0\u751F\u6210\u8BA1\u5212\u5E76\u786E\u8BA4\u3002", 2);
597
+ let result;
598
+ try {
599
+ result = await request("", saved.payload);
600
+ } catch (e) {
601
+ e.hint = (e.hint || "") + " \u8BA1\u5212 ID\uFF1A" + saved.payload.requestId;
602
+ throw e;
603
+ }
604
+ if (saved.payload.action === "cycleApply") {
605
+ emit({ ...result, topicId: result.week, applied: true, siteUrl: site + "#/weekly?week=" + result.week });
606
+ return;
607
+ }
608
+ const agenda = saved.payload.action.startsWith("proposal");
609
+ const meeting = saved.payload.action === "meetingApply";
610
+ emit({ ok: true, applied: true, topicId: saved.payload.topicId, ...meeting ? { week: result.item.week } : {}, ...agenda ? { id: result.item.id, status: result.item.status, week: result.item.week } : {}, changed: result.changed ?? saved.changed, revision: result.revision ?? result.item?.revision, replay: !!result.replay, siteUrl: site + "#/" + (meeting ? "history" : agenda ? "weekly?week=" + encodeURIComponent(result.item.week) : (saved.payload.action === "linksApply" ? "products?module=" : "roadmap?topic=") + saved.payload.topicId + (saved.payload.action === "linksApply" && saved.payload.document.week ? "&week=" + encodeURIComponent(saved.payload.document.week) : "")) });
611
+ }
612
+ async function change(resource, document) {
613
+ const { plan } = await request("", { action: resource === "links" ? "linksPreview" : "roadmapPreview", topicId: document.topicId, document });
614
+ return savePlan({ action: resource === "links" ? "linksApply" : "roadmapImport", topicId: document.topicId, document, stamp: plan.stamp }, plan);
615
+ }
616
+ async function savePlan(input, plan) {
617
+ const id2 = randomUUID(), payload = { ...input, requestId: id2 };
618
+ const saved = { schemaVersion: 1, endpoint, payload, sha256: hash(payload), changes: plan.changes, changed: plan.changed };
619
+ await mkdir(join(stateDir, "plans"), { recursive: true, mode: 448 });
620
+ await writeFile(join(stateDir, "plans", id2 + ".json"), JSON.stringify(saved), { flag: "wx", mode: 384 });
621
+ const preview = { local: !!o.local, ok: true, applied: false, planId: id2, topicId: payload.topicId, changed: plan.changed, changes: plan.changes.filter((x) => x.kind !== "unchanged") };
622
+ if (!plan.changed || o["dry-run"]) {
623
+ emit(preview);
624
+ return 0;
625
+ }
626
+ if (!o.yes && !await confirmPreview(preview)) return 2;
627
+ await applyPlan(saved);
628
+ return 0;
629
+ }
630
+ if (["meetings cycles", "meetings detail", "meetings template", "meetings export", "meetings draft", "meetings publish", "meetings close", "meetings pending", "meetings carry"].includes(key)) {
631
+ exact(0);
632
+ const output = async (value) => {
633
+ if (o.out) await outputFile(JSON.stringify(value, null, 2) + "\n");
634
+ else emit(value);
635
+ };
636
+ if (key === "meetings template") {
637
+ if (!o.date || !o.title) usage("\u8BF7\u63D0\u4F9B --date \u548C --title");
638
+ await output({ schemaVersion: 1, week: week(), revision: 0, date: o.date, title: o.title, items: [{ id: "replace-with-stable-id", topicId: "cross", title: "", owner: "", question: "", materials: [] }] });
639
+ return 0;
640
+ }
641
+ const op = key.split(" ")[1];
642
+ let document, data2;
643
+ if (["draft", "close"].includes(op)) {
644
+ if (!o.file) usage("\u8BF7\u63D0\u4F9B --file");
645
+ document = decode(await readFile(o.file, "utf8"));
646
+ } else {
647
+ data2 = await request("?resource=cycles" + (op === "cycles" ? "" : "&week=" + week()));
648
+ if (data2.resource !== "cycles") throw new CliError("PROTOCOL", "\u5F53\u524D\u540E\u7AEF\u5C1A\u672A\u90E8\u7F72\u4F1A\u8BAE\u5468\u671F\u63A5\u53E3\u3002");
649
+ if (op === "cycles" || op === "detail") {
650
+ emit(data2);
651
+ return 0;
652
+ }
653
+ if (!data2.item) usage("\u4F1A\u8BAE\u5C1A\u672A\u5EFA\u8349\u7A3F\uFF0C\u8BF7\u5148 meetings template / draft");
654
+ if (op === "pending") {
655
+ emit(data2.pending);
656
+ return 0;
657
+ }
658
+ if (op === "export") {
659
+ const m = data2.item;
660
+ if (o.results) document = { schemaVersion: 1, week: m.week, revision: m.revision, recordUrl: data2.recordUrl, results: m.items.map((i) => ({ id: i.id, decision: "", disposition: "", actions: (i.carryFrom?.actions || []).map((a) => ({ ...a })) })) };
661
+ else document = { schemaVersion: 1, week: m.week, revision: m.revision, date: m.date, title: m.title, items: m.items.map(({ sourceProposal, topicName, ...i }) => i) };
662
+ await output(document);
663
+ return 0;
664
+ }
665
+ document = { week: week(), revision: data2.item.revision };
666
+ if (op === "carry") {
667
+ if (!o["from-week"] || !o.item) usage("\u9700\u8981 --from-week \u548C --item");
668
+ Object.assign(document, { fromWeek: o["from-week"], itemId: o.item });
669
+ }
670
+ }
671
+ const { plan } = await request("", { action: "cyclePreview", operation: op, document });
672
+ return await savePlan({ action: "cycleApply", operation: op, topicId: document.week, document, stamp: plan.stamp }, plan);
673
+ }
674
+ if (key.startsWith("meetings ")) {
675
+ exact(0);
676
+ const data2 = await request("?resource=meetings");
677
+ if (data2.resource !== "meetings") throw new CliError("PROTOCOL", "\u4E91\u7AEF\u5C1A\u672A\u652F\u6301\u7EAA\u8981\u63A5\u53E3\u3002");
678
+ if (key === "meetings list") {
679
+ emit(data2.items);
680
+ return 0;
681
+ }
682
+ const w = week(), row2 = data2.items.find((x) => x.week === w);
683
+ if (!row2) usage("\u8BE5\u6C47\u62A5\u5468\u5C1A\u672A\u6536\u5F55\u5468\u4F1A\uFF0C\u8BF7\u5148 meetings list \u6838\u5BF9\u3002");
684
+ if (key === "meetings show") {
685
+ emit(row2);
686
+ return 0;
687
+ }
688
+ if (key === "meetings set" && !o["record-url"]?.trim()) usage("\u8BF7\u901A\u8FC7 --record-url \u6307\u5B9AHTTPS\u94FE\u63A5\uFF0C\u6E05\u9664\u7528 meetings clear\u3002");
689
+ const document = { schemaVersion: 1, week: w, revision: row2.revision, baselineHash: row2.baselineHash, recordUrl: key === "meetings clear" ? "" : o["record-url"].trim() };
690
+ const { plan } = await request("", { action: "meetingPreview", document });
691
+ return await savePlan({ action: "meetingApply", topicId: w, document, stamp: plan.stamp }, plan);
692
+ }
693
+ if (key === "topics") {
694
+ exact(0);
695
+ emit(Object.entries(TOPICS).map(([id2, name]) => ({ id: id2, name })));
696
+ return 0;
697
+ }
698
+ if (key === "init") {
699
+ exact(0);
700
+ const id2 = topic();
701
+ await mkdir(stateDir, { recursive: true, mode: 448 });
702
+ await writeFile(join(stateDir, "config.json"), JSON.stringify({ topic: id2 }), { mode: 384 });
703
+ emit({ topic: id2, name: TOPICS[id2], next: "workbench links show --week YYYY-Www" });
704
+ return 0;
705
+ }
706
+ if (key === "status") {
707
+ exact(0);
708
+ const data2 = await request("?resource=links");
709
+ emit({ version: VERSION, topic: config.topic || "(\u672A\u8BBE\u7F6E)", cloud: "connected", topics: data2.items.length, site: SITE });
710
+ return 0;
711
+ }
712
+ if (key === "apply") {
713
+ exact(1);
714
+ if (!/^[a-f0-9-]{36}$/.test(rest[0])) usage("\u8BA1\u5212 ID \u65E0\u6548\u3002");
715
+ const saved = JSON.parse(await readFile(join(stateDir, "plans", rest[0] + ".json"), "utf8"));
716
+ if (!o.yes && !await confirmPreview({ planId: rest[0], topicId: saved.payload.topicId, changes: saved.changes.filter((x) => x.kind !== "unchanged"), changed: saved.changed })) return 2;
717
+ await applyPlan(saved);
718
+ return 0;
719
+ }
720
+ if (key === "agenda list") {
721
+ exact(0);
722
+ emit((await request("?resource=proposals&week=" + week())).items);
723
+ return 0;
724
+ }
725
+ if (key.startsWith("agenda ")) {
726
+ const adding = key === "agenda add";
727
+ exact(adding ? 0 : 1);
728
+ if (!adding && !/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/.test(rest[0])) usage("\u8BAE\u9898 ID \u5FC5\u987B\u662F\u6709\u6548 UUID\u3002");
729
+ const previous2 = adding ? null : (await request("?resource=proposals&allWeeks=1")).items.find((x) => x.id === rest[0]);
730
+ if (!adding && !previous2) throw new CliError("NOT_FOUND", "\u8BAE\u9898\u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148 agenda list --week YYYY-Www\u3002");
731
+ if (key === "agenda show") {
732
+ emit(previous2);
733
+ return 0;
734
+ }
735
+ if (previous2 && previous2.status !== "submitted") throw new CliError("CONFLICT", "\u8BAE\u9898\u5DF2\u64A4\u56DE\u6216\u7EB3\u5165\uFF1B\u8865\u5145\u5185\u5BB9\u8BF7\u65B0\u589E\u8BAE\u9898\u3002", "\u5148\u8BFB\u53D6\u6700\u65B0\u8BAE\u9898\u72B6\u6001\u3002", 3);
736
+ const mapping = { topic: "topicId", submitter: "submitter", title: "title", question: "question", "material-url": "materialUrl" };
737
+ if (key === "agenda update" && !Object.keys(mapping).some((k) => o[k] !== void 0)) usage("\u81F3\u5C11\u6307\u5B9A\u4E00\u4E2A\u8BAE\u9898\u5B57\u6BB5\u3002");
738
+ const values = Object.fromEntries(Object.entries(mapping).map(([flag, field]) => [field, o[flag] !== void 0 ? o[flag].trim() : previous2?.[field] ?? (field === "topicId" ? config.topic : "") ?? ""]));
739
+ const payload = { action: adding || key === "agenda update" ? "proposalSave" : key === "agenda withdraw" ? "proposalWithdraw" : "proposalLink", id: previous2?.id || randomUUID(), revision: previous2?.revision || 0, topicId: values.topicId };
740
+ if (payload.action === "proposalSave") Object.assign(payload, values, { week: previous2?.week || week() });
741
+ if (payload.action === "proposalLink") payload.formalUrl = o["formal-url"]?.trim();
742
+ try {
743
+ validateAgenda(payload);
744
+ } catch (e) {
745
+ usage(e.message);
746
+ }
747
+ const changes = agendaChanges(payload, previous2);
748
+ return await savePlan(payload, { changes, changed: changes.length });
749
+ }
750
+ if (key.startsWith("links ")) {
751
+ const id2 = topic(rest[0]);
752
+ const data2 = await request("?resource=links"), row2 = data2.items.find((x) => x.id === id2);
753
+ if (!row2?.baselineHash) throw new CliError("PROTOCOL", "\u4E91\u7AEF\u94FE\u63A5\u63A5\u53E3\u7248\u672C\u4E0D\u517C\u5BB9\u3002");
754
+ if (key === "links show") {
755
+ const w2 = week();
756
+ emit({ topic: id2, week: w2, revision: row2.revision, reportHub: row2.reportHub, weeklyReport: row2.weeklyReports[w2] || "", dashboardUrl: row2.dashboardUrl });
757
+ return 0;
758
+ }
759
+ const updates = {};
760
+ if (key === "links clear") {
761
+ if (!Object.hasOwn(linkFields, o.field)) usage("--field \u5FC5\u987B\u4E3A report-hub\u3001weekly-report \u6216 dashboard\u3002");
762
+ updates[linkFields[o.field]] = "";
763
+ } else for (const [flag, field] of Object.entries(linkFields)) if (o[flag] !== void 0) {
764
+ if (!o[flag]) usage("\u6E05\u7A7A\u94FE\u63A5\u8BF7\u4F7F\u7528 links clear\u3002");
765
+ updates[field] = o[flag];
766
+ }
767
+ if (!Object.keys(updates).length) usage("\u81F3\u5C11\u6307\u5B9A\u4E00\u4E2A\u94FE\u63A5\u5B57\u6BB5\u3002");
768
+ const w = Object.hasOwn(updates, "weeklyReport") ? week() : o.week;
769
+ if (w && !/^20\d{2}-W(0[1-9]|[1-4]\d|5[0-3])$/.test(w)) usage("--week \u683C\u5F0F\u65E0\u6548\u3002");
770
+ return await change("links", { schemaVersion: 2, topicId: id2, revision: row2.revision, baselineHash: row2.baselineHash, ...w ? { week: w } : {}, changes: updates });
771
+ }
772
+ if (key === "roadmap import") {
773
+ exact(1);
774
+ return await change("roadmap", decode(await readFile(rest[0], "utf8")));
775
+ }
776
+ const single = ["roadmap show", "roadmap update", "roadmap archive", "roadmap restore"].includes(key);
777
+ if (single) exact(1);
778
+ if (key === "roadmap add") exact(0);
779
+ const id = topic(single || key === "roadmap add" ? void 0 : rest[0]);
780
+ if (key === "roadmap template") {
781
+ await outputFile(toJsonc(template({ id })));
782
+ return 0;
783
+ }
784
+ const data = await request("?resource=roadmap"), row = data.items.find((x) => x.id === id);
785
+ if (!row) throw new CliError("NOT_FOUND", "\u4E13\u9898\u4E0D\u5B58\u5728\u3002");
786
+ if (key === "roadmap list") {
787
+ emit(row.records.map((x) => ({ id: x.id, title: x.title, month: x.month, state: x.state, owner: x.owner, archived: x.archived })));
788
+ return 0;
789
+ }
790
+ if (key === "roadmap history") {
791
+ emit(row.store.changes);
792
+ return 0;
793
+ }
794
+ if (key === "roadmap export") {
795
+ await outputFile(toJsonc(exportTopic(id, new Map(row.records.map((x) => [x.id, x])), row.store)));
796
+ return 0;
797
+ }
798
+ if (key === "roadmap add" && o.id && row.records.some((item2) => item2.id === o.id)) usage("\u4E8B\u9879 ID \u5DF2\u5B58\u5728\uFF1B\u65B0\u589E\u8BF7\u4F7F\u7528\u65B0 ID\uFF0C\u4FEE\u6539\u5DF2\u6709\u4E8B\u9879\u8BF7\u7528 roadmap update\u3002");
799
+ const previous = single ? row.records.find((x) => x.id === rest[0]) : null;
800
+ if (single && !previous) throw new CliError("NOT_FOUND", "\u4E8B\u9879\u4E0D\u5C5E\u4E8E\u5F53\u524D\u4E13\u9898\u6216\u4E0D\u5B58\u5728\u3002", "\u7528 roadmap list --topic <id> \u67E5\u770B ID\u3002");
801
+ if (key === "roadmap show") {
802
+ emit(previous);
803
+ return 0;
804
+ }
805
+ let item;
806
+ if (["roadmap archive", "roadmap restore"].includes(key)) item = { id: previous.id, revision: previous.revision, action: key.split(" ")[1], reason: o.reason || "" };
807
+ else {
808
+ if (key === "roadmap update" && !fields.some((f) => o[f] !== void 0)) usage("\u81F3\u5C11\u6307\u5B9A\u4E00\u4E2A\u9700\u8981\u66F4\u65B0\u7684\u5B57\u6BB5\u3002");
809
+ item = { ...Object.fromEntries(fields.map((f) => [f, o[f] ?? previous?.[f] ?? (f === "state" ? "planned" : "")])), id: previous?.id || o.id || id + "-" + randomUUID(), revision: previous?.revision || 0, action: "upsert", reason: o.reason || "" };
810
+ }
811
+ return await change("roadmap", { schemaVersion: 1, topicId: id, items: [item] });
812
+ } catch (error) {
813
+ const e = error instanceof CliError ? error : new CliError(error.code || "RUNTIME", error.message, error.hint || (error.code === "EEXIST" ? "\u6587\u4EF6\u5DF2\u5B58\u5728\uFF0C\u8BF7\u6362\u4E00\u4E2A\u8F93\u51FA\u6587\u4EF6\u540D\u3002" : ""), error.exitCode || 1);
814
+ stderr(json ? JSON.stringify({ ok: false, error: { code: e.code, message: e.message, hint: e.hint } }) + "\n" : `\u9519\u8BEF [${e.code}] ${clean(e.message)}
815
+ ${e.hint ? "\u5EFA\u8BAE\uFF1A" + clean(e.hint) + "\n" : ""}`);
816
+ return e.exitCode;
817
+ }
818
+ }
819
+ if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) process.exitCode = await main(process.argv.slice(2));
820
+ export {
821
+ CliError,
822
+ VERSION,
823
+ main
824
+ };