skill-home 0.1.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 +167 -0
- package/dist/cli/cmd_read.js +150 -31
- 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 +44 -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/archive.d.ts +13 -0
- package/dist/fetch/archive.d.ts.map +1 -0
- package/dist/fetch/archive.js +115 -0
- package/dist/fetch/archive.js.map +1 -0
- package/dist/fetch/backup.d.ts +77 -0
- package/dist/fetch/backup.d.ts.map +1 -0
- package/dist/fetch/backup.js +292 -0
- package/dist/fetch/backup.js.map +1 -0
- package/dist/fetch/index.d.ts +2 -1
- package/dist/fetch/index.d.ts.map +1 -1
- package/dist/fetch/index.js +3 -1
- package/dist/fetch/index.js.map +1 -1
- package/dist/fetch/paths.d.ts +5 -3
- package/dist/fetch/paths.d.ts.map +1 -1
- package/dist/fetch/paths.js +19 -24
- package/dist/fetch/paths.js.map +1 -1
- package/dist/health.d.ts +41 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +507 -0
- package/dist/health.js.map +1 -0
- package/dist/help/helpAssets.d.ts +38 -0
- package/dist/help/helpAssets.d.ts.map +1 -0
- package/dist/help/helpAssets.js +1122 -0
- package/dist/help/helpAssets.js.map +1 -0
- package/dist/help/helpFile.d.ts +62 -0
- package/dist/help/helpFile.d.ts.map +1 -0
- package/dist/help/helpFile.js +219 -0
- package/dist/help/helpFile.js.map +1 -0
- package/dist/help/index.d.ts +3 -0
- package/dist/help/index.d.ts.map +1 -1
- package/dist/help/index.js +6 -0
- package/dist/help/index.js.map +1 -1
- package/dist/help/manifest.d.ts +11 -0
- package/dist/help/manifest.d.ts.map +1 -0
- package/dist/help/manifest.js +38 -0
- package/dist/help/manifest.js.map +1 -0
- package/dist/help/output.d.ts +12 -0
- package/dist/help/output.d.ts.map +1 -0
- package/dist/help/output.js +65 -0
- package/dist/help/output.js.map +1 -0
- package/package.json +9 -4
package/SKILL.md
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: skill-home
|
|
3
|
+
description: "「居家管家HELP」→home.help.lookup 缺省落一份 HELP 文件并回执绝对路径(老骨架 9 域/30 组/73 场景);唯一出口 home-cmd-read。触发词:居家管家 帮助、居家管家帮助、居家管家能做什么、查物品、看物品、录物品、改物品、盘物品、查位置、查快递、查保修、查证件、看统计、初始化。"
|
|
4
|
+
help_wake_word: "居家管家 帮助"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 居家管家(home)SKILL
|
|
8
|
+
|
|
9
|
+
家庭物品全生命周期管理:搜/看/录/改、标签分类、盘点、位置、穿搭出行、统计、购物快递、票据凭证、家庭协作、初始化运维。唯一出口 `home-cmd-read <home.key>`,argv+JSON(stdout)+exit,非 0 走 stderr。写走 receipt(直通即真相)。
|
|
10
|
+
|
|
11
|
+
## 快速开始
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
home-cmd-read home.item.search --params '{"name":"牛奶"}'
|
|
15
|
+
home-cmd-read home.item.add --params '{"name":"牛奶","category_id":1,"location":"客厅/冰箱"}'
|
|
16
|
+
home-cmd-read home.help.lookup --params '{"q":"查物品"}'
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## HELP 交付(「居家管家HELP」这条命令交什么)
|
|
20
|
+
|
|
21
|
+
对用户说「居家管家 帮助」时,**这条命令的缺省行为就是落一份 HELP HTML 文件**,并把绝对路径回执进 stdout:
|
|
22
|
+
|
|
23
|
+
- **缺省**(不给 `q`/`mode`):产物落 `<库目录>/<产物目录>/居家管家_HELP_<YYYYMMDD_HHMMSS>.html`——`<库目录>`=配置文件 `~/.ilife/home.yaml` 的 `db.dir`,空串=数据目录 `~/.ilife/data/`;`<产物目录>`=同文件的 `html.dir`(默认 `home_manager_html`);**缺省(24 小时内)重复触发回同一路径、不新建**——需要每次落新件时传 `--params '{"reuseHours":0}'`(此时同秒冲突才走 `_2` 递补);stdout 顶层多一个 `delivery{mode,path,bytes}`,`path` 恒为**绝对路径** ⇒ **把 `delivery.path` 告诉用户**(他要打开的就是这一份),落点一律以回执为准。
|
|
24
|
+
- **速查表**(`--params '{"mode":"lookup"}'`):落同一产物目录下 `居家管家_速查表_<时间戳>.html`(与 HELP 文件**分名**——别让用户按一个名字打开到另一个东西)。
|
|
25
|
+
- **现找**(`--params '{"q":"查物品"}'`):只在 stdout 回命中,**不落盘**(检索式问答不刷目录)。
|
|
26
|
+
- 这两支**互斥**:`mode` 与 `q` 同给、或 `mode` 不是 `lookup`,一律 exit 2(stderr 是 `ERR 2: …`),失败路径上 stdout 保持干净。
|
|
27
|
+
- **反复读不再涨目录(#245)**:同一主体**一天内只留一份**——24 小时内再读就**复用已有那份**(不新建、不改写;回执给的就是它)。要别的窗口给 `--params '{"reuseHours":3}'`(小时);要**每次都要一份最新的**给 `{"reuseHours":0}`。窗口内若已有一份、而你刚改过 HELP 内容,那份旧产物**不会被自动刷新**(窗口语义如此)——真要新的就带 `reuseHours:0`。`reuseHours` 给坏值(非数/负数)exit 2。
|
|
28
|
+
|
|
29
|
+
**完成判据**:`delivery.path` 指的文件**存在**,且它的大小 = 回执里的 `delivery.bytes`(两处对不上就是没做完,别把回执当完成)。
|
|
30
|
+
|
|
31
|
+
两件容易踩的:
|
|
32
|
+
|
|
33
|
+
- 这条命令**不开库**:它在开库之前分派,跑完产物目录里 0 个 `.db`(落文件仍然要求库目录定得下来:配置文件 `~/.ilife/home.yaml` 的 `db.dir`,空串=数据目录,故从不因「没设什么」而失败)。
|
|
34
|
+
- 页面由**通用 help 模板**(`base-paint/help-shell` 的 `renderHelpShellHtml`)渲染,与卡路里/饼干记账/私家大厨同款;要动观感就去改模板源再跑它的生成器。
|
|
35
|
+
|
|
36
|
+
**本节不管**(各有归属,别在这里找):其余 20 条 `home.*` 命令(各有自己的节);`--html <路径>`(所有命令通用的分节页出口,与 HELP 交付不是一回事);把文件送进面板/侧栏(属另一条线)。
|
|
37
|
+
|
|
38
|
+
## 装出来的那份怎么判新旧
|
|
39
|
+
|
|
40
|
+
装到 agent 读得到位置的那份与仓内这个包是不是同一份,对**安装点**(`<技能目录>` 取实际安装点:本机常见形如 `~/.agents/skills/<技能名>`,该处没有同名目录时说明装到了别处)跑 `Get-Item <技能目录> -Force | Select-Object LinkType` 一看便知:**链接(Junction/目录联接)=同一份**,改仓内立即生效;**拷贝**才谈新旧——只能比 `SKILL.md` 的哈希(拷贝里那份与仓里这份哈希不同=旧的,重新把技能装到 agent 读得到的位置)。
|
|
41
|
+
|
|
42
|
+
## 口径
|
|
43
|
+
|
|
44
|
+
- 分类:8 顶级统一不带前缀(食物与饮品/衣物与穿戴/家居与陈设/工具与器材/数码与电子/健康与医药/文体与娱乐/资产与凭证);录物品须 category_id 正整数(从 categories 表查);分类名禁数字前缀/emoji。
|
|
45
|
+
- 位置:须两级路径(如 客厅/冰箱);11 状态(在家/备用/穿着中/旅游中/洗护中/借用中/维修中/已用完/快递中/待处理/已废弃);数量非负整数,0 自动清位。
|
|
46
|
+
- 账号:master-key ≥8 位门;密码 AES-256-CBC 本地加密;看密码仅对话 JSON 回显,HTML 快照脱敏占位。
|
|
47
|
+
- 历史/照片墙折入 detail/search(view/wall 分流),管照片折入 update(photo 参数);闲置/过期折入 alert(kind 分流);购物改新增“改购物清单”(老家无直接词,补齐写链)。
|
|
48
|
+
- 废弃词(联动 3 词:联动总览/记到卡路里/记到记账)不路由,combos 登记走后续票。
|
|
49
|
+
- 坏输入与缺失一律阻断(exit 2/4),不返空数组冒充正常。
|
|
50
|
+
|
|
51
|
+
## 联动速查(构建期注入,勿手改)
|
|
52
|
+
|
|
53
|
+
<!-- HELP-AUTO-START -->
|
|
54
|
+
| 唤醒词 | key | shape | 例 |
|
|
55
|
+
|---|---|---|---|
|
|
56
|
+
| 居家管家 帮助 | home.help.lookup | list | `home-cmd-read home.help.lookup` |
|
|
57
|
+
| 居家管家帮助 | home.help.lookup | list | `home-cmd-read home.help.lookup` |
|
|
58
|
+
| 居家管家能做什么 | home.help.lookup | list | `home-cmd-read home.help.lookup` |
|
|
59
|
+
| 查物品(HTML) | home.item.search | list | `home-cmd-read home.item.search` |
|
|
60
|
+
| 看物品(HTML) | home.item.detail | detail | `home-cmd-read home.item.detail --params '{"id":1}'` |
|
|
61
|
+
| 统物品(HTML) | home.stats.overview | stat | `home-cmd-read home.stats.overview` |
|
|
62
|
+
| 查物品 | home.item.search | list | `home-cmd-read home.item.search` |
|
|
63
|
+
| 看物品 | home.item.detail | detail | `home-cmd-read home.item.detail --params '{"id":1}'` |
|
|
64
|
+
| 录物品 | home.item.add | receipt | `home-cmd-read home.item.add` |
|
|
65
|
+
| 拍物品 | home.item.add | receipt | `home-cmd-read home.item.add --params '{"photo":"1"}'` |
|
|
66
|
+
| 改物品 | home.item.update | receipt | `home-cmd-read home.item.update --params '{"id":1}'` |
|
|
67
|
+
| 移物品 | home.item.update | receipt | `home-cmd-read home.item.update --params '{"op":"move","id":1}'` |
|
|
68
|
+
| 补物品 | home.item.update | receipt | `home-cmd-read home.item.update --params '{"op":"qty","id":1}'` |
|
|
69
|
+
| 减物品 | home.item.update | receipt | `home-cmd-read home.item.update --params '{"op":"qty","id":1}'` |
|
|
70
|
+
| 标物品 | home.item.update | receipt | `home-cmd-read home.item.update --params '{"op":"tags","id":1}'` |
|
|
71
|
+
| 废物品 | home.item.update | receipt | `home-cmd-read home.item.update --params '{"op":"status","id":1}'` |
|
|
72
|
+
| 借物品 | home.item.update | receipt | `home-cmd-read home.item.update --params '{"op":"status","id":1}'` |
|
|
73
|
+
| 修物品 | home.item.update | receipt | `home-cmd-read home.item.update --params '{"op":"status","id":1}'` |
|
|
74
|
+
| 盘物品 | home.inventory.round | receipt | `home-cmd-read home.inventory.round --params '{"op":"round"}'` |
|
|
75
|
+
| 盘全部 | home.inventory.round | receipt | `home-cmd-read home.inventory.round --params '{"op":"round","scope":"all"}'` |
|
|
76
|
+
| 穿什么 | home.outfit.pick | list | `home-cmd-read home.outfit.pick` |
|
|
77
|
+
| 带物品 | home.trip.manage | receipt | `home-cmd-read home.trip.manage --params '{"mode":"pack"}'` |
|
|
78
|
+
| 归物品 | home.trip.manage | receipt | `home-cmd-read home.trip.manage --params '{"mode":"return"}'` |
|
|
79
|
+
| 统物品 | home.stats.overview | stat | `home-cmd-read home.stats.overview --params '{"kind":"summary"}'` |
|
|
80
|
+
| 查高频 | home.stats.overview | stat | `home-cmd-read home.stats.overview --params '{"kind":"summary"}'` |
|
|
81
|
+
| 查低频 | home.stats.alert | list | `home-cmd-read home.stats.alert --params '{"kind":"idle"}'` |
|
|
82
|
+
| 查过期 | home.stats.alert | list | `home-cmd-read home.stats.alert --params '{"kind":"expiring"}'` |
|
|
83
|
+
| 看标签 | home.tag.query | list | `home-cmd-read home.tag.query` |
|
|
84
|
+
| 合标签 | home.tag.write | receipt | `home-cmd-read home.tag.write --params '{"op":"merge"}'` |
|
|
85
|
+
| 查快递 | home.shopping.query | list | `home-cmd-read home.shopping.query --params '{"kind":"express"}'` |
|
|
86
|
+
| 推位置 | home.location.query | list | `home-cmd-read home.location.query --params '{"mode":"suggest"}'` |
|
|
87
|
+
| 找位置 | home.location.query | list | `home-cmd-read home.location.query --params '{"mode":"find"}'` |
|
|
88
|
+
| 查账号 | home.ticket.query | list | `home-cmd-read home.ticket.query --params '{"kind":"account"}'` |
|
|
89
|
+
| 存账号 | home.ticket.write | receipt | `home-cmd-read home.ticket.write --params '{"kind":"account","op":"add"}'` |
|
|
90
|
+
| 改账号 | home.ticket.write | receipt | `home-cmd-read home.ticket.write --params '{"kind":"account","op":"update"}'` |
|
|
91
|
+
| 看密码 | home.ticket.write | receipt | `home-cmd-read home.ticket.write --params '{"kind":"account","op":"show"}'` |
|
|
92
|
+
| 查异常 | home.care.query | list | `home-cmd-read home.care.query --params '{"kind":"lint"}'` |
|
|
93
|
+
| 借用 | home.care.query | list | `home-cmd-read home.care.query --params '{"kind":"borrow"}'` |
|
|
94
|
+
| 家人档案 | home.care.query | list | `home-cmd-read home.care.query --params '{"kind":"member"}'` |
|
|
95
|
+
| 管位置 | home.location.write | receipt | `home-cmd-read home.location.write --params '{"op":"manage"}'` |
|
|
96
|
+
| 固定位 | home.location.write | receipt | `home-cmd-read home.location.write --params '{"op":"fixed"}'` |
|
|
97
|
+
| 收纳建议 | home.location.query | list | `home-cmd-read home.location.query --params '{"mode":"storage"}'` |
|
|
98
|
+
| 空间视图 | home.location.query | list | `home-cmd-read home.location.query --params '{"mode":"space"}'` |
|
|
99
|
+
| 查闲置 | home.stats.alert | list | `home-cmd-read home.stats.alert --params '{"kind":"idle"}'` |
|
|
100
|
+
| 盘点统计 | home.stats.overview | stat | `home-cmd-read home.stats.overview --params '{"kind":"inventory"}'` |
|
|
101
|
+
| 首次使用 | home.care.write | receipt | `home-cmd-read home.care.write --params '{"kind":"init"}'` |
|
|
102
|
+
| 备份导出 | home.care.write | receipt | `home-cmd-read home.care.write --params '{"kind":"backup"}'` |
|
|
103
|
+
| 导入恢复 | home.care.write | receipt | `home-cmd-read home.care.write --params '{"kind":"import"}'` |
|
|
104
|
+
| 批量录入 | home.item.add | receipt | `home-cmd-read home.item.add --params '{"op":"batch"}'` |
|
|
105
|
+
| 补录 | home.item.add | receipt | `home-cmd-read home.item.add --params '{"op":"backfill"}'` |
|
|
106
|
+
| 紧急定位 | home.item.search | list | `home-cmd-read home.item.search --params '{"locate":true}'` |
|
|
107
|
+
| 筛选浏览 | home.item.search | list | `home-cmd-read home.item.search --params '{"browse":true}'` |
|
|
108
|
+
| 拍照找物品 | home.item.search | list | `home-cmd-read home.item.search --params '{"photo":true}'` |
|
|
109
|
+
| 查重复 | home.item.search | list | `home-cmd-read home.item.search --params '{"dupes":true}'` |
|
|
110
|
+
| 合并物品 | home.item.update | receipt | `home-cmd-read home.item.update --params '{"op":"merge","id":1}'` |
|
|
111
|
+
| 撤销操作 | home.item.update | receipt | `home-cmd-read home.item.update --params '{"op":"undo"}'` |
|
|
112
|
+
| 物品关联 | home.item.update | receipt | `home-cmd-read home.item.update --params '{"op":"relate","id":1}'` |
|
|
113
|
+
| 管标签 | home.tag.write | receipt | `home-cmd-read home.tag.write --params '{"op":"overview"}'` |
|
|
114
|
+
| 管分类 | home.tag.write | receipt | `home-cmd-read home.tag.write --params '{"op":"category"}'` |
|
|
115
|
+
| 整理建议 | home.tag.write | receipt | `home-cmd-read home.tag.write --params '{"op":"tidy"}'` |
|
|
116
|
+
| 查看照片 | home.item.detail | detail | `home-cmd-read home.item.detail --params '{"view":"photos","id":1}'` |
|
|
117
|
+
| 管照片 | home.item.update | receipt | `home-cmd-read home.item.update --params '{"op":"photo","id":1}'` |
|
|
118
|
+
| 照片墙 | home.item.search | list | `home-cmd-read home.item.search --params '{"wall":true}'` |
|
|
119
|
+
| 盘点记录 | home.inventory.records | list | `home-cmd-read home.inventory.records` |
|
|
120
|
+
| 差异处理 | home.inventory.round | receipt | `home-cmd-read home.inventory.round --params '{"op":"resolve"}'` |
|
|
121
|
+
| 搬家盘点 | home.inventory.round | receipt | `home-cmd-read home.inventory.round --params '{"op":"move"}'` |
|
|
122
|
+
| 历史 | home.item.detail | detail | `home-cmd-read home.item.detail --params '{"view":"history","id":1}'` |
|
|
123
|
+
| 数量变更 | home.item.update | receipt | `home-cmd-read home.item.update --params '{"op":"qty","id":1}'` |
|
|
124
|
+
| 状态变更 | home.item.update | receipt | `home-cmd-read home.item.update --params '{"op":"status","id":1}'` |
|
|
125
|
+
| 盘点 | home.inventory.round | receipt | `home-cmd-read home.inventory.round --params '{"op":"round"}'` |
|
|
126
|
+
| 购物清单 | home.shopping.query | list | `home-cmd-read home.shopping.query --params '{"kind":"list"}'` |
|
|
127
|
+
| 缺货检测 | home.shopping.query | list | `home-cmd-read home.shopping.query --params '{"kind":"missing"}'` |
|
|
128
|
+
| 囤货盘点 | home.shopping.query | list | `home-cmd-read home.shopping.query --params '{"kind":"stock"}'` |
|
|
129
|
+
| 查购买记录 | home.ticket.query | list | `home-cmd-read home.ticket.query --params '{"kind":"purchase"}'` |
|
|
130
|
+
| 查上月购买 | home.ticket.query | list | `home-cmd-read home.ticket.query --params '{"kind":"purchase","range":"last-month"}'` |
|
|
131
|
+
| 查今年花费 | home.ticket.query | list | `home-cmd-read home.ticket.query --params '{"kind":"purchase","range":"year"}'` |
|
|
132
|
+
| 查退货窗口 | home.ticket.query | list | `home-cmd-read home.ticket.query --params '{"kind":"purchase","range":"return"}'` |
|
|
133
|
+
| 登记购买记录 | home.ticket.write | receipt | `home-cmd-read home.ticket.write --params '{"kind":"purchase","op":"add"}'` |
|
|
134
|
+
| 查保修状态 | home.ticket.query | list | `home-cmd-read home.ticket.query --params '{"kind":"warranty"}'` |
|
|
135
|
+
| 登记保修 | home.ticket.write | receipt | `home-cmd-read home.ticket.write --params '{"kind":"warranty","op":"register"}'` |
|
|
136
|
+
| 记录维修 | home.ticket.write | receipt | `home-cmd-read home.ticket.write --params '{"kind":"warranty","op":"repair"}'` |
|
|
137
|
+
| 设置保养周期 | home.ticket.write | receipt | `home-cmd-read home.ticket.write --params '{"kind":"warranty","op":"cycle"}'` |
|
|
138
|
+
| 执行保养 | home.ticket.write | receipt | `home-cmd-read home.ticket.write --params '{"kind":"warranty","op":"maintain"}'` |
|
|
139
|
+
| 查证件到期 | home.ticket.query | list | `home-cmd-read home.ticket.query --params '{"kind":"cert"}'` |
|
|
140
|
+
| 登记证件 | home.ticket.write | receipt | `home-cmd-read home.ticket.write --params '{"kind":"cert","op":"add"}'` |
|
|
141
|
+
| 证件归档 | home.ticket.write | receipt | `home-cmd-read home.ticket.write --params '{"kind":"cert","op":"archive"}'` |
|
|
142
|
+
| 更新证件 | home.ticket.write | receipt | `home-cmd-read home.ticket.write --params '{"kind":"cert","op":"update"}'` |
|
|
143
|
+
| 衣橱分析 | home.outfit.pick | list | `home-cmd-read home.outfit.pick --params '{"kind":"wardrobe"}'` |
|
|
144
|
+
| 换季 | home.outfit.pick | list | `home-cmd-read home.outfit.pick --params '{"kind":"season"}'` |
|
|
145
|
+
| 旅行穿搭 | home.outfit.pick | list | `home-cmd-read home.outfit.pick --params '{"kind":"trip-plan"}'` |
|
|
146
|
+
| 改购物清单 | home.shopping.write | receipt | `home-cmd-read home.shopping.write --params '{"op":"check"}'` |
|
|
147
|
+
|
|
148
|
+
相关场景:home.care.query、home.care.write、home.help.lookup、home.inventory.records、home.inventory.round、home.item.add、home.item.detail、home.item.search、home.item.update、home.location.query、home.location.write、home.outfit.pick、home.shopping.query、home.shopping.write、home.stats.alert、home.stats.overview、home.tag.query、home.tag.write、home.ticket.query、home.ticket.write、home.trip.manage(21 联动,key 字符串后续票落表时冻结)。
|
|
149
|
+
<!-- HELP-AUTO-END -->
|
|
150
|
+
|
|
151
|
+
## 环境与出 scope
|
|
152
|
+
|
|
153
|
+
- 路径类取值一律读配置文件 `~/.ilife/home.yaml`(**配置文件是唯一真相,环境变量不参与配置**):库目录=`db.dir`(空串=数据目录 `~/.ilife/data/`,首次读时自动建)、库文件名=`db.name`(默认 `home.db`)、产物目录=`html.dir`(默认 `home_manager_html`)、备份目录=`backup.dir`(默认 `backups`,相对库目录解)、HELP 与速查表的主体名=`files.help`/`files.lookup`;`ILIFE_CONFIG_DIR` 设定且非空即整体接管配置目录。**没有照片目录这项配置**(照片是记录里的一列,不解析照片目录)。取值面与环境项见 docs/env.md。
|
|
154
|
+
- 出 scope:定时任务(老家零定时代码)、面板(二期单 MAP)、本技能外联动登记(combos.yaml 一律不碰,走后续票;SM9 3 场景 prompt 复制不迁);真实数据禁迁,测试 tmp 隔离。
|
|
155
|
+
|
|
156
|
+
## 公共安装器运行时(skills-cli 装完必读,#47)
|
|
157
|
+
|
|
158
|
+
- 本仓库 `dist/` 不进 git:skills-cli 只把本目录(含本文件)装进 agent,不带可执行文件;「不走 npm」的只是 skill 发现这一步,运行时走 npm(npm 包名即目录名 `skill-home`,二进制名 `home-cmd-read`)。
|
|
159
|
+
- 取运行时二选一:`npm install -g skill-home`(一劳永逸),或免安装 `npx -p skill-home home-cmd-read …`(每次现拉)。**但这两条现在必失败:`skill-home@0.1.0` 虽已发布到 npm(2026-09-07,registry 在册),它的依赖 `"base-link-core": "workspace:^0.1.0"` 未改写 ⇒ npm 报 `EUNSUPPORTEDPROTOCOL`**(已发布包待重发,发版流修,见 docs/public-installer-47.md「已发布包阻塞」)——重发前一律走下面那条本仓构建产物验证链路。
|
|
160
|
+
- HELP 端到端验证(本仓构建产物;node>=22.13 是 `engines` 钉死的门槛;隔离靠把**配置目录**指到临时目录——`ILIFE_CONFIG_DIR` 设定且非空即整体接管配置目录,配置里的 `db.dir` 再决定库与产物落哪,故临时目录里自己写一份 `home.yaml`;完整口径见 docs/public-installer-47.md):
|
|
161
|
+
|
|
162
|
+
```sh
|
|
163
|
+
cfg="$(mktemp -d)" && mkdir -p "$cfg/db" && printf 'db:\n dir: %s\n' "$cfg/db" > "$cfg/home.yaml"
|
|
164
|
+
ILIFE_CONFIG_DIR="$cfg" node packages/skill-home/dist/cli/cmd_read.js home.help.lookup
|
|
165
|
+
```
|
|
166
|
+
- 上面那条跑通的样子:stdout 一行 envelope JSON,`delivery.path` 指的文件存在且大小=`delivery.bytes`(与「HELP 交付」节的完成判据同源);按上面那份 `home.yaml`,落点是 `$cfg/db/home_manager_html/`。
|
|
167
|
+
- 版本钉死登记:本节**命令不写版本号**(重发前钉了也装不上,没有意义;npm 现值 `0.1.0`);重发后照 docs/public-installer-47.md「版本钉死登记」把 SKILL.md/该文档/`test/skills-export-47.test.mjs` 三处硬编码一起同步。
|
package/dist/cli/cmd_read.js
CHANGED
|
@@ -3,10 +3,16 @@
|
|
|
3
3
|
// 退出码对齐 skilllink 冻结:0 ok;1 预检;2 用法/参数;3 key;4 取数/超时;5 envelope/渲染/落盘。
|
|
4
4
|
// stdout 纯净:成功只打 envelope JSON 一行。写走 receipt(直通 create/update/remove 即真相)。
|
|
5
5
|
import { writeFileSync } from 'node:fs';
|
|
6
|
-
import {
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { HomeFetchError, HomePolicyError, resolveDbDir, resolveDbPath, dbFilename, openHomeDb, closeHomeDb, addItem, getItemById, listLocationsByItem, listTagsByItem, searchItems, updateItem, adjustQuantity, setLocationStatus, moveLocation, setItemTags, listAllTags, mergeTags, listCategories, getCategoryById, encryptPassword, decryptPassword, assertMasterKey, addInventoryRecord, listInventoryRecords, listLocationNodes, ensureLocationNode, listShopping, addShopping, checkShopping, missingItems, stockList, setThreshold, listPurchases, addPurchase, purchaseYearStats, listWarranties, addWarranty, addServiceEvent, listCerts, addCert, listAccounts, listMembers, addMember, listBorrows, addBorrow, statsOverview, highFreq, idleItems, expiringItems, createBackup, listBackups, restoreBackup, exportData, } from '../fetch/index.js';
|
|
7
8
|
import { normalizeLocation, normalizeStatus, isFoodItem, validateAddInput, parseUpdateOp, needId, parseTicketKind, checkDate, checkMoney, parseCareKind, } from '../policy/index.js';
|
|
8
9
|
import { homeShapeFor, buildHomeEnvelope, renderEnvelopeHtml, assertHtmlSize, loadTemplate, templateFor, fillTemplate, toItemCard, buildSearchList, buildDetail, buildReceipt, buildTagList, buildInventoryRecords, buildLocationList, buildOutfitList, buildStatsOverview, buildStatsAlert, buildShoppingList, buildTicketList, buildCareList, buildHelpItems, HomeRenderError, } from '../render/index.js';
|
|
9
|
-
import { buildHelpLookup } from '../help/index.js';
|
|
10
|
+
import { buildHelpLookup, buildHomeHelpFileData, renderHomeHelpHtml, deliverHomeHelp } from '../help/index.js';
|
|
11
|
+
import { helpDirName, helpFileStem, lookupFileStem } from '../help/manifest.js';
|
|
12
|
+
import { isConfigKey, runConfigKey } from './config.js';
|
|
13
|
+
// #706 · 配置体检:设置页专用的一条只读命令,同走「进分派层之前拦下」这条口(判据住 src/health.ts)。
|
|
14
|
+
import { isHealthCheckKey, runHealthCheckKey } from './health.js';
|
|
15
|
+
import { helpReuseWindowOf } from 'base-paint/save-html';
|
|
10
16
|
const DEFAULT_TIMEOUT_MS = 30000;
|
|
11
17
|
function fail(code, msg) { console.error('ERR ' + code + ': ' + msg); process.exit(code); }
|
|
12
18
|
function toast(msg) { console.error('TOAST: ' + msg); }
|
|
@@ -15,10 +21,6 @@ function preflight() {
|
|
|
15
21
|
const v = process.versions.node.split('.').map(Number);
|
|
16
22
|
if (!(v[0] > 22 || (v[0] === 22 && v[1] >= 13)))
|
|
17
23
|
fail(1, 'node 低于 22.13:' + process.versions.node);
|
|
18
|
-
const p = process.env.SKILLS_DB_PATH;
|
|
19
|
-
if (!p)
|
|
20
|
-
fail(1, 'SKILLS_DB_PATH 未设置(无默认值,必设)');
|
|
21
|
-
return p;
|
|
22
24
|
}
|
|
23
25
|
function asInt(v, field) {
|
|
24
26
|
if (v === undefined)
|
|
@@ -27,6 +29,96 @@ function asInt(v, field) {
|
|
|
27
29
|
fail(2, field + ' 须为正整数');
|
|
28
30
|
return v;
|
|
29
31
|
}
|
|
32
|
+
/** 复用窗口(毫秒):坏参抛 `RangeError` ⇒ 交出口的「参数错」那一档(共用件工厂,别家同形)。 */
|
|
33
|
+
const helpWindowOrFail = helpReuseWindowOf((m) => fail(2, m));
|
|
34
|
+
function dispatchHelp(params) {
|
|
35
|
+
const dbDir = resolveDbDir();
|
|
36
|
+
const now = new Date();
|
|
37
|
+
const mode = params.mode === undefined ? undefined : String(params.mode);
|
|
38
|
+
if (params.q !== undefined && typeof params.q !== 'string') {
|
|
39
|
+
fail(2, '参数 q 须为字符串(收到 ' + (Array.isArray(params.q) ? 'array' : typeof params.q) + '):q=现找关键词');
|
|
40
|
+
}
|
|
41
|
+
const q = params.q;
|
|
42
|
+
if (mode !== undefined && q !== undefined)
|
|
43
|
+
fail(2, '参数 q 与 mode 互斥:q=现找,mode=速查表产物');
|
|
44
|
+
if (mode !== undefined && mode !== 'lookup')
|
|
45
|
+
fail(2, 'mode 非法(' + String(mode) + '):本键只认 lookup');
|
|
46
|
+
// 参数面校验单点(#190 D2):三支分派**之前**一次过完,坏 `reuseHours` 无论走哪支都同一个 exit 2。
|
|
47
|
+
const reuseMs = helpWindowOrFail(params);
|
|
48
|
+
const all = buildHelpLookup().map((h) => ({ phrase: h.phrase, key: h.key, shape: h.shape, cli: h.cli, desc: h.desc }));
|
|
49
|
+
if (q !== undefined)
|
|
50
|
+
return { data: buildHelpItems(all, q) };
|
|
51
|
+
const targetDir = join(dbDir, helpDirName());
|
|
52
|
+
if (mode === 'lookup') {
|
|
53
|
+
return {
|
|
54
|
+
data: buildHelpItems(all, undefined),
|
|
55
|
+
deliver: { targetDir, stem: lookupFileStem(), reuseMs },
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const html = renderHomeHelpHtml(buildHomeHelpFileData(now, { dbPath: join(dbDir, dbFilename()) }));
|
|
59
|
+
return {
|
|
60
|
+
data: buildHelpItems(all, undefined),
|
|
61
|
+
deliver: { html, targetDir, stem: helpFileStem(), reuseMs },
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/* ── #707 · 备份导出/导入恢复(**在开库之前**走)───────────────────────────────────────────
|
|
65
|
+
*
|
|
66
|
+
* 为什么不进 `dispatch`:恢复=拿备份覆盖 `home.db`,而 `dispatch` 一进来就 `openHomeDb` 并持有句柄;
|
|
67
|
+
* 库在手里时覆盖库文件+清 WAL/SHM 会把库写坏。故照 #190 的 help 支,本函数自管短命连接、在 `dispatch` 之前分派。
|
|
68
|
+
* 认的 (key,kind):`home.care.query`+`backup-list`/`home.care.write`+`backup`|`export`|`import-preview`|`import`。
|
|
69
|
+
* 其余一律返回 `null` 照旧走 `dispatch`(含开库);`dispatch` 里那几条 kind 走到就是路由坏了(fail(1))。
|
|
70
|
+
*/
|
|
71
|
+
function keepNOf(params) {
|
|
72
|
+
const v = params.keep_n ?? params.keepN;
|
|
73
|
+
if (v === undefined)
|
|
74
|
+
return undefined;
|
|
75
|
+
if (!Number.isInteger(v) || v <= 0)
|
|
76
|
+
fail(2, 'keep_n 须为正整数');
|
|
77
|
+
return v;
|
|
78
|
+
}
|
|
79
|
+
function fmtBytes(n) {
|
|
80
|
+
if (n < 1024)
|
|
81
|
+
return n + ' 字节';
|
|
82
|
+
if (n < 1024 * 1024)
|
|
83
|
+
return (n / 1024).toFixed(1) + ' KiB';
|
|
84
|
+
return (n / 1024 / 1024).toFixed(1) + ' MiB';
|
|
85
|
+
}
|
|
86
|
+
function dispatchBackup(key, params) {
|
|
87
|
+
const kind = typeof params.kind === 'string' ? params.kind : '';
|
|
88
|
+
if (key === 'home.care.query' && kind === 'backup-list') {
|
|
89
|
+
const l = listBackups({ keepN: keepNOf(params) });
|
|
90
|
+
const rows = l.history.map((h) => ({
|
|
91
|
+
name: h.file + '(' + fmtBytes(h.size) + ',' + h.days_ago + ' 天前)', count: h.size,
|
|
92
|
+
}));
|
|
93
|
+
return buildCareList(rows.length ? rows : [{ name: '无备份(目录:' + l.dir + ')', count: 0 }]);
|
|
94
|
+
}
|
|
95
|
+
if (key !== 'home.care.write')
|
|
96
|
+
return null;
|
|
97
|
+
if (kind === 'backup') {
|
|
98
|
+
const r = createBackup({ keepN: keepNOf(params) });
|
|
99
|
+
return buildReceipt('已备份:' + r.items + ' 件 → ' + r.file + '(' + fmtBytes(r.size) + ',保留 ' + r.keep_n + ' 份)');
|
|
100
|
+
}
|
|
101
|
+
if (kind === 'export') {
|
|
102
|
+
const r = exportData({ format: params.format, output: params.output });
|
|
103
|
+
return buildReceipt('已导出:' + r.format.toUpperCase() + ' ' + r.rows + ' 行 → ' + r.file + '(' + fmtBytes(r.size) + ')');
|
|
104
|
+
}
|
|
105
|
+
if (kind !== 'import-preview' && kind !== 'import')
|
|
106
|
+
return null;
|
|
107
|
+
const file = params.file;
|
|
108
|
+
if (typeof file !== 'string' || !file)
|
|
109
|
+
fail(2, '导入恢复须给 file(备份文件名或绝对路径)');
|
|
110
|
+
if (kind === 'import-preview') {
|
|
111
|
+
const p = restoreBackup(file, { dryRun: true });
|
|
112
|
+
return buildReceipt('dry_run' in p ? p.message : '');
|
|
113
|
+
}
|
|
114
|
+
if (params.confirm !== true) {
|
|
115
|
+
fail(2, '导入恢复会**整库覆盖**,须显式 confirm:true(先跑 kind=import-preview 看预告)');
|
|
116
|
+
}
|
|
117
|
+
const r = restoreBackup(file, { keepN: keepNOf(params) });
|
|
118
|
+
if ('dry_run' in r)
|
|
119
|
+
fail(1, '内部错误:确认支不该拿到预告结果');
|
|
120
|
+
return buildReceipt(r.message + '(' + r.before_items + ' → ' + r.items_total + ' 件;恢复前自备份 ' + r.safety_backup + ')');
|
|
121
|
+
}
|
|
30
122
|
function dispatch(key, params) {
|
|
31
123
|
const dbPath = resolveDbPath();
|
|
32
124
|
const handle = openHomeDb(dbPath);
|
|
@@ -726,8 +818,9 @@ function dispatch(key, params) {
|
|
|
726
818
|
const st = statsOverview(handle);
|
|
727
819
|
return buildCareList([{ name: '首次使用:' + st.items + ' 件已录', count: st.items }]);
|
|
728
820
|
}
|
|
729
|
-
// backup-list
|
|
730
|
-
|
|
821
|
+
// #707:backup-list 归 dispatchBackup(在开库之前走);这里走到就是路由被改坏了。
|
|
822
|
+
fail(1, '内部错误:care kind=backup-list 须走 dispatchBackup(开库之前)');
|
|
823
|
+
return null;
|
|
731
824
|
}
|
|
732
825
|
case 'home.care.write': {
|
|
733
826
|
const kind = parseCareKind(params, 'init');
|
|
@@ -759,26 +852,17 @@ function dispatch(key, params) {
|
|
|
759
852
|
}
|
|
760
853
|
if (kind === 'init')
|
|
761
854
|
return buildReceipt('已初始化:home.db 幂等(' + statsOverview(handle).items + ' 件)');
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
}
|
|
766
|
-
if (kind === 'import-preview' || kind === 'import') {
|
|
767
|
-
const file = params.file;
|
|
768
|
-
if (!file)
|
|
769
|
-
fail(2, '导入须预告 file');
|
|
770
|
-
if (kind === 'import-preview')
|
|
771
|
-
return buildReceipt('预告通过:' + file + '(确认后 kind=import 落盘)');
|
|
772
|
-
return buildReceipt('已导入:' + file + '(mode=' + String(params.mode ?? 'skip') + ')');
|
|
855
|
+
// #707:backup/export/import-preview/import 归 dispatchBackup(在开库之前走)。
|
|
856
|
+
if (kind === 'backup' || kind === 'export' || kind === 'import-preview' || kind === 'import') {
|
|
857
|
+
fail(1, '内部错误:care kind=' + kind + ' 须走 dispatchBackup(开库之前)');
|
|
773
858
|
}
|
|
774
859
|
fail(2, '未知 care kind:' + kind);
|
|
775
860
|
return null;
|
|
776
861
|
}
|
|
777
|
-
case 'home.help.lookup':
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
return
|
|
781
|
-
}
|
|
862
|
+
case 'home.help.lookup':
|
|
863
|
+
// #190:本键由 `dispatchHelp` 在**开库之前**处理(只读页不建库);走到这里说明 main 的路由被改坏了。
|
|
864
|
+
fail(1, '内部错误:home.help.lookup 须走 dispatchHelp(开库之前)');
|
|
865
|
+
return null;
|
|
782
866
|
default:
|
|
783
867
|
fail(3, '未知 home key:' + key);
|
|
784
868
|
return null;
|
|
@@ -809,7 +893,6 @@ async function main() {
|
|
|
809
893
|
const o = parseArgs(process.argv.slice(2));
|
|
810
894
|
if (!o.key)
|
|
811
895
|
fail(2, '用法:home-cmd-read <home.key> [--params JSON对象] [--html 输出路径] [--timeout 毫秒]');
|
|
812
|
-
preflight();
|
|
813
896
|
let params = {};
|
|
814
897
|
if (o.params !== undefined) {
|
|
815
898
|
try {
|
|
@@ -821,6 +904,19 @@ async function main() {
|
|
|
821
904
|
if (typeof params !== 'object' || params === null || Array.isArray(params))
|
|
822
905
|
fail(2, '--params 须为 JSON 对象');
|
|
823
906
|
}
|
|
907
|
+
// #695:三个配置 key(`home.config.read/write/reset`)在库目录预检与形状表之前拦下——
|
|
908
|
+
// 读写配置不该要求库已配,它们也不进 `HOME_KEY_SHAPES`(不是唤醒词命令,见 `src/cli/config.ts`)。
|
|
909
|
+
if (isConfigKey(o.key)) {
|
|
910
|
+
process.stdout.write(runConfigKey(o.key, params) + '\n');
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
// #706 · 配置体检(`home.config.check`):同样是设置页专用的只读命令,同样在预检之前拦下——
|
|
914
|
+
// 它要报的正是「库在哪、通不通」,不能先要求库目录已配。只读:不建目录、不写文件、不落默认配置。
|
|
915
|
+
if (isHealthCheckKey(o.key)) {
|
|
916
|
+
process.stdout.write(runHealthCheckKey(o.key) + '\n');
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
preflight();
|
|
824
920
|
try {
|
|
825
921
|
homeShapeFor(o.key);
|
|
826
922
|
}
|
|
@@ -834,14 +930,22 @@ async function main() {
|
|
|
834
930
|
}, o.timeout);
|
|
835
931
|
if (typeof timer.unref === 'function')
|
|
836
932
|
timer.unref();
|
|
933
|
+
let delivery;
|
|
837
934
|
try {
|
|
838
|
-
|
|
839
|
-
const
|
|
935
|
+
// #190:`home.help.lookup` 在**开库之前**分派(只读页不建库);#707:备份四支同理(恢复要覆盖库文件)。
|
|
936
|
+
const help = key === 'home.help.lookup' ? dispatchHelp(params) : null;
|
|
937
|
+
const offline = help ? null : dispatchBackup(key, params);
|
|
938
|
+
const env = buildHomeEnvelope(key, help ? help.data : offline !== null ? offline : dispatch(key, params));
|
|
939
|
+
// 分节页(模板填充后):`--html` 支与速查支共用这一处,不抄第二份。
|
|
940
|
+
const sectionHtml = () => {
|
|
941
|
+
const html = fillTemplate(loadTemplate(templateFor(key)), renderEnvelopeHtml(env));
|
|
942
|
+
assertHtmlSize(html);
|
|
943
|
+
return html;
|
|
944
|
+
};
|
|
945
|
+
// `--html <路径>` 既有语义**原样保留**(所有 key 通用的产物出口):写本包 envelope 分节页。
|
|
840
946
|
if (o.html !== undefined) {
|
|
841
947
|
try {
|
|
842
|
-
|
|
843
|
-
assertHtmlSize(html);
|
|
844
|
-
writeFileSync(o.html, html, 'utf8');
|
|
948
|
+
writeFileSync(o.html, sectionHtml(), 'utf8');
|
|
845
949
|
note('HTML 已写:' + o.html + '(utf8)');
|
|
846
950
|
}
|
|
847
951
|
catch (e) {
|
|
@@ -850,7 +954,18 @@ async function main() {
|
|
|
850
954
|
fail(5, 'HTML 写盘失败:' + o.html + '(' + e.message + ')');
|
|
851
955
|
}
|
|
852
956
|
}
|
|
853
|
-
|
|
957
|
+
// #190:本键的产物(缺省=HELP 全壳页自带 html;`mode:"lookup"`=速查表分节页)。
|
|
958
|
+
if (help?.deliver !== undefined) {
|
|
959
|
+
const html = help.deliver.html ?? sectionHtml();
|
|
960
|
+
if (help.deliver.html !== undefined)
|
|
961
|
+
assertHtmlSize(html);
|
|
962
|
+
delivery = deliverHomeHelp({
|
|
963
|
+
targetDir: help.deliver.targetDir, stem: help.deliver.stem, html, reuseMs: help.deliver.reuseMs,
|
|
964
|
+
});
|
|
965
|
+
note('HTML 已写:' + delivery.path + '(' + delivery.bytes + ' 字节 utf8)');
|
|
966
|
+
}
|
|
967
|
+
// #83 口径的顶层追加:`delivery{mode,path,bytes}` **只追加**,既有字段一字不改、序不变。
|
|
968
|
+
process.stdout.write(JSON.stringify(delivery ? { ...env, delivery } : env) + '\n');
|
|
854
969
|
clearTimeout(timer);
|
|
855
970
|
}
|
|
856
971
|
catch (e) {
|
|
@@ -861,6 +976,10 @@ async function main() {
|
|
|
861
976
|
fail(4, e.message);
|
|
862
977
|
if (e instanceof HomeRenderError)
|
|
863
978
|
fail(5, e.message);
|
|
979
|
+
// 配置件(`base-link-core`)的报错本身就是人话(带行号与文件名):归「预检」那一档原样交回。
|
|
980
|
+
// #695:这一档也接住「测试缺隔离」——跑在测试运行器里却没设 `ILIFE_CONFIG_DIR` 时响亮失败。
|
|
981
|
+
if (/(配置文件|配置项|测试缺隔离)/.test(e.message ?? ''))
|
|
982
|
+
fail(1, e.message);
|
|
864
983
|
fail(4, '取数失败:' + e.message);
|
|
865
984
|
}
|
|
866
985
|
}
|