skill-memo-ilife 0.3.24 → 0.3.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/cmd_read.js +328 -83
- package/package.json +3 -3
package/dist/cli/cmd_read.js
CHANGED
|
@@ -1,87 +1,332 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
2
|
+
// memo 唯一出口 cmd_read(M5 #35):argv+JSON(stdout)+exit;非 0 走 stderr;超时 terminate+TOAST 降级标记。
|
|
3
|
+
// 退出码对齐 skilllink 冻结:0 ok;1 预检;2 用法/参数;3 key;4 取数/超时;5 envelope/渲染/落盘。
|
|
4
|
+
// stdout 纯净:成功只打 envelope JSON 一行。
|
|
5
|
+
import { existsSync } from 'node:fs';
|
|
6
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
7
|
+
import { saveHtmlFile, helpReuseWindowOf } from 'base-paint/save-html';
|
|
8
|
+
// #855:复制区载荷的装配与信封取形(`buildDataText`/`buildLogText`/`MemoCopyEnvelope` 位)随 `memo.init`
|
|
9
|
+
// 一起搬进 `src/init/run.ts`——只有那条命令在用,不留第二份。
|
|
10
|
+
import { openMemoDb, closeMemoDb } from '../db/readonly.js';
|
|
11
|
+
import { LARK_WEBSITE_LINE } from '../sync/index.js';
|
|
12
|
+
// #855:参数校验口径(`crud*`/`normalize*`/`needId`)随备忘域命令一起搬进 `src/memo/run.ts`;
|
|
13
|
+
// 本件只剩开库前分派与交付装配,不再直接做域校验。
|
|
14
|
+
// #855:跨域的写侧合成(`reconcileWishes`)随 `memo.sync` 一起搬进 `src/sync/run.ts`;
|
|
15
|
+
// 本件只剩开库前分派与交付装配,不再直调域实现。
|
|
16
|
+
// #855:**命令登记查表**——各域自己的声明(`src/<域>/commands.ts`)由生成器汇成本表;命中即走该域的运行件。
|
|
17
|
+
import { REGISTRY } from './registry.js';
|
|
18
|
+
// #855:行适配已无人用本件这份(`toRows` 随 `memo.batch` 搬进 `src/memo/run.ts`,出口侧交付件不拼行)。
|
|
19
|
+
import { fail } from '../shared/exit.js';
|
|
20
|
+
import { memoShapeFor, buildMemoEnvelope, renderEnvelopeHtml, assertHtmlSize, MemoRenderError } from '../render/index.js';
|
|
21
|
+
import { buildMemoHelpFileData, renderMemoHelpHtml } from '../help/helpFile.js';
|
|
22
|
+
import { buildHelpSceneIndex } from '../help/sceneData.js';
|
|
23
|
+
import { buildHelpLookup } from '../help/index.js';
|
|
24
|
+
import { helpHtmlDirName, helpFileStem, lookupFileStem } from '../help/manifest.js';
|
|
25
|
+
// #855:产物名主体(`bookletFileStem`)与诊断读取/两页装配(`INIT_SCENE_ID`/`readInitDiagnosis`/
|
|
26
|
+
// `InitInputError`/`renderInitPage`/`InitPageMode`)随 `memo.sync`/`memo.init` 搬进各自的域——
|
|
27
|
+
// 本件只剩开库前分派与交付装配。
|
|
28
|
+
import { resolveDbDir, dbFilename } from '../shared/paths.js';
|
|
29
|
+
import { isConfigKey, runConfigKey } from './config.js';
|
|
30
|
+
// #706 · 配置体检:设置页专用的一条只读命令,同走「进分派层之前拦下」这条口(判据住 src/health.ts)。
|
|
31
|
+
import { isHealthCheckKey, runHealthCheckKey } from './health.js';
|
|
32
|
+
import { MemoFetchError, MemoPolicyError } from '../shared/errors.js';
|
|
33
|
+
const DEFAULT_TIMEOUT_MS = 30000;
|
|
34
|
+
// #855:失败约定(`fail`)提到共用位 `src/shared/exit.ts`——出口与各域运行件同一档退出码。
|
|
35
|
+
function toast(msg) { console.error('TOAST: ' + msg); }
|
|
36
|
+
function preflight() {
|
|
37
|
+
const v = process.versions.node.split('.').map(Number);
|
|
38
|
+
if (!(v[0] > 22 || (v[0] === 22 && v[1] >= 13)))
|
|
39
|
+
fail(1, 'node 低于 22.13:' + process.versions.node);
|
|
40
|
+
}
|
|
41
|
+
function needStr(params, name) {
|
|
42
|
+
const v = params[name];
|
|
43
|
+
if (typeof v !== 'string' || v.length === 0)
|
|
44
|
+
fail(2, '缺参数 ' + name);
|
|
45
|
+
return v;
|
|
46
|
+
}
|
|
47
|
+
// ── #229 · 「备忘录 help」的交付装配(**在开库之前**走,照 skill-bill/src/cli/cmd_read.ts:493-495) ────
|
|
48
|
+
//
|
|
49
|
+
// 缺省(不给任何参数)= 全量 HELP 文件:`<SKILLS_DB_PATH>/memo_html/备忘录_HELP_<YYYYMMDD_HHMMSS>[_N].html`
|
|
50
|
+
// (扁平、不加 `help/` 层:裁决 1),独占落盘 + **绝对路径**回执(`delivery` 顶层追加,序在既有五字段之后)。
|
|
51
|
+
// 显式 `mode:"lookup"` = 速查表分名文件(主体 `备忘录_速查表`,裁决 2:一个键两种产物就分两个名字)。
|
|
52
|
+
// 显式 `q` = 现找:只回命中(stdout),给 `--html <路径>` 才落盘(检索式问答不刷目录)。
|
|
53
|
+
// 显式 `reuseHours`(小时)= 复用窗口:`0`=每次都落新的;不给=**一天**(#245)——24 小时内反复读
|
|
54
|
+
// 同一份 HELP 产物只留一份、不再新建(判据=落盘名里的时间戳,不看 mtime)。两种 HELP 产物都吃窗口。
|
|
55
|
+
// 全程**不开库**:初始化判据=「**memo 库目录存在**」(票 6 V4:新库是目录 `<SKILLS_DB_PATH>/memo`,
|
|
56
|
+
// 不是老家的 `memo.db` 文件——本机实测 0 字节空壳 `memo.db` 在、真目录不在,老家口径当场判错),
|
|
57
|
+
// 且**只 stat、绝不建库**:免得「看帮助」把库目录 `mkdir` 出来。
|
|
58
|
+
//
|
|
59
|
+
// #240(欠债清偿):命名与落盘**不再自持**——时间戳、同秒 `_N` 递补、独占创建、写后回读字节数全在
|
|
60
|
+
// 共用件 `saveHtmlFile`(`base-paint/save-html`,唯一定义地 `packages/base-render/src/output/saveHtml.ts`)。
|
|
61
|
+
// 本文件只出**落点意图**(`{dir, stem}`,主体与目录名取自 `../help/manifest.js` 的三个值),
|
|
62
|
+
// 原先包内那份第 4 份同逻辑实现 `src/help/memoOutput.ts` 已删(裁决 3 挂的债,见 `#240`)。
|
|
63
|
+
const HELP_MODE_FILE = 'file';
|
|
64
|
+
/** HELP 支的复用窗口(毫秒)。#245:缺省**一天**(共用件 `HELP_REUSE_DEFAULT_HOURS`)——24 小时内反复读
|
|
65
|
+
* 同一份 HELP 产物只留一份、不再新建;`--params` 的 `reuseHours` 可改(`0`=每次都落新的)。
|
|
66
|
+
* 换算与校验都在共用件,坏参抛 `RangeError` ⇒ 用 `helpReuseWindowOf` 翻成出口的「参数错」那一档
|
|
67
|
+
* (exit 2),与其余四家同档:坏参绝不静默当 0。 */
|
|
68
|
+
const helpReuseWindow = helpReuseWindowOf((m) => fail(2, m));
|
|
69
|
+
/** 缺省交付的落点意图:`<库目录>/<memo_html>/〈主体〉`(目录名取自 `../help/manifest.js` 的
|
|
70
|
+
* `helpHtmlDirName()`,主体是那两个回常量的 `helpFileStem()`/`lookupFileStem()`)。 */
|
|
71
|
+
function landingOf(dbPath, stem) {
|
|
72
|
+
return { dir: join(resolve(dbPath), helpHtmlDirName()), stem };
|
|
73
|
+
}
|
|
74
|
+
/** 交付一次 HELP 产物(本包**唯一落盘点**):
|
|
75
|
+
* - `explicit`(`--html <路径>`,用户逐字指定)→ **覆盖写**(共用件 `file` + `onExists:'overwrite'`:逐字落点、
|
|
76
|
+
* 不带时间戳、不递补),与该参数的既有口径一致;**不吃复用窗口**(逐字落点=说哪落哪);
|
|
77
|
+
* - `landing`(本次产物按通式算出的落点意图)→ **独占创建 + 同秒递补**(共用件缺省 `succession` + `stem`);
|
|
78
|
+
* 带 `window`(#245)时改为**窗口内复用**(共用件 `{reuse:{byAge}}`):已有那份不超龄就返回它、不新建;
|
|
79
|
+
* - 两者都给时 `explicit` 优先(用户指定胜过默认落点)。
|
|
80
|
+
* 命名、独占、回执全在共用件里;本函数只表态「这次落哪个」。写不进去**不静默降级**:共用件的
|
|
81
|
+
* `code`(`EEXIST`/`EINVAL`/`EIO`/`ENOTDIR`…)原样穿过,由 main 归到 exit 5(目的地是「明确拿到文件」)。
|
|
24
82
|
*
|
|
25
|
-
*
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
83
|
+
* ⚠️ **缺省支那句不许传 `onExists`**:共用件的缺省是 `'succession'`(通式名 + 同秒 `_N` 递补)。
|
|
84
|
+
* 一旦显式传成 `'overwrite'`,产物就退化成固定名 `备忘录_HELP.html`、同秒连跑互相覆盖——
|
|
85
|
+
* `#230` 的 ①③④ 会当场红(本票已用这一步做过变异自证)。 */
|
|
86
|
+
function deliverMemoHtml(input) {
|
|
87
|
+
if (input.explicit !== undefined && input.explicit.length > 0) {
|
|
88
|
+
const abs = resolve(input.explicit);
|
|
89
|
+
return saveHtmlFile({ dir: dirname(abs), file: basename(abs), html: input.html, onExists: 'overwrite' });
|
|
90
|
+
}
|
|
91
|
+
if (input.landing === undefined) {
|
|
92
|
+
throw new Error('[skill-memo-ilife] deliverMemoHtml 缺落点(`explicit` 与 `landing` 至少给一个)');
|
|
93
|
+
}
|
|
94
|
+
return saveHtmlFile({
|
|
95
|
+
dir: input.landing.dir,
|
|
96
|
+
stem: input.landing.stem,
|
|
97
|
+
html: input.html,
|
|
98
|
+
...(input.window === undefined ? {} : { onExists: { reuse: { byAge: input.window } } }),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/** 初始化状态:memo 老库文件存在=已初始化(老 `_help_initialized`,新仓直连老库)。只 `stat`、
|
|
102
|
+
* 不建文件;判定本身异常 ⇒ `false`=横幅照显(fail-open:误显只多一条提示,误藏会让新用户找不到入口)。 */
|
|
103
|
+
function helpInitialized(dbPath) {
|
|
104
|
+
try {
|
|
105
|
+
return existsSync(join(dbPath, dbFilename()));
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/** 速查支的 `list` 载荷:一行一场景主名(短语/key/形状/调用形/一句话),从各域路由声明的生成物派生。 */
|
|
112
|
+
function buildLookupItems() {
|
|
113
|
+
return buildHelpLookup().map((h) => ({
|
|
114
|
+
id: h.phrase,
|
|
115
|
+
title: h.cli,
|
|
116
|
+
category: h.key,
|
|
117
|
+
shape: h.shape,
|
|
118
|
+
desc: h.desc,
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
function dispatchHelp(params, dbPath) {
|
|
122
|
+
const now = new Date();
|
|
123
|
+
const mode = params.mode === undefined ? undefined : String(params.mode);
|
|
124
|
+
const q = params.q === undefined ? undefined : String(params.q);
|
|
125
|
+
const initialized = helpInitialized(dbPath);
|
|
126
|
+
if (mode !== undefined && q !== undefined)
|
|
127
|
+
fail(2, '参数 q 与 mode 互斥:q=现找,mode=速查表产物');
|
|
128
|
+
if (mode !== undefined && mode !== 'lookup')
|
|
129
|
+
fail(2, 'mode 非法(' + mode + '):本键只认 lookup');
|
|
130
|
+
// #245:两种 HELP 产物(缺省 HELP 文件/`mode:"lookup"` 速查表)都吃复用窗口——缺省一天内只留一份。
|
|
131
|
+
// `q`(现找)那支不落盘,自然不吃;`--html` 逐字落点那支由 `deliverMemoHtml` 另走覆盖写。
|
|
132
|
+
const window = helpReuseWindow(params);
|
|
133
|
+
if (q !== undefined) {
|
|
134
|
+
const items = buildLookupItems().filter((it) => q.includes(String(it.id)));
|
|
135
|
+
return { data: { items, total: items.length, mode: 'lookup', query: q } };
|
|
136
|
+
}
|
|
137
|
+
if (mode === 'lookup') {
|
|
138
|
+
const items = buildLookupItems();
|
|
139
|
+
return {
|
|
140
|
+
data: { items, total: items.length, mode: 'lookup' },
|
|
141
|
+
deliver: { landing: landingOf(dbPath, lookupFileStem()), window },
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
const data = buildMemoHelpFileData(now, { initialized });
|
|
145
|
+
if (String(data.version) !== buildHelpSceneIndex().version) {
|
|
146
|
+
fail(5, 'HELP 世代不一致:载荷 ' + data.version + ' ≠ 资产 ' + buildHelpSceneIndex().version);
|
|
147
|
+
}
|
|
148
|
+
const html = renderMemoHelpHtml(data);
|
|
149
|
+
assertHtmlSize(html);
|
|
150
|
+
// 索引载荷(`list` 形):一行一域,计数全派生;`memo.help.lookup`=HELP 文件的交付索引。
|
|
151
|
+
return {
|
|
152
|
+
data: { ...buildHelpSceneIndex(), mode: HELP_MODE_FILE },
|
|
153
|
+
deliver: { html, landing: landingOf(dbPath, helpFileStem()), window },
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function ok(data) { return { data, exit: 0 }; }
|
|
157
|
+
function dispatch(key, params, db) {
|
|
158
|
+
// #855:**分派只认登记表**——一条命令的声明住它自己的能力目录(`src/<域>/commands.ts`),
|
|
159
|
+
// 生成物 `registry.ts` 把各域声明汇成这张查表,命中即调该域的运行件。
|
|
160
|
+
// 加/改命令改的是自己域里的声明;**本件不再认键**(不留 `case`——`test/cmd-registry-855.test.mjs` 守着这点)。
|
|
161
|
+
const spec = REGISTRY[key];
|
|
162
|
+
if (spec !== undefined)
|
|
163
|
+
return spec.kind === 'pre-open' ? spec.run(params) : spec.run(params, db);
|
|
164
|
+
// 下面两条是**内部断言**(不是功能):走到这里说明 `main` 的开库前分派被改坏了。
|
|
165
|
+
// 照 skill-bill/src/cli/cmd_read.ts:449-451 的同一道断言——防的是「改回无条件开库」这个静默回退。
|
|
166
|
+
if (key === 'memo.help.lookup') {
|
|
167
|
+
fail(1, '内部错误:memo.help.lookup 须走 dispatchHelp(开库之前)');
|
|
168
|
+
return ok(null);
|
|
169
|
+
}
|
|
170
|
+
if (key === 'memo.init') {
|
|
171
|
+
fail(1, '内部错误:memo.init 须走开库前分派(库不存在时也要能跑)');
|
|
172
|
+
return ok(null);
|
|
173
|
+
}
|
|
174
|
+
fail(3, '未知 memo key:' + key);
|
|
175
|
+
return ok(null);
|
|
176
|
+
}
|
|
177
|
+
function parseArgs(a) {
|
|
178
|
+
const o = { key: a[0], params: undefined, html: undefined, timeout: DEFAULT_TIMEOUT_MS };
|
|
179
|
+
for (let i = 1; i < a.length; i++) {
|
|
180
|
+
if (a[i] === '--params' && i + 1 < a.length)
|
|
181
|
+
o.params = a[++i];
|
|
182
|
+
else if (a[i] === '--html' && i + 1 < a.length)
|
|
183
|
+
o.html = a[++i];
|
|
184
|
+
else if (a[i] === '--timeout' && i + 1 < a.length) {
|
|
185
|
+
o.timeout = Number(a[++i]);
|
|
186
|
+
if (!Number.isFinite(o.timeout) || o.timeout <= 0)
|
|
187
|
+
fail(2, '--timeout 须为正数毫秒');
|
|
188
|
+
}
|
|
189
|
+
else
|
|
190
|
+
fail(2, '未知参数:' + a[i]);
|
|
191
|
+
}
|
|
192
|
+
return o;
|
|
34
193
|
}
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
194
|
+
async function main() {
|
|
195
|
+
const o = parseArgs(process.argv.slice(2));
|
|
196
|
+
if (!o.key)
|
|
197
|
+
fail(2, '用法:memo-cmd-read <memo.key> [--params JSON对象] [--html 输出路径] [--timeout 毫秒]');
|
|
198
|
+
let params = {};
|
|
199
|
+
if (o.params !== undefined) {
|
|
200
|
+
try {
|
|
201
|
+
params = JSON.parse(o.params);
|
|
202
|
+
}
|
|
203
|
+
catch (e) {
|
|
204
|
+
fail(2, '--params 须为 JSON');
|
|
205
|
+
}
|
|
206
|
+
if (typeof params !== 'object' || params === null || Array.isArray(params))
|
|
207
|
+
fail(2, '--params 须为 JSON 对象');
|
|
208
|
+
}
|
|
209
|
+
// #695:三个配置 key(`memo.config.read/write/reset`)在库目录预检与形状表之前拦下——
|
|
210
|
+
// 读写配置不该要求库已配,它们也不进 `MEMO_KEY_SHAPES`(不是唤醒词命令,见 `src/cli/config.ts`)。
|
|
211
|
+
if (isConfigKey(o.key)) {
|
|
212
|
+
process.stdout.write(runConfigKey(o.key, params) + '\n');
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
// #706 · 配置体检(`memo.config.check`):同样是设置页专用的只读命令,同样在预检之前拦下——
|
|
216
|
+
// 它要报的正是「库在哪、通不通」,不能先要求库目录已配。只读:不建目录、不写文件、不落默认配置。
|
|
217
|
+
if (isHealthCheckKey(o.key)) {
|
|
218
|
+
process.stdout.write(runHealthCheckKey(o.key) + '\n');
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
preflight();
|
|
222
|
+
// 读配置算库目录:配置件的报错本身就是人话(带行号与文件名),归「预检」那一档原样交回
|
|
223
|
+
// (不能让它裸抛——那会吐一整段 node 崩溃栈,用户看不到「该在哪配」)。
|
|
224
|
+
let dbPath = '';
|
|
225
|
+
try {
|
|
226
|
+
dbPath = resolveDbDir();
|
|
227
|
+
}
|
|
228
|
+
catch (e) {
|
|
229
|
+
fail(1, e instanceof Error ? e.message : String(e));
|
|
230
|
+
}
|
|
231
|
+
let shape = null;
|
|
232
|
+
try {
|
|
233
|
+
shape = memoShapeFor(o.key);
|
|
234
|
+
}
|
|
235
|
+
catch (e) {
|
|
236
|
+
fail(3, e.message);
|
|
237
|
+
}
|
|
238
|
+
void shape;
|
|
239
|
+
const timer = setTimeout(() => { toast('cmd_read 超时 terminate(' + o.timeout + 'ms),已终止取数'); process.exit(4); }, o.timeout);
|
|
240
|
+
timer.unref();
|
|
241
|
+
let env = null;
|
|
242
|
+
let delivery;
|
|
243
|
+
// #661:合成写「最终没达成」时回执照打、退出码在写完回执之后落实(分字段是回执的本分,不能因为非 0 就吞掉)。
|
|
244
|
+
let writeExit = 0;
|
|
245
|
+
try {
|
|
246
|
+
// #229:`memo.help.lookup` 在**开库之前**分派(只读页不建库);其余键照旧走 dispatch。
|
|
247
|
+
// #665:开的是老库文件(直连,不建库);用完即关,失败也关。
|
|
248
|
+
// #855:开库前分派统一走登记表——`pre-open` 类的命令(今天只有 `memo.init`,声明住 `src/init/commands.ts`)
|
|
249
|
+
// 在开库之前调,库不存在时也能跑;`memo.help.lookup` 的交付装配仍走框架位 `dispatchHelp`(HELP 面,不进域门)。
|
|
250
|
+
const help = o.key === 'memo.help.lookup' ? dispatchHelp(params, dbPath) : null;
|
|
251
|
+
const pre = o.key === 'memo.help.lookup' ? undefined : REGISTRY[o.key];
|
|
252
|
+
let db = null;
|
|
253
|
+
let out;
|
|
254
|
+
try {
|
|
255
|
+
if (help !== null) {
|
|
256
|
+
out = { data: help.data, exit: 0 };
|
|
257
|
+
}
|
|
258
|
+
else if (pre !== undefined && pre.kind === 'pre-open') {
|
|
259
|
+
out = pre.run(params);
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
db = openMemoDb(dbPath);
|
|
263
|
+
out = dispatch(o.key, params, db);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
finally {
|
|
267
|
+
if (db) {
|
|
268
|
+
try {
|
|
269
|
+
closeMemoDb(db);
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
// 关库失败不掩盖主流程结果。
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
writeExit = out.exit;
|
|
277
|
+
env = buildMemoEnvelope(o.key, out.data);
|
|
278
|
+
const built = env;
|
|
279
|
+
// B4 既有语义:`--html <路径>` 逐字写用户给的路径,内容仍是本包的 envelope 片段(`renderEnvelopeHtml`)。
|
|
280
|
+
const sectionHtml = () => {
|
|
281
|
+
const html = renderEnvelopeHtml(built);
|
|
282
|
+
assertHtmlSize(html);
|
|
283
|
+
return html;
|
|
284
|
+
};
|
|
285
|
+
if (help?.deliver !== undefined) {
|
|
286
|
+
// 本键的产物:缺省=HELP 全壳页(自带 html);`mode:"lookup"`=速查表分节页(由 envelope 渲染)。
|
|
287
|
+
const html = help.deliver.html ?? sectionHtml();
|
|
288
|
+
if (help.deliver.html !== undefined)
|
|
289
|
+
assertHtmlSize(html);
|
|
290
|
+
delivery = deliverMemoHtml({ explicit: o.html, landing: help.deliver.landing, html, window: help.deliver.window });
|
|
291
|
+
}
|
|
292
|
+
else if (out.deliver !== undefined) {
|
|
293
|
+
// #665 整页交付:缺省落 `memo_html/<页名>.html`(独占递补),显式 `--html` 逐字覆盖写。
|
|
294
|
+
delivery = deliverMemoHtml({ explicit: o.html, landing: landingOf(dbPath, out.deliver.stem), html: out.deliver.html });
|
|
295
|
+
}
|
|
296
|
+
else if (o.html) {
|
|
297
|
+
delivery = deliverMemoHtml({ explicit: o.html, html: sectionHtml() });
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
catch (e) {
|
|
301
|
+
// #760:飞书相关命令缺 CLI/未登录的失败回执带上安装指引(与面板「复制安装指引」按钮同一内容)。
|
|
302
|
+
if (e instanceof MemoFetchError && e.code.startsWith('LARK_')) {
|
|
303
|
+
fail(4, '取数失败:' + e.message + '。' + LARK_WEBSITE_LINE + '完整安装指引(含复制给 AI 的 prompt)见 memo.config.read 回执的 lark.prompt。');
|
|
304
|
+
}
|
|
305
|
+
if (e instanceof MemoFetchError)
|
|
306
|
+
fail(4, '取数失败:' + e.message);
|
|
307
|
+
if (e instanceof MemoPolicyError)
|
|
308
|
+
fail(2, '口径失败:' + e.message);
|
|
309
|
+
if (e instanceof MemoRenderError)
|
|
310
|
+
fail(5, '渲染失败:' + e.message);
|
|
311
|
+
// 落盘错误:`EACCES`/`ENOTDIR`/`ENOSPC`… 一律 exit 5(不静默当成功、不换形态降级)。
|
|
312
|
+
if (e?.code && /^E[A-Z]+$/.test(String(e.code))) {
|
|
313
|
+
fail(5, '落盘失败:' + (e.message || String(e)));
|
|
314
|
+
}
|
|
315
|
+
// 配置件(`base-link-core`)的报错本身就是人话(带行号与文件名):归「预检」那一档原样交回。
|
|
316
|
+
if (/(配置文件|配置项|测试缺隔离)/.test(e.message ?? ''))
|
|
317
|
+
fail(1, e.message);
|
|
318
|
+
fail(4, '未知失败:' + (e.message || String(e)));
|
|
319
|
+
}
|
|
320
|
+
finally {
|
|
321
|
+
clearTimeout(timer);
|
|
322
|
+
}
|
|
323
|
+
// #83/#144 口径的顶层追加:`delivery{mode,path,bytes}` 只追加,envelope 既有五字段一字不改、序不变。
|
|
324
|
+
process.stdout.write(JSON.stringify(delivery ? { ...env, delivery } : env) + '\n');
|
|
325
|
+
// #661:本地那一侧成了、远端那一侧没成 ⇒ 回执已分字段写明,退出码仍要如实反映「这一趟没达成」(契约 A3/A6)。
|
|
326
|
+
if (writeExit !== 0) {
|
|
327
|
+
console.error('ERR ' + writeExit + ': 合成写没达成(本地侧已落,远端侧见回执 remote 那一格)');
|
|
328
|
+
process.exit(writeExit);
|
|
329
|
+
}
|
|
67
330
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const isXunjiEntry = !key.includes('.');
|
|
71
|
-
|
|
72
|
-
const hit = table[key];
|
|
73
|
-
/** 按日期指定失败(替代退役的 `CALORIE_LAND_BATCH_FAIL_DATE`:逐天链里某天要红时用)。 */
|
|
74
|
-
const failDate = process.env.T676_LAND_FIXTURE_FAIL_DATE;
|
|
75
|
-
const dateArg = (() => {
|
|
76
|
-
const i = process.argv.indexOf('--date');
|
|
77
|
-
return i >= 0 ? process.argv[i + 1] : undefined;
|
|
78
|
-
})();
|
|
79
|
-
const forced = failDate !== undefined && failDate !== '' && failDate === dateArg;
|
|
80
|
-
const code = forced ? 4 : (typeof hit?.code === 'number' ? hit.code : 0);
|
|
81
|
-
const data = forced
|
|
82
|
-
? (DEFAULT_DATA[key] && key === 'push-plan'
|
|
83
|
-
? { ...DEFAULT_DATA['push-plan'], ok_count: 0, fail_count: 1, results: [{ session_label: '上肢', ok: false, verified: false, resp: { err: true, error_type: 'server', code: 500, attempts: 3 } }] }
|
|
84
|
-
: { ok: false, message: 'fixture 指定失败:' + String(dateArg) })
|
|
85
|
-
: (hit?.data !== undefined ? hit.data : (DEFAULT_DATA[key] ?? { ok: true, message: 'fixture 默认回执' }));
|
|
86
|
-
process.stdout.write((isXunjiEntry ? JSON.stringify(data, null, 2) : JSON.stringify({ data })) + '\n');
|
|
87
|
-
process.exit(code);
|
|
331
|
+
await main();
|
|
332
|
+
//# sourceMappingURL=cmd_read.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skill-memo-ilife",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.26",
|
|
4
4
|
"description": "备忘录SKILL一期:取数+口径+渲染+出口(M2/M3/M4/M5),依 link-core",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
"memo-cmd-read": "./dist/cli/cmd_read.js"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"base-link-core": "0.3.
|
|
22
|
-
"base-paint": "0.3.
|
|
21
|
+
"base-link-core": "0.3.26",
|
|
22
|
+
"base-paint": "0.3.26"
|
|
23
23
|
},
|
|
24
24
|
"engines": {
|
|
25
25
|
"node": ">=22.13"
|