skill-bill 0.1.0 → 0.2.0

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.
Files changed (298) hide show
  1. package/SKILL.md +127 -0
  2. package/dist/.gen-inputs.json +14 -0
  3. package/dist/cli/cmd_read.js +133 -142
  4. package/dist/cli/cmd_read.js.map +1 -1
  5. package/dist/cli/registry.d.ts +15 -0
  6. package/dist/cli/registry.d.ts.map +1 -0
  7. package/dist/cli/registry.js +22 -0
  8. package/dist/cli/registry.js.map +1 -0
  9. package/dist/fetch/db.d.ts +4 -0
  10. package/dist/fetch/db.d.ts.map +1 -1
  11. package/dist/fetch/db.js +12 -0
  12. package/dist/fetch/db.js.map +1 -1
  13. package/dist/fetch/index.d.ts +1 -1
  14. package/dist/fetch/index.d.ts.map +1 -1
  15. package/dist/fetch/index.js +1 -1
  16. package/dist/fetch/index.js.map +1 -1
  17. package/dist/help/index.d.ts +1 -0
  18. package/dist/help/index.d.ts.map +1 -1
  19. package/dist/help/index.js +1 -0
  20. package/dist/help/index.js.map +1 -1
  21. package/dist/help/lookup.d.ts.map +1 -1
  22. package/dist/help/lookup.js +3 -2
  23. package/dist/help/lookup.js.map +1 -1
  24. package/dist/help/writeWire.d.ts +16 -0
  25. package/dist/help/writeWire.d.ts.map +1 -0
  26. package/dist/help/writeWire.js +109 -0
  27. package/dist/help/writeWire.js.map +1 -0
  28. package/dist/output.d.ts +21 -0
  29. package/dist/output.d.ts.map +1 -0
  30. package/dist/output.js +48 -0
  31. package/dist/output.js.map +1 -0
  32. package/dist/policy/category.d.ts +5 -0
  33. package/dist/policy/category.d.ts.map +1 -1
  34. package/dist/policy/category.js +9 -2
  35. package/dist/policy/category.js.map +1 -1
  36. package/dist/policy/index.d.ts +1 -1
  37. package/dist/policy/index.d.ts.map +1 -1
  38. package/dist/policy/index.js +1 -1
  39. package/dist/policy/index.js.map +1 -1
  40. package/dist/policy/record.d.ts +12 -0
  41. package/dist/policy/record.d.ts.map +1 -1
  42. package/dist/policy/record.js +27 -0
  43. package/dist/policy/record.js.map +1 -1
  44. package/dist/policy/wakewords.d.ts +4 -0
  45. package/dist/policy/wakewords.d.ts.map +1 -1
  46. package/dist/policy/wakewords.js +12 -7
  47. package/dist/policy/wakewords.js.map +1 -1
  48. package/dist/query/commands.d.ts +19 -0
  49. package/dist/query/commands.d.ts.map +1 -0
  50. package/dist/query/commands.js +40 -0
  51. package/dist/query/commands.js.map +1 -0
  52. package/dist/query/detail.d.ts +26 -0
  53. package/dist/query/detail.d.ts.map +1 -0
  54. package/dist/query/detail.js +115 -0
  55. package/dist/query/detail.js.map +1 -0
  56. package/dist/query/index.d.ts +13 -0
  57. package/dist/query/index.d.ts.map +1 -0
  58. package/dist/query/index.js +12 -0
  59. package/dist/query/index.js.map +1 -0
  60. package/dist/query/list.d.ts +78 -0
  61. package/dist/query/list.d.ts.map +1 -0
  62. package/dist/query/list.js +110 -0
  63. package/dist/query/list.js.map +1 -0
  64. package/dist/query/read.d.ts +21 -0
  65. package/dist/query/read.d.ts.map +1 -0
  66. package/dist/query/read.js +301 -0
  67. package/dist/query/read.js.map +1 -0
  68. package/dist/record/collect.d.ts +18 -0
  69. package/dist/record/collect.d.ts.map +1 -0
  70. package/dist/record/collect.js +15 -0
  71. package/dist/record/collect.js.map +1 -0
  72. package/dist/record/collectBody.d.ts +29 -0
  73. package/dist/record/collectBody.d.ts.map +1 -0
  74. package/dist/record/collectBody.js +196 -0
  75. package/dist/record/collectBody.js.map +1 -0
  76. package/dist/record/commands.d.ts +11 -0
  77. package/dist/record/commands.d.ts.map +1 -0
  78. package/dist/record/commands.js +22 -0
  79. package/dist/record/commands.js.map +1 -0
  80. package/dist/record/index.d.ts +14 -0
  81. package/dist/record/index.d.ts.map +1 -0
  82. package/dist/record/index.js +12 -0
  83. package/dist/record/index.js.map +1 -0
  84. package/dist/record/receipt.d.ts +16 -0
  85. package/dist/record/receipt.d.ts.map +1 -0
  86. package/dist/record/receipt.js +11 -0
  87. package/dist/record/receipt.js.map +1 -0
  88. package/dist/record/receiptBody.d.ts +5 -0
  89. package/dist/record/receiptBody.d.ts.map +1 -0
  90. package/dist/record/receiptBody.js +112 -0
  91. package/dist/record/receiptBody.js.map +1 -0
  92. package/dist/record/scene-batch.d.ts +3 -0
  93. package/dist/record/scene-batch.d.ts.map +1 -0
  94. package/dist/record/scene-batch.js +211 -0
  95. package/dist/record/scene-batch.js.map +1 -0
  96. package/dist/record/scene-borrow.d.ts +3 -0
  97. package/dist/record/scene-borrow.d.ts.map +1 -0
  98. package/dist/record/scene-borrow.js +285 -0
  99. package/dist/record/scene-borrow.js.map +1 -0
  100. package/dist/record/scene-collect.d.ts +3 -0
  101. package/dist/record/scene-collect.d.ts.map +1 -0
  102. package/dist/record/scene-collect.js +294 -0
  103. package/dist/record/scene-collect.js.map +1 -0
  104. package/dist/record/scene-expense.d.ts +3 -0
  105. package/dist/record/scene-expense.d.ts.map +1 -0
  106. package/dist/record/scene-expense.js +21 -0
  107. package/dist/record/scene-expense.js.map +1 -0
  108. package/dist/record/scene-income.d.ts +3 -0
  109. package/dist/record/scene-income.d.ts.map +1 -0
  110. package/dist/record/scene-income.js +219 -0
  111. package/dist/record/scene-income.js.map +1 -0
  112. package/dist/record/scene-installment.d.ts +3 -0
  113. package/dist/record/scene-installment.d.ts.map +1 -0
  114. package/dist/record/scene-installment.js +248 -0
  115. package/dist/record/scene-installment.js.map +1 -0
  116. package/dist/record/scene-lend.d.ts +3 -0
  117. package/dist/record/scene-lend.d.ts.map +1 -0
  118. package/dist/record/scene-lend.js +273 -0
  119. package/dist/record/scene-lend.js.map +1 -0
  120. package/dist/record/scene-photo.d.ts +3 -0
  121. package/dist/record/scene-photo.d.ts.map +1 -0
  122. package/dist/record/scene-photo.js +262 -0
  123. package/dist/record/scene-photo.js.map +1 -0
  124. package/dist/record/scene-plain.d.ts +3 -0
  125. package/dist/record/scene-plain.d.ts.map +1 -0
  126. package/dist/record/scene-plain.js +221 -0
  127. package/dist/record/scene-plain.js.map +1 -0
  128. package/dist/record/scene-refund.d.ts +3 -0
  129. package/dist/record/scene-refund.d.ts.map +1 -0
  130. package/dist/record/scene-refund.js +281 -0
  131. package/dist/record/scene-refund.js.map +1 -0
  132. package/dist/record/scene-reimburse-done.d.ts +3 -0
  133. package/dist/record/scene-reimburse-done.d.ts.map +1 -0
  134. package/dist/record/scene-reimburse-done.js +288 -0
  135. package/dist/record/scene-reimburse-done.js.map +1 -0
  136. package/dist/record/scene-reimburse.d.ts +3 -0
  137. package/dist/record/scene-reimburse.d.ts.map +1 -0
  138. package/dist/record/scene-reimburse.js +224 -0
  139. package/dist/record/scene-reimburse.js.map +1 -0
  140. package/dist/record/scene-repay.d.ts +3 -0
  141. package/dist/record/scene-repay.d.ts.map +1 -0
  142. package/dist/record/scene-repay.js +294 -0
  143. package/dist/record/scene-repay.js.map +1 -0
  144. package/dist/record/scene-restore.d.ts +3 -0
  145. package/dist/record/scene-restore.d.ts.map +1 -0
  146. package/dist/record/scene-restore.js +250 -0
  147. package/dist/record/scene-restore.js.map +1 -0
  148. package/dist/record/scene-undo.d.ts +3 -0
  149. package/dist/record/scene-undo.d.ts.map +1 -0
  150. package/dist/record/scene-undo.js +262 -0
  151. package/dist/record/scene-undo.js.map +1 -0
  152. package/dist/record/scene-update.d.ts +3 -0
  153. package/dist/record/scene-update.d.ts.map +1 -0
  154. package/dist/record/scene-update.js +249 -0
  155. package/dist/record/scene-update.js.map +1 -0
  156. package/dist/record/scene.d.ts +79 -0
  157. package/dist/record/scene.d.ts.map +1 -0
  158. package/dist/record/scene.js +39 -0
  159. package/dist/record/scene.js.map +1 -0
  160. package/dist/record/slots.d.ts +16 -0
  161. package/dist/record/slots.d.ts.map +1 -0
  162. package/dist/record/slots.js +72 -0
  163. package/dist/record/slots.js.map +1 -0
  164. package/dist/record/write.d.ts +9 -0
  165. package/dist/record/write.d.ts.map +1 -0
  166. package/dist/record/write.js +186 -0
  167. package/dist/record/write.js.map +1 -0
  168. package/dist/render/envelope.d.ts.map +1 -1
  169. package/dist/render/envelope.js +21 -8
  170. package/dist/render/envelope.js.map +1 -1
  171. package/dist/render/errors.d.ts +1 -1
  172. package/dist/render/errors.d.ts.map +1 -1
  173. package/dist/render/errors.js.map +1 -1
  174. package/dist/render/helpFile.d.ts +85 -0
  175. package/dist/render/helpFile.d.ts.map +1 -0
  176. package/dist/render/helpFile.js +110 -0
  177. package/dist/render/helpFile.js.map +1 -0
  178. package/dist/render/helpPaths.d.ts +19 -0
  179. package/dist/render/helpPaths.d.ts.map +1 -0
  180. package/dist/render/helpPaths.js +19 -0
  181. package/dist/render/helpPaths.js.map +1 -0
  182. package/dist/render/html.js +1 -1
  183. package/dist/render/html.js.map +1 -1
  184. package/dist/render/index.d.ts +5 -1
  185. package/dist/render/index.d.ts.map +1 -1
  186. package/dist/render/index.js +4 -1
  187. package/dist/render/index.js.map +1 -1
  188. package/dist/render/templates.js +1 -1
  189. package/dist/render/templates.js.map +1 -1
  190. package/dist/render/views.d.ts +0 -22
  191. package/dist/render/views.d.ts.map +1 -1
  192. package/dist/render/views.js +1 -16
  193. package/dist/render/views.js.map +1 -1
  194. package/dist/shared/blockedSlots.d.ts +39 -0
  195. package/dist/shared/blockedSlots.d.ts.map +1 -0
  196. package/dist/shared/blockedSlots.js +105 -0
  197. package/dist/shared/blockedSlots.js.map +1 -0
  198. package/dist/shared/candidatePick.d.ts +50 -0
  199. package/dist/shared/candidatePick.d.ts.map +1 -0
  200. package/dist/shared/candidatePick.js +115 -0
  201. package/dist/shared/candidatePick.js.map +1 -0
  202. package/dist/shared/collectFrame.d.ts +15 -0
  203. package/dist/shared/collectFrame.d.ts.map +1 -0
  204. package/dist/shared/collectFrame.js +34 -0
  205. package/dist/shared/collectFrame.js.map +1 -0
  206. package/dist/shared/commandSpec.d.ts +72 -0
  207. package/dist/shared/commandSpec.d.ts.map +1 -0
  208. package/dist/shared/commandSpec.js +2 -0
  209. package/dist/shared/commandSpec.js.map +1 -0
  210. package/dist/shared/copyArea.d.ts +51 -0
  211. package/dist/shared/copyArea.d.ts.map +1 -0
  212. package/dist/shared/copyArea.js +122 -0
  213. package/dist/shared/copyArea.js.map +1 -0
  214. package/dist/shared/diffTable.d.ts +26 -0
  215. package/dist/shared/diffTable.d.ts.map +1 -0
  216. package/dist/shared/diffTable.js +77 -0
  217. package/dist/shared/diffTable.js.map +1 -0
  218. package/dist/shared/docPage.d.ts +17 -0
  219. package/dist/shared/docPage.d.ts.map +1 -0
  220. package/dist/shared/docPage.js +111 -0
  221. package/dist/shared/docPage.js.map +1 -0
  222. package/dist/shared/duplicateNote.d.ts +40 -0
  223. package/dist/shared/duplicateNote.d.ts.map +1 -0
  224. package/dist/shared/duplicateNote.js +96 -0
  225. package/dist/shared/duplicateNote.js.map +1 -0
  226. package/dist/shared/emptyNote.d.ts +15 -0
  227. package/dist/shared/emptyNote.d.ts.map +1 -0
  228. package/dist/shared/emptyNote.js +27 -0
  229. package/dist/shared/emptyNote.js.map +1 -0
  230. package/dist/shared/errorReceipt.d.ts +21 -0
  231. package/dist/shared/errorReceipt.d.ts.map +1 -0
  232. package/dist/shared/errorReceipt.js +37 -0
  233. package/dist/shared/errorReceipt.js.map +1 -0
  234. package/dist/shared/flowSteps.d.ts +25 -0
  235. package/dist/shared/flowSteps.d.ts.map +1 -0
  236. package/dist/shared/flowSteps.js +49 -0
  237. package/dist/shared/flowSteps.js.map +1 -0
  238. package/dist/shared/installmentPreview.d.ts +23 -0
  239. package/dist/shared/installmentPreview.d.ts.map +1 -0
  240. package/dist/shared/installmentPreview.js +144 -0
  241. package/dist/shared/installmentPreview.js.map +1 -0
  242. package/dist/shared/outsideScan.d.ts +34 -0
  243. package/dist/shared/outsideScan.d.ts.map +1 -0
  244. package/dist/shared/outsideScan.js +116 -0
  245. package/dist/shared/outsideScan.js.map +1 -0
  246. package/dist/shared/pageIdentity.d.ts +30 -0
  247. package/dist/shared/pageIdentity.d.ts.map +1 -0
  248. package/dist/shared/pageIdentity.js +33 -0
  249. package/dist/shared/pageIdentity.js.map +1 -0
  250. package/dist/shared/pageShell.d.ts +24 -0
  251. package/dist/shared/pageShell.d.ts.map +1 -0
  252. package/dist/shared/pageShell.js +42 -0
  253. package/dist/shared/pageShell.js.map +1 -0
  254. package/dist/shared/photoEscape.d.ts +53 -0
  255. package/dist/shared/photoEscape.d.ts.map +1 -0
  256. package/dist/shared/photoEscape.js +139 -0
  257. package/dist/shared/photoEscape.js.map +1 -0
  258. package/dist/shared/prefillNote.d.ts +21 -0
  259. package/dist/shared/prefillNote.d.ts.map +1 -0
  260. package/dist/shared/prefillNote.js +78 -0
  261. package/dist/shared/prefillNote.js.map +1 -0
  262. package/dist/shared/receiptParts.d.ts +12 -0
  263. package/dist/shared/receiptParts.d.ts.map +1 -0
  264. package/dist/shared/receiptParts.js +38 -0
  265. package/dist/shared/receiptParts.js.map +1 -0
  266. package/dist/shared/recentPicks.d.ts +26 -0
  267. package/dist/shared/recentPicks.d.ts.map +1 -0
  268. package/dist/shared/recentPicks.js +41 -0
  269. package/dist/shared/recentPicks.js.map +1 -0
  270. package/dist/shared/recordPicker.d.ts +32 -0
  271. package/dist/shared/recordPicker.d.ts.map +1 -0
  272. package/dist/shared/recordPicker.js +201 -0
  273. package/dist/shared/recordPicker.js.map +1 -0
  274. package/dist/shared/rowEditorTable.d.ts +31 -0
  275. package/dist/shared/rowEditorTable.d.ts.map +1 -0
  276. package/dist/shared/rowEditorTable.js +103 -0
  277. package/dist/shared/rowEditorTable.js.map +1 -0
  278. package/dist/shared/summaryRow.d.ts +44 -0
  279. package/dist/shared/summaryRow.d.ts.map +1 -0
  280. package/dist/shared/summaryRow.js +82 -0
  281. package/dist/shared/summaryRow.js.map +1 -0
  282. package/dist/shared/typeBadge.d.ts +21 -0
  283. package/dist/shared/typeBadge.d.ts.map +1 -0
  284. package/dist/shared/typeBadge.js +68 -0
  285. package/dist/shared/typeBadge.js.map +1 -0
  286. package/dist/shared/userWording.d.ts +54 -0
  287. package/dist/shared/userWording.d.ts.map +1 -0
  288. package/dist/shared/userWording.js +131 -0
  289. package/dist/shared/userWording.js.map +1 -0
  290. package/dist/shared/writeParts.d.ts +62 -0
  291. package/dist/shared/writeParts.d.ts.map +1 -0
  292. package/dist/shared/writeParts.js +39 -0
  293. package/dist/shared/writeParts.js.map +1 -0
  294. package/dist/triggers/wake-assets.d.ts +37 -0
  295. package/dist/triggers/wake-assets.d.ts.map +1 -0
  296. package/dist/triggers/wake-assets.js +951 -0
  297. package/dist/triggers/wake-assets.js.map +1 -0
  298. package/package.json +10 -4
package/SKILL.md ADDED
@@ -0,0 +1,127 @@
1
+ ---
2
+ name: skill-bill
3
+ description: "「饼干记账HELP」→bill.help.lookup 缺省落一份老技能同款 HELP 文件(7 域/74 场景,与卡路里同一套共享 help 模板);唯一出口 bill-cmd-read。触发词:饼干记账 HELP、饼干记账帮助、查帮助、能做什么、记支出、记收入、拍账单、批量录入、记退款、记报销、报销到账、记借出、记借入、记收回、记偿还、记分期、记一笔、改记录、撤销、恢复、查今天、查昨天、查某天、查最近、查账单、查周、查月、查区间、查分类、查账户、查账本、搜备注、查标签、查欠款、查待报销、查分期、查账单详情、看月度、看年度、看总览、看周报、看分类、看账户、看账本、看结构、做统计、看对比、看双区间、看同比、看分类对比、看趋势、看分类趋势、看大额、看高频、看分布、看活跃、看洞察、看异常、看借贷、看报销、看分期、看退款、设定预算、设定目标、看预算、看目标、新增账户、改账户、账户转账、看账户汇总、买东西、吃饭、初始化、初始化状态、备份、恢复备份、导入"
4
+ ---
5
+
6
+ # 饼干记账(bill)SKILL
7
+
8
+ 本地记账:记支出/收入、查今天/区间/分类/标签、看月度/对比/趋势、预算目标、账户转账、买东西/吃饭联动、初始化备份导入。唯一出口 `bill-cmd-read <bill.key>`,argv+JSON(stdout)+exit,非 0 走 stderr。写走 receipt(直通即真相)。
9
+
10
+ ## 快速开始
11
+
12
+ ```sh
13
+ bill-cmd-read bill.help.lookup # 说「饼干记账help」就这一条:落一份 HELP 文件
14
+ bill-cmd-read bill.record.today --params '{"date":"2026-09-06"}'
15
+ bill-cmd-read bill.record.add --params '{"category":"餐饮/外卖/午餐","amount":-35,"note":"午饭"}'
16
+ ```
17
+
18
+ ## 口径
19
+
20
+ - 分类:支出 L1 10 个 + 收入 L1 6 个(餐饮/居家/穿着/出行/玩乐/学习/健康/社交/宠物/其他)+ 借贷/分期/转账隔离;amount 符号即分类依据(支出负/收入正),不单设 type 列;至多 L1/L2/L3 三级,旧数据无 / 视为 L1。
21
+ - 时间:YYYY-MM-DD HH:mm:ss(日期缺时分补 12:00:00);查区间须 start/end 同给;搜备注空查询不返全量;#tag 精确匹配(#旅行计划不命中 #旅行)。
22
+ - 软删:撤销=deleted_at 置 now,恢复=置 NULL(G7 契约);转账=双笔 #转账(转出支出 + 转入收入,账本 转账,不入收支统计,余额含转账)。
23
+ - 预算覆盖:同月同分类已存在默认拒绝(conflict),确认后加 --force 重跑;导入列映射 dry-run 先行;拍账单图片识别以外置为准。
24
+ - 坏输入与缺失一律阻断(exit 2/4),不返空数组冒充正常;账单为 record.add 子功能(receipt 回执带复制 prompt)。
25
+
26
+ ## 联动速查(构建期注入,勿手改)
27
+
28
+ <!-- HELP-AUTO-START -->
29
+ | 唤醒词 | key | shape | 例 |
30
+ |---|---|---|---|
31
+ | 饼干记账 HELP | bill.help.lookup | list | `bill-cmd-read bill.help.lookup` |
32
+ | 饼干记账帮助 | bill.help.lookup | list | `bill-cmd-read bill.help.lookup` |
33
+ | 查帮助 | bill.help.lookup | list | `bill-cmd-read bill.help.lookup` |
34
+ | 能做什么 | bill.help.lookup | list | `bill-cmd-read bill.help.lookup` |
35
+ | 记支出 | bill.record.add | receipt | `bill-cmd-read bill.record.add --params '{"kind":"expense","category":"餐饮","amount":-35,"note":"午饭"}'` |
36
+ | 记收入 | bill.record.add | receipt | `bill-cmd-read bill.record.add --params '{"kind":"income","category":"餐饮","amount":-35,"note":"午饭"}'` |
37
+ | 拍账单 | bill.record.add | receipt | `bill-cmd-read bill.record.add --params '{"kind":"photo","category":"餐饮","amount":-35,"note":"午饭"}'` |
38
+ | 批量录入 | bill.record.add | receipt | `bill-cmd-read bill.record.add --params '{"kind":"batch","category":"餐饮","amount":-35,"note":"午饭"}'` |
39
+ | 记退款 | bill.record.add | receipt | `bill-cmd-read bill.record.add --params '{"kind":"refund","category":"餐饮","amount":-35,"note":"午饭"}'` |
40
+ | 记报销 | bill.record.add | receipt | `bill-cmd-read bill.record.add --params '{"kind":"reimburse","category":"餐饮","amount":-35,"note":"午饭"}'` |
41
+ | 报销到账 | bill.record.add | receipt | `bill-cmd-read bill.record.add --params '{"kind":"reimburse-done","category":"餐饮","amount":-35,"note":"午饭"}'` |
42
+ | 记借出 | bill.record.add | receipt | `bill-cmd-read bill.record.add --params '{"kind":"lend","category":"餐饮","amount":-35,"note":"午饭"}'` |
43
+ | 记借入 | bill.record.add | receipt | `bill-cmd-read bill.record.add --params '{"kind":"borrow","category":"餐饮","amount":-35,"note":"午饭"}'` |
44
+ | 记收回 | bill.record.add | receipt | `bill-cmd-read bill.record.add --params '{"kind":"collect","category":"餐饮","amount":-35,"note":"午饭"}'` |
45
+ | 记偿还 | bill.record.add | receipt | `bill-cmd-read bill.record.add --params '{"kind":"repay","category":"餐饮","amount":-35,"note":"午饭"}'` |
46
+ | 记分期 | bill.record.add | receipt | `bill-cmd-read bill.record.add --params '{"kind":"installment","category":"餐饮","amount":-35,"note":"午饭"}'` |
47
+ | 记一笔 | bill.record.add | receipt | `bill-cmd-read bill.record.add --params '{"category":"餐饮","amount":-35,"note":"午饭"}'` |
48
+ | 改记录 | bill.record.update | receipt | `bill-cmd-read bill.record.update --params '{"id":1}'` |
49
+ | 撤销 | bill.record.update | receipt | `bill-cmd-read bill.record.update --params '{"op":"undo","id":1}'` |
50
+ | 恢复 | bill.record.update | receipt | `bill-cmd-read bill.record.update --params '{"op":"restore","id":1}'` |
51
+ | 查今天 | bill.record.today | list | `bill-cmd-read bill.record.today` |
52
+ | 查昨天 | bill.record.today | list | `bill-cmd-read bill.record.today --params '{"date":"yesterday"}'` |
53
+ | 查某天 | bill.record.today | list | `bill-cmd-read bill.record.today --params '{"date":"2026-09-06"}'` |
54
+ | 查最近 | bill.record.today | list | `bill-cmd-read bill.record.today --params '{"recent":true}'` |
55
+ | 查账单 | bill.record.today | list | `bill-cmd-read bill.record.today` |
56
+ | 查周 | bill.record.range | list | `bill-cmd-read bill.record.range --params '{"range":"week"}'` |
57
+ | 查月 | bill.record.range | list | `bill-cmd-read bill.record.range --params '{"range":"month"}'` |
58
+ | 查区间 | bill.record.range | list | `bill-cmd-read bill.record.range --params '{"start":"2026-09-01","end":"2026-09-01"}'` |
59
+ | 查分类 | bill.record.range | list | `bill-cmd-read bill.record.range --params '{"category":"餐饮"}'` |
60
+ | 查账户 | bill.record.range | list | `bill-cmd-read bill.record.range --params '{"account":"支付宝"}'` |
61
+ | 查账本 | bill.record.range | list | `bill-cmd-read bill.record.range --params '{"ledger":"生活"}'` |
62
+ | 搜备注 | bill.record.search | list | `bill-cmd-read bill.record.search --params '{"q":"午饭"}'` |
63
+ | 查标签 | bill.record.search | list | `bill-cmd-read bill.record.search --params '{"kind":"tag","tag":"旅行"}'` |
64
+ | 查欠款 | bill.record.search | list | `bill-cmd-read bill.record.search --params '{"kind":"debt"}'` |
65
+ | 查待报销 | bill.record.search | list | `bill-cmd-read bill.record.search --params '{"kind":"reimburse"}'` |
66
+ | 查分期 | bill.record.search | list | `bill-cmd-read bill.record.search --params '{"kind":"installment"}'` |
67
+ | 查账单详情 | bill.record.detail | detail | `bill-cmd-read bill.record.detail --params '{"id":1}'` |
68
+ | 看月度 | bill.analysis.overview | stat | `bill-cmd-read bill.analysis.overview --params '{"kind":"monthly"}'` |
69
+ | 看年度 | bill.analysis.overview | stat | `bill-cmd-read bill.analysis.overview --params '{"kind":"yearly"}'` |
70
+ | 看总览 | bill.analysis.overview | stat | `bill-cmd-read bill.analysis.overview --params '{"kind":"overview"}'` |
71
+ | 看周报 | bill.analysis.overview | stat | `bill-cmd-read bill.analysis.overview --params '{"kind":"week"}'` |
72
+ | 看分类 | bill.analysis.overview | stat | `bill-cmd-read bill.analysis.overview --params '{"kind":"category"}'` |
73
+ | 看账户 | bill.analysis.overview | stat | `bill-cmd-read bill.analysis.overview --params '{"kind":"account"}'` |
74
+ | 看账本 | bill.analysis.overview | stat | `bill-cmd-read bill.analysis.overview --params '{"kind":"ledger"}'` |
75
+ | 看结构 | bill.analysis.overview | stat | `bill-cmd-read bill.analysis.overview --params '{"kind":"structure"}'` |
76
+ | 做统计 | bill.analysis.overview | stat | `bill-cmd-read bill.analysis.overview --params '{"kind":"stats"}'` |
77
+ | 看对比 | bill.analysis.compare | analysis | `bill-cmd-read bill.analysis.compare --params '{"kind":"period"}'` |
78
+ | 看双区间 | bill.analysis.compare | analysis | `bill-cmd-read bill.analysis.compare --params '{"kind":"range"}'` |
79
+ | 看同比 | bill.analysis.compare | analysis | `bill-cmd-read bill.analysis.compare --params '{"kind":"yoy"}'` |
80
+ | 看分类对比 | bill.analysis.compare | analysis | `bill-cmd-read bill.analysis.compare --params '{"kind":"category"}'` |
81
+ | 看趋势 | bill.analysis.trend | analysis | `bill-cmd-read bill.analysis.trend --params '{"kind":"trend"}'` |
82
+ | 看分类趋势 | bill.analysis.trend | analysis | `bill-cmd-read bill.analysis.trend --params '{"kind":"category"}'` |
83
+ | 看大额 | bill.analysis.trend | analysis | `bill-cmd-read bill.analysis.trend --params '{"kind":"top"}'` |
84
+ | 看高频 | bill.analysis.trend | analysis | `bill-cmd-read bill.analysis.trend --params '{"kind":"frequent"}'` |
85
+ | 看分布 | bill.analysis.trend | analysis | `bill-cmd-read bill.analysis.trend --params '{"kind":"distribution"}'` |
86
+ | 看活跃 | bill.analysis.trend | analysis | `bill-cmd-read bill.analysis.trend --params '{"kind":"activity"}'` |
87
+ | 看洞察 | bill.analysis.trend | analysis | `bill-cmd-read bill.analysis.trend --params '{"kind":"insight"}'` |
88
+ | 看异常 | bill.analysis.trend | analysis | `bill-cmd-read bill.analysis.trend --params '{"kind":"anomaly"}'` |
89
+ | 看借贷 | bill.analysis.trend | analysis | `bill-cmd-read bill.analysis.trend --params '{"kind":"debt"}'` |
90
+ | 看报销 | bill.analysis.trend | analysis | `bill-cmd-read bill.analysis.trend --params '{"kind":"reimburse"}'` |
91
+ | 看分期 | bill.analysis.trend | analysis | `bill-cmd-read bill.analysis.trend --params '{"kind":"installment"}'` |
92
+ | 看退款 | bill.analysis.trend | analysis | `bill-cmd-read bill.analysis.trend --params '{"kind":"refund"}'` |
93
+ | 设定预算 | bill.goal.write | receipt | `bill-cmd-read bill.goal.write --params '{"op":"set-budget","amount":35}'` |
94
+ | 设定目标 | bill.goal.write | receipt | `bill-cmd-read bill.goal.write --params '{"op":"set-saving","name":"招行卡","amount":35}'` |
95
+ | 看预算 | bill.goal.query | list | `bill-cmd-read bill.goal.query --params '{"op":"budget"}'` |
96
+ | 看目标 | bill.goal.query | list | `bill-cmd-read bill.goal.query --params '{"op":"saving"}'` |
97
+ | 新增账户 | bill.account.write | receipt | `bill-cmd-read bill.account.write --params '{"op":"add","name":"招行卡"}'` |
98
+ | 改账户 | bill.account.write | receipt | `bill-cmd-read bill.account.write --params '{"op":"update","name":"招行卡"}'` |
99
+ | 账户转账 | bill.account.write | receipt | `bill-cmd-read bill.account.write --params '{"op":"transfer","amount":35,"from":"支付宝","to":"招行卡"}'` |
100
+ | 看账户汇总 | bill.account.query | list | `bill-cmd-read bill.account.query` |
101
+ | 买东西 | bill.link.submit | receipt | `bill-cmd-read bill.link.submit --params '{"scene":"purchase"}'` |
102
+ | 吃饭 | bill.link.submit | receipt | `bill-cmd-read bill.link.submit --params '{"scene":"meal"}'` |
103
+ | 初始化 | bill.setup.run | receipt | `bill-cmd-read bill.setup.run --params '{"op":"init"}'` |
104
+ | 初始化状态 | bill.setup.run | receipt | `bill-cmd-read bill.setup.run --params '{"op":"init-status"}'` |
105
+ | 备份 | bill.setup.run | receipt | `bill-cmd-read bill.setup.run --params '{"op":"backup-create"}'` |
106
+ | 恢复备份 | bill.setup.run | receipt | `bill-cmd-read bill.setup.run --params '{"op":"restore"}'` |
107
+ | 导入 | bill.setup.run | receipt | `bill-cmd-read bill.setup.run --params '{"op":"import","file":"bills.csv"}'` |
108
+
109
+ 相关场景:bill.account.query、bill.account.write、bill.analysis.compare、bill.analysis.overview、bill.analysis.trend、bill.goal.query、bill.goal.write、bill.help.lookup、bill.link.submit、bill.record.add、bill.record.detail、bill.record.range、bill.record.search、bill.record.today、bill.record.update、bill.setup.run(16 联动,key 字符串后续票落表时冻结)。
110
+ <!-- HELP-AUTO-END -->
111
+
112
+ ## HELP 交付(说「饼干记账help」或「查帮助」走这里)
113
+
114
+ - **缺省就是交付物**:`bill-cmd-read bill.help.lookup` **原样调用**即落一份能打开的 HELP 文件——`<SKILLS_DB_PATH>/biscuit_accountant_html/饼干记账_HELP_<YYYYMMDD_HHMMSS>.html`(7 域/74 场景,与卡路里同一套共享 help 模板)。stdout 的 `delivery.path` 是**绝对路径**,`delivery.bytes` 是文件字节数;回话就把这个路径给用户(回执即真相)。
115
+ **完成标准**:`delivery.path` 指向的文件真的存在,且大小=`delivery.bytes`。
116
+ - **要全量速查表才加参数**:`--params '{"mode":"lookup"}'` 出 77 条唤醒词速查表,落同目录 `饼干记账_速查表_<时间戳>.html`(与 HELP 文件分名,两份产物不撞车);这一支同样吃下面的复用窗口。
117
+ - **反复读不再涨目录(#245)**:同一主体**一天内只留一份**——24 小时内再读就**复用已有那份**(不新建、不改写;回执给的就是它)。要别的窗口给 `--params '{"reuseHours":3}'`(小时);要**每次都要一份最新的**给 `{"reuseHours":0}`。窗口内若已有一份、而你刚改过 HELP 内容,那份旧产物**不会被自动刷新**(窗口语义如此)——真要新的就带 `reuseHours:0`。
118
+ - **要现找才加参数**:`--params '{"q":"查今天"}'` 回命中条目(只出 JSON,不落盘;要落盘就给 `--html`)。`q` 与 `mode` 互斥、`mode` 只认 `lookup`,违反即 exit 2。
119
+ - **`--html <路径>`=显式落点**:逐字使用、覆盖写、缺父目录自动建(不参与同秒 `_N` 递补);其它 15 条命令的 `--html` 语义不变(仍写收据页)。
120
+ - **边界**:面板/侧栏的 HELP 入口不在本技能范围(属插件的桥/面板那条线);不写固定名镜像(用户 Q10=A)。
121
+ - 触发词总表见上「联动速查」构建期注入块(`<!-- HELP-AUTO-START/END -->`,由 `scripts/build-help.mjs` 重写,勿手改)。
122
+
123
+ ## 环境与出 scope
124
+
125
+ - SKILLS_DB_PATH(必设,无默认值)+ BILL_FORCE_PROD 哨兵(非 tmp 写库须 opt-in),见 docs/env.md。
126
+ - --html 套模板输出完整收据页(section 片段经 CONTENT 注入对应模板,非片段直写;超体积阻断);`bill.help.lookup` 的 `--html` 改写到该键的产物(HELP 全页/速查页),语义见上「HELP 交付」节。
127
+ - 出 scope:定时任务(老家零定时代码)、面板(二期单 MAP)、本技能外联动登记(combos.yaml 一律不碰,走后续票;link 跨技能仅复制 prompt);真实数据禁迁,测试 tmp 隔离;Python 老家只读对照。
@@ -0,0 +1,14 @@
1
+ {
2
+ "version": 2,
3
+ "note": "饼干记账生成器输入的内容印记:每条记 {src, dist} 配对,由 `pnpm build` 调 `--stamp` 写入;`pnpm gen`/`gen:check` 按它判陈旧、按现场配对门判脱钩。",
4
+ "files": {
5
+ "query/commands.ts": {
6
+ "src": "3b0adbca90a6b756ae797ec9ff1c9fbd0972614b94708550acdb5ae156262011",
7
+ "dist": "9a8e4bd18e192f67b053b9328e3cf14b9442dae4a936c14f5d49db97e7e10889"
8
+ },
9
+ "record/commands.ts": {
10
+ "src": "e10e943942ea01803f0aa6b0f21b34bc2967a94a6e5c935b92d97838a506608b",
11
+ "dist": "c31d83861dc651df3ec6ee685b2c5b3640347b0adcc4f9c52b8e846056b492d8"
12
+ }
13
+ }
14
+ }
@@ -2,12 +2,17 @@
2
2
  // 饼干记账唯一出口 cmd_read:argv+JSON(stdout)+exit;非 0 走 stderr;超时 terminate+TOAST 降级标记。
3
3
  // 退出码对齐 skilllink 冻结:0 ok;1 预检;2 用法/参数;3 key;4 取数/超时;5 envelope/渲染/落盘。
4
4
  // stdout 纯净:成功只打 envelope JSON 一行。写走 receipt(直通即真相)。
5
- import { writeFileSync, readFileSync, copyFileSync, readdirSync, existsSync, mkdirSync } from 'node:fs';
6
- import { join, basename } from 'node:path';
7
- import { BillFetchError, BillPolicyError, resolveDbPath, resolveGoalsPath, assertWritablePath, openBillDb, closeBillDb, fetchAll, listToday, listRange, getById, searchKeyword, listByTag, addBill, updateBill, undoBill, restoreBill, loadGoals, saveGoals, } from '../fetch/index.js';
8
- import { validateAddInput, validateUpdateInput, needId, parseRecordOp, resolveQueryDate, resolveRange, parseOverviewKind, parseCompareKind, parseTrendKind, parseGoalOp, validateSetBudget, validateSetSaving, parseAccountOp, needName, validateTransfer, TRANSFER_OUT_CATEGORY, TRANSFER_IN_CATEGORY, TRANSFER_LEDGER, validateCategory, } from '../policy/index.js';
9
- import { billShapeFor, buildBillEnvelope, renderEnvelopeHtml, assertHtmlSize, templateFor, loadTemplate, fillTemplate, toBillItem, buildRecordToday, buildRecordRange, buildRecordSearch, buildRecordDetail, buildRecordReceipt, buildOverview, buildCompare, buildTrend, buildGoalQuery, buildAccountQuery, buildHelpItems, BillRenderError, } from '../render/index.js';
5
+ import { readFileSync, copyFileSync, readdirSync, existsSync, mkdirSync } from 'node:fs';
6
+ import { join, basename, resolve } from 'node:path';
7
+ import { BillFetchError, BillPolicyError, resolveDbPath, resolveDbDir, resolveGoalsPath, assertWritablePath, openBillDb, closeBillDb, fetchAll, listRange, addBill, loadGoals, saveGoals, } from '../fetch/index.js';
8
+ import { resolveRange, parseOverviewKind, parseCompareKind, parseTrendKind, monthRange, parseGoalOp, validateSetBudget, validateSetSaving, parseAccountOp, needName, validateTransfer, TRANSFER_OUT_CATEGORY, TRANSFER_IN_CATEGORY, TRANSFER_LEDGER, validateCategory, } from '../policy/index.js';
9
+ import { billShapeFor, buildBillEnvelope, renderEnvelopeHtml, assertHtmlSize, templateFor, loadTemplate, fillTemplate, toBillItem, buildRecordReceipt, buildOverview, buildCompare, buildTrend, buildGoalQuery, buildAccountQuery, buildHelpItems, buildHelpIndex, buildHelpFileData, renderHelpFileHtml, HELP_FILE_STEM, LOOKUP_FILE_STEM, HELP_HTML_DIR_NAME, BillRenderError, } from '../render/index.js';
10
+ import { deliverHtml } from '../output.js';
11
+ import { helpReuseWindowOf } from 'base-paint/save-html';
10
12
  import { buildHelpLookup } from '../help/index.js';
13
+ import { REGISTRY } from './registry.js';
14
+ import { runRecordWrite } from '../record/index.js';
15
+ import { runQueryRead } from '../query/index.js';
11
16
  const DEFAULT_TIMEOUT_MS = 30000;
12
17
  function fail(code, msg) { console.error('ERR ' + code + ': ' + msg); process.exit(code); }
13
18
  function toast(msg) { console.error('TOAST: ' + msg); }
@@ -21,33 +26,98 @@ function preflight() {
21
26
  fail(1, 'SKILLS_DB_PATH 未设置(无默认值,必设)');
22
27
  return p;
23
28
  }
24
- function monthRange(month) {
25
- const [y, m] = month.split('-').map(Number);
26
- const last = new Date(y, m, 0).getDate();
27
- const dd = String(last).padStart(2, '0');
28
- return { start: month + '-01', end: month + '-' + dd };
29
+ /** 吃复用窗口的 HELP 产物名(本技能自己的两个主体)。#245:判据**按落点名**而不是按 key——
30
+ * `bill.help.lookup` 这条命令下挂着两种产物(HELP 文件与速查表),两种都算「反复读的 HELP 产物」;
31
+ * 而 `--html` 那支是用户逐字指定的落点(共用件的 `file` 口子),本来不吃复用。 */
32
+ const HELP_REUSE_STEMS = [HELP_FILE_STEM, LOOKUP_FILE_STEM];
33
+ /** 本次交付吃不吃复用窗口 ⇒ 给出窗口毫秒数(不吃 = `undefined`,交付退回「独占创建 + 递补」老口径)。
34
+ *
35
+ * 窗口来自 `--params` 的 `reuseHours`(小时):不给=缺省一天(`HELP_REUSE_DEFAULT_HOURS`)、
36
+ * `0`=每次都落新的、正数=该窗口。换算与校验都在共用件,坏参抛 `RangeError` ⇒ 用共用件的
37
+ * `helpReuseWindowOf` 把它翻成出口的「参数错」那一档(exit 2)——五家技能同一档,坏参绝不静默当 0。 */
38
+ const windowOf = helpReuseWindowOf((m) => fail(2, m));
39
+ function windowForHelpDelivery(stem, params) {
40
+ if (!HELP_REUSE_STEMS.includes(stem))
41
+ return undefined;
42
+ return windowOf(params);
29
43
  }
30
- function yesterdayStr() {
31
- const d = new Date();
32
- d.setDate(d.getDate() - 1);
33
- return d.toISOString().slice(0, 10);
44
+ /** 初始化状态:DB **文件存在**=已初始化(照老 `render_help._is_initialized`);
45
+ * 判定本身异常 ⇒ `false`=横幅照显(fail-open,理由见 render/helpFile.ts 头注释)。 */
46
+ function helpInitialized() {
47
+ try {
48
+ return existsSync(resolveDbPath());
49
+ }
50
+ catch {
51
+ return false;
52
+ }
34
53
  }
35
- function weekRange() {
54
+ function dispatchHelp(params) {
55
+ const dbDir = resolveDbDir();
36
56
  const now = new Date();
37
- const day = (now.getDay() + 6) % 7;
38
- const mon = new Date(now);
39
- mon.setDate(now.getDate() - day);
40
- const sun = new Date(mon);
41
- sun.setDate(mon.getDate() + 6);
42
- return { start: mon.toISOString().slice(0, 10), end: sun.toISOString().slice(0, 10) };
57
+ const mode = params.mode === undefined ? undefined : String(params.mode);
58
+ const q = params.q === undefined ? undefined : String(params.q);
59
+ if (mode !== undefined && q !== undefined)
60
+ fail(2, '参数 q 与 mode 互斥:q=现找,mode=速查表产物');
61
+ if (mode !== undefined && mode !== 'lookup')
62
+ fail(2, 'mode 非法(' + String(mode) + '):本命令只认 lookup');
63
+ if (q !== undefined) {
64
+ const hits = buildHelpItems(buildHelpLookup(), q);
65
+ return { data: { ...hits, mode: 'lookup', query: q } };
66
+ }
67
+ if (mode === 'lookup') {
68
+ const hits = buildHelpItems(buildHelpLookup(), undefined);
69
+ const stem = LOOKUP_FILE_STEM;
70
+ return {
71
+ data: { ...hits, mode: 'lookup' },
72
+ deliver: {
73
+ target: { dir: join(resolve(dbDir), HELP_HTML_DIR_NAME), stem },
74
+ reuseMs: windowForHelpDelivery(stem, params),
75
+ },
76
+ };
77
+ }
78
+ const html = renderHelpFileHtml(buildHelpFileData(now, { initialized: helpInitialized() }));
79
+ const stem = HELP_FILE_STEM;
80
+ return {
81
+ data: { ...buildHelpIndex(), mode: 'file', bytes: Buffer.byteLength(html, 'utf8') },
82
+ deliver: {
83
+ html,
84
+ target: { dir: join(resolve(dbDir), HELP_HTML_DIR_NAME), stem },
85
+ reuseMs: windowForHelpDelivery(stem, params),
86
+ },
87
+ };
43
88
  }
44
- // 十六键分发:读走 fetch 读,写走 fetch 写+policy 校验;未知键上游已拦,此处再拦一道。
89
+ /** 迁移过的命令入口(写入域两条 + 查询域四条):查注册表命中即走能力目录,按 `kind` 静态分两支。
90
+ * 开库/关库与写前置守卫与老路同一套;读命令不碰写库守卫(照旧只读)。
91
+ * 这些命令的整页(采集页/回执页/查询列表页)住各自能力目录,
92
+ * 故 `dispatch` 的 switch 里**不再有**它们的 case(一个命令恰住一处)。 */
93
+ function runRegistered(key, params) {
94
+ const spec = REGISTRY[key];
95
+ if (spec === undefined)
96
+ fail(3, '未知 bill 命令:' + key);
97
+ const dbPath = resolveDbPath();
98
+ if (spec.kind === 'write')
99
+ assertWritablePath(dbPath);
100
+ const handle = openBillDb(dbPath);
101
+ try {
102
+ if (handle.initialized)
103
+ note('记账 DB 已初始化:' + dbPath);
104
+ return spec.kind === 'write'
105
+ ? runRecordWrite(key, params, handle)
106
+ : runQueryRead(key, params, handle);
107
+ }
108
+ finally {
109
+ try {
110
+ closeBillDb(handle);
111
+ }
112
+ catch { /* ignore */ }
113
+ }
114
+ }
115
+ // 十六条命令分发:读走 fetch 读,写走 fetch 写+policy 校验;未知命令名上游已拦,此处再拦一道。
45
116
  function dispatch(key, params) {
46
117
  const dbPath = resolveDbPath();
47
118
  const goalsPath = resolveGoalsPath();
48
- // 写键前置守卫(B6):非 tmp 写库须 BILL_FORCE_PROD=1;读键不受影响。
49
- if (key === 'bill.record.add' || key === 'bill.record.update')
50
- assertWritablePath(dbPath);
119
+ // 会改数据库的命令前置守卫(B6):非 tmp 写库须 BILL_FORCE_PROD=1;查询命令不受影响。
120
+ // 记一笔/改记录已迁进能力目录(走上面的 `runRegistered`),守卫也跟着搬过去了。
51
121
  if (key === 'bill.goal.write')
52
122
  assertWritablePath(goalsPath);
53
123
  if (key === 'bill.account.write') {
@@ -59,102 +129,6 @@ function dispatch(key, params) {
59
129
  if (handle.initialized)
60
130
  note('记账 DB 已初始化:' + dbPath);
61
131
  switch (key) {
62
- case 'bill.record.add': {
63
- const input = validateAddInput(params);
64
- const r = addBill(handle, input);
65
- const kind = typeof params.kind === 'string' ? params.kind : '';
66
- const extra = kind === 'photo' ? '(拍账单图片识别以外置为准)' : kind === 'batch' ? '(批量逐笔校验其一)' : kind ? `(${kind})` : '';
67
- return buildRecordReceipt(`已记录:${r.category} ${r.amount.toFixed(2)}${extra}(id=${r.id},账单回执可复制 prompt)`);
68
- }
69
- case 'bill.record.update': {
70
- const op = parseRecordOp(params);
71
- if (op === 'undo') {
72
- const id = needId(params);
73
- const r = undoBill(handle, id);
74
- return buildRecordReceipt(`已撤销:${r.id}(软删,恢复走 restore)`);
75
- }
76
- if (op === 'restore') {
77
- const id = needId(params);
78
- const r = restoreBill(handle, id);
79
- return buildRecordReceipt(`已恢复:${r.id}`);
80
- }
81
- const { id, patch } = validateUpdateInput(params);
82
- const r = updateBill(handle, id, patch);
83
- return buildRecordReceipt(`已修改:${r.id}(${Object.keys(patch).join('/')})`);
84
- }
85
- case 'bill.record.today': {
86
- if (params.recent === true) {
87
- const limit = params.limit === undefined ? 10 : params.limit;
88
- if (!Number.isInteger(limit) || limit < 1 || limit > 200)
89
- fail(2, 'recent limit 须为 1~200 的整数');
90
- const rows = fetchAll(handle).sort((a, b) => b.time.localeCompare(a.time) || b.id - a.id).slice(0, limit);
91
- return buildRecordToday('recent', rows);
92
- }
93
- const date = params.date === 'yesterday' ? yesterdayStr() : resolveQueryDate(params);
94
- const rows = listToday(handle, date);
95
- if (!rows.length)
96
- note('当日无记录:' + date + '(真实无,非故障)');
97
- return buildRecordToday(date, rows);
98
- }
99
- case 'bill.record.range': {
100
- let start;
101
- let end;
102
- if (typeof params.range === 'string' && params.range === 'week')
103
- ({ start, end } = weekRange());
104
- else if (typeof params.range === 'string' && params.range === 'month')
105
- ({ start, end } = monthRange(new Date().toISOString().slice(0, 7)));
106
- else if (params.start !== undefined || params.end !== undefined)
107
- ({ start, end } = resolveRange(params));
108
- else if (params.category !== undefined || params.account !== undefined || params.ledger !== undefined) {
109
- const rows = fetchAll(handle, {
110
- category: params.category,
111
- account: params.account,
112
- ledger: params.ledger,
113
- });
114
- if (!rows.length)
115
- throw new BillFetchError('BILL_EMPTY_RANGE', '条件无记录(缺失阻断,不返空统计)');
116
- return buildRecordRange('', '', rows);
117
- }
118
- else
119
- fail(2, '缺槽位 start/end(或 range=week/month,或 category/account/ledger 条件)');
120
- const rows = fetchAll(handle, {
121
- fromTime: start + ' 00:00:00', toTime: end + ' 23:59:59',
122
- category: params.category,
123
- account: params.account,
124
- ledger: params.ledger,
125
- });
126
- if (!rows.length)
127
- throw new BillFetchError('BILL_EMPTY_RANGE', `区间无记录:${start}~${end}(缺失阻断,不返空统计)`);
128
- return buildRecordRange(start, end, rows);
129
- }
130
- case 'bill.record.search': {
131
- const kind = params.kind === undefined ? '' : String(params.kind);
132
- if (kind === 'tag') {
133
- const tag = params.tag;
134
- if (typeof tag !== 'string' || !tag.trim())
135
- fail(2, '查标签须给 tag');
136
- return buildRecordSearch('tag:' + tag, listByTag(handle, tag));
137
- }
138
- if (kind === 'debt') {
139
- const rows = fetchAll(handle).filter((r) => r.category.startsWith('借贷/') || r.note.includes('#未还'));
140
- return buildRecordSearch('debt', rows);
141
- }
142
- if (kind === 'reimburse') {
143
- const rows = fetchAll(handle).filter((r) => r.note.includes('#待报销'));
144
- return buildRecordSearch('reimburse', rows);
145
- }
146
- if (kind === 'installment') {
147
- const rows = fetchAll(handle).filter((r) => r.category.startsWith('分期/') || r.note.includes('#分期'));
148
- return buildRecordSearch('installment', rows);
149
- }
150
- const q = params.q;
151
- if (typeof q !== 'string' || !q.trim())
152
- fail(2, '搜备注须给 q');
153
- return buildRecordSearch('search:' + q, searchKeyword(handle, q));
154
- }
155
- case 'bill.record.detail': {
156
- return buildRecordDetail(getById(handle, needId(params)));
157
- }
158
132
  case 'bill.analysis.overview': {
159
133
  const kind = parseOverviewKind(params);
160
134
  let rows;
@@ -430,13 +404,12 @@ function dispatch(key, params) {
430
404
  fail(2, 'setup op 非法(期望 init/init-status/backup-create/backup-list/restore/import)');
431
405
  return null;
432
406
  }
433
- case 'bill.help.lookup': {
434
- const all = buildHelpLookup();
435
- const q = params.q === undefined ? undefined : String(params.q);
436
- return buildHelpItems(all, q);
437
- }
407
+ case 'bill.help.lookup':
408
+ // #144:本命令由 `dispatchHelp` 在**开库之前**处理(只读页不建库);走到这里说明 main 的路由被改坏了。
409
+ fail(1, '内部错误:bill.help.lookup 须走 dispatchHelp(开库之前)');
410
+ return null;
438
411
  default:
439
- fail(3, '未知 bill key:' + key);
412
+ fail(3, '未知 bill 命令:' + key);
440
413
  return null;
441
414
  }
442
415
  }
@@ -468,6 +441,7 @@ async function main() {
468
441
  const o = parseArgs(process.argv.slice(2));
469
442
  if (!o.key)
470
443
  fail(2, '用法:bill-cmd-read <bill.key> [--params JSON对象] [--html 输出路径] [--timeout 毫秒]');
444
+ const key = o.key;
471
445
  const dbPath = preflight();
472
446
  void dbPath;
473
447
  let params = {};
@@ -483,7 +457,7 @@ async function main() {
483
457
  }
484
458
  let shape = null;
485
459
  try {
486
- shape = billShapeFor(o.key);
460
+ shape = billShapeFor(key);
487
461
  }
488
462
  catch (e) {
489
463
  fail(3, e.message);
@@ -492,20 +466,33 @@ async function main() {
492
466
  const timer = setTimeout(() => { toast('cmd_read 超时 terminate(' + o.timeout + 'ms),已终止取数'); process.exit(4); }, o.timeout);
493
467
  timer.unref();
494
468
  let env = null;
469
+ let delivery;
495
470
  try {
496
- const data = dispatch(o.key, params);
497
- env = buildBillEnvelope(o.key, data);
498
- if (o.html) {
499
- // B4:--html 套模板输出完整收据页(section 片段经 CONTENT 注入模板,非片段直写)。
500
- const section = renderEnvelopeHtml(env);
501
- const full = fillTemplate(loadTemplate(templateFor(o.key)), section);
502
- assertHtmlSize(full);
503
- try {
504
- writeFileSync(o.html, full, 'utf8');
505
- }
506
- catch (e) {
507
- fail(5, 'HTML 写盘失败:' + o.html);
508
- }
471
+ // #144:HELP 在开库之前分派(只读页不建库);迁移过的命令(写入域两条+查询域四条)先查注册表走能力目录;
472
+ // 其余照旧走 dispatch。
473
+ const help = key === 'bill.help.lookup' ? dispatchHelp(params) : null;
474
+ const abilityOut = help === null && REGISTRY[key] !== undefined ? runRegistered(key, params) : null;
475
+ const built = buildBillEnvelope(key, abilityOut ? abilityOut.data : (help ? help.data : dispatch(key, params)));
476
+ env = built;
477
+ // B4 既有语义:`--html` 套模板输出完整收据页(section 片段经 CONTENT 注入模板,非片段直写)。
478
+ // 迁移过的命令另有整页(采集页/回执页住 `src/record/`、查询列表页住 `src/query/`),不再套老模板。
479
+ const sectionHtml = () => {
480
+ if (abilityOut)
481
+ return abilityOut.html;
482
+ return fillTemplate(loadTemplate(templateFor(key)), renderEnvelopeHtml(built));
483
+ };
484
+ // B1 复核整改:体积门对准**实际交付的那串**——交给 `deliverHtml` 的字符串先过 `gatedHtml()`,
485
+ // 判的就是写下去的那一串(`delivery.bytes` 也照它算)。四条交付路都从这里过:迁移过的命令
486
+ // (回执页/采集页/查询列表页整页,由能力目录出)、未迁移的命令(section 片段经 CONTENT 注入模板)、
487
+ // HELP 缺省整页、HELP 速查表分节页。
488
+ const gatedHtml = (html) => { assertHtmlSize(html); return html; };
489
+ if (help?.deliver !== undefined) {
490
+ // 本命令的产物:缺省=HELP 整页(共享 help 模板自带 html);`mode:"lookup"`=速查表分节页(由 envelope 渲染)。
491
+ const html = gatedHtml(help.deliver.html ?? sectionHtml());
492
+ delivery = deliverHtml({ explicit: o.html, target: help.deliver.target, html, reuseMs: help.deliver.reuseMs });
493
+ }
494
+ else if (o.html) {
495
+ delivery = deliverHtml({ explicit: o.html, html: gatedHtml(sectionHtml()) });
509
496
  }
510
497
  }
511
498
  catch (e) {
@@ -515,6 +502,9 @@ async function main() {
515
502
  fail(2, '口径失败:' + e.message);
516
503
  if (e instanceof BillRenderError)
517
504
  fail(5, '渲染失败:' + e.message);
505
+ if (e?.code && /^E[A-Z]+$/.test(String(e.code))) {
506
+ fail(5, '落盘失败:' + (e.message || String(e)));
507
+ }
518
508
  if (e.message?.includes('SKILLS_DB_PATH'))
519
509
  fail(1, e.message);
520
510
  if (e.message?.includes('BILL_FORCE_PROD'))
@@ -524,7 +514,8 @@ async function main() {
524
514
  finally {
525
515
  clearTimeout(timer);
526
516
  }
527
- process.stdout.write(JSON.stringify(env) + '\n');
517
+ // #83 口径的顶层追加:`delivery{mode,path,bytes}` 只追加,既有五字段一字不改、序不变。
518
+ process.stdout.write(JSON.stringify(delivery ? { ...env, delivery } : env) + '\n');
528
519
  }
529
520
  await main();
530
521
  //# sourceMappingURL=cmd_read.js.map