skill-memo-ilife 0.2.0 → 0.2.1
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/SKILL.md +9 -7
- package/dist/cli/cmd_read.js +356 -52
- package/dist/cli/cmd_read.js.map +1 -1
- package/dist/cli/config.d.ts +16 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +84 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/health.d.ts +7 -0
- package/dist/cli/health.d.ts.map +1 -0
- package/dist/cli/health.js +32 -0
- package/dist/cli/health.js.map +1 -0
- package/dist/config.d.ts +47 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +65 -0
- package/dist/config.js.map +1 -0
- package/dist/fetch/auth.d.ts +13 -0
- package/dist/fetch/auth.d.ts.map +1 -0
- package/dist/fetch/auth.js +94 -0
- package/dist/fetch/auth.js.map +1 -0
- package/dist/fetch/batch.d.ts +20 -0
- package/dist/fetch/batch.d.ts.map +1 -0
- package/dist/fetch/batch.js +45 -0
- package/dist/fetch/batch.js.map +1 -0
- package/dist/fetch/db.d.ts +72 -21
- package/dist/fetch/db.d.ts.map +1 -1
- package/dist/fetch/db.js +197 -85
- package/dist/fetch/db.js.map +1 -1
- package/dist/fetch/errors.d.ts +3 -1
- package/dist/fetch/errors.d.ts.map +1 -1
- package/dist/fetch/errors.js.map +1 -1
- package/dist/fetch/feishu.d.ts +1 -1
- package/dist/fetch/feishu.d.ts.map +1 -1
- package/dist/fetch/feishu.js +15 -13
- package/dist/fetch/feishu.js.map +1 -1
- package/dist/fetch/index.d.ts +14 -2
- package/dist/fetch/index.d.ts.map +1 -1
- package/dist/fetch/index.js +10 -1
- package/dist/fetch/index.js.map +1 -1
- package/dist/fetch/paths.d.ts +9 -0
- package/dist/fetch/paths.d.ts.map +1 -0
- package/dist/fetch/paths.js +24 -0
- package/dist/fetch/paths.js.map +1 -0
- package/dist/fetch/reminders.d.ts +30 -0
- package/dist/fetch/reminders.d.ts.map +1 -0
- package/dist/fetch/reminders.js +284 -0
- package/dist/fetch/reminders.js.map +1 -0
- package/dist/fetch/sentinel.d.ts +28 -0
- package/dist/fetch/sentinel.d.ts.map +1 -0
- package/dist/fetch/sentinel.js +150 -0
- package/dist/fetch/sentinel.js.map +1 -0
- package/dist/fetch/taskWrite.d.ts +27 -0
- package/dist/fetch/taskWrite.d.ts.map +1 -0
- package/dist/fetch/taskWrite.js +70 -0
- package/dist/fetch/taskWrite.js.map +1 -0
- package/dist/fetch/tasks.d.ts +19 -0
- package/dist/fetch/tasks.d.ts.map +1 -0
- package/dist/fetch/tasks.js +77 -0
- package/dist/fetch/tasks.js.map +1 -0
- package/dist/health.d.ts +41 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +594 -0
- package/dist/health.js.map +1 -0
- package/dist/help/lookup.d.ts.map +1 -1
- package/dist/help/lookup.js +3 -2
- package/dist/help/lookup.js.map +1 -1
- package/dist/help/manifest.d.ts +7 -16
- package/dist/help/manifest.d.ts.map +1 -1
- package/dist/help/manifest.js +30 -10
- package/dist/help/manifest.js.map +1 -1
- package/dist/help/sceneData.js +1 -1
- package/dist/help/scenes/checkin.d.ts +1 -1
- package/dist/help/scenes/checkin.js +1 -1
- package/dist/help/scenes/init.d.ts +1 -1
- package/dist/help/scenes/init.js +1 -1
- package/dist/help/scenes/memo.d.ts +1 -1
- package/dist/help/scenes/memo.js +1 -1
- package/dist/help/scenes/mood.d.ts +1 -1
- package/dist/help/scenes/mood.js +1 -1
- package/dist/help/scenes/remind.d.ts +1 -1
- package/dist/help/scenes/remind.js +1 -1
- package/dist/help/scenes/search.d.ts +1 -1
- package/dist/help/scenes/search.js +1 -1
- package/dist/help/scenes/sync.d.ts +1 -1
- package/dist/help/scenes/sync.js +2 -2
- package/dist/help/scenes/sync.js.map +1 -1
- package/dist/help/scenes/wish.d.ts +1 -1
- package/dist/help/scenes/wish.js +2 -2
- package/dist/help/scenes/wish.js.map +1 -1
- package/dist/policy/category.d.ts.map +1 -1
- package/dist/policy/category.js +5 -2
- package/dist/policy/category.js.map +1 -1
- package/dist/policy/crud.d.ts +6 -2
- package/dist/policy/crud.d.ts.map +1 -1
- package/dist/policy/crud.js +14 -5
- package/dist/policy/crud.js.map +1 -1
- package/dist/policy/index.d.ts +4 -3
- package/dist/policy/index.d.ts.map +1 -1
- package/dist/policy/index.js +4 -2
- package/dist/policy/index.js.map +1 -1
- package/dist/policy/media.d.ts +15 -0
- package/dist/policy/media.d.ts.map +1 -0
- package/dist/policy/media.js +88 -0
- package/dist/policy/media.js.map +1 -0
- package/dist/policy/reminder.d.ts +8 -1
- package/dist/policy/reminder.d.ts.map +1 -1
- package/dist/policy/reminder.js +82 -4
- package/dist/policy/reminder.js.map +1 -1
- package/dist/policy/wakewords.d.ts +1 -1
- package/dist/policy/wakewords.d.ts.map +1 -1
- package/dist/policy/wakewords.js +1 -0
- package/dist/policy/wakewords.js.map +1 -1
- package/dist/render/envelope.d.ts.map +1 -1
- package/dist/render/envelope.js +3 -1
- package/dist/render/envelope.js.map +1 -1
- package/dist/render/errors.d.ts +1 -1
- package/dist/render/errors.d.ts.map +1 -1
- package/dist/render/errors.js +1 -1
- package/dist/render/errors.js.map +1 -1
- package/dist/render/html.d.ts +0 -3
- package/dist/render/html.d.ts.map +1 -1
- package/dist/render/html.js +2 -12
- package/dist/render/html.js.map +1 -1
- package/dist/render/index.d.ts +4 -1
- package/dist/render/index.d.ts.map +1 -1
- package/dist/render/index.js +5 -1
- package/dist/render/index.js.map +1 -1
- package/dist/render/pageAssets.d.ts +9 -0
- package/dist/render/pageAssets.d.ts.map +1 -0
- package/dist/render/pageAssets.js +73 -0
- package/dist/render/pageAssets.js.map +1 -0
- package/dist/render/pages.d.ts +54 -0
- package/dist/render/pages.d.ts.map +1 -0
- package/dist/render/pages.js +175 -0
- package/dist/render/pages.js.map +1 -0
- package/dist/wish/complete.d.ts +10 -0
- package/dist/wish/complete.d.ts.map +1 -0
- package/dist/wish/complete.js +72 -0
- package/dist/wish/complete.js.map +1 -0
- package/dist/wish/due.d.ts +16 -0
- package/dist/wish/due.d.ts.map +1 -0
- package/dist/wish/due.js +54 -0
- package/dist/wish/due.js.map +1 -0
- package/dist/wish/ensure.d.ts +60 -0
- package/dist/wish/ensure.d.ts.map +1 -0
- package/dist/wish/ensure.js +233 -0
- package/dist/wish/ensure.js.map +1 -0
- package/dist/wish/gate.d.ts +10 -0
- package/dist/wish/gate.d.ts.map +1 -0
- package/dist/wish/gate.js +14 -0
- package/dist/wish/gate.js.map +1 -0
- package/dist/wish/index.d.ts +11 -0
- package/dist/wish/index.d.ts.map +1 -0
- package/dist/wish/index.js +8 -0
- package/dist/wish/index.js.map +1 -0
- package/dist/wish/mark.d.ts +5 -0
- package/dist/wish/mark.d.ts.map +1 -0
- package/dist/wish/mark.js +15 -0
- package/dist/wish/mark.js.map +1 -0
- package/dist/wish/reconcile.d.ts +24 -0
- package/dist/wish/reconcile.d.ts.map +1 -0
- package/dist/wish/reconcile.js +159 -0
- package/dist/wish/reconcile.js.map +1 -0
- package/dist/wish/taskRemove.d.ts +4 -0
- package/dist/wish/taskRemove.d.ts.map +1 -0
- package/dist/wish/taskRemove.js +22 -0
- package/dist/wish/taskRemove.js.map +1 -0
- package/dist/wish/taskSync.d.ts +19 -0
- package/dist/wish/taskSync.d.ts.map +1 -0
- package/dist/wish/taskSync.js +50 -0
- package/dist/wish/taskSync.js.map +1 -0
- package/dist/wish/wizards.d.ts +38 -0
- package/dist/wish/wizards.d.ts.map +1 -0
- package/dist/wish/wizards.js +92 -0
- package/dist/wish/wizards.js.map +1 -0
- package/package.json +1 -1
- package/templates/change_category.html +2 -2
- package/templates/init_report.html +2 -2
- package/templates/memo_query.html +2 -2
- package/templates/sync_report.html +2 -2
- package/templates/wish_complete.html +2 -2
- package/templates/wish_plan.html +2 -2
- package/dist/help/memoOutput.d.ts +0 -29
- package/dist/help/memoOutput.d.ts.map +0 -1
- package/dist/help/memoOutput.js +0 -115
- package/dist/help/memoOutput.js.map +0 -1
package/SKILL.md
CHANGED
|
@@ -35,11 +35,12 @@ memo-cmd-read memo.help.lookup # 说「备忘录 HELP」(
|
|
|
35
35
|
| 看备忘 | memo.detail | detail | `memo-cmd-read memo.detail --params '{"id":"<id>"}'` |
|
|
36
36
|
| 看提醒 | memo.remind | list | `memo-cmd-read memo.remind` |
|
|
37
37
|
| 查提醒 | memo.remind | list | `memo-cmd-read memo.remind` |
|
|
38
|
-
| 设提醒 | memo.create | receipt | `memo-cmd-read memo.create --params '{"remindAt":"2026-10-01"}'` |
|
|
39
|
-
| 记提醒 | memo.create | receipt | `memo-cmd-read memo.create --params '{"remindAt":"2026-10-01"}'` |
|
|
38
|
+
| 设提醒 | memo.create | receipt | `memo-cmd-read memo.create --params '{"remindAt":"2026-10-01 09:00"}'` |
|
|
39
|
+
| 记提醒 | memo.create | receipt | `memo-cmd-read memo.create --params '{"remindAt":"2026-10-01 09:00"}'` |
|
|
40
40
|
| 废弃提醒 | memo.remove | receipt | `memo-cmd-read memo.remove --params '{"mode":"abandon"}'` |
|
|
41
41
|
| 完成心愿 | memo.update | receipt | `memo-cmd-read memo.update --params '{"done":true}'` |
|
|
42
42
|
| 心愿排期 | memo.wish | list | `memo-cmd-read memo.wish` |
|
|
43
|
+
| 飞书授权 | memo.auth | receipt | `memo-cmd-read memo.auth` |
|
|
43
44
|
| 记一条 | memo.create | receipt | `memo-cmd-read memo.create` |
|
|
44
45
|
| 添加笔记 | memo.create | receipt | `memo-cmd-read memo.create` |
|
|
45
46
|
| 记心愿 | memo.create | receipt | `memo-cmd-read memo.create --params '{"category":"心愿"}'` |
|
|
@@ -55,21 +56,22 @@ memo-cmd-read memo.help.lookup # 说「备忘录 HELP」(
|
|
|
55
56
|
| 删打卡 | memo.update | receipt | `memo-cmd-read memo.update --params '{"category":"打卡","id":"<id>"}'` |
|
|
56
57
|
| 删情绪日记 | memo.update | receipt | `memo-cmd-read memo.update --params '{"category":"情绪日记","id":"<id>"}'` |
|
|
57
58
|
|
|
58
|
-
相关场景:memo.batch、memo.create、memo.detail、memo.help.lookup、memo.remind、memo.remove、memo.search、memo.stats、memo.sync、memo.update、memo.wish(
|
|
59
|
+
相关场景:memo.auth、memo.batch、memo.create、memo.detail、memo.help.lookup、memo.remind、memo.remove、memo.search、memo.stats、memo.sync、memo.update、memo.wish(12 联动,key 字符串 P8 落表时冻结)。
|
|
59
60
|
<!-- HELP-AUTO-END -->
|
|
60
61
|
|
|
61
62
|
## HELP 交付(说「备忘录 HELP」走这里,不分大小写)
|
|
62
63
|
|
|
63
|
-
- **缺省就是交付物**:`memo-cmd-read memo.help.lookup` **原样调用**即落一份能打开的 HELP
|
|
64
|
+
- **缺省就是交付物**:`memo-cmd-read memo.help.lookup` **原样调用**即落一份能打开的 HELP 文件——`<库目录>/<产物目录>/备忘录_HELP_<YYYYMMDD_HHMMSS>.html`(`<库目录>`=配置文件 `~/.ilife/memo.yaml` 的 `db.dir`,空串=数据目录 `~/.ilife/data/`;`<产物目录>`=同文件的 `html.dir`,默认 `memo_html`,**扁平一段、不加 `help/` 一层**;8 域/13 二级组/30 场景,走仓内通用 help 模板)。stdout 的 `delivery.path` 是**绝对路径**、`delivery.bytes` 是文件字节数;回话就把这个路径给用户(回执即真相)。
|
|
64
65
|
**完成标准**:`delivery.path` 指的那个文件真的存在、大小=`delivery.bytes`、退出码 0——三条都核过才算交付,核不过就照实说失败。
|
|
65
|
-
-
|
|
66
|
+
- **反复读不再涨目录(#245)**:同一主体**一天内只留一份**——24 小时内再读就**复用已有那份**(不新建、不改写;回执给的就是它)。要别的窗口给 `--params '{"reuseHours":3}'`(小时);要**每次都要一份最新的**给 `{"reuseHours":0}`。窗口内若已有一份、而你刚改过 HELP 内容,那份旧产物**不会被自动刷新**(窗口语义如此)——真要新的就带 `reuseHours:0`。
|
|
67
|
+
- **要速查表才加参数**:`--params '{"mode":"lookup"}'` 落同目录 `备忘录_速查表_<YYYYMMDD_HHMMSS>.html`(28 条唤醒词一行一条:唤醒词/命令/形状/调用形/一句话)。与 HELP 文件**分名**,两份产物不撞车;这一支同样吃上面的复用窗口。
|
|
66
68
|
- **要现找才加参数**:`--params '{"q":"帮我搜备忘"}'` 只回命中条目(JSON),**不落盘**;要落盘再给 `--html`。`q` 与 `mode` 互斥,`mode` 只认 `lookup`,违反即退出码 2。
|
|
67
69
|
- **`--html <路径>`=显式落点**:逐字用你给的路径、缺父目录自动建、**覆盖写**(不参与同一秒 `_N` 递补)。给 `--html` 时缺省那支落整页 HELP,`q`/`mode:"lookup"` 那两支落该次的分节页。
|
|
68
|
-
- **边界**:看 HELP
|
|
70
|
+
- **边界**:看 HELP **不开库、不建库**——跑完数据目录里仍没有 `memo.db`;这条命令只交页面与索引,不读也不写笔记内容。
|
|
69
71
|
- **与上一节的关系**:正文「联动速查」块是**构建期注入的索引**(`scripts/build-help.mjs` 从唤醒词表注入 SKILL.md,勿手改),给 AI 按唤醒词找命令;**HELP 文件是缺省交付物**,给用户打开看。两者同源(同一张唤醒词表)、不是一份东西;显式 `mode:"lookup"` 落的那份是索引的页面版(`备忘录_速查表`),也不是 HELP 文件。
|
|
70
72
|
- **入口只认一条**:「备忘录 HELP」(不分大小写,用户 2026-09-12 裁定);其余唤醒词是场景别名,住上一节索引块,不另立 HELP 入口。
|
|
71
73
|
|
|
72
74
|
## 环境与出 scope
|
|
73
75
|
|
|
74
|
-
-
|
|
76
|
+
- 路径类取值一律读配置文件 `~/.ilife/memo.yaml`(**配置文件是唯一真相,环境变量不参与配置**):库目录=`db.dir`(空串=数据目录 `~/.ilife/data/`,首次读时自动建)、库文件名=`db.name`(默认 `memo.db`)、产物目录=`html.dir`(默认 `memo_html`)、附件目录=`media.dir`(默认 `media`)、飞书 CLI 路径=`lark.cliPath`(**空串=没有显式值,走本机自动探测**)、飞书扫码目录=`lark.qrDir`(空串=系统临时目录下的 `memo_feishu_qr`)、HELP 与速查表的主体名=`files.help`/`files.lookup`;`ILIFE_CONFIG_DIR` 设定且非空即整体接管配置目录。飞书同步另须 lark-cli 四门全绿。取值面与环境项见 docs/env.md。
|
|
75
77
|
- 出 scope:定时任务、面板、本技能外联动;Python 老家只读对照。
|
package/dist/cli/cmd_read.js
CHANGED
|
@@ -3,14 +3,22 @@
|
|
|
3
3
|
// 退出码对齐 skilllink 冻结:0 ok;1 预检;2 用法/参数;3 key;4 取数/超时;5 envelope/渲染/落盘。
|
|
4
4
|
// stdout 纯净:成功只打 envelope JSON 一行。
|
|
5
5
|
import { existsSync } from 'node:fs';
|
|
6
|
-
import { join } from 'node:path';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
6
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
7
|
+
import { saveHtmlFile, helpReuseWindowOf } from 'base-paint/save-html';
|
|
8
|
+
import { openMemoDb, closeMemoDb, listNotes, getNote, searchNotes, abandonReminder, checkDueReminders, listCompletedReminders, listReminderRows, countReminderRowsOfNote, collectBatchItems, countNotesByCategory, applyBatchCategory, authInit, authQr, authPoll, authStatus, runSentinel, MemoFetchError, } from '../fetch/index.js';
|
|
9
|
+
import { normalizeTop, normalizeSub, needId, normalizeMediaPath, crudCreate, crudUpdate, crudRemove } from '../policy/index.js';
|
|
10
|
+
// #661:心愿类的对外面——记/改/删/批量排期四条写命令与反向对账都经这一个门(`src/wish/index.ts`)。
|
|
11
|
+
// #665:完成心愿走原子转换(`completeWish`,老 `complete-wish`);排期/完成向导收集走 `wizards`。
|
|
12
|
+
import { dueForCategory, dueMatches, ensureWish, updateWish, removeWish, setWishDue, reconcileWishes, completeWish, planWizard, completeWizard } from '../wish/index.js';
|
|
13
|
+
import { memoShapeFor, buildMemoEnvelope, renderEnvelopeHtml, assertHtmlSize, fillMemoPage, pageEnvelope, wishPlanSnapshot, wishCompleteSnapshot, changeCategorySnapshot, syncSnapshot, MemoRenderError } from '../render/index.js';
|
|
10
14
|
import { buildMemoHelpFileData, renderMemoHelpHtml } from '../help/helpFile.js';
|
|
11
15
|
import { buildHelpSceneIndex } from '../help/sceneData.js';
|
|
12
16
|
import { buildHelpLookup } from '../help/index.js';
|
|
13
|
-
import {
|
|
17
|
+
import { helpHtmlDirName, helpFileStem, lookupFileStem } from '../help/manifest.js';
|
|
18
|
+
import { resolveDbDir, dbFilename } from '../fetch/paths.js';
|
|
19
|
+
import { isConfigKey, runConfigKey } from './config.js';
|
|
20
|
+
// #706 · 配置体检:设置页专用的一条只读命令,同走「进分派层之前拦下」这条口(判据住 src/health.ts)。
|
|
21
|
+
import { isHealthCheckKey, runHealthCheckKey } from './health.js';
|
|
14
22
|
import { MemoPolicyError } from '../fetch/errors.js';
|
|
15
23
|
const DEFAULT_TIMEOUT_MS = 30000;
|
|
16
24
|
function fail(code, msg) { console.error('ERR ' + code + ': ' + msg); process.exit(code); }
|
|
@@ -19,10 +27,6 @@ function preflight() {
|
|
|
19
27
|
const v = process.versions.node.split('.').map(Number);
|
|
20
28
|
if (!(v[0] > 22 || (v[0] === 22 && v[1] >= 13)))
|
|
21
29
|
fail(1, 'node 低于 22.13:' + process.versions.node);
|
|
22
|
-
const p = process.env.SKILLS_DB_PATH;
|
|
23
|
-
if (!p)
|
|
24
|
-
fail(1, 'SKILLS_DB_PATH 未设置(无默认值,必设)');
|
|
25
|
-
return p;
|
|
26
30
|
}
|
|
27
31
|
function needStr(params, name) {
|
|
28
32
|
const v = params[name];
|
|
@@ -36,15 +40,59 @@ function needStr(params, name) {
|
|
|
36
40
|
// (扁平、不加 `help/` 层:裁决 1),独占落盘 + **绝对路径**回执(`delivery` 顶层追加,序在既有五字段之后)。
|
|
37
41
|
// 显式 `mode:"lookup"` = 速查表分名文件(主体 `备忘录_速查表`,裁决 2:一个键两种产物就分两个名字)。
|
|
38
42
|
// 显式 `q` = 现找:只回命中(stdout),给 `--html <路径>` 才落盘(检索式问答不刷目录)。
|
|
43
|
+
// 显式 `reuseHours`(小时)= 复用窗口:`0`=每次都落新的;不给=**一天**(#245)——24 小时内反复读
|
|
44
|
+
// 同一份 HELP 产物只留一份、不再新建(判据=落盘名里的时间戳,不看 mtime)。两种 HELP 产物都吃窗口。
|
|
39
45
|
// 全程**不开库**:初始化判据=「**memo 库目录存在**」(票 6 V4:新库是目录 `<SKILLS_DB_PATH>/memo`,
|
|
40
46
|
// 不是老家的 `memo.db` 文件——本机实测 0 字节空壳 `memo.db` 在、真目录不在,老家口径当场判错),
|
|
41
47
|
// 且**只 stat、绝不建库**:免得「看帮助」把库目录 `mkdir` 出来。
|
|
48
|
+
//
|
|
49
|
+
// #240(欠债清偿):命名与落盘**不再自持**——时间戳、同秒 `_N` 递补、独占创建、写后回读字节数全在
|
|
50
|
+
// 共用件 `saveHtmlFile`(`base-paint/save-html`,唯一定义地 `packages/base-render/src/output/saveHtml.ts`)。
|
|
51
|
+
// 本文件只出**落点意图**(`{dir, stem}`,主体与目录名取自 `../help/manifest.js` 的三个值),
|
|
52
|
+
// 原先包内那份第 4 份同逻辑实现 `src/help/memoOutput.ts` 已删(裁决 3 挂的债,见 `#240`)。
|
|
42
53
|
const HELP_MODE_FILE = 'file';
|
|
43
|
-
/**
|
|
44
|
-
*
|
|
54
|
+
/** HELP 支的复用窗口(毫秒)。#245:缺省**一天**(共用件 `HELP_REUSE_DEFAULT_HOURS`)——24 小时内反复读
|
|
55
|
+
* 同一份 HELP 产物只留一份、不再新建;`--params` 的 `reuseHours` 可改(`0`=每次都落新的)。
|
|
56
|
+
* 换算与校验都在共用件,坏参抛 `RangeError` ⇒ 用 `helpReuseWindowOf` 翻成出口的「参数错」那一档
|
|
57
|
+
* (exit 2),与其余四家同档:坏参绝不静默当 0。 */
|
|
58
|
+
const helpReuseWindow = helpReuseWindowOf((m) => fail(2, m));
|
|
59
|
+
/** 缺省交付的落点意图:`<库目录>/<memo_html>/〈主体〉`(目录名与主体是备忘录自己的三个值,
|
|
60
|
+
* #695 起从配置文件取:`html.dir`/`files.help`/`files.lookup`,默认逐字等于老常量)。 */
|
|
61
|
+
function landingOf(dbPath, stem) {
|
|
62
|
+
return { dir: join(resolve(dbPath), helpHtmlDirName()), stem };
|
|
63
|
+
}
|
|
64
|
+
/** 交付一次 HELP 产物(本包**唯一落盘点**):
|
|
65
|
+
* - `explicit`(`--html <路径>`,用户逐字指定)→ **覆盖写**(共用件 `file` + `onExists:'overwrite'`:逐字落点、
|
|
66
|
+
* 不带时间戳、不递补),与该参数的既有口径一致;**不吃复用窗口**(逐字落点=说哪落哪);
|
|
67
|
+
* - `landing`(本次产物按通式算出的落点意图)→ **独占创建 + 同秒递补**(共用件缺省 `succession` + `stem`);
|
|
68
|
+
* 带 `window`(#245)时改为**窗口内复用**(共用件 `{reuse:{byAge}}`):已有那份不超龄就返回它、不新建;
|
|
69
|
+
* - 两者都给时 `explicit` 优先(用户指定胜过默认落点)。
|
|
70
|
+
* 命名、独占、回执全在共用件里;本函数只表态「这次落哪个」。写不进去**不静默降级**:共用件的
|
|
71
|
+
* `code`(`EEXIST`/`EINVAL`/`EIO`/`ENOTDIR`…)原样穿过,由 main 归到 exit 5(目的地是「明确拿到文件」)。
|
|
72
|
+
*
|
|
73
|
+
* ⚠️ **缺省支那句不许传 `onExists`**:共用件的缺省是 `'succession'`(通式名 + 同秒 `_N` 递补)。
|
|
74
|
+
* 一旦显式传成 `'overwrite'`,产物就退化成固定名 `备忘录_HELP.html`、同秒连跑互相覆盖——
|
|
75
|
+
* `#230` 的 ①③④ 会当场红(本票已用这一步做过变异自证)。 */
|
|
76
|
+
function deliverMemoHtml(input) {
|
|
77
|
+
if (input.explicit !== undefined && input.explicit.length > 0) {
|
|
78
|
+
const abs = resolve(input.explicit);
|
|
79
|
+
return saveHtmlFile({ dir: dirname(abs), file: basename(abs), html: input.html, onExists: 'overwrite' });
|
|
80
|
+
}
|
|
81
|
+
if (input.landing === undefined) {
|
|
82
|
+
throw new Error('[skill-memo-ilife] deliverMemoHtml 缺落点(`explicit` 与 `landing` 至少给一个)');
|
|
83
|
+
}
|
|
84
|
+
return saveHtmlFile({
|
|
85
|
+
dir: input.landing.dir,
|
|
86
|
+
stem: input.landing.stem,
|
|
87
|
+
html: input.html,
|
|
88
|
+
...(input.window === undefined ? {} : { onExists: { reuse: { byAge: input.window } } }),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/** 初始化状态:memo 老库文件存在=已初始化(老 `_help_initialized`,新仓直连老库)。只 `stat`、
|
|
92
|
+
* 不建文件;判定本身异常 ⇒ `false`=横幅照显(fail-open:误显只多一条提示,误藏会让新用户找不到入口)。 */
|
|
45
93
|
function helpInitialized(dbPath) {
|
|
46
94
|
try {
|
|
47
|
-
return existsSync(join(dbPath,
|
|
95
|
+
return existsSync(join(dbPath, dbFilename()));
|
|
48
96
|
}
|
|
49
97
|
catch {
|
|
50
98
|
return false;
|
|
@@ -69,6 +117,9 @@ function dispatchHelp(params, dbPath) {
|
|
|
69
117
|
fail(2, '参数 q 与 mode 互斥:q=现找,mode=速查表产物');
|
|
70
118
|
if (mode !== undefined && mode !== 'lookup')
|
|
71
119
|
fail(2, 'mode 非法(' + mode + '):本键只认 lookup');
|
|
120
|
+
// #245:两种 HELP 产物(缺省 HELP 文件/`mode:"lookup"` 速查表)都吃复用窗口——缺省一天内只留一份。
|
|
121
|
+
// `q`(现找)那支不落盘,自然不吃;`--html` 逐字落点那支由 `deliverMemoHtml` 另走覆盖写。
|
|
122
|
+
const window = helpReuseWindow(params);
|
|
72
123
|
if (q !== undefined) {
|
|
73
124
|
const items = buildLookupItems().filter((it) => q.includes(String(it.id)));
|
|
74
125
|
return { data: { items, total: items.length, mode: 'lookup', query: q } };
|
|
@@ -77,7 +128,7 @@ function dispatchHelp(params, dbPath) {
|
|
|
77
128
|
const items = buildLookupItems();
|
|
78
129
|
return {
|
|
79
130
|
data: { items, total: items.length, mode: 'lookup' },
|
|
80
|
-
deliver: {
|
|
131
|
+
deliver: { landing: landingOf(dbPath, lookupFileStem()), window },
|
|
81
132
|
};
|
|
82
133
|
}
|
|
83
134
|
const data = buildMemoHelpFileData(now, { initialized });
|
|
@@ -89,87 +140,284 @@ function dispatchHelp(params, dbPath) {
|
|
|
89
140
|
// 索引载荷(`list` 形):一行一域,计数全派生;`memo.help.lookup`=HELP 文件的交付索引。
|
|
90
141
|
return {
|
|
91
142
|
data: { ...buildHelpSceneIndex(), mode: HELP_MODE_FILE },
|
|
92
|
-
deliver: { html,
|
|
143
|
+
deliver: { html, landing: landingOf(dbPath, helpFileStem()), window },
|
|
93
144
|
};
|
|
94
145
|
}
|
|
95
|
-
|
|
146
|
+
function ok(data) { return { data, exit: 0 }; }
|
|
147
|
+
function asIds(value) {
|
|
148
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
149
|
+
fail(2, 'ids 须为非空数组');
|
|
150
|
+
return value.map((v) => needId(v, 'ids'));
|
|
151
|
+
}
|
|
152
|
+
const toRows = (xs) => xs.map((x) => ({ ...x }));
|
|
96
153
|
function dispatch(key, params, db) {
|
|
97
154
|
switch (key) {
|
|
98
155
|
case 'memo.search': {
|
|
99
156
|
const items = params.q !== undefined
|
|
100
157
|
? searchNotes(db, String(params.q), { category: params.category, sub: params.sub })
|
|
101
158
|
: listNotes(db).filter((n) => (params.category === undefined || n.category === params.category));
|
|
102
|
-
|
|
159
|
+
const hit = items.filter((n) => dueMatches(n, params));
|
|
160
|
+
return ok({ items: hit, total: hit.length });
|
|
103
161
|
}
|
|
104
|
-
case 'memo.detail': return { item: getNote(db,
|
|
162
|
+
case 'memo.detail': return ok({ item: getNote(db, needId(params.id, '详情')) });
|
|
105
163
|
case 'memo.create': {
|
|
106
164
|
const c = crudCreate(params);
|
|
107
165
|
const top = normalizeTop(params.category);
|
|
108
166
|
const sub = normalizeSub(params.sub);
|
|
109
|
-
const
|
|
110
|
-
const
|
|
111
|
-
|
|
167
|
+
const media = params.media !== undefined ? normalizeMediaPath(params.media) : null;
|
|
168
|
+
const r = ensureWish(db, {
|
|
169
|
+
title: c.title,
|
|
170
|
+
body: c.body,
|
|
171
|
+
category: top,
|
|
172
|
+
sub,
|
|
173
|
+
media,
|
|
174
|
+
remindAt: params.remindAt,
|
|
175
|
+
repeatType: params.repeatType,
|
|
176
|
+
repeatRule: params.repeatRule,
|
|
177
|
+
due: params.due,
|
|
178
|
+
});
|
|
179
|
+
return { data: r.receipt, exit: r.exit };
|
|
112
180
|
}
|
|
113
181
|
case 'memo.update': {
|
|
182
|
+
// 批量排期(老 `set-due`):一批 id + 一个排期日期(空值=清期),走心愿那条合成写。
|
|
183
|
+
if (params.ids !== undefined) {
|
|
184
|
+
const r = setWishDue(db, { ids: asIds(params.ids), due: params.due });
|
|
185
|
+
return { data: r.receipt, exit: r.exit };
|
|
186
|
+
}
|
|
114
187
|
const id = crudUpdate(params).id;
|
|
188
|
+
// 完成心愿走原子转换(老 `complete-wish`:删心愿 + 生成打卡;`content` 即打卡内容,缺省拷贝心愿原文)。
|
|
189
|
+
if (params.done === true) {
|
|
190
|
+
const r = completeWish(db, { id, content: params.content });
|
|
191
|
+
return { data: r.receipt, exit: r.exit };
|
|
192
|
+
}
|
|
193
|
+
if (params.done !== undefined)
|
|
194
|
+
fail(2, 'done 只认 true(完成心愿);改字段另给参数');
|
|
115
195
|
const patch = {};
|
|
116
196
|
if (params.title !== undefined || params.body !== undefined) {
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
if (
|
|
120
|
-
|
|
197
|
+
const t = typeof params.title === 'string' ? params.title.trim() : '';
|
|
198
|
+
const b = typeof params.body === 'string' ? params.body.trim() : '';
|
|
199
|
+
if (!t && !b)
|
|
200
|
+
fail(2, '正文不可改成空');
|
|
201
|
+
patch.content = b !== '' ? b : t !== '' ? t : getNote(db, id).content;
|
|
121
202
|
}
|
|
122
203
|
if (params.category !== undefined)
|
|
123
204
|
patch.category = normalizeTop(params.category);
|
|
124
205
|
if (params.sub !== undefined)
|
|
125
|
-
patch.
|
|
206
|
+
patch.sub_category = normalizeSub(params.sub);
|
|
207
|
+
if (params.media !== undefined)
|
|
208
|
+
patch.media_path = normalizeMediaPath(params.media);
|
|
209
|
+
if (params.reminderId !== undefined)
|
|
210
|
+
patch.reminder_id = needId(params.reminderId, '关联提醒');
|
|
211
|
+
// 老实现没有「改提醒时间」这一路:提醒时间定盘即不可改,错了废弃重建,大声失败不静默。
|
|
126
212
|
if (params.remindAt !== undefined)
|
|
127
|
-
|
|
128
|
-
if (params.
|
|
129
|
-
patch.
|
|
130
|
-
|
|
131
|
-
|
|
213
|
+
fail(2, '提醒时间不可改(废弃旧提醒、重建一条)');
|
|
214
|
+
if (params.due !== undefined)
|
|
215
|
+
patch.due = dueForCategory(patch.category ?? getNote(db, id).category, params.due);
|
|
216
|
+
if (Object.keys(patch).length === 0)
|
|
217
|
+
fail(2, '至少需要提供一个更新字段:content/category/sub/media/reminderId/due');
|
|
218
|
+
const r = updateWish(db, { id, patch });
|
|
219
|
+
return { data: r.receipt, exit: r.exit };
|
|
132
220
|
}
|
|
133
221
|
case 'memo.remove': {
|
|
222
|
+
// 废弃提醒(老 `dismiss`):按提醒 id 标 dismissed,笔记保留。
|
|
134
223
|
if (params.mode === 'abandon') {
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
return { ok: true, message: '
|
|
224
|
+
const rid = needId(params.id, '废弃提醒');
|
|
225
|
+
abandonReminder(db, rid);
|
|
226
|
+
return ok({ ok: true, message: '提醒已废弃(笔记保留):' + rid });
|
|
138
227
|
}
|
|
139
228
|
const r = crudRemove(params);
|
|
140
|
-
|
|
141
|
-
|
|
229
|
+
// 老 `delete`:有关联提醒必须显式级联(`--with-reminders`),否则大声失败;
|
|
230
|
+
// 有未触发提醒时的二次确认是交互动作,出口非交互,改显式参数一次讲清。
|
|
231
|
+
const related = countReminderRowsOfNote(db, r.id);
|
|
232
|
+
if (related > 0 && params.withReminders !== true) {
|
|
233
|
+
fail(2, '笔记 ' + r.id + ' 关联 ' + related + ' 个提醒,请加 withReminders:true 级联删除(提醒不会被自动删除)');
|
|
234
|
+
}
|
|
235
|
+
// #661 · C 口径:默认照老「远端标完成」,显式 `purge:true` 才连飞书任务一起删(两种语义用参数讲清)。
|
|
236
|
+
const w = removeWish(db, r.id, params.purge === true);
|
|
237
|
+
return { data: w.receipt, exit: w.exit };
|
|
142
238
|
}
|
|
143
239
|
case 'memo.remind': {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
240
|
+
// 到期判定(老 `due`):读+写 notified,定时壳不搬。
|
|
241
|
+
if (params.mode === 'due' || params.due === true) {
|
|
242
|
+
const items = checkDueReminders(db);
|
|
243
|
+
return ok({ items, total: items.length });
|
|
244
|
+
}
|
|
245
|
+
// 已完成视图(老 `completed`);`done:true` 是它的兼容写法。
|
|
246
|
+
if (params.mode === 'done' || params.done === true) {
|
|
247
|
+
const items = listCompletedReminders(db);
|
|
248
|
+
return ok({ items, total: items.length });
|
|
249
|
+
}
|
|
250
|
+
const status = params.status === undefined ? 'active' : String(params.status);
|
|
251
|
+
if (status !== 'active' && status !== 'dismissed')
|
|
252
|
+
fail(2, 'status 只认 active/dismissed');
|
|
253
|
+
const items = listReminderRows(db, status);
|
|
254
|
+
return ok({ items, total: items.length });
|
|
148
255
|
}
|
|
149
256
|
case 'memo.wish': {
|
|
150
|
-
|
|
151
|
-
|
|
257
|
+
// #665 向导:`wizard: plan` 出排期向导页(默认全勾选),`wizard: complete` 出完成向导页(默认不勾选);
|
|
258
|
+
// 不带即老形状(只回列表,#661 行为)。
|
|
259
|
+
if (params.wizard === 'plan' || params.wizard === 'complete') {
|
|
260
|
+
if (params.wizard === 'plan') {
|
|
261
|
+
const w = planWizard(db, { ids: params.ids, all: params.all, suggestDue: params.suggestDue });
|
|
262
|
+
const snap = wishPlanSnapshot(toRows(w.items), w.suggestDue, w.includeAll);
|
|
263
|
+
const message = '找到 ' + w.items.length + ' 个心愿' + (w.includeAll ? '(含已排期)' : '(仅未排期)');
|
|
264
|
+
const payload = pageEnvelope({
|
|
265
|
+
commandCn: '心愿排期', wakeWord: '心愿排期', sceneId: 'wish-batch-plan',
|
|
266
|
+
title: snap.title, summary: snap.summary, sections: snap.sections,
|
|
267
|
+
copyLog: {
|
|
268
|
+
thinking: '过程型向导 · 只读收集心愿列表,勾选+填排期后复制指令回 AI(HTML 不写库)',
|
|
269
|
+
data_structure: "notes 表(category='心愿')· id/content/category/sub_category/due/feishu_task_guid",
|
|
270
|
+
call_chain: 'memo.wish wizard:plan → render_wish_plan → 共享 filler',
|
|
271
|
+
exception: '无',
|
|
272
|
+
},
|
|
273
|
+
extra: { items: w.items, suggest_due: w.suggestDue, all: w.includeAll },
|
|
274
|
+
message,
|
|
275
|
+
});
|
|
276
|
+
return {
|
|
277
|
+
data: { items: w.items, total: w.items.length, suggestDue: w.suggestDue, all: w.includeAll },
|
|
278
|
+
exit: 0,
|
|
279
|
+
deliver: { html: fillMemoPage('wish_plan', payload), stem: '心愿排期向导' },
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
const w = completeWizard(db, { ids: params.ids, onlyOverdue: params.onlyOverdue, all: params.all, content: params.content });
|
|
283
|
+
const snap = wishCompleteSnapshot(toRows(w.items));
|
|
284
|
+
const message = '找到 ' + w.items.length + ' 个心愿' + (w.onlyOverdue ? '(仅未排期+已过期)' : '');
|
|
285
|
+
const payload = pageEnvelope({
|
|
286
|
+
commandCn: '心愿完成', wakeWord: '完成心愿', sceneId: 'wish-complete',
|
|
287
|
+
title: snap.title, summary: snap.summary, sections: snap.sections,
|
|
288
|
+
copyLog: {
|
|
289
|
+
thinking: '过程型向导 · 勾选+填打卡内容后复制指令回 AI(completeWish 原子转换)',
|
|
290
|
+
data_structure: "notes 表(category='心愿')· id/content/due/feishu_task_guid",
|
|
291
|
+
call_chain: 'memo.wish wizard:complete → render_wish_complete → 共享 filler',
|
|
292
|
+
exception: '无',
|
|
293
|
+
},
|
|
294
|
+
extra: { items: w.items, default_content: w.defaultContent, only_overdue: w.onlyOverdue },
|
|
295
|
+
message,
|
|
296
|
+
});
|
|
297
|
+
return {
|
|
298
|
+
data: { items: w.items, total: w.items.length, defaultContent: w.defaultContent, onlyOverdue: w.onlyOverdue },
|
|
299
|
+
exit: 0,
|
|
300
|
+
deliver: { html: fillMemoPage('wish_complete', payload), stem: '心愿完成向导' },
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
const items = listNotes(db).filter((n) => n.category === '心愿').filter((n) => dueMatches(n, params));
|
|
304
|
+
return ok({ items, total: items.length });
|
|
152
305
|
}
|
|
153
306
|
case 'memo.sync': {
|
|
154
|
-
|
|
155
|
-
|
|
307
|
+
// #661:反向对账三步(本地缺标识补建/远端完成→本地/远端改期→本地),回执带 11 项统计。
|
|
308
|
+
// #665:同步报告页随行(#661 遗留 HELP 承诺,出页归这一支)。
|
|
309
|
+
const r = reconcileWishes(db);
|
|
310
|
+
const snap = syncSnapshot(r.receipt);
|
|
311
|
+
const payload = pageEnvelope({
|
|
312
|
+
commandCn: '备忘录同步', wakeWord: '备忘录同步', sceneId: 'sync-from-feishu',
|
|
313
|
+
title: snap.title, summary: snap.summary, sections: snap.sections,
|
|
314
|
+
copyLog: {
|
|
315
|
+
thinking: '双向对账 · 飞书 done/due 反向同步到本机(只读扫描 + 有变更才写)',
|
|
316
|
+
data_structure: 'reconcile 11 项统计(backfilled/synced/due_*/skipped_*/errors)',
|
|
317
|
+
call_chain: 'memo.sync → reconcileWishes → render_sync_report → 共享 filler',
|
|
318
|
+
exception: r.receipt.errors.length ? r.receipt.errors.join('; ') : '无',
|
|
319
|
+
},
|
|
320
|
+
extra: { ...r.receipt },
|
|
321
|
+
message: r.receipt.message,
|
|
322
|
+
});
|
|
323
|
+
return { data: r.receipt, exit: r.exit, deliver: { html: fillMemoPage('sync_report', payload), stem: '同步报告' } };
|
|
324
|
+
}
|
|
325
|
+
case 'memo.batch': {
|
|
326
|
+
// #665 批量改分类:不带目标分类即收集(出向导页);带目标分类+ids 即执行。
|
|
327
|
+
const from = params.fromCategory !== undefined ? normalizeTop(params.fromCategory) : null;
|
|
328
|
+
const to = params.toCategory !== undefined ? normalizeTop(params.toCategory) : null;
|
|
329
|
+
if (from !== null && to !== null && from === to)
|
|
330
|
+
fail(2, '原分类与目标分类相同:' + from);
|
|
331
|
+
// 执行(老 `update-category` 逐条):目标分类+一批 id;只给目标分类不给 id 即收集预览。
|
|
332
|
+
if (params.ids !== undefined) {
|
|
333
|
+
if (to === null)
|
|
334
|
+
fail(2, '执行改分类须给 toCategory(只收集不执行时别给 ids)');
|
|
335
|
+
const r = applyBatchCategory(db, asIds(params.ids), to);
|
|
336
|
+
const doneAll = r.errors.length === 0;
|
|
337
|
+
return {
|
|
338
|
+
data: {
|
|
339
|
+
ok: doneAll,
|
|
340
|
+
message: '改分类完成:更新=' + r.updated + ',跳过=' + r.skipped,
|
|
341
|
+
updated: r.updated,
|
|
342
|
+
skipped: r.skipped,
|
|
343
|
+
errors: r.errors,
|
|
344
|
+
},
|
|
345
|
+
exit: doneAll ? 0 : 4,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
const items = collectBatchItems(db, from);
|
|
349
|
+
const snap = changeCategorySnapshot(toRows(items), from, to);
|
|
350
|
+
const message = "原分类 '" + (from ?? '<全部>') + "' 下 " + items.length + ' 条笔记';
|
|
351
|
+
const payload = pageEnvelope({
|
|
352
|
+
commandCn: '批量改分类', wakeWord: '备忘改分类', sceneId: 'batch-update-category',
|
|
353
|
+
title: snap.title, summary: snap.summary, sections: snap.sections,
|
|
354
|
+
copyLog: {
|
|
355
|
+
thinking: '过程型向导 · 勾选后复制改分类指令回 AI(只改顶层分类,sub_category 不动)',
|
|
356
|
+
data_structure: 'notes 表 · id/content/category/sub_category/media_path/due',
|
|
357
|
+
call_chain: 'memo.batch → render_change_category → 共享 filler',
|
|
358
|
+
exception: '无',
|
|
359
|
+
},
|
|
360
|
+
extra: { items, from_category: from, to_category: to, target_conflict_count: to ? countNotesByCategory(db, to) : 0 },
|
|
361
|
+
message,
|
|
362
|
+
});
|
|
363
|
+
return {
|
|
364
|
+
data: { ok: true, message, items, total: items.length, fromCategory: from, toCategory: to },
|
|
365
|
+
exit: 0,
|
|
366
|
+
deliver: { html: fillMemoPage('change_category', payload), stem: '批量改分类' },
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
case 'memo.auth': {
|
|
370
|
+
// #665 飞书授权引导(三步非阻塞,Q4 批新增 key):init/qr/poll+status 诊断。
|
|
371
|
+
// #666 自检 sentinel:step:'diag'(显式诊断,D-03 任务半场)。
|
|
372
|
+
const step = params.step === undefined ? 'status' : String(params.step);
|
|
373
|
+
if (step === 'init') {
|
|
374
|
+
const brand = params.brand === undefined ? 'feishu' : String(params.brand);
|
|
375
|
+
return ok({
|
|
376
|
+
ok: true,
|
|
377
|
+
message: '授权已发起:在浏览器打开链接或扫码,完成后调 step poll 续轮询',
|
|
378
|
+
step: 'init',
|
|
379
|
+
...authInit(brand),
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
if (step === 'qr') {
|
|
383
|
+
if (typeof params.url !== 'string' || params.url.length === 0)
|
|
384
|
+
fail(2, 'qr 须给 url(init 回的 verification_url)');
|
|
385
|
+
const qrPath = authQr(params.url, params.outDir === undefined ? undefined : String(params.outDir));
|
|
386
|
+
return ok({ ok: true, message: '二维码已生成', step: 'qr', qrPath });
|
|
387
|
+
}
|
|
388
|
+
if (step === 'poll') {
|
|
389
|
+
if (typeof params.deviceCode !== 'string' || params.deviceCode.length === 0) {
|
|
390
|
+
fail(2, 'poll 须给 deviceCode(init 回的 device_code,过期请重走 init)');
|
|
391
|
+
}
|
|
392
|
+
const domain = params.domain === undefined ? 'task' : String(params.domain);
|
|
393
|
+
return ok({ ok: true, message: '授权续轮询完成', step: 'poll', ...authPoll(params.deviceCode, domain) });
|
|
394
|
+
}
|
|
395
|
+
if (step === 'status') {
|
|
396
|
+
return ok({ ok: true, message: '授权状态', step: 'status', ...authStatus() });
|
|
397
|
+
}
|
|
398
|
+
if (step === 'diag') {
|
|
399
|
+
// #666 自检 sentinel(D-03 任务半场):显式才跑;默认四步不碰它,零写。
|
|
400
|
+
const r = runSentinel(params.dryRun === true ? { dryRun: true } : undefined);
|
|
401
|
+
return { data: r.receipt, exit: r.exit };
|
|
402
|
+
}
|
|
403
|
+
fail(2, 'step 只认 init/qr/poll/status/diag');
|
|
404
|
+
return ok(null);
|
|
156
405
|
}
|
|
157
|
-
case 'memo.batch': return { ok: true, message: '批量改分类向导须交互确认(M5 只登记意图)' };
|
|
158
406
|
case 'memo.stats': {
|
|
159
407
|
const all = listNotes(db);
|
|
160
408
|
const metrics = { count: all.length };
|
|
161
409
|
for (const n of all)
|
|
162
410
|
metrics['cat.' + n.category] = (metrics['cat.' + n.category] || 0) + 1;
|
|
163
|
-
return { metrics };
|
|
411
|
+
return ok({ metrics });
|
|
164
412
|
}
|
|
165
413
|
// #229:本键由 `dispatchHelp` 在**开库之前**处理(只读页不建库);走到这里说明 main 的路由被改坏了。
|
|
166
414
|
// 照 skill-bill/src/cli/cmd_read.ts:449-451 的同一道内部断言——防的是「改回无条件开库」这个静默回退。
|
|
167
415
|
case 'memo.help.lookup':
|
|
168
416
|
fail(1, '内部错误:memo.help.lookup 须走 dispatchHelp(开库之前)');
|
|
169
|
-
return null;
|
|
417
|
+
return ok(null);
|
|
170
418
|
default:
|
|
171
419
|
fail(3, '未知 memo key:' + key);
|
|
172
|
-
return null;
|
|
420
|
+
return ok(null);
|
|
173
421
|
}
|
|
174
422
|
}
|
|
175
423
|
function parseArgs(a) {
|
|
@@ -193,7 +441,6 @@ async function main() {
|
|
|
193
441
|
const o = parseArgs(process.argv.slice(2));
|
|
194
442
|
if (!o.key)
|
|
195
443
|
fail(2, '用法:memo-cmd-read <memo.key> [--params JSON对象] [--html 输出路径] [--timeout 毫秒]');
|
|
196
|
-
const dbPath = preflight();
|
|
197
444
|
let params = {};
|
|
198
445
|
if (o.params !== undefined) {
|
|
199
446
|
try {
|
|
@@ -205,6 +452,28 @@ async function main() {
|
|
|
205
452
|
if (typeof params !== 'object' || params === null || Array.isArray(params))
|
|
206
453
|
fail(2, '--params 须为 JSON 对象');
|
|
207
454
|
}
|
|
455
|
+
// #695:三个配置 key(`memo.config.read/write/reset`)在库目录预检与形状表之前拦下——
|
|
456
|
+
// 读写配置不该要求库已配,它们也不进 `MEMO_KEY_SHAPES`(不是唤醒词命令,见 `src/cli/config.ts`)。
|
|
457
|
+
if (isConfigKey(o.key)) {
|
|
458
|
+
process.stdout.write(runConfigKey(o.key, params) + '\n');
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
// #706 · 配置体检(`memo.config.check`):同样是设置页专用的只读命令,同样在预检之前拦下——
|
|
462
|
+
// 它要报的正是「库在哪、通不通」,不能先要求库目录已配。只读:不建目录、不写文件、不落默认配置。
|
|
463
|
+
if (isHealthCheckKey(o.key)) {
|
|
464
|
+
process.stdout.write(runHealthCheckKey(o.key) + '\n');
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
preflight();
|
|
468
|
+
// 读配置算库目录:配置件的报错本身就是人话(带行号与文件名),归「预检」那一档原样交回
|
|
469
|
+
// (不能让它裸抛——那会吐一整段 node 崩溃栈,用户看不到「该在哪配」)。
|
|
470
|
+
let dbPath = '';
|
|
471
|
+
try {
|
|
472
|
+
dbPath = resolveDbDir();
|
|
473
|
+
}
|
|
474
|
+
catch (e) {
|
|
475
|
+
fail(1, e instanceof Error ? e.message : String(e));
|
|
476
|
+
}
|
|
208
477
|
let shape = null;
|
|
209
478
|
try {
|
|
210
479
|
shape = memoShapeFor(o.key);
|
|
@@ -217,10 +486,35 @@ async function main() {
|
|
|
217
486
|
timer.unref();
|
|
218
487
|
let env = null;
|
|
219
488
|
let delivery;
|
|
489
|
+
// #661:合成写「最终没达成」时回执照打、退出码在写完回执之后落实(分字段是回执的本分,不能因为非 0 就吞掉)。
|
|
490
|
+
let writeExit = 0;
|
|
220
491
|
try {
|
|
221
|
-
// #229:`memo.help.lookup`
|
|
492
|
+
// #229:`memo.help.lookup` 在**开库之前**分派(只读页不建库);其余键照旧走 dispatch。
|
|
493
|
+
// #665:开的是老库文件(直连,不建库);用完即关,失败也关。
|
|
222
494
|
const help = o.key === 'memo.help.lookup' ? dispatchHelp(params, dbPath) : null;
|
|
223
|
-
|
|
495
|
+
let db = null;
|
|
496
|
+
let out;
|
|
497
|
+
try {
|
|
498
|
+
if (help === null) {
|
|
499
|
+
db = openMemoDb(dbPath);
|
|
500
|
+
out = dispatch(o.key, params, db);
|
|
501
|
+
}
|
|
502
|
+
else {
|
|
503
|
+
out = { data: help.data, exit: 0 };
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
finally {
|
|
507
|
+
if (db) {
|
|
508
|
+
try {
|
|
509
|
+
closeMemoDb(db);
|
|
510
|
+
}
|
|
511
|
+
catch {
|
|
512
|
+
// 关库失败不掩盖主流程结果。
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
writeExit = out.exit;
|
|
517
|
+
env = buildMemoEnvelope(o.key, out.data);
|
|
224
518
|
const built = env;
|
|
225
519
|
// B4 既有语义:`--html <路径>` 逐字写用户给的路径,内容仍是本包的 envelope 片段(`renderEnvelopeHtml`)。
|
|
226
520
|
const sectionHtml = () => {
|
|
@@ -233,7 +527,11 @@ async function main() {
|
|
|
233
527
|
const html = help.deliver.html ?? sectionHtml();
|
|
234
528
|
if (help.deliver.html !== undefined)
|
|
235
529
|
assertHtmlSize(html);
|
|
236
|
-
delivery = deliverMemoHtml({ explicit: o.html,
|
|
530
|
+
delivery = deliverMemoHtml({ explicit: o.html, landing: help.deliver.landing, html, window: help.deliver.window });
|
|
531
|
+
}
|
|
532
|
+
else if (out.deliver !== undefined) {
|
|
533
|
+
// #665 整页交付:缺省落 `memo_html/<页名>.html`(独占递补),显式 `--html` 逐字覆盖写。
|
|
534
|
+
delivery = deliverMemoHtml({ explicit: o.html, landing: landingOf(dbPath, out.deliver.stem), html: out.deliver.html });
|
|
237
535
|
}
|
|
238
536
|
else if (o.html) {
|
|
239
537
|
delivery = deliverMemoHtml({ explicit: o.html, html: sectionHtml() });
|
|
@@ -250,7 +548,8 @@ async function main() {
|
|
|
250
548
|
if (e?.code && /^E[A-Z]+$/.test(String(e.code))) {
|
|
251
549
|
fail(5, '落盘失败:' + (e.message || String(e)));
|
|
252
550
|
}
|
|
253
|
-
|
|
551
|
+
// 配置件(`base-link-core`)的报错本身就是人话(带行号与文件名):归「预检」那一档原样交回。
|
|
552
|
+
if (/(配置文件|配置项|测试缺隔离)/.test(e.message ?? ''))
|
|
254
553
|
fail(1, e.message);
|
|
255
554
|
fail(4, '未知失败:' + (e.message || String(e)));
|
|
256
555
|
}
|
|
@@ -259,6 +558,11 @@ async function main() {
|
|
|
259
558
|
}
|
|
260
559
|
// #83/#144 口径的顶层追加:`delivery{mode,path,bytes}` 只追加,envelope 既有五字段一字不改、序不变。
|
|
261
560
|
process.stdout.write(JSON.stringify(delivery ? { ...env, delivery } : env) + '\n');
|
|
561
|
+
// #661:本地那一侧成了、远端那一侧没成 ⇒ 回执已分字段写明,退出码仍要如实反映「这一趟没达成」(契约 A3/A6)。
|
|
562
|
+
if (writeExit !== 0) {
|
|
563
|
+
console.error('ERR ' + writeExit + ': 合成写没达成(本地侧已落,远端侧见回执 remote 那一格)');
|
|
564
|
+
process.exit(writeExit);
|
|
565
|
+
}
|
|
262
566
|
}
|
|
263
567
|
await main();
|
|
264
568
|
//# sourceMappingURL=cmd_read.js.map
|