annotatable-html 1.38.3 → 1.45.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.
- package/README.md +76 -13
- package/agent/annot_poll.js +11 -4
- package/bin/cli.js +105 -21
- package/lib/claude.js +242 -0
- package/lib/codex-desktop.js +130 -0
- package/lib/codex-headless.js +129 -0
- package/lib/codex.js +216 -0
- package/lib/core.js +130 -122
- package/lib/document.js +56 -0
- package/lib/duty-process.js +190 -0
- package/lib/duty-worker.js +5 -0
- package/lib/duty.js +334 -0
- package/lib/lang-en.js +1 -1
- package/lib/opencode.js +195 -0
- package/lib/poll-process.js +42 -0
- package/lib/serve.js +28 -3
- package/package.json +51 -50
- package/templates/SKILL.md +35 -81
- package/templates/annotatable-doc.html +12 -18
- package/templates/commands/html4agent.md +3 -34
- package/templates/opencode/annotatable-html.js +12 -0
- package/templates/references/compatibility.md +14 -0
- package/templates/references/hosts.md +47 -0
- package/templates/references/operations.md +37 -0
- package/templates/AGENTS.md +0 -107
package/README.md
CHANGED
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
## 快速上手
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
npx annotatable-html
|
|
14
|
+
npx annotatable-html skill # 安装批注工作流 skill;OpenCode 同时安装会话插件
|
|
15
|
+
npx annotatable-html init # 可选:导出目录内 agent 工具、模板与主题
|
|
15
16
|
npx annotatable-html new 我的文档.html # 新建一份可批注文档
|
|
16
17
|
npx annotatable-html md 笔记.md # 一键 Markdown → 可批注 HTML(正文遵照原文,仅加批注层)
|
|
17
18
|
npx annotatable-html serve # 本地服务器:全自动保存(可选但推荐)
|
|
@@ -25,10 +26,62 @@ npx annotatable-html inject 任意.html # 给已有 HTML 注入批注层(原
|
|
|
25
26
|
3. **人**:刷新页面,回答以绿色块出现在批注上;可「⤢ 展开大页」细读——**回答本身还能继续划线批注、页内追问**;
|
|
26
27
|
4. 任何时刻可打开「⏪ 版本历史」回滚到任意版本(回滚也是一次新提交,可再撤销)。
|
|
27
28
|
|
|
28
|
-
全部命令:`init` / `new` / `md` / `serve` / `read` / `reply` / `edit` / `poll` / `inject` / `theme` / `template` / `studio` / `skill`(`annotatable help` 查看用法)。
|
|
29
|
+
全部命令:`init` / `new` / `md` / `serve` / `read` / `reply` / `edit` / `poll` / `duty` / `inject` / `theme` / `template` / `studio` / `skill`(`annotatable help` 查看用法)。
|
|
30
|
+
|
|
31
|
+
**批注协议通过 skill 安装**:`annotatable skill` 安装 SKILL.md 和 references/ 下的按需说明,带版本自检。默认兼容 ZCode、Claude Code、Codex、OpenCode 和 agents 共享仓;slash 命令仅安装到 ZCode、Claude Code。OpenCode 同时安装会话上下文插件。
|
|
32
|
+
|
|
33
|
+
`init` 只导出目录内工具、模板和主题,不创建或更新工作区的 `AGENTS.md`。旧版的 `--agents-md` 参数已停用;已有指针、用户规则和全局指令文件均保持原样。工具不向这些文件写入值守状态或悬单清单;只检查用户指定文档的批注。
|
|
34
|
+
|
|
35
|
+
**教学与批注分开**:批注中的疑问统一回复到原对话。工具不按“教我、入门”等词切换教学模式,不决定课程、检查点或学习进度。独立教学工作流自行决定内容与格式。旧 answers-store 保留兼容读写;teach 教学生成命令已停用。
|
|
36
|
+
|
|
37
|
+
**凭页面地址接管**:同一台电脑上,直接把正在运行的 localhost 页面地址交给新会话。`annotatable read http://localhost:端口/文档.html` 会定位本地文档;之后挂当前会话的 poll。read、reply、edit、poll 均接受页面地址。服务须为新版 annotatable serve;旧服务需重启一次以提供定位接口。工具不扫描全局悬单。
|
|
29
38
|
|
|
30
39
|
---
|
|
31
40
|
|
|
41
|
+
## Codex 自动值守
|
|
42
|
+
|
|
43
|
+
在目标 Codex 会话里启动后台监听:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
annotatable poll 文档.html --codex
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
默认使用 `CODEX_THREAD_ID`;可用 `--codex-thread ID` 指定会话。桌面会话继承了 `CODEX_APP_TOOLS_PIPE_PATH` 时,工具优先使用桌面的 `read_thread` 和 `send_message_to_thread` 接口。通知会明确标为“批注工具自动通知”,发送到同一会话,保留模型和权限设置。后台进程须继承该环境变量。
|
|
50
|
+
|
|
51
|
+
无桌面连接时,使用 Node.js 22+ 的 app-server 通知入口。目标会话必须已加载到该服务;仅能读取历史记录时会提前报错,不启动监听。工具不会在其他服务恢复同一会话。可用 `--codex-transport desktop|app-server` 显式选择入口。桌面调用的来源轮次默认从会话历史读取;已知当前轮次时可传 `--codex-turn ID`,避免依赖历史服务。原生 CLI 不在标准位置时,加 `--codex-bin 路径`。
|
|
52
|
+
|
|
53
|
+
`CODEX_READY` 只表示预检通过,`CODEX_NOTIFIED` 只表示通知已被接口接受。确认自动值守须实测空闲会话被唤醒、HTML 中回复完成,并重新挂监听。失败会报 `CODEX_NOTIFY_ERROR`;发送超时的结果不确定,须先核对会话,不能直接重发。超时和纯高亮不会启动模型。普通 `poll` 保留原来的 Node.js 14+ 支持和退出码。
|
|
54
|
+
|
|
55
|
+
需要桌面退出后继续回复时,可显式启用自动续接(Windows、Node.js 22+):
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
annotatable duty 文档.html --codex
|
|
59
|
+
annotatable duty 文档.html --status
|
|
60
|
+
annotatable duty 文档.html --stop
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
后台程序管理保存服务和监听。桌面在线时沿用桌面唤醒;确认桌面主进程及运行时退出后,才由无头 Codex 恢复同一聊天。无头结束后再交还桌面,沿用模型和权限。使用输出的 `pageUrl` 打开文档;也支持已有 HTTP localhost 页面地址。不要同时为同页同聊天挂 poll。此功能不设置自启、不改 AGENTS.md;当前工作区 v1.45.2 尚未发布 npm,使用源码或本地安装包。独立页已完成真实退出、无头回复及重开后唤醒验收,细节见 [续接方案与验收](docs/Codex批注续接方案.md)。
|
|
64
|
+
|
|
65
|
+
## OpenCode 自动值守
|
|
66
|
+
|
|
67
|
+
先运行 `annotatable skill --opencode`,再新开 OpenCode。安装的上下文插件会自动提供当前会话信息。
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
annotatable poll 文档.html --opencode
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
在 OpenCode 中将这条命令挂到后台。命中后通过官方异步会话接口通知当前会话,agent 回复后重新挂监听。也可手动指定 `--opencode-url URL --opencode-session ID`。服务认证沿用 `OPENCODE_SERVER_PASSWORD` 和可选的 `OPENCODE_SERVER_USERNAME`。
|
|
74
|
+
|
|
75
|
+
`OPENCODE_NOTIFIED` 表示接口接受;回复完成以 HTML 中的 AI 轮次为准。连接失败报 `OPENCODE_NOTIFY_ERROR`,超时和纯高亮不通知。此模式保持零运行时依赖。
|
|
76
|
+
|
|
77
|
+
原服务退出后继续使用同一会话,可在原会话运行 `annotatable duty 文档.html --opencode`。它复用上述会话上下文插件和共用控制器;确认原服务退出后才启动原生服务恢复同一编号,保留模型、提供方和权限。重开原服务后自动交还;同页不再另挂 poll。
|
|
78
|
+
|
|
79
|
+
## Claude Code 自动续接
|
|
80
|
+
|
|
81
|
+
在原会话用 Bash 工具的 `run_in_background=true` 运行 `annotatable duty 文档.html --claude`。缺少会话信息时加 `--claude-session 原会话UUID`。首次注册会在该项目的 `.claude/settings.local.json` 安装一组原生启动与回合结束钩子。当前进程按通知自动续挂;重开后由原生钩子接管,用户无需重挂。
|
|
82
|
+
|
|
83
|
+
原 Claude 进程退出后,后台使用原生 `--resume` 恢复同一 UUID,保留模型、配置和权限。在原项目重开同一会话时,启动钩子自动接回通知,无需发送启动提示。钩子复用现有控制器与会话锁,其他会话静默退出,无头子进程跳过自己的钩子;最后一个注册停止后移除本工具的项目钩子,保留用户设置。无需额外插件或 npm 依赖,不配置全局钩子。当前使用源码入口,Windows / Node.js 22+,详见 [宿主说明](templates/references/hosts.md)。ZCode 暂不提供无头续接。
|
|
84
|
+
|
|
32
85
|
## 自定义外观与模板(v1.17.0)
|
|
33
86
|
|
|
34
87
|
两条路,都通向同一套主题机制(`annotatable.theme.css` 的 `:root` 变量 + 追加覆盖 CSS):
|
|
@@ -76,14 +129,14 @@ npx annotatable-html template --reset # 撤销,回到内置模板
|
|
|
76
129
|
可选:annotatable serve(127.0.0.1 静态伺服 + 受控写回端点,全自动保存)
|
|
77
130
|
```
|
|
78
131
|
|
|
79
|
-
-
|
|
132
|
+
- 批注数据存在于 HTML 内部的 `annotations-store` JSON 区块。agent 运行 read 获取待回应对话,无需读取整份 HTML。旧文档的 `answers-store` 扩展内容继续保留;普通回复不写入该区块。
|
|
80
133
|
- 任何写回必须 **rev+1 并维护 history 快照**(rev 铁律):浏览器靠 rev 检测冲突拒绝互相覆盖,工具链已封装;
|
|
81
134
|
- 状态机:批注 `status:"new"` → agent 回答后置 `answered` → 人类可追问(新轮次重新入账)。
|
|
82
135
|
|
|
83
136
|
## 项目架构
|
|
84
137
|
|
|
85
138
|
```
|
|
86
|
-
bin/cli.js 命令入口(init/new/md/theme/template/studio/skill/serve/inject/read/reply/edit/
|
|
139
|
+
bin/cli.js 命令入口(init/new/md/theme/template/studio/skill/serve/inject/read/reply/edit/poll)
|
|
87
140
|
lib/core.js 全部核心逻辑(950 行):readPending / applyReplies / applyEdits
|
|
88
141
|
(含 buildHtmlIndex 锚定索引器 + bestOccurrence 消歧)/ pollAnnotations
|
|
89
142
|
/ injectHtml(备份统一落同目录 .annotatable/)/ installSkill / 双层账本
|
|
@@ -92,11 +145,16 @@ lib/core.js 全部核心逻辑(950 行):readPending / applyRepli
|
|
|
92
145
|
/ annotParts(注入四件套提取器,inject 与 templateUse 共用防双源)
|
|
93
146
|
lib/serve.js 本地伴随服务器(122 行):静态伺服 + /__annot/ping + /__annot/save 受控写回
|
|
94
147
|
+ /__annot/studio 工坊页 + /__annot/theme 工坊保存
|
|
148
|
+
lib/codex.js 可选 Codex 通知(poll --codex):官方会话接口、失败预检、静默超时
|
|
149
|
+
lib/opencode.js 可选 OpenCode 通知(poll --opencode):当前会话预检、异步消息与服务认证
|
|
150
|
+
lib/document.js 从本机页面地址定位文档,供新会话按地址接管
|
|
151
|
+
lib/poll-process.js 两种通知共用的原监听子进程与结构化命中读取
|
|
95
152
|
templates/
|
|
96
153
|
annotatable-doc.html 单文件模板(约 4000 行,产品本体):文档版式 + 批注层 + Word 编辑引擎 + 首绑引导;
|
|
97
154
|
:root 暴露 wrap-max / root-fs / lh / font-body / font-head 外观挂钩
|
|
98
155
|
theme-studio.html 主题工坊壳(studioHtml 装配:控件 + 真实模板实时预览 + 保存)
|
|
99
|
-
|
|
156
|
+
SKILL.md 唯一技能入口
|
|
157
|
+
references/ 宿主、正文操作和旧数据兼容说明(按需读取)
|
|
100
158
|
annotatable.theme.css 默认主题(init 复制,可改后自动应用)
|
|
101
159
|
commands/ slash 命令定义(/html4agent 等)
|
|
102
160
|
agent/ init 分发给用户目录的薄封装:annot_read / annot_reply / annot_edit / annot_poll.js
|
|
@@ -132,7 +190,7 @@ npm 包 `annotatable-html`(bin 命令 `annotatable`),`files` 只含 `bin/
|
|
|
132
190
|
- **read**:只解析 store 区块,列 `status:"new"` 批注的完整对话线(原句+上下文+全部轮次);
|
|
133
191
|
- **reply**:`replies.json` → 追加 ai 轮次 + 置 answered + 快照 + rev+1;
|
|
134
192
|
- **edit**:`edits.json`(批语是祈使句「这里改成X」时)→ `buildHtmlIndex` 定位原句 → 替换为 newText → **重算该批注锚定**(批注跟着正文走)→ bodyOps 记账供回滚;混合批注用 reply 字段一并答疑;
|
|
135
|
-
- **poll**:阻塞轮询,只比对 `{rev, 总数, 待回应}` 签名(文件被写盘中时静默跳过本轮);人类批注保存后打印 `POLL_HIT <json>`
|
|
193
|
+
- **poll**:阻塞轮询,只比对 `{rev, 总数, 待回应}` 签名(文件被写盘中时静默跳过本轮);人类批注保存后打印 `POLL_HIT <json>` 以命中退出码结束——放进 agent harness 的后台任务,进程结束即唤醒 agent 续接读批注循环。退出码按 harness 选(v1.38.4):zcode 等「任务退出即唤醒」的用默认(命中 0 / `--timeout` 到点 3);Claude Code 等非零才唤醒的加 `--exit-code 1`,超时轮空不想叫人再加 `--exit-code-timeout 0`。另:read/poll/serve 触碰文档目录时会自动把 init 分发的过期 lib/agent 拷贝同步到当前包版(旧版留 `.bak-kit-*` 备份)——拷贝静止腐烂曾是「同一份数据 read 与 poll 判定分裂」的根因。
|
|
136
194
|
|
|
137
195
|
### Word 编辑模式(人类直改正文)
|
|
138
196
|
- **✏️ 进入**:`article` 设 contenteditable + 悬浮工具栏(加粗/斜体/字号步进/五色字/五色高亮/格式刷双击连续/💬批注);批注 mark 进编辑态时整体剥离(批注是层不是正文);点五色高亮出 ✕ 小钮可删高亮(unwrapMark,文字保留,Ctrl+Z 可撤销);
|
|
@@ -185,8 +243,10 @@ npm 包 `annotatable-html`(bin 命令 `annotatable`),`files` 只含 `bin/
|
|
|
185
243
|
|
|
186
244
|
| 组件 | Token |
|
|
187
245
|
|---|---|
|
|
188
|
-
|
|
|
189
|
-
| SKILL.md(skill
|
|
246
|
+
| references/operations.md(正文操作,按需读取) | 见当前 Token 审计 |
|
|
247
|
+
| SKILL.md(skill 入口,触发时读取) | 见当前 Token 审计 |
|
|
248
|
+
|
|
249
|
+
运行 `node test/token-audit.js` 查看当前版本的实际数量。
|
|
190
250
|
|
|
191
251
|
### 生成侧:创建一份可批注文档
|
|
192
252
|
|
|
@@ -267,23 +327,26 @@ window.__annotSaveStrip = function (clone) {
|
|
|
267
327
|
|
|
268
328
|
## 让任意 agent 触发本工作流
|
|
269
329
|
|
|
270
|
-
-
|
|
271
|
-
-
|
|
272
|
-
-
|
|
330
|
+
- **安装工作流**:运行 `npx annotatable-html skill`;可用 `--codex`、`--opencode` 等参数选择工具。
|
|
331
|
+
- **生成文档**:对 agent 说「生成一份可批注的 HTML 文档《XX》」。需要目录内工具或主题时再运行 `init`。
|
|
332
|
+
- **接续批注**:告诉 agent 目标文档,再说「读批注」或「恢复值守」。通用会话启动时不扫描全局待办。
|
|
273
333
|
|
|
274
334
|
## 测试
|
|
275
335
|
|
|
276
336
|
```bash
|
|
277
|
-
node test/run.js #
|
|
337
|
+
node test/run.js # 锚定、账本、回滚、协议、渲染、技能安装与宿主指令保留
|
|
338
|
+
npm run test:hosts # 续接、通知、回复传参和页面接管回归(Node.js 22+)
|
|
278
339
|
node test/token-audit.js # Token 审计(本文档数据来源)
|
|
279
340
|
node test/theme-e2e.js # 主题/自定义模板 端到端(11 项)
|
|
280
341
|
```
|
|
281
342
|
|
|
343
|
+
宿主回归默认使用隔离夹具,不调用真实模型或关闭桌面。真实宿主验证及范围见 [续接方案](docs/Codex批注续接方案.md);脱敏结果见 [验收摘要](record/continuation-verification.json)。原始会话、账号上下文、浏览器资料和安装备份仅保留在本机,排除在 Git 与 npm 包之外。
|
|
344
|
+
|
|
282
345
|
另有十余个无头 e2e(puppeteer-core + Edge,经 `annotatable serve` 的 http 访问)覆盖 编辑→落盘→回滚全链路、面板分组、注入宿主、首绑引导、导航窗格几何与跳转精度、尾节垫片、阅读模式、章节折叠、Markdown 全链路(md 转换→活批注面→编辑态 md 输入→导航书签移除/恢复)、块拖拽(⠿ 手柄换序→落盘,模板文档与 inject 注入宿主各测一份)、**主题工坊(studio 控件→预览烤入→保存/设全局→new/md 接入,v1.17.0)**。
|
|
283
346
|
|
|
284
347
|
## 设计说明
|
|
285
348
|
|
|
286
|
-
批注锚定采用 W3C Web Annotation(TextQuoteSelector
|
|
349
|
+
批注锚定采用 W3C Web Annotation(TextQuoteSelector)思路。工作流公开在包内 `templates/SKILL.md` 与按需说明中。任意具备文件读写能力的 agent(Claude Code / Codex / Cursor / ZCode / OpenCode…)均可接入。一个浏览器与一个能读写文件的 agent 即可使用。
|
|
287
350
|
|
|
288
351
|
## License
|
|
289
352
|
|
package/agent/annot_poll.js
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
|
-
/* 用法: node agent/annot_poll.js <html路径> [--timeout 秒] [--interval 秒]
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
/* 用法: node agent/annot_poll.js <html路径> [--timeout 秒] [--interval 秒] [--exit-code N] [--exit-code-timeout N]
|
|
4
|
+
* 阻塞等待新批注:已有待回应立即退出;否则轮询,人类批注保存后打印 POLL_HIT <json> 并退出唤醒 agent;
|
|
5
|
+
* 超时以 3 退出(默认)。退出码按 harness 选(issue#8):
|
|
6
|
+
* zcode 等后台任务退出即唤醒 → 默认即可(命中 0 / 超时 3);
|
|
7
|
+
* Claude Code 等非零退出码才唤醒 → --exit-code 1,且超时轮空不想叫人再加 --exit-code-timeout 0。
|
|
6
8
|
* 逻辑在 ../lib/core.js */
|
|
7
9
|
var args = process.argv.slice(3);
|
|
8
10
|
function flag(name, dft) {
|
|
9
11
|
var i = args.indexOf(name);
|
|
10
12
|
return i !== -1 ? args[i + 1] : dft;
|
|
11
13
|
}
|
|
12
|
-
require('../lib/core.js').pollAnnotations(process.argv[2], {
|
|
14
|
+
require('../lib/core.js').pollAnnotations(process.argv[2], {
|
|
15
|
+
timeout: flag('--timeout', 0),
|
|
16
|
+
interval: flag('--interval', 3),
|
|
17
|
+
exitCode: parseInt(flag('--exit-code', 0), 10),
|
|
18
|
+
timeoutExitCode: parseInt(flag('--exit-code-timeout', 3), 10)
|
|
19
|
+
});
|
package/bin/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
/* annotatable-html 命令行入口
|
|
5
|
-
* annotatable init [dir]
|
|
5
|
+
* annotatable init [dir] 导出目录内工具、模板与主题
|
|
6
6
|
* annotatable new <名称.html> [--template 路径]
|
|
7
7
|
* 新建可批注文档(自动应用目录内主题/自定义模板)
|
|
8
8
|
* annotatable md <文件.md> [-o 输出.html] [--title 标题] [--template 路径]
|
|
@@ -15,19 +15,20 @@
|
|
|
15
15
|
* annotatable read <file.html> 列出待回应批注
|
|
16
16
|
* annotatable reply <file.html> <replies.json>
|
|
17
17
|
* 写回 AI 回复(追加ai轮次+快照+rev+1)
|
|
18
|
-
* annotatable poll <file.html> [--timeout 秒] [--interval 秒]
|
|
19
|
-
*
|
|
18
|
+
* annotatable poll <file.html> [--timeout 秒] [--interval 秒] [--exit-code N] [--exit-code-timeout N]
|
|
19
|
+
* 阻塞等待新批注(配后台任务:出批注即退出唤醒;退出码按 harness 选——zcode 默认 0/3,Claude Code 等非零才唤醒 → --exit-code 1)
|
|
20
|
+
* annotatable poll <file.html> --codex 用官方会话接口通知当前 Codex(Node.js 22+)
|
|
20
21
|
*/
|
|
21
22
|
|
|
22
23
|
var path = require('path');
|
|
23
24
|
var core = require('../lib/core.js');
|
|
24
25
|
|
|
25
26
|
var args = process.argv.slice(2);
|
|
26
|
-
/*
|
|
27
|
+
/* 短旗标 -o 等价 --out。 */
|
|
27
28
|
args = args.map(function (a) { return a === '-o' ? '--out' : a; });
|
|
28
29
|
var cmd = args[0] || 'help';
|
|
29
30
|
var positional = [], flags = {};
|
|
30
|
-
var boolFlags = { zcode: true, claude: true, reset: true, stdin: true, go: true, force: true };
|
|
31
|
+
var boolFlags = { zcode: true, claude: true, codex: true, opencode: true, reset: true, stdin: true, go: true, force: true, status: true, stop: true, listen: true, hook: true };
|
|
31
32
|
for (var i = 1; i < args.length; i++) {
|
|
32
33
|
if (args[i].indexOf('--') === 0) {
|
|
33
34
|
var k = args[i].slice(2);
|
|
@@ -47,7 +48,7 @@ function usage() {
|
|
|
47
48
|
'annotatable-html — 可批注 HTML:人类划线批注,AI agent 读批注回答',
|
|
48
49
|
'',
|
|
49
50
|
'用法:',
|
|
50
|
-
' annotatable init [目录]
|
|
51
|
+
' annotatable init [目录] 导出 agent 工具、模板与主题;批注协议通过 skill 安装',
|
|
51
52
|
' annotatable new <名称.html> [--template 路径] [--lang en]',
|
|
52
53
|
' 新建可批注文档(自动应用主题/自定义模板)',
|
|
53
54
|
' annotatable md <文件.md> [-o 输出.html] [--title 标题] [--lang en]',
|
|
@@ -57,7 +58,8 @@ function usage() {
|
|
|
57
58
|
' annotatable template [名称.html] 导出内置模板供深度改造',
|
|
58
59
|
' annotatable template --use <文件.html> 把你的 HTML 一键设为全局默认模板(整页保留原样式)',
|
|
59
60
|
' annotatable template --reset 撤销全局默认模板,恢复内置',
|
|
60
|
-
' annotatable skill [--zcode] [--claude] 安装 skill
|
|
61
|
+
' annotatable skill [--zcode] [--claude] [--codex] [--opencode] 安装 skill(v1.40.0 默认投主流四家 harness',
|
|
62
|
+
' 的 skill 目录;slash 命令仅 zcode/claude)+ /html4agent 命令(任意目录触发)',
|
|
61
63
|
' annotatable serve [目录] [--port N] 启动本地服务器(全自动保存,全浏览器支持;--port 0=系统挑空闲端口)',
|
|
62
64
|
' annotatable inject <file.html> 给任意 HTML 注入批注层',
|
|
63
65
|
' annotatable regen <活页.html> <图纸.html> [--go] 图纸页安全重建:报表手改清单,--go 上盘(store/DOC_ID 保留)',
|
|
@@ -69,27 +71,41 @@ function usage() {
|
|
|
69
71
|
' annotatable reply <file.html> --stream-end \'<全文>\' [--id <批注id>]',
|
|
70
72
|
' 收尾流式回答(全文替换段落累计,落快照)',
|
|
71
73
|
' annotatable edit <file.html> <edits.json> 按批注指令改正文(json 是文件路径):[{"id":"..","newText":".."}]',
|
|
72
|
-
' annotatable answer <file.html> [json]
|
|
73
|
-
'
|
|
74
|
-
'
|
|
75
|
-
'
|
|
76
|
-
' annotatable poll <file.html> [--
|
|
77
|
-
'
|
|
74
|
+
' annotatable answer <file.html> [json] 兼容既有 answers-store 数据(显式调用;非默认回复流程)',
|
|
75
|
+
' annotatable poll <file.html> [--timeout 秒] [--interval 秒] [--exit-code N] [--exit-code-timeout N]',
|
|
76
|
+
' 阻塞等待新批注:有新批注打印 POLL_HIT 并退出唤醒 agent(默认命中 0/超时 3;',
|
|
77
|
+
' Claude Code 等非零才唤醒的 harness → --exit-code 1,超时轮空不叫人再加 --exit-code-timeout 0),',
|
|
78
|
+
' annotatable poll <file.html> --codex [--codex-thread ID] [--codex-bin 原生CLI路径]',
|
|
79
|
+
' 默认取 CODEX_THREAD_ID;桌面会话优先使用继承的本机工具连接,',
|
|
80
|
+
' --codex-transport auto|desktop|app-server 选择入口;--codex-turn ID 指定来源轮次',
|
|
81
|
+
' annotatable poll <file.html> --opencode [--opencode-url URL] [--opencode-session ID]',
|
|
82
|
+
' OpenCode 异步会话通知;上下文插件自动传递地址和会话 ID',
|
|
83
|
+
' annotatable duty <file.html> --codex|--claude|--opencode [--port N] 注册原会话自动续接(Windows、Node.js 22+)',
|
|
84
|
+
' 原宿主在线通知,确认退出后才无头回复;输出保存服务的页面地址',
|
|
85
|
+
' Claude 用原生后台任务启动;可加 --claude-session ID、--claude-bin 路径',
|
|
86
|
+
' Claude 的项目启动/结束钩子自动接回通知,重开原会话无需手动重挂',
|
|
87
|
+
' annotatable duty <file.html> --status 查看注册、处理者与待办状态,不启动模型',
|
|
88
|
+
' annotatable duty <file.html> --stop 停止续接;已有无头轮次完成后退出',
|
|
78
89
|
'',
|
|
79
90
|
'图纸页维护:纯文字改动直接 edit/手改,别整页重生成;必须重生成(改图/大改版)时走 regen:',
|
|
80
|
-
'先报表(默认),把手改同步进图纸后 --go
|
|
91
|
+
'先报表(默认),把手改同步进图纸后 --go 上盘。人类编辑模式中,回复退出码 6、regen 退出码 3——让路等收笔。',
|
|
92
|
+
'read/reply/edit/poll 可使用本机 localhost 页面地址;需要新版 annotatable serve 正在运行。',
|
|
81
93
|
'',
|
|
82
94
|
'典型循环: 人类在浏览器批注 → AI 运行 read → 生成 replies.json → 运行 reply → 人类刷新可见。'
|
|
83
95
|
].join('\n'));
|
|
84
96
|
}
|
|
85
97
|
|
|
98
|
+
function runCommand() {
|
|
86
99
|
try {
|
|
87
100
|
/* skill 副本过期自检(skill 命令自身就会刷新升级,不再提示) */
|
|
88
|
-
if (['init', 'new', 'md', 'theme', 'template', 'studio', 'inject', 'upgrade', 'regen', 'read', 'reply', 'edit', 'answer', '
|
|
101
|
+
if (!flags.hook && ['init', 'new', 'md', 'theme', 'template', 'studio', 'inject', 'upgrade', 'regen', 'read', 'reply', 'edit', 'answer', 'poll', 'duty', 'serve'].indexOf(cmd) !== -1) {
|
|
89
102
|
var notice = core.staleSkillNotice(path.join(__dirname, '..'));
|
|
90
|
-
if (notice) console.log(notice);
|
|
103
|
+
if (notice) (cmd === 'duty' && flags.claude && !flags.status && !flags.stop ? console.error : console.log)(notice);
|
|
91
104
|
}
|
|
92
105
|
if (cmd === 'init') {
|
|
106
|
+
if (Object.keys(flags).some(function (flag) { return /^agents-md(?:=|$)/.test(flag); })) {
|
|
107
|
+
throw new Error('init 不再写入 AGENTS.md;请运行 annotatable skill 安装批注工作流。');
|
|
108
|
+
}
|
|
93
109
|
core.initKit(positional[0] || process.cwd(), path.join(__dirname, '..'));
|
|
94
110
|
} else if (cmd === 'new') {
|
|
95
111
|
if (!positional[0]) { usage(); process.exit(2); }
|
|
@@ -127,7 +143,7 @@ try {
|
|
|
127
143
|
} catch (e) { /* 同上 */ }
|
|
128
144
|
});
|
|
129
145
|
} else if (cmd === 'skill') {
|
|
130
|
-
core.installSkill({ zcode: flags.zcode, claude: flags.claude }, path.join(__dirname, '..'), flags.dir);
|
|
146
|
+
core.installSkill({ zcode: flags.zcode, claude: flags.claude, codex: flags.codex, opencode: flags.opencode }, path.join(__dirname, '..'), flags.dir);
|
|
131
147
|
} else if (cmd === 'serve') {
|
|
132
148
|
var fs = require('fs');
|
|
133
149
|
var serve = require('../lib/serve.js');
|
|
@@ -186,6 +202,8 @@ try {
|
|
|
186
202
|
if (!positional[0]) { usage(); process.exit(2); }
|
|
187
203
|
core.readPending(positional[0]);
|
|
188
204
|
} else if (cmd === 'reply') {
|
|
205
|
+
var streaming = flags.stream !== undefined || flags['stream-end'] !== undefined;
|
|
206
|
+
if (positional.length > (streaming ? 1 : 2)) throw new Error('回复参数被拆成多段;长文本请通过 UTF-8 JSON 文件或 --stdin 传入。');
|
|
189
207
|
if (!positional[0]) { usage(); process.exit(2); }
|
|
190
208
|
var sText, sEnd = false;
|
|
191
209
|
if (flags.stream !== undefined) { sText = flags.stream; }
|
|
@@ -220,16 +238,67 @@ try {
|
|
|
220
238
|
} else if (cmd === 'edit') {
|
|
221
239
|
if (!positional[0] || !positional[1]) { usage(); process.exit(2); }
|
|
222
240
|
core.applyEdits(positional[0], positional[1]);
|
|
241
|
+
} else if (cmd === 'duty') {
|
|
242
|
+
if (flags.hook) {
|
|
243
|
+
if (!flags.claude || flags.codex || flags.opencode || flags.listen || flags.stop || flags.status) throw new Error('--hook 只用于 Claude 原生事件');
|
|
244
|
+
var chunks = [], bytes = 0;
|
|
245
|
+
process.stdin.on('data', function (chunk) { bytes += chunk.length; if (bytes > 1024 * 1024) { console.error('DUTY_ERROR: hook 输入过大'); process.exit(1); } chunks.push(chunk); });
|
|
246
|
+
process.stdin.on('end', function () {
|
|
247
|
+
Promise.resolve().then(function () { return require('../lib/claude').resumeHook(JSON.parse(Buffer.concat(chunks).toString('utf8'))); })
|
|
248
|
+
.catch(function (error) { console.error('DUTY_ERROR: ' + error.message); process.exitCode = 1; });
|
|
249
|
+
});
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (!positional[0]) { usage(); process.exit(2); }
|
|
253
|
+
var duty = require('../lib/duty.js');
|
|
254
|
+
if (flags.status) console.log(JSON.stringify(duty.status(positional[0]), null, 2));
|
|
255
|
+
else if (flags.stop) console.log(JSON.stringify(duty.stop(positional[0]), null, 2));
|
|
256
|
+
else {
|
|
257
|
+
var hosts = ['codex', 'claude', 'opencode'].filter(function (name) { return flags[name]; });
|
|
258
|
+
if (hosts.length !== 1) throw new Error('自动续接需显式选择一个宿主:--codex、--claude 或 --opencode');
|
|
259
|
+
var host = hosts[0];
|
|
260
|
+
if (flags.listen && host !== 'claude') throw new Error('--listen 只用于 Claude 的通知任务');
|
|
261
|
+
(flags.listen ? duty.listen : duty.register)(positional[0], { host: host, events: host === 'claude', codexThread: flags['codex-thread'], codexTurn: flags['codex-turn'],
|
|
262
|
+
codexBin: flags['codex-bin'], codexPipe: flags['codex-pipe'], claudeSession: flags['claude-session'], claudeBin: flags['claude-bin'],
|
|
263
|
+
opencodeUrl: flags['opencode-url'], opencodeSession: flags['opencode-session'], opencodeBin: flags['opencode-bin'], port: flags.port, pageContext: flags.dutyContext })
|
|
264
|
+
.then(function (result) {
|
|
265
|
+
if (result.stopped) { console.error('DUTY_STOPPED 用户已停止,不自动重新启动'); return; }
|
|
266
|
+
(host === 'claude' ? console.error : console.log)(host.toUpperCase() + '_DUTY_READY ' + JSON.stringify(result));
|
|
267
|
+
if (host === 'claude' && !result.alreadyListening) return duty.events(positional[0]);
|
|
268
|
+
})
|
|
269
|
+
.catch(function (error) { console.error('DUTY_ERROR: ' + error.message); process.exitCode = 1; });
|
|
270
|
+
}
|
|
223
271
|
} else if (cmd === 'answer') {
|
|
224
272
|
if (!positional[0]) { usage(); process.exit(2); }
|
|
225
|
-
if (!positional[1]) core.listAnswers(positional[0]); //
|
|
273
|
+
if (!positional[1]) core.listAnswers(positional[0]); // 旧数据的显式兼容入口
|
|
226
274
|
else core.applyAnswers(positional[0], positional[1]);
|
|
227
275
|
} else if (cmd === 'teach') {
|
|
228
|
-
|
|
229
|
-
core.teachDoc(positional[0], { out: flags.out, title: flags.title }, path.join(__dirname, '..'));
|
|
276
|
+
throw new Error('教学模式已从批注工具移出;请使用独立教学工作流。批注回复使用 annotatable reply。');
|
|
230
277
|
} else if (cmd === 'poll') {
|
|
231
278
|
if (!positional[0]) { usage(); process.exit(2); }
|
|
232
|
-
|
|
279
|
+
if (flags.codex && flags.opencode) throw new Error('poll 一次只能选择 Codex 或 OpenCode');
|
|
280
|
+
var pollOpts = {
|
|
281
|
+
timeout: flags.timeout, interval: flags.interval,
|
|
282
|
+
exitCode: parseInt(flags['exit-code'], 10) || 0,
|
|
283
|
+
timeoutExitCode: flags['exit-code-timeout'] !== undefined ? parseInt(flags['exit-code-timeout'], 10) : undefined
|
|
284
|
+
};
|
|
285
|
+
if (flags.codex) {
|
|
286
|
+
pollOpts.codexThread = flags['codex-thread'];
|
|
287
|
+
pollOpts.codexBin = flags['codex-bin'];
|
|
288
|
+
pollOpts.codexTransport = flags['codex-transport'];
|
|
289
|
+
pollOpts.codexTurn = flags['codex-turn'];
|
|
290
|
+
require('../lib/codex.js').pollAnnotations(positional[0], pollOpts).catch(function (err) {
|
|
291
|
+
console.error('CODEX_NOTIFY_ERROR ' + (err.message || err));
|
|
292
|
+
process.exitCode = 1;
|
|
293
|
+
});
|
|
294
|
+
} else if (flags.opencode) {
|
|
295
|
+
pollOpts.opencodeUrl = flags['opencode-url'];
|
|
296
|
+
pollOpts.opencodeSession = flags['opencode-session'];
|
|
297
|
+
require('../lib/opencode.js').pollAnnotations(positional[0], pollOpts).catch(function (err) {
|
|
298
|
+
console.error('OPENCODE_NOTIFY_ERROR ' + (err.message || err));
|
|
299
|
+
process.exitCode = 1;
|
|
300
|
+
});
|
|
301
|
+
} else core.pollAnnotations(positional[0], pollOpts);
|
|
233
302
|
} else {
|
|
234
303
|
usage();
|
|
235
304
|
}
|
|
@@ -237,3 +306,18 @@ try {
|
|
|
237
306
|
console.error('错误: ' + (e.message || e));
|
|
238
307
|
process.exit(1);
|
|
239
308
|
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (['read', 'reply', 'edit', 'answer', 'poll', 'duty'].indexOf(cmd) !== -1 && /^https?:\/\//i.test(positional[0] || '')) {
|
|
312
|
+
require('../lib/document.js').resolveDocument(positional[0], { includeContext: cmd === 'duty' }).then(function (file) {
|
|
313
|
+
if (cmd === 'duty') {
|
|
314
|
+
flags.dutyContext = file;
|
|
315
|
+
if (flags.port === undefined) flags.port = file.port;
|
|
316
|
+
positional[0] = file.file;
|
|
317
|
+
} else positional[0] = file;
|
|
318
|
+
runCommand();
|
|
319
|
+
}).catch(function (err) {
|
|
320
|
+
console.error('DOCUMENT_RESOLVE_ERROR ' + (err.message || err));
|
|
321
|
+
process.exitCode = 1;
|
|
322
|
+
});
|
|
323
|
+
} else runCommand();
|