annotatable-html 0.0.0-stage → 1.38.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/LICENSE +21 -0
- package/README.md +289 -2
- package/agent/annot_answer.js +7 -0
- package/agent/annot_edit.js +6 -0
- package/agent/annot_poll.js +12 -0
- package/agent/annot_read.js +5 -0
- package/agent/annot_reply.js +50 -0
- package/bin/cli.js +239 -0
- package/lib/core.js +2198 -0
- package/lib/lang-en.js +462 -0
- package/lib/serve.js +293 -0
- package/lib/vendor/marked.cjs +2811 -0
- package/lib/vendor/temml.cjs +14710 -0
- package/package.json +48 -4
- package/templates/AGENTS.md +107 -0
- package/templates/SKILL.md +99 -0
- package/templates/annotatable-doc.html +5708 -0
- package/templates/annotatable.theme.css +28 -0
- package/templates/commands/html4agent.md +37 -0
- package/templates/theme-studio.html +330 -0
package/bin/cli.js
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/* annotatable-html 命令行入口
|
|
5
|
+
* annotatable init [dir] 初始化目录(AGENTS.md + 工具 + 模板 + 主题)
|
|
6
|
+
* annotatable new <名称.html> [--template 路径]
|
|
7
|
+
* 新建可批注文档(自动应用目录内主题/自定义模板)
|
|
8
|
+
* annotatable md <文件.md> [-o 输出.html] [--title 标题] [--template 路径]
|
|
9
|
+
* 一键 Markdown → 可批注 HTML(正文遵照原文,仅加批注层)
|
|
10
|
+
* annotatable theme 导出默认主题文件(annotatable.theme.css)供微调
|
|
11
|
+
* annotatable studio [--port 端口] 打开可视化主题工坊(配色/字体/字号/页宽,实时预览)
|
|
12
|
+
* annotatable template [名称.html] 导出内置模板供深度改造
|
|
13
|
+
* annotatable template --use <文件.html> 把你的 HTML 一键设为全局默认模板(整页保留原样式)
|
|
14
|
+
* annotatable template --reset 撤销全局默认模板,恢复内置
|
|
15
|
+
* annotatable read <file.html> 列出待回应批注
|
|
16
|
+
* annotatable reply <file.html> <replies.json>
|
|
17
|
+
* 写回 AI 回复(追加ai轮次+快照+rev+1)
|
|
18
|
+
* annotatable poll <file.html> [--timeout 秒] [--interval 秒]
|
|
19
|
+
* 阻塞等待新批注(配后台任务:出批注即退出唤醒)
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
var path = require('path');
|
|
23
|
+
var core = require('../lib/core.js');
|
|
24
|
+
|
|
25
|
+
var args = process.argv.slice(2);
|
|
26
|
+
/* 短旗标别名:usage 宣传的 -o(md/teach)等价 --out——解析器只认 -- 长旗标,不归一化则 -o 静默落进位置参数(v1.37.3 及之前 -o 一直没生效的根因) */
|
|
27
|
+
args = args.map(function (a) { return a === '-o' ? '--out' : a; });
|
|
28
|
+
var cmd = args[0] || 'help';
|
|
29
|
+
var positional = [], flags = {};
|
|
30
|
+
var boolFlags = { zcode: true, claude: true, reset: true, stdin: true, go: true, force: true };
|
|
31
|
+
for (var i = 1; i < args.length; i++) {
|
|
32
|
+
if (args[i].indexOf('--') === 0) {
|
|
33
|
+
var k = args[i].slice(2);
|
|
34
|
+
if (boolFlags[k]) { flags[k] = true; }
|
|
35
|
+
else if (args[i + 1] && args[i + 1].indexOf('--') !== 0) { flags[k] = args[i + 1]; i++; } // 下一个是旗标时不当成值(--stream --id x)
|
|
36
|
+
else { flags[k] = ''; }
|
|
37
|
+
} else positional.push(args[i]);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* 长文本(流式段落)经管道进:annotatable reply f.html --stream --stdin < 段落.txt */
|
|
41
|
+
function readStdinSync() {
|
|
42
|
+
try { return require('fs').readFileSync(0, 'utf8'); } catch (e) { return ''; }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function usage() {
|
|
46
|
+
console.log([
|
|
47
|
+
'annotatable-html — 可批注 HTML:人类划线批注,AI agent 读批注回答',
|
|
48
|
+
'',
|
|
49
|
+
'用法:',
|
|
50
|
+
' annotatable init [目录] 初始化(AGENTS.md + agent 工具 + 模板 + 主题)',
|
|
51
|
+
' annotatable new <名称.html> [--template 路径] [--lang en]',
|
|
52
|
+
' 新建可批注文档(自动应用主题/自定义模板)',
|
|
53
|
+
' annotatable md <文件.md> [-o 输出.html] [--title 标题] [--lang en]',
|
|
54
|
+
' 一键 Markdown → 可批注 HTML(正文遵照原文,仅加批注层)',
|
|
55
|
+
' annotatable theme 导出主题文件 annotatable.theme.css 供微调',
|
|
56
|
+
' annotatable studio [--port 端口] 打开可视化主题工坊(配色/字体/字号/页宽,实时预览)',
|
|
57
|
+
' annotatable template [名称.html] 导出内置模板供深度改造',
|
|
58
|
+
' annotatable template --use <文件.html> 把你的 HTML 一键设为全局默认模板(整页保留原样式)',
|
|
59
|
+
' annotatable template --reset 撤销全局默认模板,恢复内置',
|
|
60
|
+
' annotatable skill [--zcode] [--claude] 安装 skill + /html4agent 命令(任意目录触发)',
|
|
61
|
+
' annotatable serve [目录] [--port N] 启动本地服务器(全自动保存,全浏览器支持;--port 0=系统挑空闲端口)',
|
|
62
|
+
' annotatable inject <file.html> 给任意 HTML 注入批注层',
|
|
63
|
+
' annotatable regen <活页.html> <图纸.html> [--go] 图纸页安全重建:报表手改清单,--go 上盘(store/DOC_ID 保留)',
|
|
64
|
+
' annotatable upgrade <文件或目录> 旧层页批量静默换新层(read/reply/poll/serve 触达时也会自动做)',
|
|
65
|
+
' annotatable read <file.html> 列出待回应批注(含对话线+结构体检)',
|
|
66
|
+
' annotatable reply <file.html> <replies.json> 写回 AI 回复(json 是文件路径,非内联):[{"id":"..","reply":".."}]',
|
|
67
|
+
' annotatable reply <file.html> --stream \'<段落>\' --id <批注id>',
|
|
68
|
+
' 流式追加一段回答(值守协议;不带 --id 自动接最近打开的流式轮;退出码 4=被打断 5=目标已删 6=人类编辑中,让路 7=没有打开中的流式轮)',
|
|
69
|
+
' annotatable reply <file.html> --stream-end \'<全文>\' [--id <批注id>]',
|
|
70
|
+
' 收尾流式回答(全文替换段落累计,落快照)',
|
|
71
|
+
' annotatable edit <file.html> <edits.json> 按批注指令改正文(json 是文件路径):[{"id":"..","newText":".."}]',
|
|
72
|
+
' annotatable answer <file.html> [json] 写入长答/教学卡(不带 json=列出既有卡);',
|
|
73
|
+
' json: [{"annId":"..","title":"..","rungs":[{"title":"..","body":"..","checkpoint":"..","answer":".."}]}]',
|
|
74
|
+
' annotatable teach <源.html> [-o 输出.html] [--title 标题]',
|
|
75
|
+
' 从源文档批注生成教学文档脚手架(agent 随后用 edit 填充)',
|
|
76
|
+
' annotatable poll <file.html> [--timeout 秒] [--interval 秒]',
|
|
77
|
+
' 阻塞等待新批注:有新批注打印 POLL_HIT 并退出(0=有批注 3=超时),',
|
|
78
|
+
'',
|
|
79
|
+
'图纸页维护:纯文字改动直接 edit/手改,别整页重生成;必须重生成(改图/大改版)时走 regen:',
|
|
80
|
+
'先报表(默认),把手改同步进图纸后 --go 上盘。人类编辑模式中,回复/教学卡退出码 6、regen 退出码 3——让路等收笔。',
|
|
81
|
+
'',
|
|
82
|
+
'典型循环: 人类在浏览器批注 → AI 运行 read → 生成 replies.json → 运行 reply → 人类刷新可见。'
|
|
83
|
+
].join('\n'));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
/* skill 副本过期自检(skill 命令自身就会刷新升级,不再提示) */
|
|
88
|
+
if (['init', 'new', 'md', 'theme', 'template', 'studio', 'inject', 'upgrade', 'regen', 'read', 'reply', 'edit', 'answer', 'teach', 'poll', 'serve'].indexOf(cmd) !== -1) {
|
|
89
|
+
var notice = core.staleSkillNotice(path.join(__dirname, '..'));
|
|
90
|
+
if (notice) console.log(notice);
|
|
91
|
+
}
|
|
92
|
+
if (cmd === 'init') {
|
|
93
|
+
core.initKit(positional[0] || process.cwd(), path.join(__dirname, '..'));
|
|
94
|
+
} else if (cmd === 'new') {
|
|
95
|
+
if (!positional[0]) { usage(); process.exit(2); }
|
|
96
|
+
core.newDoc(process.cwd(), positional[0], path.join(__dirname, '..'), { template: flags.template, lang: flags.lang });
|
|
97
|
+
} else if (cmd === 'md') {
|
|
98
|
+
if (!positional[0]) { usage(); process.exit(2); }
|
|
99
|
+
core.mdDoc(positional[0], { out: flags.out, title: flags.title, template: flags.template, lang: flags.lang }, path.join(__dirname, '..'));
|
|
100
|
+
} else if (cmd === 'theme') {
|
|
101
|
+
core.exportTheme(process.cwd(), path.join(__dirname, '..'));
|
|
102
|
+
} else if (cmd === 'template') {
|
|
103
|
+
if (flags.use) {
|
|
104
|
+
core.templateUse(flags.use, path.join(__dirname, '..'), {});
|
|
105
|
+
} else if (flags.reset) {
|
|
106
|
+
core.templateReset({});
|
|
107
|
+
} else {
|
|
108
|
+
core.exportTemplate(process.cwd(), positional[0], path.join(__dirname, '..'));
|
|
109
|
+
}
|
|
110
|
+
} else if (cmd === 'studio') {
|
|
111
|
+
var sServe = require('../lib/serve.js');
|
|
112
|
+
var sPort = parseInt(flags.port, 10);
|
|
113
|
+
if (isNaN(sPort)) sPort = 8974;
|
|
114
|
+
var sPkg = path.join(__dirname, '..');
|
|
115
|
+
sServe.start(process.cwd(), sPort, { studioProvider: function () { return core.studioHtml(sPkg); } })
|
|
116
|
+
.then(function (info) {
|
|
117
|
+
var url = info.url + '/__annot/studio';
|
|
118
|
+
console.log('🎨 主题工坊已启动: ' + url);
|
|
119
|
+
console.log(' 左侧调配色/字体/字号/页宽,右侧实时预览(批注、导航窗格可直接试)。');
|
|
120
|
+
console.log(' 调好后点「保存到当前目录」或「设为全局默认」,之后 new / md 自动应用。按 Ctrl+C 停止。');
|
|
121
|
+
try {
|
|
122
|
+
if (!process.env.ANNOT_NO_OPEN) { // e2e 等无人值守场景置 1 跳过自动开浏览器
|
|
123
|
+
var opener = process.platform === 'win32' ? 'start "" "' + url + '"'
|
|
124
|
+
: process.platform === 'darwin' ? 'open "' + url + '"' : 'xdg-open "' + url + '"';
|
|
125
|
+
require('child_process').exec(opener, function () { /* 打不开就手动复制地址 */ });
|
|
126
|
+
}
|
|
127
|
+
} catch (e) { /* 同上 */ }
|
|
128
|
+
});
|
|
129
|
+
} else if (cmd === 'skill') {
|
|
130
|
+
core.installSkill({ zcode: flags.zcode, claude: flags.claude }, path.join(__dirname, '..'), flags.dir);
|
|
131
|
+
} else if (cmd === 'serve') {
|
|
132
|
+
var fs = require('fs');
|
|
133
|
+
var serve = require('../lib/serve.js');
|
|
134
|
+
var dir = positional[0] || process.cwd();
|
|
135
|
+
var port = parseInt(flags.port, 10);
|
|
136
|
+
if (isNaN(port)) port = 8973; // --port 0 = 系统挑空闲端口(合法值,不能用 || 吞掉)
|
|
137
|
+
serve.start(dir, port).then(function (info) {
|
|
138
|
+
console.log('可批注文档服务器已启动: ' + info.url);
|
|
139
|
+
console.log('伺服目录: ' + info.root);
|
|
140
|
+
core.upgradeDir(info.root); // v1.36.0 开服即扫目录:旧层页静默换新层(有升级会各打一行)
|
|
141
|
+
try {
|
|
142
|
+
fs.readdirSync(info.root).forEach(function (f) {
|
|
143
|
+
if (/\.html?$/i.test(f)) console.log(' 文档 → ' + info.url + '/' + encodeURIComponent(f));
|
|
144
|
+
});
|
|
145
|
+
} catch (e) { /* 目录尚为空或未创建,浏览器访问时再列 */ }
|
|
146
|
+
console.log('服务器模式:批注自动保存进文件,无需绑定/授权。按 Ctrl+C 停止。');
|
|
147
|
+
});
|
|
148
|
+
} else if (cmd === 'inject') {
|
|
149
|
+
if (!positional[0]) { usage(); process.exit(2); }
|
|
150
|
+
core.injectHtml(positional[0], path.join(__dirname, '..'), { lang: flags.lang });
|
|
151
|
+
} else if (cmd === 'upgrade') {
|
|
152
|
+
if (!positional[0]) { usage(); process.exit(2); }
|
|
153
|
+
var fsU = require('fs');
|
|
154
|
+
var upT = path.resolve(positional[0]);
|
|
155
|
+
var upIsDir;
|
|
156
|
+
try { upIsDir = fsU.statSync(upT).isDirectory(); } catch (e) { throw new Error('路径不存在: ' + upT); }
|
|
157
|
+
if (upIsDir) {
|
|
158
|
+
var upN = core.upgradeDir(upT);
|
|
159
|
+
console.log(upN ? '✅ 静默升级完成:' + upN + ' 个旧层页已换新层。' : '没有需要升级的旧层页(已是最新或不可自动换装)。');
|
|
160
|
+
} else {
|
|
161
|
+
var upR = core.silentUpgrade(upT);
|
|
162
|
+
if (!upR || upR.status === 'skip') console.log('未升级:' + ((upR && upR.note) || '定位失败') + '(正文未动)');
|
|
163
|
+
else if (upR.status === 'current') console.log('✅ 已是当前层 v' + core.pkgVersion(path.join(__dirname, '..')) + ',无需升级');
|
|
164
|
+
else if (upR.status === 'error') console.log('⚠ ' + (upR.note || '升级失败'));
|
|
165
|
+
}
|
|
166
|
+
} else if (cmd === 'regen') {
|
|
167
|
+
if (!positional[0] || !positional[1]) { usage(); process.exit(2); }
|
|
168
|
+
var rg = core.regenDoc(positional[0], positional[1], { go: !!flags.go, force: !!flags.force });
|
|
169
|
+
if (rg.status === 'wordmode') {
|
|
170
|
+
console.log('WORD_MODE 人类编辑中(wordLock 旗鲜活):重生成挂起,等收笔再跑');
|
|
171
|
+
process.exitCode = 3;
|
|
172
|
+
} else if (rg.status === 'manual-edits') {
|
|
173
|
+
console.log('MANUAL_EDITS 盘上有 ' + rg.lost.length + ' 行手改内容是图纸里没有的——直接上盘会盖掉它们:');
|
|
174
|
+
rg.lost.slice(0, 20).forEach(function (l) { console.log(' · ' + l.slice(0, 90)); });
|
|
175
|
+
console.log(' 把手改同步进图纸、重出 fresh 后再 --go;确要丢弃手改加 --force(此报表已记名)');
|
|
176
|
+
process.exitCode = 2;
|
|
177
|
+
} else if (rg.status === 'clean') {
|
|
178
|
+
console.log('CLEAN 图纸与盘面对齐、无手改残留——加 --go 即可安全上盘');
|
|
179
|
+
} else if (rg.status === 'swapped') {
|
|
180
|
+
console.log('✅ 重生成上盘完成:store rev=' + rg.rev + '(' + rg.ann + ' 条批注)逐字节保留 · 备份 ' + rg.bak);
|
|
181
|
+
} else if (rg.status === 'stale' || rg.status === 'error') {
|
|
182
|
+
console.log('⚠ ' + (rg.note || rg.status));
|
|
183
|
+
process.exitCode = 1;
|
|
184
|
+
}
|
|
185
|
+
} else if (cmd === 'read') {
|
|
186
|
+
if (!positional[0]) { usage(); process.exit(2); }
|
|
187
|
+
core.readPending(positional[0]);
|
|
188
|
+
} else if (cmd === 'reply') {
|
|
189
|
+
if (!positional[0]) { usage(); process.exit(2); }
|
|
190
|
+
var sText, sEnd = false;
|
|
191
|
+
if (flags.stream !== undefined) { sText = flags.stream; }
|
|
192
|
+
else if (flags['stream-end'] !== undefined) { sText = flags['stream-end']; sEnd = true; }
|
|
193
|
+
if (sText !== undefined) {
|
|
194
|
+
if (flags.stdin || sText === '--stdin') sText = readStdinSync();
|
|
195
|
+
var sr = core.streamReply(positional[0], { text: sText, id: flags.id, end: sEnd });
|
|
196
|
+
if (sr.status === 'ok') {
|
|
197
|
+
console.log((sr.done ? '✅ 回答完成' : sr.started ? '▶️ 流式回答开始' : '➕ 已追加段落') + ' · 批注 ' + sr.annId + ' · rev=' + sr.rev);
|
|
198
|
+
if (sr.closed && sr.closed.length) console.log('ℹ️ 顺手封轮(未收尾的遗留流式轮,文本保留): ' + sr.closed.join(', '));
|
|
199
|
+
} else if (sr.status === 'wordmode') {
|
|
200
|
+
console.log('WORD_MODE 人类编辑中(wordLock 旗鲜活):回答挂起不落盘,等收笔再发');
|
|
201
|
+
process.exitCode = 6;
|
|
202
|
+
} else if (sr.status === 'interrupted') {
|
|
203
|
+
console.log('INTERRUPTED 人类已按⏹停止(批注 ' + sr.annId + ' · rev=' + sr.rev + ')——本轮作答终止,不要再追加');
|
|
204
|
+
if (sr.closed && sr.closed.length) console.log('ℹ️ 顺手封轮(未收尾的遗留流式轮,文本保留): ' + sr.closed.join(', '));
|
|
205
|
+
process.exitCode = 4;
|
|
206
|
+
} else if (sr.status === 'target-gone') {
|
|
207
|
+
console.log('TARGET_GONE 批注 ' + sr.annId + ' 已被删除——本轮作答终止');
|
|
208
|
+
process.exitCode = 5;
|
|
209
|
+
} else if (sr.status === 'empty') {
|
|
210
|
+
console.log('空段落:--stream 需要文本或 --stdin');
|
|
211
|
+
process.exitCode = 2;
|
|
212
|
+
} else {
|
|
213
|
+
console.log('NO_STREAM 没有打开中的流式回答——首段请带 --id 批注id(退出码 7)');
|
|
214
|
+
process.exitCode = 7;
|
|
215
|
+
}
|
|
216
|
+
} else {
|
|
217
|
+
if (!positional[1]) { usage(); process.exit(2); }
|
|
218
|
+
core.applyReplies(positional[0], positional[1]);
|
|
219
|
+
}
|
|
220
|
+
} else if (cmd === 'edit') {
|
|
221
|
+
if (!positional[0] || !positional[1]) { usage(); process.exit(2); }
|
|
222
|
+
core.applyEdits(positional[0], positional[1]);
|
|
223
|
+
} else if (cmd === 'answer') {
|
|
224
|
+
if (!positional[0]) { usage(); process.exit(2); }
|
|
225
|
+
if (!positional[1]) core.listAnswers(positional[0]); // 不带 json = 列出既有教学卡(「继续」前先看阶次)
|
|
226
|
+
else core.applyAnswers(positional[0], positional[1]);
|
|
227
|
+
} else if (cmd === 'teach') {
|
|
228
|
+
if (!positional[0]) { usage(); process.exit(2); }
|
|
229
|
+
core.teachDoc(positional[0], { out: flags.out, title: flags.title }, path.join(__dirname, '..'));
|
|
230
|
+
} else if (cmd === 'poll') {
|
|
231
|
+
if (!positional[0]) { usage(); process.exit(2); }
|
|
232
|
+
core.pollAnnotations(positional[0], { timeout: flags.timeout, interval: flags.interval });
|
|
233
|
+
} else {
|
|
234
|
+
usage();
|
|
235
|
+
}
|
|
236
|
+
} catch (e) {
|
|
237
|
+
console.error('错误: ' + (e.message || e));
|
|
238
|
+
process.exit(1);
|
|
239
|
+
}
|