@wirecat/cli-memo 0.131.67 → 0.131.69

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 (152) hide show
  1. package/dist/bin/memo.js +2 -2
  2. package/dist/bundle/{all-profiles-W3GRNNLJ.js → all-profiles-6JKSPBBO.js} +5 -5
  3. package/dist/bundle/{anthropic-GWMYHBO4.js → anthropic-YA2N6EJ4.js} +2 -2
  4. package/dist/bundle/{app-server-RQ26EXNI.js → app-server-UP3AL2XQ.js} +35 -35
  5. package/dist/bundle/auto-IKZIROGF.js +85 -0
  6. package/dist/bundle/{bun-KKLPQTG5.js → bun-OUFOAYV4.js} +2 -2
  7. package/dist/bundle/{bun-sqlite-WKIZJNTG.js → bun-sqlite-5RZTFU2Q.js} +2 -2
  8. package/dist/bundle/{chunk-R4H5HSQZ.js → chunk-22CDLXSR.js} +4 -4
  9. package/dist/bundle/{chunk-XI5UABKO.js → chunk-267N22E4.js} +1 -1
  10. package/dist/bundle/{chunk-7IKET6ES.js → chunk-276KPJI5.js} +2 -2
  11. package/dist/bundle/{chunk-32ROGX5N.js → chunk-2C7Z7CKD.js} +1 -1
  12. package/dist/bundle/{chunk-NNUJWCEJ.js → chunk-2DQYP2K3.js} +1 -1
  13. package/dist/bundle/{chunk-TQXXOUOX.js → chunk-2F35N6BI.js} +1 -1
  14. package/dist/bundle/{chunk-BOOEDDVY.js → chunk-2NFI7SDW.js} +83 -63
  15. package/dist/bundle/{chunk-ECMYCWG2.js → chunk-3LGLKLBA.js} +2 -2
  16. package/dist/bundle/{chunk-Q4JYHQ47.js → chunk-3TFK7RBA.js} +342 -323
  17. package/dist/bundle/{chunk-VVTC2YAX.js → chunk-3YVLNOPE.js} +4 -4
  18. package/dist/bundle/{chunk-MLF5FLAP.js → chunk-4HSWIXYE.js} +4 -4
  19. package/dist/bundle/{chunk-GU3MRYK2.js → chunk-53EAI4VZ.js} +1 -1
  20. package/dist/bundle/{chunk-ZU6HZLZP.js → chunk-5EHUHZU5.js} +429 -90
  21. package/dist/bundle/{chunk-MCA265JM.js → chunk-5MUR6D2G.js} +19 -1
  22. package/dist/bundle/{chunk-ZVXXJYNW.js → chunk-5OMM4CDT.js} +2 -2
  23. package/dist/bundle/{chunk-AGNWFOAJ.js → chunk-67VEVKMJ.js} +1 -1
  24. package/dist/bundle/{chunk-JG7EDH7K.js → chunk-7JFLHRQL.js} +1 -1
  25. package/dist/bundle/{chunk-CQNYBTWP.js → chunk-7L4PBN5S.js} +2 -2
  26. package/dist/bundle/{chunk-WJ2OYD2D.js → chunk-BGWPKEKK.js} +4 -4
  27. package/dist/bundle/{chunk-TPYXULNG.js → chunk-BJJULUUC.js} +5 -5
  28. package/dist/bundle/{chunk-VUCD4DAQ.js → chunk-ECHN36DC.js} +4 -4
  29. package/dist/bundle/{chunk-TXWVZVDK.js → chunk-EDPA54WQ.js} +3 -3
  30. package/dist/bundle/{chunk-JEKIBI7P.js → chunk-EKSOPEFQ.js} +4 -4
  31. package/dist/bundle/{chunk-ZOIW2YEU.js → chunk-EO4JMJFZ.js} +7 -7
  32. package/dist/bundle/{chunk-7DANHJF6.js → chunk-FQ54GW57.js} +31 -31
  33. package/dist/bundle/{chunk-DNDELSVT.js → chunk-FZHYGJQ2.js} +5 -5
  34. package/dist/bundle/{chunk-IKZBO7NV.js → chunk-GGFL64HF.js} +623 -509
  35. package/dist/bundle/{chunk-FBSHWCIZ.js → chunk-GVEY6743.js} +9 -9
  36. package/dist/bundle/{chunk-TNHFKR6P.js → chunk-GVP6CAPH.js} +21 -21
  37. package/dist/bundle/{chunk-4JTCNMW6.js → chunk-IHHXGTMX.js} +1 -1
  38. package/dist/bundle/{chunk-TWTAWPC7.js → chunk-IJAILIZR.js} +12 -12
  39. package/dist/bundle/{chunk-XK4VVGMH.js → chunk-JMGQATR6.js} +2 -2
  40. package/dist/bundle/{chunk-GHN2KYMO.js → chunk-LYAJUZM4.js} +21 -21
  41. package/dist/bundle/{chunk-YAFSD345.js → chunk-MK6SQ32Q.js} +12 -12
  42. package/dist/bundle/{chunk-XVPMSMSP.js → chunk-MWQI6FSU.js} +3 -3
  43. package/dist/bundle/{chunk-7OMKWYFN.js → chunk-MYLNRBDF.js} +16 -9
  44. package/dist/bundle/{chunk-W7FYMZOW.js → chunk-N73YBQ42.js} +6 -6
  45. package/dist/bundle/{chunk-ZNBLWLCD.js → chunk-OAO5UCPJ.js} +151 -151
  46. package/dist/bundle/{chunk-OUSLPKWY.js → chunk-OBQ7TSUB.js} +1 -1
  47. package/dist/bundle/{chunk-TFAUB6JO.js → chunk-OHA7HDPX.js} +374 -306
  48. package/dist/bundle/{chunk-QCWA3JUT.js → chunk-PHSYMVYX.js} +2 -2
  49. package/dist/bundle/{chunk-HUPZ6XYH.js → chunk-PSYQPO6D.js} +2 -2
  50. package/dist/bundle/{chunk-SKBDI7LU.js → chunk-QAXIZHK7.js} +28 -28
  51. package/dist/bundle/{chunk-GUWRR7F2.js → chunk-QZU5IM5E.js} +2 -2
  52. package/dist/bundle/{chunk-SUCG5HBF.js → chunk-RHW3DDWI.js} +1 -1
  53. package/dist/bundle/{chunk-OU4PN7PF.js → chunk-TV75OHKJ.js} +7 -7
  54. package/dist/bundle/{chunk-4C6RU7DG.js → chunk-TVZXLAID.js} +4 -4
  55. package/dist/bundle/{chunk-7KYVUVDS.js → chunk-TXOXPQN2.js} +2 -2
  56. package/dist/bundle/{chunk-QMTCYKAA.js → chunk-UEMI6FXR.js} +6 -6
  57. package/dist/bundle/{chunk-QFSMJTEA.js → chunk-V2YS63Z5.js} +1 -1
  58. package/dist/bundle/{chunk-6SFTVGNL.js → chunk-VPKJZKXV.js} +1 -1
  59. package/dist/bundle/{chunk-XYL7GDCX.js → chunk-W2FFA7T5.js} +5 -5
  60. package/dist/bundle/{chunk-DNS2QWK3.js → chunk-WNA2WNDK.js} +3 -3
  61. package/dist/bundle/{chunk-ORKWFXWV.js → chunk-XTEJXJJ3.js} +10 -10
  62. package/dist/bundle/{chunk-GJ3P7RJF.js → chunk-YTJWIJIG.js} +5 -5
  63. package/dist/bundle/{chunk-PBOVHHYG.js → chunk-YXXAPVAZ.js} +8 -8
  64. package/dist/bundle/{command-contract-46BU5YKL.js → command-contract-OGW2DX4C.js} +11 -11
  65. package/dist/bundle/{decode-KRVQZPSB.js → decode-IJMDTGTW.js} +2 -2
  66. package/dist/bundle/{echarts-O6QQRVZE.js → echarts-EHYUXOKP.js} +2 -2
  67. package/dist/bundle/{embed-DA4H2F4F.js → embed-RUY3UIVC.js} +3 -3
  68. package/dist/bundle/{embedder-HWAFLIYM.js → embedder-P6WBMEGP.js} +6 -6
  69. package/dist/bundle/{entry-DE7GJOIZ.js → entry-7S2ONYSF.js} +4 -4
  70. package/dist/bundle/failure-NRZUB77S.js +65 -0
  71. package/dist/bundle/{first-run-GOBPIKQF.js → first-run-MLIFZSVH.js} +5 -5
  72. package/dist/bundle/{first-screen-3V3HWSQM.js → first-screen-DGTTK6VK.js} +12 -12
  73. package/dist/bundle/{instructions-GYYMBSZW.js → instructions-ZMG4FLGO.js} +1 -1
  74. package/dist/bundle/{mcp-6RDHWRXG.js → mcp-CFIL7WZU.js} +35 -35
  75. package/dist/bundle/memo.js +6 -6
  76. package/dist/bundle/{node-R3OFASC5.js → node-QYL4B7JS.js} +2 -2
  77. package/dist/bundle/{node-sqlite-DYHDZJ5A.js → node-sqlite-ESTT2OCF.js} +3 -3
  78. package/dist/bundle/{oauth-SMCDSCXQ.js → oauth-CFQFSIRQ.js} +4 -4
  79. package/dist/bundle/{open-GX7RPGQW.js → open-3XO2HMIT.js} +2 -2
  80. package/dist/bundle/{openai-WM4TRXCC.js → openai-STUNDBD6.js} +7 -7
  81. package/dist/bundle/{process-NT5OZH2V.js → process-P4UAJCZX.js} +1 -1
  82. package/dist/bundle/{program-YQ3UT2LI.js → program-BXL5ZOVQ.js} +2052 -1870
  83. package/dist/bundle/{prompt-5XJNSXCW.js → prompt-ZCYMFMIG.js} +2 -2
  84. package/dist/bundle/{recognize-DQNGZLRX.js → recognize-V3BY33FK.js} +1 -1
  85. package/dist/bundle/{remote-RMRYJZSZ.js → remote-IP4QR7SP.js} +4 -4
  86. package/dist/bundle/{rerank-5J6JM43S.js → rerank-VKUSF3DM.js} +4 -4
  87. package/dist/bundle/{serve-4RVWOQVC.js → serve-VBIUARHJ.js} +6 -6
  88. package/dist/bundle/server-AASCSGCI.js +61 -0
  89. package/dist/bundle/{server-NKCRFK26.js → server-JLPZUC42.js} +49 -49
  90. package/dist/bundle/server-YQLPZK4Z.js +48 -0
  91. package/dist/bundle/{state-MGVPOLPZ.js → state-UBAIZ2WP.js} +3 -3
  92. package/dist/bundle/{store-IEK4PPOE.js → store-VQQVUA4W.js} +9 -9
  93. package/dist/bundle/store-maintenance-command-GCBABKEZ.js +47 -0
  94. package/dist/bundle/update-M54Z3SEU.js +68 -0
  95. package/dist/digest/backfill.d.ts +9 -0
  96. package/dist/digest/backfill.d.ts.map +1 -0
  97. package/dist/digest/backfill.js +142 -0
  98. package/dist/digest/backfill.js.map +1 -0
  99. package/dist/digest/command.d.ts +2 -0
  100. package/dist/digest/command.d.ts.map +1 -1
  101. package/dist/digest/command.js +18 -1
  102. package/dist/digest/command.js.map +1 -1
  103. package/dist/digest/digest.d.ts +6 -1
  104. package/dist/digest/digest.d.ts.map +1 -1
  105. package/dist/digest/digest.js +26 -15
  106. package/dist/digest/digest.js.map +1 -1
  107. package/dist/digest/sync-health.d.ts +1 -2
  108. package/dist/digest/sync-health.d.ts.map +1 -1
  109. package/dist/digest/tasks.d.ts +14 -1
  110. package/dist/digest/tasks.d.ts.map +1 -1
  111. package/dist/digest/tasks.js +31 -4
  112. package/dist/digest/tasks.js.map +1 -1
  113. package/dist/discovery/command.d.ts.map +1 -1
  114. package/dist/discovery/command.js +1 -0
  115. package/dist/discovery/command.js.map +1 -1
  116. package/dist/examples.d.ts.map +1 -1
  117. package/dist/examples.js +7 -1
  118. package/dist/examples.js.map +1 -1
  119. package/dist/knowledge/command.d.ts.map +1 -1
  120. package/dist/knowledge/command.js +17 -6
  121. package/dist/knowledge/command.js.map +1 -1
  122. package/dist/knowledge/project-context.d.ts.map +1 -1
  123. package/dist/knowledge/project-context.js +3 -2
  124. package/dist/knowledge/project-context.js.map +1 -1
  125. package/dist/knowledge/relationships.d.ts +2 -0
  126. package/dist/knowledge/relationships.d.ts.map +1 -1
  127. package/dist/knowledge/relationships.js +11 -2
  128. package/dist/knowledge/relationships.js.map +1 -1
  129. package/dist/program.d.ts.map +1 -1
  130. package/dist/program.js +2 -0
  131. package/dist/program.js.map +1 -1
  132. package/dist/read-only.d.ts +1 -1
  133. package/dist/reasons.d.ts.map +1 -1
  134. package/dist/reasons.js +18 -0
  135. package/dist/reasons.js.map +1 -1
  136. package/dist/schedule/command.d.ts +2 -1
  137. package/dist/schedule/command.d.ts.map +1 -1
  138. package/dist/schedule/command.js +6 -2
  139. package/dist/schedule/command.js.map +1 -1
  140. package/dist/version.d.ts +1 -1
  141. package/dist/version.js +1 -1
  142. package/package.json +4 -4
  143. package/skills/cli-memo/topics/projects.md +7 -0
  144. package/skills/mail-triage/SKILL.md +25 -19
  145. package/skills/morning-digest/SKILL.md +15 -1
  146. package/dist/bundle/auto-FR6LX5KO.js +0 -85
  147. package/dist/bundle/failure-U3VNIQNX.js +0 -65
  148. package/dist/bundle/server-HH2X6G7V.js +0 -61
  149. package/dist/bundle/server-MS3JKMGF.js +0 -48
  150. package/dist/bundle/store-maintenance-command-GSS2AY4J.js +0 -47
  151. package/dist/bundle/update-S5XRRWTH.js +0 -68
  152. /package/dist/bundle/{chunk-DS7NXKAN.js → chunk-AWVQHWIS.js} +0 -0
@@ -6,17 +6,17 @@ import {
6
6
  autoState,
7
7
  lastAutoRun,
8
8
  recordAutoRun
9
- } from "./chunk-JEKIBI7P.js";
9
+ } from "./chunk-EKSOPEFQ.js";
10
10
  import {
11
11
  installer,
12
12
  latest,
13
13
  upgradeCommand
14
- } from "./chunk-TPYXULNG.js";
14
+ } from "./chunk-BJJULUUC.js";
15
15
  import {
16
16
  mcpOwnerAllowed,
17
17
  readOnlyAvailability,
18
18
  refuseWritesUnderLock
19
- } from "./chunk-XYL7GDCX.js";
19
+ } from "./chunk-W2FFA7T5.js";
20
20
  import {
21
21
  answers,
22
22
  atDetail,
@@ -27,7 +27,7 @@ import {
27
27
  trackAnswers,
28
28
  wholeList,
29
29
  withStore
30
- } from "./chunk-4C6RU7DG.js";
30
+ } from "./chunk-TVZXLAID.js";
31
31
  import {
32
32
  ATTACHMENT_CHARS,
33
33
  DIALECTS,
@@ -59,22 +59,24 @@ import {
59
59
  showMail,
60
60
  unsetConfigKey,
61
61
  updateConfig
62
- } from "./chunk-7DANHJF6.js";
62
+ } from "./chunk-FQ54GW57.js";
63
63
  import "./chunk-QP3KNC5D.js";
64
- import "./chunk-7KYVUVDS.js";
65
- import "./chunk-ZOIW2YEU.js";
66
- import "./chunk-XVPMSMSP.js";
64
+ import "./chunk-TXOXPQN2.js";
65
+ import "./chunk-EO4JMJFZ.js";
66
+ import "./chunk-MWQI6FSU.js";
67
67
  import {
68
68
  errorObject,
69
69
  recognisable
70
- } from "./chunk-VVTC2YAX.js";
70
+ } from "./chunk-3YVLNOPE.js";
71
71
  import {
72
72
  AGENT_INPUT_MAX_CHARS,
73
73
  DISCOVERY_DESCRIPTIONS,
74
74
  DOCTOR_DEEP_OPTION,
75
+ SCHEDULE_DEFAULTS,
75
76
  addContractInfo,
76
77
  addMeetingPreparationCommand,
77
78
  advancePastSkipped,
79
+ backfillScheduleRun,
78
80
  checkJson,
79
81
  dateOrNone,
80
82
  entityFields,
@@ -97,42 +99,45 @@ import {
97
99
  projectStatusText,
98
100
  projectWindowOf,
99
101
  readMailFindingsJson,
102
+ readSchedule,
100
103
  recordRuns,
101
104
  runsCommand,
102
105
  scheduleCommandFor,
106
+ scheduleFile,
103
107
  scheduledRuns,
104
108
  sharedCommandTemplate,
105
109
  skillCommand as skillCommand2,
110
+ spentToday,
106
111
  tasksCommand,
107
112
  tasksStatsCommand,
108
113
  templatesCommand,
109
114
  withSqliteSidecars
110
- } from "./chunk-TFAUB6JO.js";
111
- import "./chunk-JG7EDH7K.js";
112
- import "./chunk-SUCG5HBF.js";
115
+ } from "./chunk-OHA7HDPX.js";
116
+ import "./chunk-7JFLHRQL.js";
117
+ import "./chunk-RHW3DDWI.js";
113
118
  import {
114
119
  allHitItem,
115
120
  coverageGaps,
116
121
  messageHitItem,
117
122
  noteHitItem,
118
123
  shownOf
119
- } from "./chunk-GHN2KYMO.js";
120
- import "./chunk-OUSLPKWY.js";
124
+ } from "./chunk-LYAJUZM4.js";
125
+ import "./chunk-OBQ7TSUB.js";
121
126
  import {
122
127
  REASONS as REASONS2,
123
128
  SKILL_TARGETS,
124
129
  skillCommand,
125
130
  skillPath,
126
131
  skillVersion
127
- } from "./chunk-7OMKWYFN.js";
132
+ } from "./chunk-MYLNRBDF.js";
128
133
  import {
129
134
  recoveryNotes,
130
135
  storeInfo,
131
136
  storeRecoveryCheck,
132
137
  storeRecoveryRun,
133
138
  storeSummary
134
- } from "./chunk-ZU6HZLZP.js";
135
- import "./chunk-TNHFKR6P.js";
139
+ } from "./chunk-5EHUHZU5.js";
140
+ import "./chunk-GVP6CAPH.js";
136
141
  import {
137
142
  LIST_QUERY_GUIDE,
138
143
  STATUS_SIGNALS,
@@ -192,18 +197,18 @@ import {
192
197
  timeLimit,
193
198
  typedQuery,
194
199
  withQuery
195
- } from "./chunk-Q4JYHQ47.js";
200
+ } from "./chunk-3TFK7RBA.js";
196
201
  import {
197
202
  readBoundedUtf8File,
198
203
  writeFileAtomic
199
- } from "./chunk-YAFSD345.js";
204
+ } from "./chunk-MK6SQ32Q.js";
200
205
  import {
201
206
  commandsText,
202
207
  inSection,
203
208
  installHelp
204
- } from "./chunk-QMTCYKAA.js";
209
+ } from "./chunk-UEMI6FXR.js";
205
210
  import "./chunk-HTQTIKWP.js";
206
- import "./chunk-XK4VVGMH.js";
211
+ import "./chunk-JMGQATR6.js";
207
212
  import {
208
213
  Command,
209
214
  DEFAULT_TEXT_MODEL,
@@ -240,23 +245,23 @@ import {
240
245
  snakeKeys,
241
246
  textModels,
242
247
  textModelsFile
243
- } from "./chunk-ZNBLWLCD.js";
244
- import "./chunk-W7FYMZOW.js";
245
- import "./chunk-ORKWFXWV.js";
248
+ } from "./chunk-OAO5UCPJ.js";
249
+ import "./chunk-N73YBQ42.js";
250
+ import "./chunk-XTEJXJJ3.js";
246
251
  import {
247
252
  isNewer
248
253
  } from "./chunk-LJCT7YG6.js";
249
254
  import "./chunk-L62FWN3Y.js";
250
- import "./chunk-TXWVZVDK.js";
251
- import "./chunk-FBSHWCIZ.js";
252
- import "./chunk-NNUJWCEJ.js";
253
- import "./chunk-R4H5HSQZ.js";
255
+ import "./chunk-EDPA54WQ.js";
256
+ import "./chunk-GVEY6743.js";
257
+ import "./chunk-2DQYP2K3.js";
258
+ import "./chunk-22CDLXSR.js";
254
259
  import {
255
260
  releaseLock,
256
261
  servingProfiles,
257
262
  takeLock
258
- } from "./chunk-CQNYBTWP.js";
259
- import "./chunk-4JTCNMW6.js";
263
+ } from "./chunk-7L4PBN5S.js";
264
+ import "./chunk-IHHXGTMX.js";
260
265
  import {
261
266
  EVIDENCE_FINDINGS_KIND,
262
267
  MAIL_FINDINGS_KIND,
@@ -298,7 +303,7 @@ import {
298
303
  plainQuery,
299
304
  providerNamed,
300
305
  refLabel
301
- } from "./chunk-IKZBO7NV.js";
306
+ } from "./chunk-GGFL64HF.js";
302
307
  import {
303
308
  CATALOG_KINDS,
304
309
  CONTRACT_KINDS,
@@ -306,6 +311,7 @@ import {
306
311
  DEAL_KINDS,
307
312
  DEAL_ROLES,
308
313
  DEAL_STATUSES,
314
+ ENGAGEMENT_LEVELS,
309
315
  FACT_STATUSES,
310
316
  FACT_TYPES,
311
317
  FINANCE_REVIEWS,
@@ -326,6 +332,7 @@ import {
326
332
  TRANSACTION_STATUSES,
327
333
  WALLET_KINDS,
328
334
  WORK_TASK_TYPES,
335
+ byEngagement,
329
336
  createUpcomingMeetingReader,
330
337
  dateRange,
331
338
  dayBoundary,
@@ -347,21 +354,21 @@ import {
347
354
  timezoneOf,
348
355
  validateQuery,
349
356
  walkQuery
350
- } from "./chunk-BOOEDDVY.js";
351
- import "./chunk-ECMYCWG2.js";
352
- import "./chunk-OU4PN7PF.js";
353
- import "./chunk-WJ2OYD2D.js";
354
- import "./chunk-QCWA3JUT.js";
355
- import "./chunk-32ROGX5N.js";
356
- import "./chunk-ZVXXJYNW.js";
357
- import "./chunk-GUWRR7F2.js";
358
- import "./chunk-DNDELSVT.js";
357
+ } from "./chunk-2NFI7SDW.js";
358
+ import "./chunk-3LGLKLBA.js";
359
+ import "./chunk-TV75OHKJ.js";
360
+ import "./chunk-BGWPKEKK.js";
361
+ import "./chunk-PHSYMVYX.js";
362
+ import "./chunk-2C7Z7CKD.js";
363
+ import "./chunk-5OMM4CDT.js";
364
+ import "./chunk-QZU5IM5E.js";
365
+ import "./chunk-FZHYGJQ2.js";
359
366
  import {
360
367
  sharedModelsDirectory
361
- } from "./chunk-DNS2QWK3.js";
368
+ } from "./chunk-WNA2WNDK.js";
362
369
  import {
363
370
  storePath
364
- } from "./chunk-QFSMJTEA.js";
371
+ } from "./chunk-V2YS63Z5.js";
365
372
  import {
366
373
  provideTerminal,
367
374
  terminalOf
@@ -371,12 +378,12 @@ import {
371
378
  REASONS,
372
379
  STORE_CLIENT,
373
380
  VERSION
374
- } from "./chunk-MCA265JM.js";
381
+ } from "./chunk-5MUR6D2G.js";
375
382
  import "./chunk-DSU6UR2X.js";
376
383
  import "./chunk-SYWHHQMC.js";
377
384
  import "./chunk-D72URWJE.js";
378
- import "./chunk-GJ3P7RJF.js";
379
- import "./chunk-AGNWFOAJ.js";
385
+ import "./chunk-YTJWIJIG.js";
386
+ import "./chunk-67VEVKMJ.js";
380
387
  import {
381
388
  configFilePath,
382
389
  createDeadline,
@@ -416,7 +423,7 @@ import {
416
423
  errorCodes,
417
424
  isCliError
418
425
  } from "./chunk-ZTONSM5X.js";
419
- import "./chunk-6SFTVGNL.js";
426
+ import "./chunk-VPKJZKXV.js";
420
427
  import {
421
428
  __name
422
429
  } from "./chunk-ZVVMGPBJ.js";
@@ -703,7 +710,7 @@ var readScope = /* @__PURE__ */ __name((command, help = ONE_ACCOUNT_HELP) => sou
703
710
  var writeScope = /* @__PURE__ */ __name((command, help) => writeOptions(command, help).option("--json", "print JSON; --json=id,title keeps only those fields"), "writeScope");
704
711
  var jsonOption = /* @__PURE__ */ __name((command, fields = "ref,title") => command.option("--json", `print JSON; --json=${fields} keeps only those fields`), "jsonOption");
705
712
 
706
- // node_modules/.pnpm/@wirecat+cli-core@0.616.0_@wirecat+cli-base@0.91.0_commander@15.0.0_esbuild@0.28.2_type_8665abbf6fdc3acbe46baf279794bee0/node_modules/@wirecat/cli-core/dist/mail/sender-scores.js
713
+ // node_modules/.pnpm/@wirecat+cli-core@0.618.0_@wirecat+cli-base@0.91.0_commander@15.0.0_esbuild@0.28.2_type_a1c6dd927639f1a41fe5f9c53e24865e/node_modules/@wirecat/cli-core/dist/mail/sender-scores.js
707
714
  var SCORED = "mail_sender_scores";
708
715
  var DAY = 864e5;
709
716
  var RECENT = 2 * 365 * DAY;
@@ -719,7 +726,7 @@ var demotedSenders = /* @__PURE__ */ __name(async (store) => {
719
726
  return senders;
720
727
  }, "demotedSenders");
721
728
 
722
- // node_modules/.pnpm/@wirecat+cli-core@0.616.0_@wirecat+cli-base@0.91.0_commander@15.0.0_esbuild@0.28.2_type_8665abbf6fdc3acbe46baf279794bee0/node_modules/@wirecat/cli-core/dist/mail/sync.js
729
+ // node_modules/.pnpm/@wirecat+cli-core@0.618.0_@wirecat+cli-base@0.91.0_commander@15.0.0_esbuild@0.28.2_type_a1c6dd927639f1a41fe5f9c53e24865e/node_modules/@wirecat/cli-core/dist/mail/sync.js
723
730
  var DAY2 = 864e5;
724
731
  var EDGE = 2 * DAY2;
725
732
 
@@ -2028,7 +2035,7 @@ import { createHash } from "node:crypto";
2028
2035
  import { existsSync, readFileSync as readFileSync2, statSync } from "node:fs";
2029
2036
  import { basename, extname, relative, sep } from "node:path";
2030
2037
 
2031
- // node_modules/.pnpm/@wirecat+cli-core@0.616.0_@wirecat+cli-base@0.91.0_commander@15.0.0_esbuild@0.28.2_type_8665abbf6fdc3acbe46baf279794bee0/node_modules/@wirecat/cli-core/dist/services/documents/extraction.js
2038
+ // node_modules/.pnpm/@wirecat+cli-core@0.618.0_@wirecat+cli-base@0.91.0_commander@15.0.0_esbuild@0.28.2_type_a1c6dd927639f1a41fe5f9c53e24865e/node_modules/@wirecat/cli-core/dist/services/documents/extraction.js
2032
2039
  var Proposal = object({
2033
2040
  kind: picklist(["summary", "digest", "fact", "preference"]),
2034
2041
  body: pipe(string(), trim(), minLength(1), maxLength(15e3)),
@@ -2036,13 +2043,13 @@ var Proposal = object({
2036
2043
  });
2037
2044
  var Proposals = object({ items: pipe(array(Proposal), maxLength(20)) });
2038
2045
 
2039
- // node_modules/.pnpm/@wirecat+cli-core@0.616.0_@wirecat+cli-base@0.91.0_commander@15.0.0_esbuild@0.28.2_type_8665abbf6fdc3acbe46baf279794bee0/node_modules/@wirecat/cli-core/dist/services/documents/mirrors.js
2046
+ // node_modules/.pnpm/@wirecat+cli-core@0.618.0_@wirecat+cli-base@0.91.0_commander@15.0.0_esbuild@0.28.2_type_a1c6dd927639f1a41fe5f9c53e24865e/node_modules/@wirecat/cli-core/dist/services/documents/mirrors.js
2040
2047
  var ID = pipe(string(), regex(/^[A-Za-z0-9_-]{1,200}$/));
2041
2048
 
2042
- // node_modules/.pnpm/@wirecat+cli-core@0.616.0_@wirecat+cli-base@0.91.0_commander@15.0.0_esbuild@0.28.2_type_8665abbf6fdc3acbe46baf279794bee0/node_modules/@wirecat/cli-core/dist/services/documents/sync.js
2049
+ // node_modules/.pnpm/@wirecat+cli-core@0.618.0_@wirecat+cli-base@0.91.0_commander@15.0.0_esbuild@0.28.2_type_a1c6dd927639f1a41fe5f9c53e24865e/node_modules/@wirecat/cli-core/dist/services/documents/sync.js
2043
2050
  var MAX_PICTURE_BYTES = 25 * 1024 * 1024;
2044
2051
 
2045
- // node_modules/.pnpm/@wirecat+cli-core@0.616.0_@wirecat+cli-base@0.91.0_commander@15.0.0_esbuild@0.28.2_type_8665abbf6fdc3acbe46baf279794bee0/node_modules/@wirecat/cli-core/dist/fetch/addresses.js
2052
+ // node_modules/.pnpm/@wirecat+cli-core@0.618.0_@wirecat+cli-base@0.91.0_commander@15.0.0_esbuild@0.28.2_type_a1c6dd927639f1a41fe5f9c53e24865e/node_modules/@wirecat/cli-core/dist/fetch/addresses.js
2046
2053
  import { isIP } from "node:net";
2047
2054
  var within3 = /* @__PURE__ */ __name((address, [prefix, bits]) => {
2048
2055
  for (let bit = 0; bit < bits; bit++) {
@@ -2113,7 +2120,7 @@ var isPublicAddress = /* @__PURE__ */ __name((address) => {
2113
2120
  return within3(bytes, [[32], 3]) && !V6_PRIVATE.some((prefix) => within3(bytes, prefix));
2114
2121
  }, "isPublicAddress");
2115
2122
 
2116
- // node_modules/.pnpm/@wirecat+cli-core@0.616.0_@wirecat+cli-base@0.91.0_commander@15.0.0_esbuild@0.28.2_type_8665abbf6fdc3acbe46baf279794bee0/node_modules/@wirecat/cli-core/dist/fetch/page.js
2123
+ // node_modules/.pnpm/@wirecat+cli-core@0.618.0_@wirecat+cli-base@0.91.0_commander@15.0.0_esbuild@0.28.2_type_a1c6dd927639f1a41fe5f9c53e24865e/node_modules/@wirecat/cli-core/dist/fetch/page.js
2117
2124
  import { lookup as dnsLookup } from "node:dns/promises";
2118
2125
  import { request as httpRequest } from "node:http";
2119
2126
  import { request as httpsRequest } from "node:https";
@@ -4426,1822 +4433,1957 @@ var skill = [
4426
4433
  // dist/contract-overrides.js
4427
4434
  var SHELL_OVERRIDES = { search, doctor, skill, mcp, commands, notes, meetings };
4428
4435
 
4429
- // dist/discovery/command.js
4430
- var CONTRACT = 4;
4431
- var commandsCommandDefinition = /* @__PURE__ */ __name((program, streams) => {
4432
- annotate(program.command("commands"), { effects: NO_EFFECTS }).argument("[path...]", "one command, such as tags add").description(DISCOVERY_DESCRIPTIONS.commands).option("--json", "print JSON; --json=id,title keeps only those fields").action((path, options) => {
4433
- const annotateAvailability = /* @__PURE__ */ __name((commands2) => commands2.map((info) => ({
4434
- ...info,
4435
- availability: environmentOf(program).discoveryAvailability?.(info),
4436
- commands: annotateAvailability(info.commands)
4437
- })), "annotateAvailability");
4438
- const plain = commandsDocument(program, { cli: "memo", version: VERSION, contract: CONTRACT });
4439
- const document = { ...plain, commands: annotateAvailability(plain.commands) };
4440
- const answer = path.length ? scoped(document, path) : document;
4441
- if (options.json)
4442
- createRenderer({ format: "json", color: false, streams }).result(answer);
4443
- else {
4444
- const found = path.length ? flatten(document.commands).find((item) => same(item.path, answer.scope)) : void 0;
4445
- streams.data(commandsText(program, found ? [found] : document.commands, found?.path));
4436
+ // dist/digest/backfill.js
4437
+ import { existsSync as existsSync6, readdirSync as readdirSync4 } from "node:fs";
4438
+ import { join as join6 } from "node:path";
4439
+
4440
+ // dist/digest/tasks.js
4441
+ import { existsSync as existsSync5, readdirSync as readdirSync3 } from "node:fs";
4442
+ import { join as join5 } from "node:path";
4443
+
4444
+ // dist/tasks/list.js
4445
+ import { createHash as createHash2 } from "node:crypto";
4446
+ var OPEN_WORK = "state:(open OR todo OR in_progress)";
4447
+ var TASKS_LIST_QUERY = {
4448
+ entity: "task",
4449
+ backend: "sql",
4450
+ fields: [
4451
+ ...PROJECT_TASK_FIELDS,
4452
+ {
4453
+ name: "source",
4454
+ type: "ref",
4455
+ operators: ["term", "phrase"],
4456
+ example: 'source:"msg:telegram/1/7/1"',
4457
+ relation: "tasks from this message, note or document; an account's tasks only"
4446
4458
  }
4447
- });
4448
- }, "commandsCommandDefinition");
4449
- var scoped = /* @__PURE__ */ __name((document, path) => {
4450
- const all = flatten(document.commands);
4451
- const found = all.find((item) => item.path.join(" ") === path.join(" "));
4452
- if (!found)
4453
- throw new CliError("not_found", "command path was not found");
4454
- const inheritedOptions = all.filter((item) => item.path.length < path.length && item.path.every((word, index) => word === path[index])).filter((item) => item.options.length > 0).map((item) => ({ path: item.path, options: item.options }));
4455
- return { ...document, scope: found.path, inherited_options: inheritedOptions, commands: [found] };
4456
- }, "scoped");
4457
- var same = /* @__PURE__ */ __name((a, b) => a.join(" ") === b?.join(" "), "same");
4458
- var SECTIONS = [
4459
- ["context", "read"],
4460
- ["show", "read"],
4461
- ["notes", "read"],
4462
- ["notes add", "write"],
4463
- ["notes edit", "write"],
4464
- ["notes remove", "write"],
4465
- ["notes export", "write"],
4466
- ["sources", "read"],
4467
- ["sources add", "write"],
4468
- ["sources remove", "write"],
4469
- ["mail", "read"],
4470
- ["events", "read"],
4471
- ["agents", "read"],
4472
- ["answers", "read"],
4473
- ["memories", "write"],
4474
- ["facts", "write"],
4475
- ["actions", "write"],
4476
- ["review", "write"],
4477
- ["notes links", "organise"],
4478
- ["organizations", "organise"],
4479
- ["invoices", "organise"],
4480
- ["orders", "organise"],
4481
- ["deals", "organise"],
4482
- ["leads", "organise"],
4483
- ["pipelines", "organise"],
4484
- ["payments", "organise"],
4485
- ["products", "organise"],
4486
- ["contracts", "organise"],
4487
- ["subscriptions", "organise"],
4488
- ["wallets", "organise"],
4489
- ["transactions", "organise"],
4490
- ["records", "organise"],
4491
- ["projects", "organise"],
4492
- ["people", "organise"],
4493
- ["relationships", "organise"],
4494
- ["reminders", "organise"],
4495
- ["vocabulary", "organise"],
4496
- ["sync", "automate"],
4497
- ["jobs", "automate"],
4498
- ["folders", "setup"],
4499
- ["config", "setup"]
4500
- ];
4501
- var placeInSections = /* @__PURE__ */ __name((program) => {
4502
- for (const [path, section2] of SECTIONS) {
4503
- let command = program;
4504
- for (const name2 of path.split(" "))
4505
- command = command?.commands.find((child) => child.name() === name2);
4506
- if (!command)
4507
- throw new Error(`no command to place in a section: ${path}`);
4508
- inSection(command, section2);
4459
+ ],
4460
+ defaults: [OPEN_WORK]
4461
+ };
4462
+ var ACCOUNT_FIELDS = /* @__PURE__ */ new Set(["state", "source"]);
4463
+ var taskItem = /* @__PURE__ */ __name((task, extra = {}) => {
4464
+ const { held, readiness } = extra;
4465
+ return {
4466
+ ref: task.ref,
4467
+ id: task.id,
4468
+ key: task.key,
4469
+ title: task.title,
4470
+ description: task.description,
4471
+ kind: task.type,
4472
+ state: task.status,
4473
+ project: task.project,
4474
+ project_key: task.projectKey,
4475
+ project_name: task.projectName,
4476
+ ...held ? {
4477
+ origin: held.origin,
4478
+ account: held.account,
4479
+ chat: held.sourceKind === "note" ? null : formatReference({
4480
+ type: "chat",
4481
+ provider: held.account.split(":")[0] ?? "",
4482
+ account: held.account.split(":").slice(1).join(":"),
4483
+ chat: held.group
4484
+ })
4485
+ } : {},
4486
+ source_message: task.source,
4487
+ external_id: task.externalId,
4488
+ source_url: task.sourceUrl,
4489
+ priority: task.priority,
4490
+ priority_label: task.priorityLabel,
4491
+ agent: task.agent,
4492
+ revision: task.revision,
4493
+ parent: task.parent,
4494
+ due_at: task.dueAt,
4495
+ closed_at: task.closedAt,
4496
+ reason: task.reason,
4497
+ category: task.category,
4498
+ claimed_by: task.claimedBy,
4499
+ claimed_until: task.claimedUntil,
4500
+ ...readiness ? { assignments: task.assignments, ready: readiness.ready, blocked_by: readiness.blockers } : {},
4501
+ created_by: task.createdBy,
4502
+ created_at: task.createdAt,
4503
+ updated_at: task.updatedAt
4504
+ };
4505
+ }, "taskItem");
4506
+ var accountTasks = /* @__PURE__ */ __name(async (store, keys, options) => {
4507
+ const pages = await Promise.all(keys.map((key) => store.knowledge.taskIds(key, options)));
4508
+ const items = [];
4509
+ for (const id of pages.flatMap((page) => page.items)) {
4510
+ const held = await store.tasks.get(id);
4511
+ if (held)
4512
+ items.push(taskItem(await store.tasks.details(id), { held }));
4509
4513
  }
4510
- }, "placeInSections");
4511
- var commandsCommand = /* @__PURE__ */ __name((program, streams) => {
4512
- customizeSharedCommand(sharedCommandTemplate(APP, "commands"), "Memo binds discovery to its command-local JSON renderer and scoped stored-data document format.", () => {
4513
- commandsCommandDefinition(program, streams);
4514
- addContractInfo(program, (value) => streams.data(JSON.stringify(value)));
4515
- const command = program.commands.find((command2) => command2.name() === "commands");
4516
- if (!command)
4517
- throw new Error("discovery builder did not install its command");
4518
- return command;
4519
- }, SHELL_OVERRIDES.commands);
4520
- }, "commandsCommand");
4521
-
4522
- // dist/doctor/command.js
4523
- import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync4 } from "node:fs";
4524
- import { homedir } from "node:os";
4525
- import { dirname, join as join3 } from "node:path";
4526
- var retiredMailAddresses = /* @__PURE__ */ __name((mail) => {
4527
- const accounts = mail?.accounts;
4528
- if (!Array.isArray(accounts))
4529
- return [];
4530
- return accounts.map((account2) => account2?.address).filter((address) => typeof address === "string");
4531
- }, "retiredMailAddresses");
4532
- var unitPaths = /* @__PURE__ */ __name((unit) => [...(/^ExecStart=(.*)$/m.exec(unit)?.[1] ?? "").matchAll(/"((?:[^"\\]|\\.)*)"/g)].map(([, path]) => path?.replace(/\\(["\\])/g, "$1") ?? "").filter((path) => path.startsWith("/")), "unitPaths");
4533
- var diagnose = /* @__PURE__ */ __name(async (env, program, { deep = false } = {}) => {
4534
- const problems = [];
4535
- const paths = resolvePaths({ appName: APP.appName, prefix: APP.envPrefix, env });
4536
- const { config, ...configReport } = inspectConfig(env, program);
4537
- const files = privateFiles({
4538
- files: [configReport.path, ...withSqliteSidecars(storePath(env))],
4539
- // A folder the owner chose with MESSAGING_STORE may rightly be shared, like the home folder itself.
4540
- dirs: [dirname(configReport.path), ...env.MESSAGING_STORE ? [] : [dirname(storePath(env))], runsDirFor(APP, env)]
4514
+ items.sort((a, b) => a.created_at.localeCompare(b.created_at));
4515
+ return listAnswer("tasks list", {
4516
+ items: items.slice(0, options.limit),
4517
+ limit: options.limit,
4518
+ has_more: items.length > options.limit || pages.some((page) => page.hasMore),
4519
+ meta: { accounts: keys.map(accountName) }
4541
4520
  });
4542
- const store = await storeSummary(env, { deep, command: "memo", version: APP.version });
4543
- if ("damage" in store && store.damage)
4544
- problems.push({ what: store.damage.what, fix: "memo store recover" });
4545
- else if ("error" in store && store.error)
4546
- problems.push({ what: `the store cannot be read: ${store.error}`, fix: "" });
4547
- if ("writable" in store && store.writable === false)
4548
- problems.push({ what: "the store is from a newer version than this memo", fix: "memo upgrade" });
4549
- const folders = noteFolders(config.notes).map(({ path, id }) => ({
4550
- path,
4551
- exists: existsSync3(path),
4552
- bound: id !== null
4553
- }));
4554
- for (const folder of folders) {
4555
- if (!folder.exists)
4556
- problems.push({
4557
- what: `the notes folder ${folder.path} is missing`,
4558
- fix: "connect its disk; if it moved, memo folders update <id> <new path>"
4559
- });
4560
- else if (!folder.bound)
4561
- problems.push({
4562
- what: `the notes folder ${folder.path} has no folder id`,
4563
- fix: `memo folders add "${folder.path}"`
4564
- });
4565
- }
4566
- if (config.mail !== void 0) {
4567
- const addresses = retiredMailAddresses(config.mail);
4568
- problems.push({
4569
- what: `memo no longer imports mail, so the config's mail settings do nothing; the mail CLI syncs ${addresses.length ? addresses.join(", ") : "each mailbox"} into the same store (mail <profile> session start --address <address>, then mail <profile> sync), and the mail already stored stays`,
4570
- fix: "memo config unset mail"
4571
- });
4572
- }
4573
- let textModel = DEFAULT_TEXT_MODEL;
4521
+ }, "accountTasks");
4522
+ var accountTerms = /* @__PURE__ */ __name((query) => {
4523
+ const leaves2 = query ? walkQuery(query.root) : [];
4524
+ const other = leaves2.find(({ field: field2 }) => !ACCOUNT_FIELDS.has(field2));
4525
+ if (other)
4526
+ throw new CliError("validation_error", `${other.field}: narrows project tasks: add project:<key>, or project:* for every project`);
4527
+ const states = new Set(leaves2.filter(({ field: field2 }) => field2 === "state").map(({ value }) => value.toLowerCase()));
4528
+ if ((states.has("todo") || states.has("in_progress")) && !states.has("open"))
4529
+ throw new CliError("validation_error", "todo and in_progress are steps of a project task: add project:");
4530
+ const closed = [...states].filter(isClosedState);
4531
+ if (closed.length > 1 || closed.length === 1 && states.size > 1)
4532
+ throw new CliError("validation_error", "an account's tasks are listed in one state: state:open, done or dismissed");
4533
+ const sources = leaves2.filter(({ field: field2 }) => field2 === "source").map(({ value }) => value);
4534
+ if (sources.length > 1)
4535
+ throw new CliError("validation_error", "source: only once here");
4536
+ return { state: closed[0] ?? "open", source: sources[0] };
4537
+ }, "accountTerms");
4538
+ var resolveProjects = /* @__PURE__ */ __name(async (store, query) => {
4539
+ for (const node of query ? walkQuery(query.root) : [])
4540
+ if (node.field === "project" && node.operator !== "wildcard" && node.operator !== "range")
4541
+ node.value = await canonicalProject(store, node.value);
4542
+ return query;
4543
+ }, "resolveProjects");
4544
+ var offsetOf = /* @__PURE__ */ __name((cursor, key) => {
4545
+ if (cursor === void 0)
4546
+ return 0;
4547
+ let read;
4574
4548
  try {
4575
- textModel = chosenTextModel(env).id;
4576
- } catch (error) {
4577
- problems.push({
4578
- what: `the embedding model choice cannot be read: ${error instanceof Error ? error.message : String(error)}`,
4579
- fix: `fix text_models in ${textModelsFile(env)}, or remove it to go back to ${DEFAULT_TEXT_MODEL}`
4580
- });
4549
+ read = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
4550
+ } catch {
4551
+ read = void 0;
4581
4552
  }
4582
- const modelDir = join3(sharedModelsDirectory("text", env), textModel);
4583
- const model = existsSync3(modelDir) && readdirSync(modelDir).length > 0;
4584
- const state = autoState(config);
4585
- const unitFile = join3(env.XDG_CONFIG_HOME ?? join3(env.HOME ?? homedir(), ".config"), "systemd", "user", "memo-import.service");
4586
- const unit = existsSync3(unitFile) ? readFileSync4(unitFile, "utf8") : void 0;
4587
- const runs = unit ? unitPaths(unit) : [];
4588
- const missing = runs.filter((path) => !existsSync3(path));
4589
- const last = lastAutoRun(env);
4590
- const failing = last && last.exitCode !== 0 ? { since: last.failingSince ?? last.at, runs: last.failures ?? 1, reason: last.reason ?? null } : null;
4591
- if (state.enabled && !unit)
4592
- problems.push({ what: "the timer is on in the config but not installed", fix: "memo jobs start" });
4593
- if (missing.length > 0)
4594
- problems.push({ what: `the timer runs a program that is gone: ${missing.join(", ")}`, fix: "memo jobs start" });
4595
- if (failing)
4596
- problems.push({ what: `the timer has failed ${failing.runs} runs since ${failing.since}`, fix: "memo jobs status" });
4597
- const skills = SKILL_TARGETS.map((target) => {
4598
- const path = skillPath(target, APP.appName, env);
4599
- const version = existsSync3(path) ? skillVersion(readFileSync4(path, "utf8")) ?? "unknown" : null;
4600
- return { target, path, version };
4553
+ if (read?.version !== 1 || read.key !== key || !Number.isSafeInteger(read.offset) || read.offset < 0)
4554
+ throw new CliError("validation_error", "the cursor belongs to another query or order; list again without it");
4555
+ return read.offset;
4556
+ }, "offsetOf");
4557
+ var projectTasks = /* @__PURE__ */ __name(async (store, typed, query, options) => {
4558
+ const { limit, sort = "priority", desc = false } = options;
4559
+ const key = createHash2("sha256").update(JSON.stringify([typed, sort, desc])).digest("base64url");
4560
+ const offset = offsetOf(options.cursor, key);
4561
+ const page = await store.tasks.board(query, { limit, offset, sort, descending: desc, zone: timezoneOf() });
4562
+ const items = [];
4563
+ for (const task of page.items)
4564
+ items.push(taskItem(task, { readiness: await store.tasks.taskReadiness(task.id) }));
4565
+ const next = offset + items.length;
4566
+ const more = next < page.total;
4567
+ return listAnswer("tasks list", {
4568
+ items,
4569
+ limit,
4570
+ has_more: more,
4571
+ paging: "cursor",
4572
+ ...more ? {
4573
+ cursor: Buffer.from(JSON.stringify({ version: 1, key, offset: next })).toString("base64url")
4574
+ } : {},
4575
+ total: { value: page.total, relation: "eq" },
4576
+ args: { query: typed, ...options.sort ? { sort } : {}, ...desc ? { desc: true } : {} }
4601
4577
  });
4602
- if (skills.some(({ version }) => version !== null && version !== VERSION))
4603
- problems.push({ what: "an installed skill is from another memo version", fix: "memo skill install --for all" });
4604
- return {
4605
- memo: { version: VERSION, node: process.version, installed_by: installer() },
4606
- config: configReport,
4607
- state: paths.state,
4608
- cache: paths.cache,
4609
- store,
4610
- notes: folders,
4611
- search_model: { name: textModel, path: modelDir, present: model },
4612
- timer: { enabled: state.enabled, every: state.every, unit: unit ? unitFile : null, runs, failing },
4613
- skills,
4614
- files: files.checked ? { ...files, problems: files.problems.map(({ path, mode, want }) => ({ path, mode, want })) } : files,
4615
- problems,
4616
- fixes: files.checked ? files.problems.map(({ fix }) => fix) : []
4617
- };
4618
- }, "diagnose");
4619
- var yes = /* @__PURE__ */ __name((value) => value ? "yes" : "no", "yes");
4620
- var fileLines = /* @__PURE__ */ __name((d, fixes) => d.files.checked ? d.files.problems.map(({ path, mode, want }, at) => `! ${path} is ${mode}, so others can read it; it should be ${want} \u2014 ${fixes[at]}`) : [], "fileLines");
4621
- var onlineLine = /* @__PURE__ */ __name(({ version }) => version.latest === null ? "online npm could not be reached" : version.newer ? `online memo ${version.latest} is published (this is ${version.current}) \u2014 memo upgrade` : `online memo ${version.current} is the newest published`, "onlineLine");
4622
- var doctorText = /* @__PURE__ */ __name((d, fixes = [], online) => [
4623
- `memo ${d.memo.version} on Node ${d.memo.node}, installed by ${d.memo.installed_by}`,
4624
- `config ${d.config.path}${d.config.found ? "" : " (none yet)"}`,
4625
- `state ${d.state}`,
4626
- `cache ${d.cache}`,
4627
- `store ${d.store.path}${!d.store.exists ? " (none yet; the first memo sync makes it)" : "schema" in d.store ? ` (schema ${d.store.schema}, memo speaks ${d.store.speaks})` : ""}`,
4628
- ..."health" in d.store && d.store.health ? [
4629
- `health ${d.store.health.ok ? "ok" : "DAMAGED"}, checked ${d.store.health.covers}${d.store.health.checked === "quick" ? " (memo doctor --deep reads every page)" : ""}`
4630
- ] : [],
4631
- ...d.notes.map((f) => `notes ${f.path} \u2014 exists ${yes(f.exists)}, folder id ${yes(f.bound)}`),
4632
- `search ${d.search_model.name} ${d.search_model.present ? "present" : "not downloaded \u2014 search is by words"}`,
4633
- `timer ${d.timer.enabled ? `on, every ${d.timer.every}` : "off"}`,
4634
- ...d.skills.map((s) => `skill ${s.path} ${s.version === null ? "(not installed)" : `(${s.version})`}`),
4635
- ...online ? [onlineLine(online)] : [],
4636
- "",
4637
- ...d.problems.length + d.config.issues.length + fileLines(d, fixes).length === 0 ? ["nothing needs attention"] : [
4638
- ...d.config.issues.map(({ what, fix }) => `! ${what}${fix ? ` \u2014 ${fix}` : ""}`),
4639
- ...fileLines(d, fixes),
4640
- ...d.problems.map(({ what, fix }) => `! ${what}${fix ? ` \u2014 ${fix}` : ""}`)
4641
- ]
4642
- ].join("\n"), "doctorText");
4643
- var sharedDoctor = sharedCommandTemplate(APP, "doctor");
4644
- var ONLINE_FLAGS = "--online";
4645
- var onlineText = sharedDoctor.options.find((option) => option.long === ONLINE_FLAGS)?.description ?? "";
4646
- var onlineCheck = /* @__PURE__ */ __name(async () => {
4647
- const started = Date.now();
4648
- const newest = await latest();
4649
- return {
4650
- ok: true,
4651
- duration_ms: Date.now() - started,
4652
- version: { current: VERSION, latest: newest ?? null, newer: newest !== void 0 && isNewer(newest, VERSION) }
4653
- };
4654
- }, "onlineCheck");
4655
- var doctorCommandDefinition = /* @__PURE__ */ __name((program, streams, env) => {
4656
- const { print } = answers(program, streams, env);
4657
- annotate(program.command("doctor"), { effects: REMOTE_READ }).description(sharedDoctor.description()).option(ONLINE_FLAGS, onlineText).option("--deep", DOCTOR_DEEP_OPTION).option("--json", "print JSON; --json=id,title keeps only those fields").addHelpText("after", "\nReads this computer only, unless --online: the config, the store, the notes folders, the search model, the timer and the agent skill, each problem with the command that fixes it. --online also asks npm whether a newer memo is published, and nothing else leaves this computer. Exits 1 when something other than the config file or a file mode needs attention; problems in the config file are listed in config.issues, and files or folders other users can read in files.problems, and neither changes the exit code.").action(async (options) => {
4658
- const { fixes, ...diagnosis } = await diagnose(env, program, { deep: options.deep === true });
4659
- const online = options.online ? await onlineCheck() : void 0;
4660
- print(options.json, itemAnswer(online ? { ...diagnosis, online } : diagnosis), doctorText(diagnosis, fixes, online));
4661
- if (options.json)
4662
- for (const fix of fixes)
4663
- streams.diagnostic(`to make it private: ${fix}`);
4664
- if (diagnosis.problems.length > 0)
4665
- process.exitCode = 1;
4578
+ }, "projectTasks");
4579
+ var listTasks = /* @__PURE__ */ __name(async (store, words, options, accounts) => {
4580
+ const flag = Array.isArray(words) ? words.findIndex((word) => /^--[a-z]+$/.test(word)) : -1;
4581
+ if (flag >= 0) {
4582
+ const field2 = words[flag]?.slice(2) ?? "";
4583
+ const value = words[flag + 1] ?? "<value>";
4584
+ const fields = TASKS_LIST_QUERY.fields.map(({ name: name2 }) => name2);
4585
+ throw new CliError("validation_error", fields.includes(field2) ? `tasks list takes ${field2} as a query word: memo tasks list ${field2}:${value}` : `tasks list has no --${field2}; its query words are ${fields.map((name2) => `${name2}:`).join(", ")}`);
4586
+ }
4587
+ const typed = typedQuery(words);
4588
+ const query = listQuery(typed, TASKS_LIST_QUERY, "tasks list", { defaultField: "text" });
4589
+ const leaves2 = query ? walkQuery(query.root) : [];
4590
+ if (leaves2.some(({ field: field2 }) => field2 === "project")) {
4591
+ if (leaves2.some(({ field: field2 }) => field2 === "source"))
4592
+ throw new CliError("validation_error", "source: narrows an account's tasks, not a project's");
4593
+ return projectTasks(store, typed, await resolveProjects(store, query), options);
4594
+ }
4595
+ if (options.cursor !== void 0 || options.sort !== void 0 || options.desc)
4596
+ throw new CliError("validation_error", "--cursor, --sort and --desc page and order project tasks: add project:");
4597
+ const { state, source } = accountTerms(query);
4598
+ return accountTasks(store, await accounts(), {
4599
+ state,
4600
+ limit: options.limit,
4601
+ ...source === void 0 ? {} : { sources: [source] }
4666
4602
  });
4667
- }, "doctorCommandDefinition");
4668
- var doctorCommand = /* @__PURE__ */ __name((...args) => {
4669
- customizeSharedCommand(sharedCommandTemplate(APP, "doctor"), "Memo diagnoses imported-data paths and stored account configuration without connecting to a messenger or querying a keyring.", () => {
4670
- doctorCommandDefinition(...args);
4671
- const command = args[0].commands.find((command2) => command2.name() === "doctor");
4672
- if (!command)
4673
- throw new Error("doctor builder did not install its command");
4674
- return command;
4675
- }, SHELL_OVERRIDES.doctor);
4676
- }, "doctorCommand");
4603
+ }, "listTasks");
4604
+ var countTasks = /* @__PURE__ */ __name(async (store, words) => {
4605
+ const query = listQuery(typedQuery(words), TASKS_LIST_QUERY, "tasks counts", { defaultField: "text" });
4606
+ if (query && walkQuery(query.root).some(({ field: field2 }) => field2 === "source"))
4607
+ throw new CliError("validation_error", "source: narrows an account's tasks; tasks counts counts project tasks");
4608
+ return store.tasks.boardCounts(await resolveProjects(store, query), { zone: timezoneOf() });
4609
+ }, "countTasks");
4677
4610
 
4678
- // node_modules/.pnpm/@wirecat+cli-core@0.616.0_@wirecat+cli-base@0.91.0_commander@15.0.0_esbuild@0.28.2_type_8665abbf6fdc3acbe46baf279794bee0/node_modules/@wirecat/cli-core/dist/calendar/sync.js
4679
- var DAY3 = 864e5;
4680
- var SLOTS_EVERY = 7 * DAY3;
4681
- var isWindow = /* @__PURE__ */ __name((value) => typeof value?.from === "number" && typeof value.to === "number", "isWindow");
4682
- var isToken = /* @__PURE__ */ __name((value) => value === null || typeof value === "string", "isToken");
4683
- var roundOf = /* @__PURE__ */ __name((value) => {
4684
- if (value === null || value === void 0)
4685
- return null;
4686
- const round = value;
4687
- if (typeof round.full !== "boolean" || !isToken(round.from) || !isWindow(round.window) || typeof round.startedAt !== "number" || typeof round.reset !== "boolean" || !isToken(round.page) || typeof round.listed !== "boolean" || !isToken(round.next) || !Array.isArray(round.series) || round.series.some((id) => typeof id !== "string") || round.everySeries !== void 0 && typeof round.everySeries !== "boolean")
4688
- return void 0;
4689
- const { full, from, window, startedAt, reset, page, listed: listed2, next, series, everySeries, unchanged } = round;
4690
- return {
4691
- full,
4692
- from,
4693
- window,
4694
- startedAt,
4695
- reset,
4696
- page,
4697
- listed: listed2,
4698
- next,
4699
- series,
4700
- ...everySeries === false ? { everySeries } : {},
4701
- ...unchanged === true ? { unchanged } : {}
4702
- };
4703
- }, "roundOf");
4704
- var parseCalendarCursor = /* @__PURE__ */ __name((value) => {
4705
- if (value === null)
4706
- return null;
4611
+ // dist/digest/sync-health.js
4612
+ import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync5 } from "node:fs";
4613
+ import { join as join4 } from "node:path";
4614
+
4615
+ // dist/knowledge/messenger-sync.js
4616
+ import { spawn } from "node:child_process";
4617
+ import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync4, realpathSync, statSync as statSync2 } from "node:fs";
4618
+ import { delimiter, join as join3 } from "node:path";
4619
+ var STATE = {
4620
+ tg: { appName: "tg-cli", envPrefix: "TG" },
4621
+ max: { appName: "max-cli", envPrefix: "MAX" },
4622
+ wa: { appName: "whatsapp-cli", envPrefix: "WA" }
4623
+ };
4624
+ var messengerToolOf = /* @__PURE__ */ __name((provider) => {
4625
+ const tool = providerNamed(provider)?.command;
4626
+ const state = tool === void 0 ? void 0 : STATE[tool];
4627
+ return tool === void 0 || state === void 0 ? void 0 : { tool, ...state };
4628
+ }, "messengerToolOf");
4629
+ var syncProfileOf = /* @__PURE__ */ __name((value, previous = {}) => {
4630
+ const [tool, profile] = value.split("=", 2);
4631
+ const tools = Object.keys(STATE);
4632
+ if (!tool || !profile || !tools.includes(tool))
4633
+ throw new InvalidArgumentError(`<tool>=<profile>, where the tool is ${tools.join(", ")}: max=default`);
4634
+ if (previous[tool] !== void 0 && previous[tool] !== profile)
4635
+ throw new InvalidArgumentError(`one profile per tool; ${tool} is already ${previous[tool]}`);
4636
+ return { ...previous, [tool]: profile };
4637
+ }, "syncProfileOf");
4638
+ var accountsDir = /* @__PURE__ */ __name((tool, env) => join3(resolvePaths({ appName: tool.appName, prefix: tool.envPrefix, env }).state, "accounts"), "accountsDir");
4639
+ var signedInAccount = /* @__PURE__ */ __name((tool, profile, env) => {
4707
4640
  try {
4708
- const cursor = JSON.parse(value);
4709
- if (cursor?.v !== 1 && cursor?.v !== 2 || !isToken(cursor.token) || typeof cursor.fullAt !== "number" || !isWindow(cursor.window))
4710
- return null;
4711
- const { token, window, fullAt } = cursor;
4712
- if (cursor.v === 1)
4713
- return { v: 2, token, window, fullAt, updatedAt: null, done: 0, round: null };
4714
- const round = roundOf(cursor.round);
4715
- if (round === void 0 || cursor.updatedAt !== null && typeof cursor.updatedAt !== "number" || typeof cursor.done !== "number")
4716
- return null;
4641
+ return JSON.parse(readFileSync4(join3(accountsDir(tool, env), `${profile}.json`), "utf8"))?.account;
4642
+ } catch {
4643
+ return void 0;
4644
+ }
4645
+ }, "signedInAccount");
4646
+ var profileFor = /* @__PURE__ */ __name((provider, account2, env, chosen = {}) => {
4647
+ const tool = messengerToolOf(provider);
4648
+ if (!tool)
4649
+ return void 0;
4650
+ const dir = accountsDir(tool, env);
4651
+ const profiles = existsSync3(dir) ? readdirSync(dir).filter((name2) => name2.endsWith(".json")).map((name2) => name2.slice(0, -".json".length)).sort() : [];
4652
+ const accountOf = /* @__PURE__ */ __name((profile) => signedInAccount(tool, profile, env), "accountOf");
4653
+ const all = profiles.join(", ") || "none";
4654
+ const pick = chosen[tool.tool];
4655
+ if (pick !== void 0) {
4656
+ if (!profiles.includes(pick))
4657
+ return { why: `${tool.tool} profile ${pick} is not signed in here; profiles: ${all}`, profiles };
4658
+ const held = accountOf(pick);
4659
+ return held === account2 ? { profile: pick } : { why: `${tool.tool} profile ${pick} is signed in to account ${String(held)}, not ${account2}`, profiles: [] };
4660
+ }
4661
+ const matching = profiles.filter((profile) => accountOf(profile) === account2);
4662
+ if (matching.length === 1 && matching[0])
4663
+ return { profile: matching[0] };
4664
+ if (matching.length > 1)
4717
4665
  return {
4718
- v: 2,
4719
- token,
4720
- window,
4721
- fullAt,
4722
- updatedAt: cursor.updatedAt ?? null,
4723
- done: cursor.done,
4724
- round,
4725
- ...cursor.restored === true ? { restored: true } : {},
4726
- ...typeof cursor.slotsTo === "number" ? { slotsTo: cursor.slotsTo } : {}
4666
+ why: `${matching.length} ${tool.tool} profiles are signed in to account ${account2}: ${matching.join(", ")} \u2014 choose one with --sync-profile ${tool.tool}=<profile>`,
4667
+ profiles: matching
4727
4668
  };
4669
+ return { why: `no ${tool.tool} profile here is signed in to account ${account2}; profiles: ${all}`, profiles: [] };
4670
+ }, "profileFor");
4671
+ var onPath = /* @__PURE__ */ __name((name2, env) => (env.PATH ?? "").split(delimiter).filter(Boolean).map((dir) => join3(dir, name2)).find((path) => {
4672
+ try {
4673
+ return statSync2(path).isFile();
4728
4674
  } catch {
4729
- return null;
4675
+ return false;
4730
4676
  }
4731
- }, "parseCalendarCursor");
4732
- var calendarStatus = /* @__PURE__ */ __name(async (store, accountId, { profile, now }) => {
4733
- const items = [];
4734
- for (const calendar of await store.calendars(accountId)) {
4735
- if (!calendar.synced || calendar.deletedAt !== null)
4736
- continue;
4737
- const cursor = parseCalendarCursor(await store.cursor(calendar.id));
4738
- const progress = progressOf({
4739
- job: `calendar ${calendar.id}`,
4740
- profile,
4741
- done: cursor?.done ?? 0,
4742
- complete: cursor !== null && cursor.round === null,
4743
- ...cursor?.updatedAt == null ? {} : { updatedAt: new Date(cursor.updatedAt).toISOString() },
4744
- now: new Date(now)
4745
- });
4746
- items.push({
4747
- calendar_id: String(calendar.id),
4748
- external_id: calendar.externalId,
4749
- title: calendar.title,
4750
- round: cursor?.round ? cursor.round.full ? "full" : "changes" : null,
4751
- ...progress,
4752
- line: progressLine(progress)
4753
- });
4754
- }
4755
- return { items, page: 1, limit: Math.max(1, items.length), hasMore: false };
4756
- }, "calendarStatus");
4757
-
4758
- // node_modules/.pnpm/@wirecat+cli-core@0.616.0_@wirecat+cli-base@0.91.0_commander@15.0.0_esbuild@0.28.2_type_8665abbf6fdc3acbe46baf279794bee0/node_modules/@wirecat/cli-core/dist/calendar/feeds.js
4759
- var FEED_LIMITS = { maxBytes: 10 * 1024 * 1024, timeoutMs: 3e4, redirects: 5 };
4760
-
4761
- // node_modules/.pnpm/@wirecat+cli-core@0.616.0_@wirecat+cli-base@0.91.0_commander@15.0.0_esbuild@0.28.2_type_8665abbf6fdc3acbe46baf279794bee0/node_modules/@wirecat/cli-core/dist/calendar/reads.js
4762
- var LIST = "events list";
4763
- var EVENT_TYPES = [
4764
- "default",
4765
- "out-of-office",
4766
- "focus",
4767
- "working-location",
4768
- "birthday",
4769
- "from-mail",
4770
- "call"
4771
- ];
4772
- var oneSided = /* @__PURE__ */ __name((field2, example) => (node) => {
4773
- if (node.operator !== "range" || node.upper !== "*")
4774
- queryError("query.unsupported-operator", node.span, `${field2} takes only an earliest moment, such as ${example}`, {
4775
- command: LIST,
4776
- guide: LIST_QUERY_GUIDE
4777
- });
4778
- }, "oneSided");
4779
- var EVENTS_QUERY = {
4780
- entity: "event",
4781
- backend: "native",
4782
- fields: queryFieldsOf(event, {
4783
- native: ["id", "starts_at", "ends_at", "changed_at", "overlapping", "calendar", "person", "event_type"],
4784
- negatable: ["event_type"],
4785
- relations: {
4786
- event_type: {
4787
- type: "enum",
4788
- values: EVENT_TYPES,
4789
- example: "event_type:default",
4790
- relation: "events with a copy of this kind; -event_type:<kind> leaves out events with one"
4791
- },
4792
- calendar: {
4793
- type: "kw",
4794
- example: "calendar:3",
4795
- relation: "one calendar, by its id, the provider's id or its title"
4796
- },
4797
- person: {
4798
- type: "kw",
4799
- example: "person:ana@example.com",
4800
- relation: "events one person attends: an address, or a person id from search"
4801
- }
4802
- },
4803
- examples: {
4804
- id: "id:42",
4805
- starts_at: "starts_at:[2026-11-02 TO 2026-11-07}",
4806
- ends_at: "ends_at>=2026-11-02",
4807
- changed_at: "changed_at>=2026-10-01",
4808
- overlapping: "overlapping:true"
4809
- },
4810
- validate: {
4811
- ends_at: oneSided("ends_at", "ends_at>=2026-11-02; for the latest, write starts_at<2026-11-07"),
4812
- changed_at: oneSided("changed_at", "changed_at>=2026-10-01")
4677
+ }), "onPath");
4678
+ var runChild = /* @__PURE__ */ __name((command, args, { env, signal, onStderr }) => new Promise((resolve6, reject) => {
4679
+ const child = spawn(command, args, { env, stdio: ["ignore", "pipe", "pipe"] });
4680
+ let stdout = "";
4681
+ let pending2 = "";
4682
+ child.stdout.setEncoding("utf8").on("data", (chunk) => {
4683
+ stdout += chunk;
4684
+ });
4685
+ child.stderr.setEncoding("utf8").on("data", (chunk) => {
4686
+ const lines2 = (pending2 + chunk).split("\n");
4687
+ pending2 = lines2.pop() ?? "";
4688
+ for (const line3 of lines2)
4689
+ if (line3)
4690
+ onStderr?.(line3);
4691
+ });
4692
+ let hard;
4693
+ const stop = /* @__PURE__ */ __name(() => {
4694
+ child.kill("SIGINT");
4695
+ hard = setTimeout(() => child.kill("SIGTERM"), 1e4);
4696
+ }, "stop");
4697
+ signal?.addEventListener("abort", stop, { once: true });
4698
+ child.on("error", reject);
4699
+ child.on("close", (code) => {
4700
+ clearTimeout(hard);
4701
+ signal?.removeEventListener("abort", stop);
4702
+ if (pending2)
4703
+ onStderr?.(pending2);
4704
+ resolve6({ code, stdout });
4705
+ });
4706
+ }), "runChild");
4707
+ var binaryOf = /* @__PURE__ */ __name(async (provider, env, run) => {
4708
+ const tool = messengerToolOf(provider);
4709
+ if (!tool)
4710
+ return void 0;
4711
+ const umbrella = onPath(`wcat-${tool.tool}`, env);
4712
+ if (umbrella) {
4713
+ const described = await run(umbrella, ["--wcat-describe"], { env }).catch(() => void 0);
4714
+ try {
4715
+ if (described?.code === 0 && JSON.parse(described.stdout)?.name === tool.tool)
4716
+ return umbrella;
4717
+ } catch {
4813
4718
  }
4814
- }).map((field2) => field2.native ? {
4815
- ...field2,
4816
- operators: field2.type === "time" ? ["term", "range"] : field2.type === "bool" ? ["term"] : ["term", "phrase"]
4817
- } : field2)
4818
- };
4819
- var nameOf2 = /* @__PURE__ */ __name(({ provider, account: account2 }) => `${provider}:${account2}`, "nameOf");
4820
- var accountsMeta = /* @__PURE__ */ __name(async (store, accounts, skipped, now) => {
4821
- const issues = [...skipped];
4822
- for (const { id, key } of accounts) {
4823
- if (isImportedProvider(key.provider))
4824
- continue;
4825
- const { items } = await calendarStatus(store, id, { profile: nameOf2(key), now });
4826
- if (items.length === 0)
4827
- issues.push({ code: "not_found", reason: "calendar.unsynced", source: nameOf2(key) });
4828
- else if (items.some(({ state }) => state !== "complete"))
4829
- issues.push({ code: "not_found", reason: "calendar.sync-incomplete", source: nameOf2(key) });
4830
4719
  }
4720
+ const short = onPath(tool.tool, env);
4721
+ if (!short)
4722
+ return void 0;
4723
+ const mark = `@wirecat/${tool.appName}`;
4724
+ const real = realpathSync(short);
4725
+ if (real.includes(`/${mark}/`))
4726
+ return short;
4727
+ return statSync2(real).size < 65536 && readFileSync4(real, "utf8").includes(mark) ? short : void 0;
4728
+ }, "binaryOf");
4729
+ var answerOf = /* @__PURE__ */ __name((stdout) => {
4730
+ try {
4731
+ return JSON.parse(stdout.trim().split("\n").at(-1) ?? "");
4732
+ } catch {
4733
+ return {};
4734
+ }
4735
+ }, "answerOf");
4736
+ var MARGIN_MS = 5e3;
4737
+ var messengerFetcher = /* @__PURE__ */ __name(({ env, note, chosen = {}, run = runChild, now = Date.now }) => {
4738
+ const binaries = /* @__PURE__ */ new Map();
4831
4739
  return {
4832
- accounts: accounts.map(({ key }) => nameOf2(key)),
4833
- complete: issues.length === 0,
4834
- ...issues.length ? { issues } : {}
4740
+ fetch: /* @__PURE__ */ __name(async ({ account: account2, chatId, after, until, signal }) => {
4741
+ const tool = messengerToolOf(account2.provider);
4742
+ if (!tool)
4743
+ return { state: "failed", reason: `no WireCat messenger CLI syncs ${account2.provider}` };
4744
+ const found = profileFor(account2.provider, account2.account, env, chosen);
4745
+ if (!found || !("profile" in found))
4746
+ return { state: "failed", reason: found?.why ?? "no profile" };
4747
+ if (!binaries.has(tool.tool))
4748
+ binaries.set(tool.tool, binaryOf(account2.provider, env, run));
4749
+ const bin = await binaries.get(tool.tool);
4750
+ if (!bin)
4751
+ return { state: "failed", reason: `${tool.tool} is not installed, or the one on PATH is not WireCat's` };
4752
+ const seconds = Math.max(1, Math.floor((until - now() - MARGIN_MS) / 1e3));
4753
+ const args = [
4754
+ found.profile,
4755
+ "messages",
4756
+ "sync",
4757
+ "--since-time",
4758
+ new Date(after).toISOString(),
4759
+ "--limit",
4760
+ "1000000",
4761
+ "--timeout",
4762
+ `${seconds}s`,
4763
+ "--json",
4764
+ chatId
4765
+ ];
4766
+ const done = await run(bin, args, {
4767
+ env,
4768
+ signal,
4769
+ onStderr: /* @__PURE__ */ __name((line3) => {
4770
+ if (!isEvent(line3))
4771
+ note(`${tool.tool}: ${line3}`);
4772
+ }, "onStderr")
4773
+ });
4774
+ const answer = answerOf(done.stdout);
4775
+ if (done.code !== 0) {
4776
+ const why = answer.error?.message?.split("\n")[0];
4777
+ const code = answer.error?.code ?? `exit ${done.code ?? "signal"}`;
4778
+ return {
4779
+ state: "partial",
4780
+ reason: `${tool.tool} ${found.profile} messages sync: ${code}${why ? ` \u2014 ${why}` : ""}`
4781
+ };
4782
+ }
4783
+ const item = answer.item ?? {};
4784
+ if (item.issue?.code)
4785
+ return { state: "partial", reason: item.issue.code };
4786
+ if (item.stopped || signal.aborted)
4787
+ return { state: "partial", reason: "fetch-budget" };
4788
+ return item.complete || item.reached_since ? { state: "complete" } : { state: "partial", reason: "limit" };
4789
+ }, "fetch")
4835
4790
  };
4836
- }, "accountsMeta");
4791
+ }, "messengerFetcher");
4792
+ var isEvent = /* @__PURE__ */ __name((line3) => {
4793
+ if (!line3.startsWith("{"))
4794
+ return false;
4795
+ try {
4796
+ return typeof JSON.parse(line3)?.event === "string";
4797
+ } catch {
4798
+ return false;
4799
+ }
4800
+ }, "isEvent");
4801
+ var chatCommand = /* @__PURE__ */ __name((chat, words, env, chosen) => {
4802
+ const tool = messengerToolOf(chat.provider);
4803
+ if (!tool)
4804
+ return null;
4805
+ const found = profileFor(chat.provider, chat.account, env, chosen);
4806
+ const line3 = /* @__PURE__ */ __name((profile) => `${tool.tool} ${profile} ${words}`, "line");
4807
+ if (found && "profile" in found)
4808
+ return { command: line3(found.profile) };
4809
+ return { command: null, line: line3("<profile>"), why: found?.why ?? "no profile" };
4810
+ }, "chatCommand");
4811
+ var chatSync = /* @__PURE__ */ __name((chat, since, env, chosen = {}) => (
4812
+ // A negative group id needs no `--`: no WireCat CLI has a digit short option.
4813
+ chatCommand(chat, `messages sync ${chat.chat} --since-time ${since}`, env, chosen)
4814
+ ), "chatSync");
4815
+ var chatRead = /* @__PURE__ */ __name((chat, since, env, chosen = {}) => chatCommand(chat, `messages list --compact ${chat.chat} "date>=${since}"`, env, chosen), "chatRead");
4816
+ var syncHint = /* @__PURE__ */ __name((chat, since, env, chosen = {}) => {
4817
+ const sync = chatSync(chat, since, env, chosen);
4818
+ if (!sync)
4819
+ return null;
4820
+ return sync.command ?? `${sync.line} (${sync.why})`;
4821
+ }, "syncHint");
4837
4822
 
4838
- // node_modules/.pnpm/@wirecat+cli-core@0.616.0_@wirecat+cli-base@0.91.0_commander@15.0.0_esbuild@0.28.2_type_8665abbf6fdc3acbe46baf279794bee0/node_modules/@wirecat/cli-core/dist/calendar/writer.js
4839
- var EVENTS_NOTIFY_KEY = "events.notify";
4840
- var EVENTS_DELETE_SERIES_KEY = "events.delete.series";
4841
- var EVENTS_REMIND_HOOK_KEY = "events.remind.hook";
4842
- var CALENDAR_WRITE_DEFAULTS = {
4843
- "events.create": "allow",
4844
- // One import writes a whole file of events, and may change events the calendar already holds under their UIDs.
4845
- "events.import": "ask",
4846
- "events.update": "allow",
4847
- "events.respond": "allow",
4848
- "events.move": "allow",
4849
- "events.delete": "ask",
4850
- [EVENTS_DELETE_SERIES_KEY]: "deny",
4851
- [EVENTS_NOTIFY_KEY]: "deny",
4852
- // Not ask: `--yes` answers ask, and anything that can type a gcal command can add it.
4853
- [EVENTS_REMIND_HOOK_KEY]: "deny"
4823
+ // dist/digest/sync-health.js
4824
+ var HEALTH_REASONS = ["sync-failed", "no-schedule", "quiet"];
4825
+ var MAIL2 = { appName: "mail-cli", envPrefix: "MAIL" };
4826
+ var SOURCE_WORDS = {
4827
+ telegram: "Telegram",
4828
+ max: "MAX",
4829
+ whatsapp: "WhatsApp",
4830
+ email: "mail"
4854
4831
  };
4855
-
4856
- // dist/events/events.js
4857
- var LIST2 = "events list";
4858
- var EVENT_TYPES2 = [
4859
- "default",
4860
- "out-of-office",
4861
- "focus",
4862
- "working-location",
4863
- "birthday",
4864
- "from-mail",
4865
- "call"
4866
- ];
4867
- var EVENTS_QUERY2 = {
4868
- entity: "event",
4869
- backend: "native",
4870
- fields: [
4871
- nativeField({
4872
- name: "starts_at",
4873
- type: "time",
4874
- operators: ["term", "range"],
4875
- example: "starts_at:[2026-11-02 TO 2026-11-07}"
4876
- }),
4877
- termField("account", "kw", 'account:"phone:pixel"', {
4878
- relation: "one calendar account, provider:account, or every account of a provider"
4879
- }),
4880
- termField("calendar", "kw", "calendar:Calls", { relation: "one calendar, by its id or its title" }),
4881
- termField("event_type", "enum", "event_type:call", {
4882
- values: EVENT_TYPES2,
4883
- relation: "events with a copy of this kind; a phone's imported calls are call"
4884
- })
4885
- ]
4886
- };
4887
- var RECENT_DAYS = 30;
4888
- var AHEAD_DAYS = 7;
4889
- var EVENTS_NOTE = `
4890
- Every calendar account in the store is read, phone imports included. Without starts_at: the last ${RECENT_DAYS} days and the next ${AHEAD_DAYS}. Latest start first.`;
4891
- var UNTRUSTED = ["title", "description", "location", "in"];
4892
- var readAccounts = /* @__PURE__ */ __name(async (store) => {
4893
- const held = await calendarAccounts(store);
4894
- const stored = await store.accounts.storedAccounts();
4895
- const read = [];
4896
- for (const key of held) {
4897
- const one2 = stored.find(({ provider, account: account2 }) => provider === key.provider && account2 === key.account);
4898
- if (one2)
4899
- read.push({ id: one2.id, key, calendars: await store.calendar.calendars(one2.id) });
4832
+ var toolOf = /* @__PURE__ */ __name((provider) => provider === "email" ? MAIL2 : messengerToolOf(provider), "toolOf");
4833
+ var stateDirs = /* @__PURE__ */ __name((tool, env) => resolvePaths({ appName: tool.appName, prefix: tool.envPrefix, env }), "stateDirs");
4834
+ var schedulesOf = /* @__PURE__ */ __name((state) => {
4835
+ const dir = join4(state, "schedules");
4836
+ if (!existsSync4(dir))
4837
+ return [];
4838
+ const last = new Map(scheduledRuns(state).map((one2) => [one2.job, one2.last]));
4839
+ return readdirSync2(dir).flatMap((name2) => {
4840
+ try {
4841
+ const schedule = JSON.parse(readFileSync5(join4(dir, name2, "schedule.json"), "utf8"));
4842
+ return [
4843
+ {
4844
+ name: String(schedule.name),
4845
+ kind: String(schedule.kind),
4846
+ argv: Array.isArray(schedule.argv) ? schedule.argv.map(String) : [],
4847
+ environment: schedule.environment ?? {},
4848
+ last: last.get(String(schedule.name))
4849
+ }
4850
+ ];
4851
+ } catch {
4852
+ return [];
4853
+ }
4854
+ });
4855
+ }, "schedulesOf");
4856
+ var signedIn = /* @__PURE__ */ __name((dir) => existsSync4(dir) ? readdirSync2(dir).filter((name2) => name2.endsWith(".json")).map((name2) => name2.slice(0, -".json".length)) : [], "signedIn");
4857
+ var profilesOf = /* @__PURE__ */ __name((provider, tool, env) => {
4858
+ if (provider !== "email")
4859
+ return new Map(signedIn(join4(stateDirs(tool, env).state, "accounts")).map((profile) => [
4860
+ profile,
4861
+ signedInAccount(tool, profile, env)
4862
+ ]));
4863
+ try {
4864
+ const config = JSON.parse(readFileSync5(configFilePath(stateDirs(MAIL2, env).config), "utf8"));
4865
+ return new Map(Object.entries(config?.profiles ?? {}).map(([profile, one2]) => [
4866
+ profile,
4867
+ one2?.address
4868
+ ]));
4869
+ } catch {
4870
+ return /* @__PURE__ */ new Map();
4900
4871
  }
4901
- return read;
4902
- }, "readAccounts");
4903
- var narrowed = /* @__PURE__ */ __name((accounts, value) => {
4904
- const chosen = accounts.filter(({ key }) => accountName(key) === value || key.provider === value);
4905
- if (chosen.length === 0)
4906
- throw new CliError("not_found", `account:${value} names no calendar account \u2014 the store holds ${accounts.map(({ key }) => accountName(key)).join(", ") || "none"}`, { reason: "events.account-unknown" });
4907
- return chosen;
4908
- }, "narrowed");
4909
- var calendarOf = /* @__PURE__ */ __name((accounts, value) => {
4910
- const all = accounts.flatMap((account2) => account2.calendars.map((calendar) => ({ account: account2, calendar })));
4911
- const byId = all.filter(({ calendar }) => String(calendar.id) === value);
4912
- const found = byId.length ? byId : all.filter(({ calendar }) => calendar.title === value);
4913
- if (found.length === 1)
4914
- return found[0];
4915
- if (found.length === 0)
4916
- throw new CliError("not_found", `calendar:${value} names no calendar in the accounts read`);
4917
- throw new CliError("validation_error", `calendar:${value} names ${found.length} calendars; give one by its id: ` + found.map(({ account: account2, calendar }) => `calendar:${calendar.id} (${accountName(account2.key)})`).join(", "));
4918
- }, "calendarOf");
4919
- var localDate = /* @__PURE__ */ __name((at, zone) => new Intl.DateTimeFormat("en-CA", { timeZone: zone, year: "numeric", month: "2-digit", day: "2-digit" }).format(at), "localDate");
4920
- var plusDays = /* @__PURE__ */ __name((date, days2) => {
4921
- const [year, month, day2] = date.split("-").map(Number);
4922
- return new Date(Date.UTC(year, month - 1, day2 + days2)).toISOString().slice(0, 10);
4923
- }, "plusDays");
4924
- var midnight = /* @__PURE__ */ __name((date, zone) => dayBoundary(date, zone, false, { start: 0, end: date.length }), "midnight");
4925
- var startsWindow = /* @__PURE__ */ __name((node, zone, now) => {
4926
- const range = node.operator === "range" ? dateRange(node.value, node.upper ?? "*", node.lower_inclusive !== false, node.upper_inclusive !== false, zone, node.span, now) : dateRange(node.value, node.value, true, true, zone, node.span, now);
4927
- return {
4928
- ...range.lower === void 0 ? {} : { lower: range.lower_inclusive ? range.lower : range.lower + 1 },
4929
- ...range.upper === void 0 ? {} : { upper: range.upper_inclusive ? range.upper + 1 : range.upper }
4872
+ }, "profilesOf");
4873
+ var profileRun = /* @__PURE__ */ __name((schedule, tool, profiles) => {
4874
+ const first = schedule.argv[0];
4875
+ return first !== void 0 && profiles.has(first) ? first : schedule.environment[`${tool.envPrefix}_PROFILE`] ?? "default";
4876
+ }, "profileRun");
4877
+ var syncRunsOf = /* @__PURE__ */ __name((env) => {
4878
+ const cache = /* @__PURE__ */ new Map();
4879
+ const read = /* @__PURE__ */ __name((provider, tool) => {
4880
+ const known2 = cache.get(tool.appName);
4881
+ if (known2)
4882
+ return known2;
4883
+ const app = { ...APP, appName: tool.appName, envPrefix: tool.envPrefix };
4884
+ const fresh = {
4885
+ schedules: schedulesOf(stateDirs(tool, env).state).filter((one2) => one2.kind === "command" && one2.argv.includes("sync")),
4886
+ serving: servingProfiles(app, env),
4887
+ profiles: profilesOf(provider, tool, env)
4888
+ };
4889
+ cache.set(tool.appName, fresh);
4890
+ return fresh;
4891
+ }, "read");
4892
+ return (key) => {
4893
+ const tool = toolOf(key.provider);
4894
+ if (!tool)
4895
+ return { runs: [], scheduled: false, serving: false, known: false };
4896
+ const { schedules, serving, profiles } = read(key.provider, tool);
4897
+ const mine = new Set([...profiles].filter(([, account2]) => account2 === key.account).map(([profile]) => profile));
4898
+ const runs = schedules.filter((one2) => mine.has(profileRun(one2, tool, profiles)));
4899
+ return {
4900
+ runs: runs.flatMap((one2) => one2.last ? [one2.last] : []),
4901
+ scheduled: runs.length > 0,
4902
+ serving: serving.some((profile) => mine.has(profile)),
4903
+ known: mine.size > 0
4904
+ };
4930
4905
  };
4931
- }, "startsWindow");
4932
- var dated = /* @__PURE__ */ __name((filter, zone) => {
4933
- if (filter.from !== void 0)
4934
- filter.fromDate = localDate(filter.from, zone);
4935
- if (filter.to !== void 0)
4936
- filter.toDate = plusDays(localDate(filter.to - 1, zone), 1);
4937
- if (filter.startsFrom !== void 0) {
4938
- const date = localDate(filter.startsFrom, zone);
4939
- filter.startsFromDate = midnight(date, zone) < filter.startsFrom ? plusDays(date, 1) : date;
4906
+ }, "syncRunsOf");
4907
+ var HOUR = 36e5;
4908
+ var DAY3 = 24 * HOUR;
4909
+ var QUIET_AFTER = 3 * DAY3;
4910
+ var QUIET_GAPS = 3;
4911
+ var hours = /* @__PURE__ */ __name((from, now) => Math.max(0, Math.floor((now - from) / HOUR)), "hours");
4912
+ var healthOf = /* @__PURE__ */ __name((sync, activity, now) => {
4913
+ const recent = /* @__PURE__ */ __name((at) => at !== null && at >= now - DAY3, "recent");
4914
+ if (recent(activity.storedAt) || sync.runs.some((run) => run.exitCode === 0 && recent(Date.parse(run.at))))
4915
+ return void 0;
4916
+ if (!sync.serving && sync.runs.length > 0 && sync.runs.every((run) => run.exitCode !== 0)) {
4917
+ const first = [...sync.runs].sort((a, b) => (a.failingSince ?? a.at).localeCompare(b.failingSince ?? b.at))[0];
4918
+ const since = first?.failingSince ?? first?.at ?? new Date(now).toISOString();
4919
+ return {
4920
+ reason: "sync-failed",
4921
+ since,
4922
+ staleHours: hours(Date.parse(since), now),
4923
+ ...first?.reason ? { failure: first.reason } : {}
4924
+ };
4940
4925
  }
4941
- return filter;
4942
- }, "dated");
4943
- var windowMeta = /* @__PURE__ */ __name((filter, zone) => {
4944
- const since = filter.startsFrom ?? filter.from;
4945
- return {
4946
- timezone: zone,
4947
- window: {
4948
- ...since === void 0 ? {} : { since: new Date(since).toISOString() },
4949
- ...filter.to === void 0 ? {} : { until: new Date(filter.to - 1).toISOString() }
4950
- }
4951
- };
4952
- }, "windowMeta");
4953
- var rowOf = /* @__PURE__ */ __name((details, accounts) => {
4954
- const live = details.entries.filter(({ deletedAt }) => deletedAt === null);
4955
- const copies = live.length ? live : details.entries;
4956
- const held = accounts.flatMap((account2) => account2.calendars.filter(({ id }) => copies.some(({ calendarId }) => calendarId === id)).map((calendar) => ({ account: accountName(account2.key), calendar: calendar.title ?? String(calendar.id) })));
4957
- return {
4958
- ...eventItem(details.event),
4959
- ref: `event:${details.event.id}`,
4960
- event_type: copies.find(({ eventType }) => eventType !== null)?.eventType ?? "default",
4961
- accounts: [...new Set(held.map(({ account: account2 }) => account2))],
4962
- in: held.map(({ account: account2, calendar }) => `${account2}/${calendar}`).join(", "),
4963
- untrusted: UNTRUSTED
4964
- };
4965
- }, "rowOf");
4966
- var listEvents = /* @__PURE__ */ __name(async (store, words, limit, { now = Date.now(), zone = timezoneOf() } = {}) => {
4967
- const terms2 = nativeTerms(listQuery(typedQuery(words), EVENTS_QUERY2, LIST2), LIST2);
4968
- const every = await readAccounts(store);
4969
- const account2 = terms2.get("account");
4970
- let accounts = account2 ? narrowed(every, account2.value) : every;
4971
- const filter = { newest: true };
4972
- const calendar = terms2.get("calendar");
4973
- if (calendar) {
4974
- const found = calendarOf(accounts, calendar.value);
4975
- accounts = [found.account];
4976
- filter.calendarId = found.calendar.id;
4926
+ if (!sync.scheduled && !sync.serving) {
4927
+ if (!sync.known)
4928
+ return void 0;
4929
+ const last = activity.newest ?? activity.storedAt;
4930
+ return { reason: "no-schedule", since: null, staleHours: last === null ? 0 : hours(last, now) };
4977
4931
  }
4978
- const kind = terms2.get("event_type");
4979
- if (kind)
4980
- filter.eventType = kind.value.toLowerCase();
4981
- const starts = terms2.get("starts_at");
4982
- if (starts) {
4983
- const { lower, upper } = startsWindow(starts, zone, now);
4984
- if (lower !== void 0)
4985
- filter.startsFrom = lower;
4986
- if (upper !== void 0)
4987
- filter.to = upper;
4988
- } else {
4989
- const today2 = localDate(now, zone);
4990
- filter.from = midnight(plusDays(today2, -RECENT_DAYS), zone);
4991
- filter.to = midnight(plusDays(today2, AHEAD_DAYS + 1), zone);
4932
+ if (activity.newest === null || activity.moved30d === 0)
4933
+ return void 0;
4934
+ const usual = Math.max(QUIET_AFTER, QUIET_GAPS * 30 * DAY3 / activity.moved30d);
4935
+ if (activity.newest < now - usual)
4936
+ return { reason: "quiet", since: new Date(activity.newest).toISOString(), staleHours: hours(activity.newest, now) };
4937
+ return void 0;
4938
+ }, "healthOf");
4939
+
4940
+ // dist/digest/tasks.js
4941
+ var DIGEST_SCHEDULE = "morning-digest";
4942
+ var DAY4 = 864e5;
4943
+ var digestSchedule = /* @__PURE__ */ __name((env) => {
4944
+ const state = resolvePaths({ appName: APP.appName, prefix: APP.envPrefix, env }).state;
4945
+ const schedule = schedulesOf(state).find((one2) => one2.name === DIGEST_SCHEDULE || one2.kind === "agent" && one2.argv.join(" ").includes(DIGEST_SCHEDULE));
4946
+ return { state, schedule };
4947
+ }, "digestSchedule");
4948
+ var runTime = /* @__PURE__ */ __name((file) => {
4949
+ const [, day2, hour, minute3, second] = /^(\d{4}-\d{2}-\d{2})T(\d{2})-(\d{2})-(\d{2})Z\./.exec(file) ?? [];
4950
+ return day2 ? Date.parse(`${day2}T${hour}:${minute3}:${second}Z`) : Number.NaN;
4951
+ }, "runTime");
4952
+ var digestReportTimes = /* @__PURE__ */ __name((env) => {
4953
+ const { state, schedule } = digestSchedule(env);
4954
+ const runs = schedule ? join5(state, "schedules", schedule.name, "runs") : void 0;
4955
+ if (!runs || !existsSync5(runs))
4956
+ return [];
4957
+ return readdirSync3(runs).filter((file) => file.endsWith(".report.json")).map(runTime).filter((at) => !Number.isNaN(at)).sort((a, b) => a - b);
4958
+ }, "digestReportTimes");
4959
+ var digestRunBefore = /* @__PURE__ */ __name((env, until) => new Date(digestReportTimes(env).findLast((at) => at < until) ?? until - DAY4).toISOString(), "digestRunBefore");
4960
+ var previousDigestAt = /* @__PURE__ */ __name((env, now) => {
4961
+ const last = digestSchedule(env).schedule?.last;
4962
+ if (!last)
4963
+ return new Date(now - DAY4).toISOString();
4964
+ return last.exitCode === 0 ? last.at : last.failingSince ?? last.at;
4965
+ }, "previousDigestAt");
4966
+ var dayOf = /* @__PURE__ */ __name((zone) => {
4967
+ const format = new Intl.DateTimeFormat("en-CA", { timeZone: zone, year: "numeric", month: "2-digit", day: "2-digit" });
4968
+ return (ms) => format.format(ms);
4969
+ }, "dayOf");
4970
+ var PAGE3 = 500;
4971
+ var openAccountTasks = /* @__PURE__ */ __name(async (store, key) => {
4972
+ const ids = [];
4973
+ for (let offset = 0; ; offset += PAGE3) {
4974
+ const page = await store.knowledge.taskIds(key, { state: "open", limit: PAGE3, offset });
4975
+ for (const id of page.items)
4976
+ ids.push(id);
4977
+ if (!page.hasMore)
4978
+ break;
4992
4979
  }
4993
- dated(filter, zone);
4994
- const read = accounts.map(({ id, key }) => ({ id, key }));
4995
- const events = read.length ? await store.calendar.events({ ...filter, accountIds: read.map(({ id }) => id), limit: limit + 1 }) : [];
4996
- const rows = [];
4997
- for (const event2 of events.slice(0, limit)) {
4998
- const details = await store.calendar.event(event2.id);
4999
- if (details)
5000
- rows.push(rowOf(details, accounts));
4980
+ return Promise.all(ids.map((id) => store.tasks.details(id)));
4981
+ }, "openAccountTasks");
4982
+ var openProjectTasks = /* @__PURE__ */ __name(async (store, zone) => {
4983
+ const query = listQuery(typedQuery("project:* state:(open OR todo OR in_progress)"), TASKS_LIST_QUERY, "review agenda", {
4984
+ defaultField: "text"
4985
+ });
4986
+ const items = [];
4987
+ for (let offset = 0; ; offset += PAGE3) {
4988
+ const page = await store.tasks.board(query, { limit: PAGE3, offset, sort: "priority", descending: false, zone });
4989
+ for (const task of page.items)
4990
+ items.push(task);
4991
+ if (page.items.length < PAGE3 || items.length >= page.total)
4992
+ break;
5001
4993
  }
5002
- const body = listAnswer(LIST2, { items: rows, limit, has_more: events.length > limit });
5003
- body.meta = {
5004
- ...body.meta,
5005
- ...await accountsMeta(store.calendar, read, [], now),
5006
- ...windowMeta(filter, zone)
5007
- };
5008
- return body;
5009
- }, "listEvents");
5010
- var showEvent = /* @__PURE__ */ __name(async (store, reference2) => {
5011
- const text3 = reference2.replace(/^event:/u, "");
5012
- if (!/^[1-9]\d*$/u.test(text3))
5013
- throw new CliError("validation_error", "an event is event:<id>");
5014
- const details = await store.calendar.event(Number(text3));
5015
- const accounts = await readAccounts(store);
5016
- const row = details && rowOf(details, accounts);
5017
- if (!details || !row?.accounts.length)
5018
- throw new CliError("not_found", `no event ${text3} in any calendar account`);
5019
- return {
5020
- ...row,
5021
- attendees: details.participants.map(({ email, name: name2, response, organizer }) => ({
5022
- email,
5023
- name: name2,
5024
- response,
5025
- organizer
5026
- })),
5027
- untrusted: [...UNTRUSTED, "attendees.name"]
5028
- };
5029
- }, "showEvent");
5030
- var accountsLine = /* @__PURE__ */ __name((accounts) => accounts.length ? `calendar accounts read: ${accounts.join(", ")}` : "no calendar account in the store", "accountsLine");
4994
+ return items;
4995
+ }, "openProjectTasks");
4996
+ var personName = /* @__PURE__ */ __name(async (store, ref) => {
4997
+ if (!ref?.startsWith("person:"))
4998
+ return null;
4999
+ return (await store.people.personByUid(ref.slice("person:".length)))?.name ?? null;
5000
+ }, "personName");
5001
+ var gatherTasks = /* @__PURE__ */ __name(async (store, keys, { now, newSince, until }) => {
5002
+ const zone = timezoneOf();
5003
+ const day2 = dayOf(zone);
5004
+ const today2 = day2(now);
5005
+ const tomorrow = day2(now + DAY4);
5006
+ const since = Date.parse(newSince);
5007
+ const seen = /* @__PURE__ */ new Map();
5008
+ let accounts = 0;
5009
+ for (const key of keys) {
5010
+ const held = await openAccountTasks(store, key);
5011
+ if (held.length)
5012
+ accounts++;
5013
+ for (const task of held)
5014
+ seen.set(task.id, task);
5015
+ }
5016
+ const projects = await openProjectTasks(store, zone);
5017
+ if (projects.length)
5018
+ accounts++;
5019
+ for (const task of projects)
5020
+ seen.set(task.id, task);
5021
+ const counts = { overdue: 0, today: 0, tomorrow: 0, new: 0, newSince, undated: 0 };
5022
+ const tasks = [];
5023
+ for (const task of seen.values()) {
5024
+ if (until !== void 0 && Date.parse(task.createdAt) > until)
5025
+ continue;
5026
+ const groups = [];
5027
+ const due = task.dueAt === null ? null : day2(Date.parse(task.dueAt));
5028
+ if (due === null)
5029
+ counts.undated++;
5030
+ if (due !== null && due < today2)
5031
+ groups.push("overdue");
5032
+ if (due === today2)
5033
+ groups.push("today");
5034
+ if (due === tomorrow)
5035
+ groups.push("tomorrow");
5036
+ if (Date.parse(task.createdAt) >= since)
5037
+ groups.push("new");
5038
+ if (groups.length === 0)
5039
+ continue;
5040
+ for (const group of groups)
5041
+ counts[group]++;
5042
+ const assignees = await Promise.all(task.assignments.filter((one2) => one2.role === "assignee").map((one2) => personName(store, one2.to)));
5043
+ tasks.push({
5044
+ task,
5045
+ groups,
5046
+ askedBy: await personName(store, await store.tasks.sourceSender(task.id)),
5047
+ for: assignees.filter(Boolean).join(", ") || null
5048
+ });
5049
+ }
5050
+ return { tasks, counts, accounts };
5051
+ }, "gatherTasks");
5031
5052
 
5032
- // dist/events/command.js
5033
- var upTo1000 = /* @__PURE__ */ __name((value) => {
5034
- const number2 = Number(value);
5035
- if (!Number.isInteger(number2) || number2 < 1 || number2 > 1e3)
5036
- throw new InvalidArgumentError("a whole number, 1 to 1000");
5037
- return number2;
5038
- }, "upTo1000");
5039
- var eventsCommand = /* @__PURE__ */ __name((program, streams, env) => {
5053
+ // dist/digest/backfill.js
5054
+ var DAY5 = 864e5;
5055
+ var DATE = /^(\d{4})-(\d{2})-(\d{2})$/;
5056
+ var localDate = /* @__PURE__ */ __name((at) => {
5057
+ const day2 = new Date(at);
5058
+ return `${day2.getFullYear()}-${String(day2.getMonth() + 1).padStart(2, "0")}-${String(day2.getDate()).padStart(2, "0")}`;
5059
+ }, "localDate");
5060
+ var dateOf = /* @__PURE__ */ __name((value, flag) => {
5061
+ const [, year, month, day2] = DATE.exec(value) ?? [];
5062
+ const date = new Date(Number(year), Number(month) - 1, Number(day2));
5063
+ if (!year || localDate(date.getTime()) !== value)
5064
+ throw new CliError("validation_error", `${flag} takes a date, YYYY-MM-DD, not "${value}"`, { param: flag });
5065
+ return date;
5066
+ }, "dateOf");
5067
+ var daysOf = /* @__PURE__ */ __name((from, to, time) => {
5068
+ const [hour, minute3] = time.split(":").map(Number);
5069
+ const days2 = [];
5070
+ for (const day2 = new Date(from); day2 <= to; day2.setDate(day2.getDate() + 1))
5071
+ days2.push({
5072
+ date: localDate(day2.getTime()),
5073
+ at: new Date(day2.getFullYear(), day2.getMonth(), day2.getDate(), hour, minute3).getTime()
5074
+ });
5075
+ return days2;
5076
+ }, "daysOf");
5077
+ var inputOf = /* @__PURE__ */ __name((date, at) => `backfill: ${date}
5078
+ window: --since-time ${new Date(at - DAY5).toISOString()} --before-time ${new Date(at).toISOString()}
5079
+ `, "inputOf");
5080
+ var usd = /* @__PURE__ */ __name((value) => `$${value.toFixed(2)}`, "usd");
5081
+ var cents = /* @__PURE__ */ __name((value) => Math.round(value * 100) / 100, "cents");
5082
+ var digestCommand = /* @__PURE__ */ __name((program, streams, env) => {
5040
5083
  const { print } = answers(program, streams, env);
5041
- const events = program.command("events").description("calendar events of every account in the store, phone calls included").summary("calendar events and phone calls of every account");
5042
- withQuery(annotate(events.command("list"), { effects: LOCAL_READ, partial: true }).description("events of every calendar account in the store, latest start first: calendars and imported phone calls").summary("events and phone calls of every account, latest first"), EVENTS_QUERY2).addHelpText("after", EVENTS_NOTE).option("--limit <n>", "events at most, 1\u20131000", upTo1000, 100).option("--json", "print JSON; --json=ref,title keeps only those fields").action(async (words, { json: json5, limit }, command) => {
5043
- const body = await withStore(env, (store) => listEvents(store, words, limit));
5044
- const accounts = body.meta?.accounts ?? [];
5045
- print(json5, body, `${accountsLine(accounts)}
5046
- ${plainText(body, command)}`);
5084
+ const digest = program.command("digest").description("the morning digest's history: build the digests of past days").summary("the morning digest's history");
5085
+ fromAppOption(annotate(digest.command("backfill"), { effects: LOCAL_WRITE })).summary("build the morning digests of past days, without sending them").description("build the morning digest each past day would have had: the morning-digest schedule's agent runs as its timer does (the same skill, flags and cost caps), reading the agenda as of that day's run time, and the result is kept as that day's run, marked backfill, for WireCat Desktop's digests screen. It is never sent to Telegram or mail. A day that already has a digest is skipped. Without --yes it only prints what it would build and the estimate: the days times the last digest run's cost. Each day is a paid model call: it stops once today's runs cost the schedule's daily cap or a day fails, and only you run it, never an agent. The store keeps what holds now, so a chat answered since reads as answered").requiredOption("--from <date>", "the first day, YYYY-MM-DD").requiredOption("--to <date>", "the last day, YYYY-MM-DD; not a day whose run time is still ahead").option("--yes", "build them; without it, only the estimate is printed").option("--json", "print JSON").action(async (given2, command) => {
5086
+ const from = dateOf(given2.from, "--from");
5087
+ const to = dateOf(given2.to, "--to");
5088
+ if (from > to)
5089
+ throw new CliError("validation_error", "--from is after --to", { param: "--from" });
5090
+ const { state, schedule } = digestSchedule(env);
5091
+ if (!schedule)
5092
+ throw new CliError("not_found", `no ${DIGEST_SCHEDULE} schedule to build from \u2014 memo skill show morning-digest says how to make one`, { reason: "backfill.no-schedule" });
5093
+ const file = scheduleFile(state, schedule.name);
5094
+ const when = readSchedule(file).when;
5095
+ if (!("daily" in when))
5096
+ throw new CliError("validation_error", `${schedule.name} does not run at a daily time, so it has no past days`, {
5097
+ reason: "backfill.not-daily",
5098
+ schedule: schedule.name
5099
+ });
5100
+ const all = daysOf(from, to, when.daily);
5101
+ if ((all.at(-1)?.at ?? 0) > Date.now())
5102
+ throw new CliError("validation_error", `the ${given2.to} digest's time has not come yet; --to an earlier day`, {
5103
+ param: "--to"
5104
+ });
5105
+ const runs = join6(file, "..", "runs");
5106
+ const done = new Set((existsSync6(runs) ? readdirSync4(runs) : []).filter((name2) => name2.endsWith(".report.json")).map((name2) => localDate(runTime(name2))));
5107
+ const days2 = all.filter((day2) => !done.has(day2.date));
5108
+ const skipped = all.filter((day2) => done.has(day2.date)).map((day2) => day2.date);
5109
+ const lastCost = schedule.last?.costUsd;
5110
+ const estimate = {
5111
+ days: days2.map((day2) => day2.date),
5112
+ skipped,
5113
+ estimateUsd: typeof lastCost === "number" ? cents(days2.length * lastCost) : null
5114
+ };
5115
+ const cap = readSchedule(file).maxCostPerDay ?? scheduleLimits(env).maxCostPerDay ?? SCHEDULE_DEFAULTS.maxCostPerDay;
5116
+ if (!given2.yes) {
5117
+ const room = cap > 0 ? cap - spentToday(file) : Number.POSITIVE_INFINITY;
5118
+ print(given2.json, itemAnswer(estimate), [
5119
+ `Would build ${days2.length} day(s)${days2.length ? `: ${estimate.days.join(", ")}` : ""}.`,
5120
+ ...skipped.length ? [`Skipped, a digest exists: ${skipped.join(", ")}.`] : [],
5121
+ estimate.estimateUsd === null ? "No digest run has a cost yet, so there is no estimate." : `Estimate: ${usd(estimate.estimateUsd)} (${days2.length} \xD7 the last run's ${usd(lastCost)}).`,
5122
+ ...estimate.estimateUsd !== null && estimate.estimateUsd > room ? [`Today's cap leaves ${usd(Math.max(0, room))}: it stops there, and the rest waits for another day.`] : [],
5123
+ ...days2.length ? ["Build them, sending nothing: add --yes."] : []
5124
+ ].join("\n"));
5125
+ return;
5126
+ }
5127
+ const who = await whoRuns(command, env, { question: `spend about ${usd(estimate.estimateUsd ?? 0)}?` });
5128
+ if (who !== "owner")
5129
+ throw new CliError("permission_error", "a backfill spends the owner's money on model calls; the owner runs it at a terminal or in WireCat Desktop", { reason: "backfill.owner-only" });
5130
+ const built = [];
5131
+ const left = [];
5132
+ for (const day2 of days2) {
5133
+ const failed = built.some((one2) => one2.exitCode !== 0);
5134
+ if (left.length || failed || cap > 0 && spentToday(file) >= cap) {
5135
+ left.push(day2.date);
5136
+ continue;
5137
+ }
5138
+ const run = await backfillScheduleRun(command, APP, env, file, {
5139
+ at: new Date(day2.at),
5140
+ input: inputOf(day2.date, day2.at)
5141
+ });
5142
+ built.push({ date: day2.date, at: run.at, exitCode: run.exitCode, costUsd: run.costUsd ?? null });
5143
+ }
5144
+ const costUsd = cents(built.reduce((sum, one2) => sum + (one2.costUsd ?? 0), 0));
5145
+ if (built.some((one2) => one2.exitCode !== 0))
5146
+ process.exitCode = 1;
5147
+ print(given2.json, itemAnswer({ ...estimate, built, left, costUsd }), [
5148
+ ...built.map((one2) => `${one2.date}: ${one2.exitCode === 0 ? "built" : `failed (exit ${one2.exitCode})`}${one2.costUsd === null ? "" : `, ${usd(one2.costUsd)}`}`),
5149
+ ...skipped.length ? [`Skipped, a digest exists: ${skipped.join(", ")}.`] : [],
5150
+ ...left.length ? [`Not built, after a failed day or once today's cap of ${usd(cap)} was spent: ${left.join(", ")}.`] : [],
5151
+ `Cost: ${usd(costUsd)}. Nothing was sent.`
5152
+ ].join("\n"));
5047
5153
  });
5048
- annotate(events.command("show"), { effects: LOCAL_READ, output: { entity: "event" } }).description("one event: its time, the accounts and calendars that hold it, and its attendees").argument("<ref>", "event:<id>").option("--json", "print JSON; --json=ref,title keeps only those fields").action(async (ref, { json: json5 }) => print(json5, itemAnswer(await withStore(env, (store) => showEvent(store, ref)))));
5049
- }, "eventsCommand");
5154
+ }, "digestCommand");
5050
5155
 
5051
- // dist/examples.js
5052
- var EXAMPLES = {
5053
- context: ["memo context telegram:'Kai Sample'", "memo context email:kai@example.com --limit 5 --json"],
5054
- show: [
5055
- "memo show chat:max/500/-1001 document:413",
5056
- "memo show note:12 msg:telegram/1/77/42 --json",
5057
- "memo show contract:2 invoice:4 --json"
5058
- ],
5059
- "notes show": ["memo notes show note:12", "memo notes show ~/notes/People/Kai.md --json"],
5060
- "notes list": [
5061
- `memo notes list 'about:"telegram:Kai Sample"'`,
5062
- "memo notes list source:internal text:budget --json",
5063
- "memo notes list source:internal --limit 50"
5064
- ],
5065
- "notes add": [
5066
- "memo notes add 'Prefers calls in the morning' --subject telegram:'Kai Sample'",
5067
- "memo notes add --text-file draft.md --title 'Harbour kickoff' --subject project:2 --subject organization:1"
5068
- ],
5069
- "notes edit": ["memo notes edit note:12 --revision 2 --text 'Prefers calls after ten'"],
5070
- "notes remove": ["memo notes remove note:12"],
5071
- "notes export": ["memo notes export --output-dir ~/notes/memo --format obsidian"],
5072
- "sources add": [
5073
- "memo sources add ~/Downloads/runner-notes.pdf --subject project:3 --keep-copy",
5074
- "memo sources add https://example.org/release-notes --title 'Release notes' --json",
5075
- "memo sources add - --title 'Call with Kai' --subject person:12"
5076
- ],
5077
- "sources show": ["memo sources show document:42", "memo sources show document:42 --bytes 65536 --json"],
5078
- "sources mentions": ["memo sources mentions document:42 --json"],
5079
- "sources remove": ["memo sources remove document:42"],
5080
- "folders add": ["memo folders add ~/notes"],
5081
- "folders update": ["memo folders update 1 ~/notes"],
5082
- "folders remove": ["memo folders remove 1", "memo folders remove 1 --allow-delete --yes"],
5083
- "folders list": ["memo folders list --json"],
5084
- "config show": ["memo config show", "memo config show --json"],
5085
- "config migrate": ["memo config migrate"],
5086
- "config unset": ["memo config unset permissions.notes.edit"],
5087
- "mentions list": [
5088
- "memo mentions list kind:phone --source notes",
5089
- "memo mentions list domain:example.com --source email:personal,email:work --json",
5090
- "memo mentions list kind:place --source mail"
5091
- ],
5092
- "mentions show": ["memo mentions show 12", "memo mentions show 40 --source email:personal --json"],
5093
- "mentions confirm": ["memo mentions confirm 12 person:7", "memo mentions confirm 40 --source email:personal"],
5094
- "mentions reject": ["memo mentions reject 12", "memo mentions reject 40 --source email:personal"],
5095
- "mail review": [
5096
- "memo mail review --holder mail-triage --source email:work --json",
5097
- "memo mail review --unanswered 1d --source email:personal",
5098
- "memo mail review 'date>2h' --source email:me@example.com"
5099
- ],
5100
- "messages acknowledge": [
5101
- "memo messages acknowledge --holder mail-triage --cursor 9123.1762090000000 --source email:work",
5102
- "memo messages acknowledge --holder mail-triage --cursor 9123.1762090000000 --source email:me@example.com --json",
5103
- "memo messages acknowledge --holder backfill --cursor 870.1762090000000 --source email:personal"
5104
- ],
5105
- "messages reset": [
5106
- "memo messages reset --holder mail-triage --last 8 --dry-run --source email:me@example.com",
5107
- "memo messages reset --holder mail-triage --since-time 2026-11-04T00:00:00Z --source email:work --json"
5108
- ],
5109
- "mail findings add": [
5110
- "memo mail findings add findings.json --source email:work",
5111
- "memo mail findings add findings.json --source email:work --key mail-triage:9123 --dry-run --json",
5112
- `memo mail findings add '{"schema_version":1,"key":"mail-triage:9123","extractor":"mail-triage/1","findings":[\u2026]}' --source email:me@example.com --json`
5113
- ],
5114
- "mail findings show": [
5115
- "memo mail findings show 41",
5116
- "memo mail findings show 41 --json",
5117
- "memo mail findings show 41 --json=id,input.findings"
5118
- ],
5119
- "mail findings list": [
5120
- "memo mail findings list status:proposed",
5121
- "memo mail findings list --source email:work --json",
5122
- "memo mail findings list status:reviewed --limit 50 --page 2"
5123
- ],
5124
- "messages show": ["memo messages show 'msg:email/me%40example.com/thread/42' --before-n 2 --after-n 2"],
5125
- "projects actions add": ["memo projects actions add findings.json"],
5126
- "projects actions list": [
5127
- "memo projects actions list SYH",
5128
- "memo projects actions list SYH status:reviewed --brief --json"
5129
- ],
5130
- "projects actions confirm": [
5131
- "memo projects actions confirm SYH 12 budget accountant --fingerprint 3f9a0c --reason 'both are stated plainly'",
5132
- "memo projects actions confirm SYH key:4"
5133
- ],
5134
- "projects actions reject": [
5135
- "memo projects actions reject SYH 12 --fingerprint 3f9a0c --reason 'not about this project'"
5136
- ],
5137
- "projects evidence": [
5138
- "memo projects evidence SYH --source telegram:1,email:personal --contact 'Alice Example'",
5139
- "memo projects evidence SYH --source all --context-query 'crane tender' --since-time 180d --json"
5140
- ],
5141
- "models list": ["memo models list kind:text --json"],
5142
- "commands info": ["memo commands info --json"],
5143
- "models download": ["memo models download e5-small", "memo models download e5-large"],
5144
- "models set": ["memo models set e5-large"],
5145
- "models consents show": ["memo models consents show --json"],
5146
- "models consents set": [
5147
- "memo models consents set openai --model gpt-4o-mini",
5148
- "memo models consents set openai --model gpt-4o-mini --yes"
5149
- ],
5150
- "models consents revoke": ["memo models consents revoke"],
5151
- "models add": [
5152
- `memo models add e5-base --payload '{"repository":"Xenova/multilingual-e5-base","onnx":"onnx/model_quantized.onnx","dims":768,"pooling":"mean","prefix":{"query":"query: ","passage":"passage: "}}'`
5153
- ],
5154
- "notes links show": ["memo notes links show telegram:101 --json"],
5155
- "notes links add": ["memo notes links add telegram:101 ~/notes/People/Kai.md"],
5156
- "notes links clear": ["memo notes links clear telegram:101"],
5157
- "tags add": ["memo tags add follow-up --note note:12", "memo tags add harbour --project HRB"],
5158
- "tags remove": ["memo tags remove follow-up --note note:12"],
5159
- "tags list": ["memo tags list tag:follow-up", "memo tags list --source email:me@example.com --json"],
5160
- "tasks link": ["memo tasks link HRB-2 HRB-1 --kind blocked-by"],
5161
- "tasks unlink": ["memo tasks unlink HRB-2 HRB-1 --kind blocked-by"],
5162
- "tasks links": ["memo tasks links HRB-2 --json"],
5163
- "tasks status": ["memo tasks status HRB-2"],
5164
- "tasks add": [
5165
- "memo tasks add 'msg:telegram/1/77/42' --account telegram:1 --type request",
5166
- "memo tasks add --project HRB --title 'Draft the budget' --type feature",
5167
- "memo tasks add --project INBOX --title 'Reply on Avito to Ivan' --external-id avito:4512345678 --source-url https://www.avito.ru/4512345678"
5168
- ],
5169
- "tasks list": [
5170
- "memo tasks list state:done --source telegram:1",
5171
- "memo tasks list project:HRB ready:true --json",
5172
- 'memo tasks list project:* priority:[high TO critical] "invoice" --sort due --json'
5173
- ],
5174
- "tasks counts": ["memo tasks counts project:* --json"],
5175
- "tasks history": ["memo tasks history HRB-1 --json"],
5176
- "tasks comments add": ["memo tasks comments add HRB-1 Booked the hall for Friday"],
5177
- "tasks comments list": ["memo tasks comments list HRB-1 --json"],
5178
- "tasks move": ["memo tasks move HRB-3 --status in_progress --before HRB-1 --revision 4"],
5179
- "people list": ["memo people list --json"],
5180
- "people add": ["memo people add Lena Example"],
5181
- "tasks agenda": [
5182
- "memo tasks agenda --source telegram:1",
5183
- "memo tasks agenda --source max:1 --waiting 3d --within 90d --json"
5184
- ],
5185
- "tasks claim": ["memo tasks claim HRB-1 --lease 2h --account telegram:1"],
5186
- "tasks release": ["memo tasks release HRB-1 --account telegram:1"],
5187
- "tasks show": ["memo tasks show 1a2b3c4d --source telegram:1 --json"],
5188
- "stats tasks show": [
5189
- "memo stats tasks show --source telegram:1",
5190
- "memo stats tasks show --type question --source telegram:1 --json"
5191
- ],
5192
- "tasks close": ["memo tasks close 7 --account telegram:1 --as dismissed --reason 'answered by phone'"],
5193
- "tasks update": [
5194
- "memo tasks update HRB-1 --title 'Send the budget' --priority high --account telegram:1",
5195
- "memo tasks update 1a2b3c4d --due 2d --title 'Send the revised budget' --account telegram:1"
5196
- ],
5197
- "tasks assign": ["memo tasks assign 7 person:3", "memo tasks assign HRB-1 person:3 --clear --revision 4"],
5198
- "organizations add": [
5199
- "memo organizations add 'Harbour Studio' --kind company --scope work",
5200
- "memo organizations add 'Harbour Family' --kind family"
5201
- ],
5202
- "organizations rename": [
5203
- "memo organizations rename organization:1 'Harbour Studio Ltd'",
5204
- "memo organizations rename 1 'Harbour Studio Ltd' --json"
5205
- ],
5206
- "organizations show": ["memo organizations show organization:1"],
5207
- "organizations list": ["memo organizations list name:*harbour*", "memo organizations list --json"],
5208
- "organizations context": ["memo organizations context organization:1"],
5209
- "projects meetings list": ["memo projects meetings list HRB --source zoom:me@example.com"],
5210
- "projects meetings link": ["memo projects meetings link HRB 31 --account zoom:me@example.com"],
5211
- "projects add": ["memo projects add 'Harbour' --key HRB --type client --organization 1"],
5212
- "projects list": ["memo projects list"],
5213
- "projects context": ["memo projects context HRB", "memo projects context project:2 --json"],
5214
- "projects status": ["memo projects status HRB --context-query 1C", "memo projects status HRB --compact"],
5215
- "projects annotations show": ["memo projects annotations show HRB --max-read-bytes 1048576 --json"],
5216
- "projects annotations export": ["memo projects annotations export HRB --output harbour-annotations.json --json"],
5217
- "projects annotations check": [
5218
- "memo projects annotations check harbour-annotations.json --target-store ./trial/wirecat.db --project project:2 --bindings bindings.json --json"
5219
- ],
5220
- "projects annotations restore": [
5221
- "memo projects annotations restore harbour-annotations.json --target-store ./trial/wirecat.db --project project:2 --bindings bindings.json --fingerprint aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa --json"
5222
- ],
5223
- "projects key-documents add": ["memo projects key-documents add HRB note:12 --reason 'the brief'"],
5224
- "projects key-documents remove": ["memo projects key-documents remove HRB note:12"],
5225
- "projects key-documents list": ["memo projects key-documents list HRB"],
5226
- "relationships update": [
5227
- "memo relationships update 12 --purpose sales",
5228
- "memo relationships update 12 --role contractor --reason 'builds the site'"
5229
- ],
5230
- "relationships add": [
5231
- "memo relationships add person:3 organization:1 --role employee",
5232
- "memo relationships add project:2 organization:1 --kind related-to --reason 'same client'"
5233
- ],
5234
- "relationships list": ['memo relationships list about:"person:3"', "memo relationships list confirmed:false"],
5235
- "relationships remove": ["memo relationships remove 4"],
5236
- "relationships confirm": ["memo relationships confirm 4"],
5237
- "relationships scan": [
5238
- "memo relationships scan HRB --source all",
5239
- "memo relationships scan HRB --source telegram:1,email:personal --yes",
5240
- "memo relationships scan --source email:me@example.com"
5241
- ],
5242
- "reminders add": [
5243
- "memo reminders add 7 --at-time 2026-10-20T09:00:00+02:00 --timezone Europe/Madrid --account telegram:1"
5244
- ],
5245
- "reminders list": ["memo reminders list --source telegram:1"],
5246
- "reminders claim": ["memo reminders claim --account telegram:1 --json"],
5247
- "reminders acknowledge": ["memo reminders acknowledge 3 3:1 --account telegram:1"],
5248
- "reminders cancel": ["memo reminders cancel 3 --account telegram:1"],
5249
- "reminders update": ["memo reminders update 3 --revision 1 --at-time 2026-10-21T09:00:00+02:00 --account telegram:1"],
5250
- "vocabulary list": ["memo vocabulary list kind:tag"],
5251
- "vocabulary show": ["memo vocabulary show Harbour --kind project"],
5252
- "vocabulary check": ["memo vocabulary check on-boarding --kind tag", "memo vocabulary check --limit 20 --json"],
5253
- "vocabulary inspect": [
5254
- "memo vocabulary inspect tag:1 tag:2 --fingerprint v1:all:all:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa --limit 5 --json"
5255
- ],
5256
- "vocabulary create": ["memo vocabulary create onboarding --kind tag --fingerprint 3f9a"],
5257
- "vocabulary aliases add": ["memo vocabulary aliases add project:2 Harbor --fingerprint 3f9a"],
5258
- "vocabulary aliases remove": ["memo vocabulary aliases remove project:2 5 --fingerprint 3f9a"],
5259
- "memories add": ["memo memories add 'Kai prefers Friday meetings' --kind preference --scope work --evidence note:12"],
5260
- "memories list": ["memo memories list status:proposed kind:preference"],
5261
- "memories show": ["memo memories show memory:4 --sources"],
5262
- "memories update": ["memo memories update memory:4 --project SYH", "memo memories update memory:4 --project none"],
5263
- "memories confirm": ["memo memories confirm memory:4"],
5264
- "memories invalidate": ["memo memories invalidate memory:4"],
5265
- "memories remove": ["memo memories remove memory:4"],
5266
- "memories check": ["memo memories check", "memo memories check --limit 2000 --json"],
5267
- "memories challenge": [
5268
- "memo memories challenge memory:4 --evidence note:15 --reason 'the later note moves delivery to the 30th'",
5269
- "memo memories challenge memory:4 --evidence note:15 --reason 'moved' --suggest 'Delivery is on the 30th' --json"
5270
- ],
5271
- "facts add": [
5272
- "memo facts add 'We moved the launch to spring' --kind decision --evidence 'msg:telegram/1/77/42' --project HRB"
5273
- ],
5274
- "facts list": ["memo facts list kind:decision project:HRB"],
5275
- "orders list": ["memo orders list status:shipped", `memo orders list number:A-1042 'supplier:"Shop One"' --json`],
5276
- "orders show": ["memo orders show order:3"],
5277
- "pipelines list": ["memo pipelines list", "memo pipelines list kind:lead --json"],
5278
- "pipelines show": ["memo pipelines show pipeline:2", "memo pipelines show Sales --since-time 2026-09-01"],
5279
- "pipelines add": [
5280
- "memo pipelines add Sales Call Quote Contract won lost",
5281
- "memo pipelines add Inbound new called --kind lead"
5282
- ],
5283
- "deals list": ["memo deals list status:open project:HRB", "memo deals list stage:Quote --json"],
5284
- "deals show": ["memo deals show deal:5"],
5285
- "deals add": [
5286
- "memo deals add 'Site for the clinic' Call 500000.00 RUB --organization organization:3 --project HRB",
5287
- "memo deals add 'Support contract' Sales/Quote --person person:7 --role decision-maker --due 2026-12-01"
5288
- ],
5289
- "deals move": ["memo deals move deal:5 Quote"],
5290
- "deals close": ["memo deals close deal:5 won", "memo deals close deal:6 lost --reason 'chose a cheaper studio'"],
5291
- "leads list": ["memo leads list status:open"],
5292
- "leads show": ["memo leads show lead:3"],
5293
- "leads add": ["memo leads add 'Call from the site'", "memo leads add 'Clinic in Kazan' contacted --person person:9"],
5294
- "leads move": ["memo leads move lead:3 qualified"],
5295
- "leads close": ["memo leads close lead:3 lost --reason 'no budget'"],
5296
- "orders add": [
5297
- "memo orders add 'Shop One' 2499.00 RUB --kind personal --title A-1042",
5298
- "memo orders add 'Office Supply' --kind client --organization organization:3"
5299
- ],
5300
- "events list": [
5301
- "memo events list event_type:call",
5302
- "memo events list account:phone:pixel 'starts_at:[2026-11-02 TO 2026-11-07}' --json"
5303
- ],
5304
- "events show": ["memo events show event:42"],
5305
- "invoices list": ["memo invoices list project:HRB open:true", "memo invoices list review:proposed --json"],
5306
- "invoices show": ["memo invoices show invoice:4"],
5307
- "invoices add": [
5308
- "memo invoices add 50000.00 RUB Bed rental, November --kind receivable --organization organization:3 --project HRB",
5309
- "memo invoices add 1200.50 EUR Hosting --kind payable --person person:7 --title 2026-118 --due 2026-11-15"
5310
- ],
5311
- "payments list": ["memo payments list project:HRB direction:in", "memo payments list review:proposed --json"],
5312
- "payments show": ["memo payments show payment:9"],
5313
- "payments add": [
5314
- "memo payments add 25000.00 RUB --kind in --organization organization:3 --occurred-time 2026-11-01 --project HRB",
5315
- "memo payments add 5000.00 RUB --kind out --organization organization:3 --refund-of payment:9"
5316
- ],
5317
- "records import": [
5318
- "memo records import payments.jsonl --account rentals --dry-run",
5319
- "memo records import envelope.json --account rentals --json",
5320
- "memo records import contacts.vcf --account phone:pixel --region gb"
5321
- ],
5322
- "records check": ["memo records check - --account rentals"],
5323
- "records list": [
5324
- "memo records list waiting:true --account sheet:rentals",
5325
- "memo records list type:organization --account sheet:rentals --json",
5326
- `memo records list 'entity:"organization:3"' deleted:false --account sheet:rentals`
5327
- ],
5328
- "records link": ["memo records link organization:client organization:3 --account sheet:rentals"],
5329
- "payments allocate": ["memo payments allocate payment:9 invoice:4 25000.00"],
5330
- "products list": ["memo products list kind:service", "memo products list archived:true --json"],
5331
- "products show": ["memo products show product:4"],
5332
- "products add": [
5333
- "memo products add 'Bike rental, month' 10000.00 RUB --kind service --unit month",
5334
- "memo products add 'Helmet' 2500.00 RUB --sku H-12 --supplier organization:3"
5335
- ],
5336
- "products update": [
5337
- "memo products update product:4 --amount 12000.00 --currency RUB",
5338
- "memo products update product:4 --archived on"
5339
- ],
5340
- "orders lines add": [
5341
- "memo orders lines add order:3 12 --product product:4",
5342
- "memo orders lines add order:3 1 Rental, first week --product product:4 --amount 2000.00",
5343
- "memo orders lines add order:3 2 Helmet --amount 2500.00"
5344
- ],
5345
- "orders lines remove": ["memo orders lines remove order:3 2"],
5346
- "invoices lines add": [
5347
- "memo invoices lines add invoice:4 1 --product product:4",
5348
- "memo invoices lines add invoice:4 2 Delivery --amount 1500.00"
5349
- ],
5350
- "invoices lines remove": ["memo invoices lines remove invoice:4 2"],
5351
- "contracts list": ["memo contracts list status:active", "memo contracts list expires:2026-12-31 --json"],
5352
- "contracts show": ["memo contracts show contract:2"],
5353
- "contracts add": [
5354
- "memo contracts add 'Rental agreement' --kind service --start-date 2026-11-01 --end-date 2027-10-31 --auto-renew on --notice-days 30 --amount 120000.00 --currency RUB",
5355
- "memo contracts add 'NDA with the studio' --kind nda --project HRB"
5356
- ],
5357
- "contracts update": [
5358
- "memo contracts update contract:2 --status terminated",
5359
- "memo contracts update contract:2 --end-date 2027-12-31"
5360
- ],
5361
- "subscriptions list": [
5362
- "memo subscriptions list status:active direction:payable",
5363
- "memo subscriptions list renews:2026-12-01 --json"
5364
- ],
5365
- "subscriptions show": ["memo subscriptions show subscription:5"],
5366
- "subscriptions add": [
5367
- "memo subscriptions add Hosting 990.00 RUB --kind payable --supplier organization:3 --period 1-month --start-date 2026-11-01",
5368
- "memo subscriptions add 'Bike rental' 10000.00 RUB --kind receivable --customer organization:3 --period 1-month --trial-end-date 2026-11-14 --status trial"
5369
- ],
5370
- "subscriptions update": [
5371
- "memo subscriptions update subscription:5 --status paused",
5372
- "memo subscriptions update subscription:5 --period 1-year"
5373
- ],
5374
- "subscriptions cancel": ["memo subscriptions cancel subscription:5 --occurred-time 2026-11-30"],
5375
- "subscriptions prices add": ["memo subscriptions prices add subscription:5 1190.00 RUB --start-date 2027-01-01"],
5376
- "wallets list": ["memo wallets list", "memo wallets list kind:card --json"],
5377
- "wallets show": ["memo wallets show wallet:2", "memo wallets show wallet:2 --until-time 2026-10-31"],
5378
- "wallets add": ["memo wallets add 'Main card' RUB --kind card --institution 'Bank One'"],
5379
- "wallets update": ["memo wallets update wallet:2 --opening-balance 15000.00 --opening-date 2026-01-01"],
5380
- "transactions list": [
5381
- `memo transactions list 'wallet:"wallet:2"' status:pending`,
5382
- "memo transactions list uncategorized:true --json"
5383
- ],
5384
- "transactions show": ["memo transactions show transaction:12"],
5385
- "transactions add": [
5386
- "memo transactions add wallet:2 500.00 --kind out --occurred-time 2026-11-14 --description 'Coffee shop'",
5387
- "memo transactions add wallet:2 10000.00 --kind in --payment payment:9"
5388
- ],
5389
- "transactions update": [
5390
- "memo transactions update transaction:12 --status booked",
5391
- "memo transactions update transaction:12 --payment payment:9"
5392
- ],
5393
- "transactions splits add": ["memo transactions splits add transaction:12 300.00 Rent --project HRB"],
5394
- "transactions splits remove": ["memo transactions splits remove transaction:12 1"],
5395
- "records show": [
5396
- "memo records show custom:quote:Q-7 --account example-erp:demo",
5397
- "memo records show organization:client --account sheet:rentals --json"
5398
- ],
5399
- "records delete": ["memo records delete payment:sheet-12 --account sheet:rentals"],
5400
- "records match": ["memo records match --account sheet:rentals"],
5401
- "payments unlink": ["memo payments unlink payment:9 invoice:4"],
5402
- "facts show": ["memo facts show fact:4 --sources"],
5403
- "facts confirm": ["memo facts confirm fact:4"],
5404
- "facts revoke": ["memo facts revoke fact:4"],
5405
- "facts reverse": ["memo facts reverse fact:4"],
5406
- "facts reject": ["memo facts reject fact:4"],
5407
- "facts remove": ["memo facts remove fact:4"],
5408
- "facts challenge": ["memo facts challenge fact:4 --evidence 'msg:telegram/1/77/50' --reason 'reversed in chat'"],
5409
- "actions add": [
5410
- `memo actions add reply --reason 'Kai asked twice' --account telegram:1 --target 'msg:telegram/1/77/42' --payload '{"text":"Friday works"}'`
5411
- ],
5412
- "meetings actions add": ["memo meetings actions add findings.json --account zoom:me@example.com"],
5413
- "meetings passage": [
5414
- "memo meetings passage meeting:1/1/1/0 --source zoom:me@example.com --before-n 1 --after-n 1 --json"
5415
- ],
5416
- "meetings prepare": [
5417
- "memo meetings prepare example-series --source zoom:me@example.com --json",
5418
- "memo meetings prepare example-series --source zoom:me@example.com --context-source tg.personal --context-query Harbour --json"
5419
- ],
5420
- "search meetings": [
5421
- "memo search meetings budget --source zoom:me@example.com --project Harbour --since-time 2026-10-01 --limit 10 --json"
5422
- ],
5423
- "meetings actions list": ["memo meetings actions list status:proposed --source zoom:me@example.com"],
5424
- "meetings actions show": ["memo meetings actions show 5 --source zoom:me@example.com"],
5425
- "meetings actions confirm": [
5426
- "memo meetings actions confirm 5 decision --fingerprint aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa --account zoom:me@example.com"
5427
- ],
5428
- "meetings actions reject": [
5429
- "memo meetings actions reject 5 --fingerprint aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa --account zoom:me@example.com"
5430
- ],
5431
- "actions list": ["memo actions list status:proposed"],
5432
- "actions show": ["memo actions show 5"],
5433
- "actions confirm": ["memo actions confirm 5"],
5434
- "actions reject": ["memo actions reject 5"],
5435
- "review list": [
5436
- "memo review list project:HRB",
5437
- "memo review list kind:relationship,fact by:agent",
5438
- "memo review list via:chat:max/1/-100"
5439
- ],
5440
- "review show": ["memo review show 17079", "memo review show 17080 --brief --json"],
5441
- "review confirm": [
5442
- "memo review confirm 17078-17081",
5443
- "memo review confirm 17080/f1,17080/f3",
5444
- "memo review confirm close:Q-15"
5445
- ],
5446
- "review reject": [
5447
- 'memo review reject 17081 --reason "not this project"',
5448
- "memo review reject close:Q-15 --reason 'still open'"
5449
- ],
5450
- "review agenda": ["memo review agenda --compact", "memo review agenda --since-time 3d --json"],
5451
- "review status": ["memo review status", "memo review status --since-time 7d --json"],
5452
- "schedule command": [
5453
- "memo schedule command digest --at 08:30 -- review status",
5454
- "memo schedule command mail-sync --every 1h -- sync --only mail"
5455
- ],
5456
- "schedule agent": [
5457
- "memo schedule agent triage claude 'read memo review status and list what needs me today' --at 08:45 --max-cost 1",
5458
- "memo schedule agent mail-triage claude 'Run memo skill show mail-triage and follow it.' --every 5m --on-new-mail mail-triage --mail-wait 1h --mail-batch 20 --max-cost 1 --max-cost-per-day 5",
5459
- "memo schedule agent weekly claude 'read memo review status and sum up the week' --at 09:00 --model opus"
5460
- ],
5461
- "schedule list": ["memo schedule list", "memo schedule list kind:agent --json"],
5462
- "schedule run": ["memo schedule run digest"],
5463
- "schedule remove": ["memo schedule remove digest"],
5464
- "schedule exec": ["memo schedule exec ~/.local/state/cli-memo/schedules/digest/schedule.json"],
5465
- "templates list": ["memo templates list", "memo templates list channel:telegram source:stored --json"],
5466
- "templates show": [
5467
- "memo templates show report/morning-digest --channel telegram",
5468
- "memo templates show report/weekly-gaps --channel email --locale en --json"
5469
- ],
5470
- "templates edit": [
5471
- "memo templates edit report/morning-digest --channel telegram",
5472
- "memo templates edit report/morning-digest --channel text --file digest.text.liquid",
5473
- "memo templates edit report/morning-digest --channel telegram --undo"
5474
- ],
5475
- "templates preview": [
5476
- "memo templates preview report/morning-digest --channel telegram",
5477
- "memo templates preview report/morning-digest --channel email --locale en --file draft.liquid"
5478
- ],
5479
- "templates reset": ["memo templates reset report/morning-digest --channel telegram"],
5480
- "agents list": ["memo agents list agent:claude-code --limit 20"],
5481
- "mcp serve": ["memo mcp serve", "memo mcp serve --http --public-url https://laptop.example.ts.net"],
5482
- "mcp revoke": ["memo mcp revoke --json"],
5483
- "mcp server start": ["memo mcp server start --public-url https://laptop.example.ts.net"],
5484
- "mcp server status": ["memo mcp server status --json"],
5485
- "mcp server stop": ["memo mcp server stop"],
5486
- "search all": [
5487
- "memo search all 'budget harbour'",
5488
- "memo search all 'content:invoice' --only mail,notes --json",
5489
- "memo search all 'Rental agreement' --only business"
5490
- ],
5491
- "search messages": ["memo search messages 'friday meeting has:voice'"],
5492
- "search mail": ["memo search mail 'mailbox:important budget' --source email:me@example.com"],
5493
- "search notes": ["memo search notes budget notes:2", "memo search notes kickoff --project HRB --json"],
5494
- "search memories": ["memo search memories friday meetings scope:work"],
5495
- "search people": ["memo search people ana", "memo search people Ana Example --source telegram --json"],
5496
- "answers create": [
5497
- "memo answers create 'what did Kai say about the budget?' --all",
5498
- "memo answers create 'when is the launch?' --source telegram:1"
5499
- ],
5500
- sync: ["memo sync", "memo sync --only mail", "memo sync --only notes --folder ~/notes --no-embed"],
5501
- "jobs start": ["memo jobs start --every 10m"],
5502
- "jobs stop": ["memo jobs stop"],
5503
- "jobs status": ["memo jobs status --json"],
5504
- doctor: ["memo doctor", "memo doctor --json"],
5505
- "runs list": ["memo runs list status:failed --limit 10", "memo runs list --page 2 --json"],
5506
- "runs show": ["memo runs show 20261025T090000Z-mail-sync-a81f2c --json"],
5507
- "runs path": ["memo runs path 20261025T090000Z-mail-sync-a81f2c"],
5508
- "store info": ["memo store info", "memo store info --json"],
5509
- "store recover": ["memo store recover", "memo store recover --json"],
5510
- "store recover run": ["memo store recover run"],
5511
- upgrade: ["memo upgrade --check"],
5512
- commands: ["memo commands --json", "memo commands tags add --json"],
5513
- "skill show": ["memo skill show"],
5514
- "skill install": ["memo skill install --for all"],
5515
- "skill status": ["memo skill status", "memo skill status --json"],
5516
- guide: ["memo guide", "memo guide person-brief --json"],
5517
- help: ["memo help options", "memo help tasks agenda"]
5518
- };
5519
- var leaves = /* @__PURE__ */ __name((command, path = []) => command.commands.flatMap((child) => {
5520
- const at = [...path, child.name()];
5521
- return child.commands.length ? leaves(child, at) : [[at.join(" "), child]];
5522
- }), "leaves");
5523
- var addExamples = /* @__PURE__ */ __name((program) => {
5524
- for (const [path, command] of leaves(program)) {
5525
- const examples = EXAMPLES[path];
5526
- if (!examples || JSON.stringify(metaOf(command).examples) === JSON.stringify(examples))
5527
- continue;
5528
- if (hasSharedContract(command))
5529
- overrideSharedContract(command, "Memo examples use explicit stored-account scope rather than messenger profile words.", (one2) => {
5530
- annotate(one2, { examples });
5531
- });
5532
- else
5533
- annotate(command, { examples });
5534
- }
5535
- }, "addExamples");
5536
-
5537
- // dist/memories/memories.js
5538
- var addMemory = /* @__PURE__ */ __name(async (store, memory, by) => store.memories.add({
5539
- kind: memory.kind,
5540
- body: memory.text,
5541
- scope: memory.scope,
5542
- evidence: memory.evidence,
5543
- author: by,
5544
- status: by === "owner" ? "confirmed" : "proposed",
5545
- ...memory.subject === void 0 ? {} : { subject: memory.subject },
5546
- ...memory.project === void 0 ? {} : { project: await canonicalProject(store, memory.project) },
5547
- ...memory.supersedes === void 0 ? {} : { supersedes: memory.supersedes }
5548
- }), "addMemory");
5549
-
5550
- // dist/memories/memory.js
5551
- var MEMORY_QUERY = {
5552
- entity: "memory",
5553
- backend: "native",
5554
- fields: [
5555
- termField("kind", "enum", "kind:preference", { values: MEMORY_KINDS }),
5556
- termField("status", "enum", "status:proposed", { values: MEMORY_STATUSES }),
5557
- termField("scope", "enum", "scope:work", { values: MEMORY_SCOPES }),
5558
- termField("subject", "ref", 'subject:"person:42"'),
5559
- termField("project", "kw", "project:SYH")
5560
- ]
5561
- };
5562
- var memoryItem = /* @__PURE__ */ __name(({ body, ...memory }) => ({
5563
- ref: memory.ref,
5564
- id: memory.id,
5565
- kind: memory.kind,
5566
- text: body,
5567
- subject: memory.subject,
5568
- subjectName: memory.subjectName,
5569
- subjectIsOwner: memory.subjectIsOwner,
5570
- project: memory.project,
5571
- projectName: memory.projectName,
5572
- author: memory.author,
5573
- model: memory.model,
5574
- confidence: memory.confidence,
5575
- status: memory.status,
5576
- scope: memory.scope,
5577
- supersedes: memory.supersedes,
5578
- evidence: memory.evidence,
5579
- lastVerifiedAt: memory.lastVerifiedAt,
5580
- sourceReview: memory.sourceReview,
5581
- createdAt: memory.createdAt,
5582
- updatedAt: memory.updatedAt
5583
- }), "memoryItem");
5584
- var listMemories = /* @__PURE__ */ __name(async (store, query, limit) => {
5585
- const terms2 = queryTerms(query, MEMORY_QUERY, "memories list");
5586
- const project = terms2.get("project");
5587
- const filters = {
5588
- ...terms2.has("kind") ? { kind: terms2.get("kind") } : {},
5589
- ...terms2.has("status") ? { status: terms2.get("status") } : {},
5590
- ...terms2.has("scope") ? { scope: terms2.get("scope") } : {},
5591
- ...terms2.has("subject") ? { subject: terms2.get("subject") } : {},
5592
- ...project === void 0 ? {} : { project: await canonicalProject(store, project) }
5593
- };
5594
- return pageOf("memories list", limit, async (most) => (await store.memories.list({ ...filters, limit: most })).map(memoryItem));
5595
- }, "listMemories");
5596
- var SEARCH = { command: "search memories", guide: LIST_QUERY_GUIDE };
5597
- var MEMORY_FIELDS = /* @__PURE__ */ new Set(["text", ...MEMORY_QUERY.fields.map(({ name: name2 }) => name2)]);
5598
- var searchMemories = /* @__PURE__ */ __name((store, query, limit) => {
5599
- const { root } = parseLucene(plainQuery(typedQuery(query), MEMORY_FIELDS), { ...SEARCH, defaultField: "text" });
5600
- const clauses = [];
5601
- if (root.kind === "predicate")
5602
- clauses.push(root);
5603
- else
5604
- for (const { occur, node } of root.clauses) {
5605
- if (occur !== "must" || node.kind !== "predicate")
5606
- queryError("query.unsupported-operator", node.span, "join words and scope: with AND only", SEARCH);
5607
- clauses.push(node);
5156
+ // dist/discovery/command.js
5157
+ var CONTRACT = 4;
5158
+ var commandsCommandDefinition = /* @__PURE__ */ __name((program, streams) => {
5159
+ annotate(program.command("commands"), { effects: NO_EFFECTS }).argument("[path...]", "one command, such as tags add").description(DISCOVERY_DESCRIPTIONS.commands).option("--json", "print JSON; --json=id,title keeps only those fields").action((path, options) => {
5160
+ const annotateAvailability = /* @__PURE__ */ __name((commands2) => commands2.map((info) => ({
5161
+ ...info,
5162
+ availability: environmentOf(program).discoveryAvailability?.(info),
5163
+ commands: annotateAvailability(info.commands)
5164
+ })), "annotateAvailability");
5165
+ const plain = commandsDocument(program, { cli: "memo", version: VERSION, contract: CONTRACT });
5166
+ const document = { ...plain, commands: annotateAvailability(plain.commands) };
5167
+ const answer = path.length ? scoped(document, path) : document;
5168
+ if (options.json)
5169
+ createRenderer({ format: "json", color: false, streams }).result(answer);
5170
+ else {
5171
+ const found = path.length ? flatten(document.commands).find((item) => same(item.path, answer.scope)) : void 0;
5172
+ streams.data(commandsText(program, found ? [found] : document.commands, found?.path));
5608
5173
  }
5609
- const words = [];
5610
- let scope;
5611
- for (const term of clauses) {
5612
- if (term.field === "text" && (term.operator === "term" || term.operator === "phrase"))
5613
- words.push(term.value);
5614
- else if (term.field === "scope" && term.operator === "term" && scope === void 0) {
5615
- const value = term.value.toLowerCase();
5616
- if (!MEMORY_SCOPES.includes(value))
5617
- queryError("query.invalid-value", term.span, `scope is ${MEMORY_SCOPES.join(" or ")}`, SEARCH);
5618
- scope = value;
5619
- } else
5620
- queryError("query.unsupported-field", term.span, "words, and scope:personal or scope:work once", SEARCH);
5174
+ });
5175
+ }, "commandsCommandDefinition");
5176
+ var scoped = /* @__PURE__ */ __name((document, path) => {
5177
+ const all = flatten(document.commands);
5178
+ const found = all.find((item) => item.path.join(" ") === path.join(" "));
5179
+ if (!found)
5180
+ throw new CliError("not_found", "command path was not found");
5181
+ const inheritedOptions = all.filter((item) => item.path.length < path.length && item.path.every((word, index) => word === path[index])).filter((item) => item.options.length > 0).map((item) => ({ path: item.path, options: item.options }));
5182
+ return { ...document, scope: found.path, inherited_options: inheritedOptions, commands: [found] };
5183
+ }, "scoped");
5184
+ var same = /* @__PURE__ */ __name((a, b) => a.join(" ") === b?.join(" "), "same");
5185
+ var SECTIONS = [
5186
+ ["context", "read"],
5187
+ ["show", "read"],
5188
+ ["notes", "read"],
5189
+ ["notes add", "write"],
5190
+ ["notes edit", "write"],
5191
+ ["notes remove", "write"],
5192
+ ["notes export", "write"],
5193
+ ["sources", "read"],
5194
+ ["sources add", "write"],
5195
+ ["sources remove", "write"],
5196
+ ["mail", "read"],
5197
+ ["events", "read"],
5198
+ ["agents", "read"],
5199
+ ["answers", "read"],
5200
+ ["memories", "write"],
5201
+ ["facts", "write"],
5202
+ ["actions", "write"],
5203
+ ["review", "write"],
5204
+ ["notes links", "organise"],
5205
+ ["organizations", "organise"],
5206
+ ["invoices", "organise"],
5207
+ ["orders", "organise"],
5208
+ ["deals", "organise"],
5209
+ ["leads", "organise"],
5210
+ ["pipelines", "organise"],
5211
+ ["payments", "organise"],
5212
+ ["products", "organise"],
5213
+ ["contracts", "organise"],
5214
+ ["subscriptions", "organise"],
5215
+ ["wallets", "organise"],
5216
+ ["transactions", "organise"],
5217
+ ["records", "organise"],
5218
+ ["projects", "organise"],
5219
+ ["people", "organise"],
5220
+ ["relationships", "organise"],
5221
+ ["reminders", "organise"],
5222
+ ["vocabulary", "organise"],
5223
+ ["sync", "automate"],
5224
+ ["jobs", "automate"],
5225
+ ["digest", "automate"],
5226
+ ["folders", "setup"],
5227
+ ["config", "setup"]
5228
+ ];
5229
+ var placeInSections = /* @__PURE__ */ __name((program) => {
5230
+ for (const [path, section2] of SECTIONS) {
5231
+ let command = program;
5232
+ for (const name2 of path.split(" "))
5233
+ command = command?.commands.find((child) => child.name() === name2);
5234
+ if (!command)
5235
+ throw new Error(`no command to place in a section: ${path}`);
5236
+ inSection(command, section2);
5621
5237
  }
5622
- return pageOf("search memories", limit, async (most) => (await store.memories.search(words.join(" "), { limit: most, ...scope === void 0 ? {} : { scope } })).map(memoryItem), { fallback: 20, cap: 200 });
5623
- }, "searchMemories");
5238
+ }, "placeInSections");
5239
+ var commandsCommand = /* @__PURE__ */ __name((program, streams) => {
5240
+ customizeSharedCommand(sharedCommandTemplate(APP, "commands"), "Memo binds discovery to its command-local JSON renderer and scoped stored-data document format.", () => {
5241
+ commandsCommandDefinition(program, streams);
5242
+ addContractInfo(program, (value) => streams.data(JSON.stringify(value)));
5243
+ const command = program.commands.find((command2) => command2.name() === "commands");
5244
+ if (!command)
5245
+ throw new Error("discovery builder did not install its command");
5246
+ return command;
5247
+ }, SHELL_OVERRIDES.commands);
5248
+ }, "commandsCommand");
5624
5249
 
5625
- // dist/digest/known.js
5626
- var knownSenders = /* @__PURE__ */ __name(async (store, accountId, externalIds) => {
5627
- if (externalIds.length === 0)
5628
- return /* @__PURE__ */ new Set();
5629
- const [facts, marks] = await Promise.all([
5630
- store.mail.emailFacts({ accountId, externalIds }),
5631
- store.mail.emailMarks(accountId, externalIds)
5632
- ]);
5633
- const known2 = /* @__PURE__ */ new Set();
5634
- const unlinked = /* @__PURE__ */ new Map();
5635
- for (const fact of facts) {
5636
- const address = fact.fromAddress?.toLowerCase();
5637
- if (!address)
5638
- continue;
5639
- if ((marks.get(fact.externalId)?.senderIdentities ?? 0) > 1)
5640
- known2.add(fact.externalId);
5641
- else
5642
- unlinked.set(address, [...unlinked.get(address) ?? [], fact.externalId]);
5250
+ // dist/doctor/command.js
5251
+ import { existsSync as existsSync7, readdirSync as readdirSync5, readFileSync as readFileSync6 } from "node:fs";
5252
+ import { homedir } from "node:os";
5253
+ import { dirname, join as join7 } from "node:path";
5254
+ var retiredMailAddresses = /* @__PURE__ */ __name((mail) => {
5255
+ const accounts = mail?.accounts;
5256
+ if (!Array.isArray(accounts))
5257
+ return [];
5258
+ return accounts.map((account2) => account2?.address).filter((address) => typeof address === "string");
5259
+ }, "retiredMailAddresses");
5260
+ var unitPaths = /* @__PURE__ */ __name((unit) => [...(/^ExecStart=(.*)$/m.exec(unit)?.[1] ?? "").matchAll(/"((?:[^"\\]|\\.)*)"/g)].map(([, path]) => path?.replace(/\\(["\\])/g, "$1") ?? "").filter((path) => path.startsWith("/")), "unitPaths");
5261
+ var diagnose = /* @__PURE__ */ __name(async (env, program, { deep = false } = {}) => {
5262
+ const problems = [];
5263
+ const paths = resolvePaths({ appName: APP.appName, prefix: APP.envPrefix, env });
5264
+ const { config, ...configReport } = inspectConfig(env, program);
5265
+ const files = privateFiles({
5266
+ files: [configReport.path, ...withSqliteSidecars(storePath(env))],
5267
+ // A folder the owner chose with MESSAGING_STORE may rightly be shared, like the home folder itself.
5268
+ dirs: [dirname(configReport.path), ...env.MESSAGING_STORE ? [] : [dirname(storePath(env))], runsDirFor(APP, env)]
5269
+ });
5270
+ const store = await storeSummary(env, { deep, command: "memo", version: APP.version });
5271
+ if ("damage" in store && store.damage)
5272
+ problems.push({ what: store.damage.what, fix: "memo store recover" });
5273
+ else if ("error" in store && store.error)
5274
+ problems.push({ what: `the store cannot be read: ${store.error}`, fix: "" });
5275
+ if ("writable" in store && store.writable === false)
5276
+ problems.push({ what: "the store is from a newer version than this memo", fix: "memo upgrade" });
5277
+ const folders = noteFolders(config.notes).map(({ path, id }) => ({
5278
+ path,
5279
+ exists: existsSync7(path),
5280
+ bound: id !== null
5281
+ }));
5282
+ for (const folder of folders) {
5283
+ if (!folder.exists)
5284
+ problems.push({
5285
+ what: `the notes folder ${folder.path} is missing`,
5286
+ fix: "connect its disk; if it moved, memo folders update <id> <new path>"
5287
+ });
5288
+ else if (!folder.bound)
5289
+ problems.push({
5290
+ what: `the notes folder ${folder.path} has no folder id`,
5291
+ fix: `memo folders add "${folder.path}"`
5292
+ });
5643
5293
  }
5644
- for (const address of await store.mail.writtenTo([...unlinked.keys()]))
5645
- for (const id of unlinked.get(address) ?? [])
5646
- known2.add(id);
5647
- return known2;
5648
- }, "knownSenders");
5649
-
5650
- // dist/digest/sync-health.js
5651
- import { existsSync as existsSync5, readdirSync as readdirSync3, readFileSync as readFileSync6 } from "node:fs";
5652
- import { join as join5 } from "node:path";
5294
+ if (config.mail !== void 0) {
5295
+ const addresses = retiredMailAddresses(config.mail);
5296
+ problems.push({
5297
+ what: `memo no longer imports mail, so the config's mail settings do nothing; the mail CLI syncs ${addresses.length ? addresses.join(", ") : "each mailbox"} into the same store (mail <profile> session start --address <address>, then mail <profile> sync), and the mail already stored stays`,
5298
+ fix: "memo config unset mail"
5299
+ });
5300
+ }
5301
+ let textModel = DEFAULT_TEXT_MODEL;
5302
+ try {
5303
+ textModel = chosenTextModel(env).id;
5304
+ } catch (error) {
5305
+ problems.push({
5306
+ what: `the embedding model choice cannot be read: ${error instanceof Error ? error.message : String(error)}`,
5307
+ fix: `fix text_models in ${textModelsFile(env)}, or remove it to go back to ${DEFAULT_TEXT_MODEL}`
5308
+ });
5309
+ }
5310
+ const modelDir = join7(sharedModelsDirectory("text", env), textModel);
5311
+ const model = existsSync7(modelDir) && readdirSync5(modelDir).length > 0;
5312
+ const state = autoState(config);
5313
+ const unitFile = join7(env.XDG_CONFIG_HOME ?? join7(env.HOME ?? homedir(), ".config"), "systemd", "user", "memo-import.service");
5314
+ const unit = existsSync7(unitFile) ? readFileSync6(unitFile, "utf8") : void 0;
5315
+ const runs = unit ? unitPaths(unit) : [];
5316
+ const missing = runs.filter((path) => !existsSync7(path));
5317
+ const last = lastAutoRun(env);
5318
+ const failing = last && last.exitCode !== 0 ? { since: last.failingSince ?? last.at, runs: last.failures ?? 1, reason: last.reason ?? null } : null;
5319
+ if (state.enabled && !unit)
5320
+ problems.push({ what: "the timer is on in the config but not installed", fix: "memo jobs start" });
5321
+ if (missing.length > 0)
5322
+ problems.push({ what: `the timer runs a program that is gone: ${missing.join(", ")}`, fix: "memo jobs start" });
5323
+ if (failing)
5324
+ problems.push({ what: `the timer has failed ${failing.runs} runs since ${failing.since}`, fix: "memo jobs status" });
5325
+ const skills = SKILL_TARGETS.map((target) => {
5326
+ const path = skillPath(target, APP.appName, env);
5327
+ const version = existsSync7(path) ? skillVersion(readFileSync6(path, "utf8")) ?? "unknown" : null;
5328
+ return { target, path, version };
5329
+ });
5330
+ if (skills.some(({ version }) => version !== null && version !== VERSION))
5331
+ problems.push({ what: "an installed skill is from another memo version", fix: "memo skill install --for all" });
5332
+ return {
5333
+ memo: { version: VERSION, node: process.version, installed_by: installer() },
5334
+ config: configReport,
5335
+ state: paths.state,
5336
+ cache: paths.cache,
5337
+ store,
5338
+ notes: folders,
5339
+ search_model: { name: textModel, path: modelDir, present: model },
5340
+ timer: { enabled: state.enabled, every: state.every, unit: unit ? unitFile : null, runs, failing },
5341
+ skills,
5342
+ files: files.checked ? { ...files, problems: files.problems.map(({ path, mode, want }) => ({ path, mode, want })) } : files,
5343
+ problems,
5344
+ fixes: files.checked ? files.problems.map(({ fix }) => fix) : []
5345
+ };
5346
+ }, "diagnose");
5347
+ var yes = /* @__PURE__ */ __name((value) => value ? "yes" : "no", "yes");
5348
+ var fileLines = /* @__PURE__ */ __name((d, fixes) => d.files.checked ? d.files.problems.map(({ path, mode, want }, at) => `! ${path} is ${mode}, so others can read it; it should be ${want} \u2014 ${fixes[at]}`) : [], "fileLines");
5349
+ var onlineLine = /* @__PURE__ */ __name(({ version }) => version.latest === null ? "online npm could not be reached" : version.newer ? `online memo ${version.latest} is published (this is ${version.current}) \u2014 memo upgrade` : `online memo ${version.current} is the newest published`, "onlineLine");
5350
+ var doctorText = /* @__PURE__ */ __name((d, fixes = [], online) => [
5351
+ `memo ${d.memo.version} on Node ${d.memo.node}, installed by ${d.memo.installed_by}`,
5352
+ `config ${d.config.path}${d.config.found ? "" : " (none yet)"}`,
5353
+ `state ${d.state}`,
5354
+ `cache ${d.cache}`,
5355
+ `store ${d.store.path}${!d.store.exists ? " (none yet; the first memo sync makes it)" : "schema" in d.store ? ` (schema ${d.store.schema}, memo speaks ${d.store.speaks})` : ""}`,
5356
+ ..."health" in d.store && d.store.health ? [
5357
+ `health ${d.store.health.ok ? "ok" : "DAMAGED"}, checked ${d.store.health.covers}${d.store.health.checked === "quick" ? " (memo doctor --deep reads every page)" : ""}`
5358
+ ] : [],
5359
+ ...d.notes.map((f) => `notes ${f.path} \u2014 exists ${yes(f.exists)}, folder id ${yes(f.bound)}`),
5360
+ `search ${d.search_model.name} ${d.search_model.present ? "present" : "not downloaded \u2014 search is by words"}`,
5361
+ `timer ${d.timer.enabled ? `on, every ${d.timer.every}` : "off"}`,
5362
+ ...d.skills.map((s) => `skill ${s.path} ${s.version === null ? "(not installed)" : `(${s.version})`}`),
5363
+ ...online ? [onlineLine(online)] : [],
5364
+ "",
5365
+ ...d.problems.length + d.config.issues.length + fileLines(d, fixes).length === 0 ? ["nothing needs attention"] : [
5366
+ ...d.config.issues.map(({ what, fix }) => `! ${what}${fix ? ` \u2014 ${fix}` : ""}`),
5367
+ ...fileLines(d, fixes),
5368
+ ...d.problems.map(({ what, fix }) => `! ${what}${fix ? ` \u2014 ${fix}` : ""}`)
5369
+ ]
5370
+ ].join("\n"), "doctorText");
5371
+ var sharedDoctor = sharedCommandTemplate(APP, "doctor");
5372
+ var ONLINE_FLAGS = "--online";
5373
+ var onlineText = sharedDoctor.options.find((option) => option.long === ONLINE_FLAGS)?.description ?? "";
5374
+ var onlineCheck = /* @__PURE__ */ __name(async () => {
5375
+ const started = Date.now();
5376
+ const newest = await latest();
5377
+ return {
5378
+ ok: true,
5379
+ duration_ms: Date.now() - started,
5380
+ version: { current: VERSION, latest: newest ?? null, newer: newest !== void 0 && isNewer(newest, VERSION) }
5381
+ };
5382
+ }, "onlineCheck");
5383
+ var doctorCommandDefinition = /* @__PURE__ */ __name((program, streams, env) => {
5384
+ const { print } = answers(program, streams, env);
5385
+ annotate(program.command("doctor"), { effects: REMOTE_READ }).description(sharedDoctor.description()).option(ONLINE_FLAGS, onlineText).option("--deep", DOCTOR_DEEP_OPTION).option("--json", "print JSON; --json=id,title keeps only those fields").addHelpText("after", "\nReads this computer only, unless --online: the config, the store, the notes folders, the search model, the timer and the agent skill, each problem with the command that fixes it. --online also asks npm whether a newer memo is published, and nothing else leaves this computer. Exits 1 when something other than the config file or a file mode needs attention; problems in the config file are listed in config.issues, and files or folders other users can read in files.problems, and neither changes the exit code.").action(async (options) => {
5386
+ const { fixes, ...diagnosis } = await diagnose(env, program, { deep: options.deep === true });
5387
+ const online = options.online ? await onlineCheck() : void 0;
5388
+ print(options.json, itemAnswer(online ? { ...diagnosis, online } : diagnosis), doctorText(diagnosis, fixes, online));
5389
+ if (options.json)
5390
+ for (const fix of fixes)
5391
+ streams.diagnostic(`to make it private: ${fix}`);
5392
+ if (diagnosis.problems.length > 0)
5393
+ process.exitCode = 1;
5394
+ });
5395
+ }, "doctorCommandDefinition");
5396
+ var doctorCommand = /* @__PURE__ */ __name((...args) => {
5397
+ customizeSharedCommand(sharedCommandTemplate(APP, "doctor"), "Memo diagnoses imported-data paths and stored account configuration without connecting to a messenger or querying a keyring.", () => {
5398
+ doctorCommandDefinition(...args);
5399
+ const command = args[0].commands.find((command2) => command2.name() === "doctor");
5400
+ if (!command)
5401
+ throw new Error("doctor builder did not install its command");
5402
+ return command;
5403
+ }, SHELL_OVERRIDES.doctor);
5404
+ }, "doctorCommand");
5653
5405
 
5654
- // dist/knowledge/messenger-sync.js
5655
- import { spawn } from "node:child_process";
5656
- import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync5, realpathSync, statSync as statSync2 } from "node:fs";
5657
- import { delimiter, join as join4 } from "node:path";
5658
- var STATE = {
5659
- tg: { appName: "tg-cli", envPrefix: "TG" },
5660
- max: { appName: "max-cli", envPrefix: "MAX" },
5661
- wa: { appName: "whatsapp-cli", envPrefix: "WA" }
5662
- };
5663
- var messengerToolOf = /* @__PURE__ */ __name((provider) => {
5664
- const tool = providerNamed(provider)?.command;
5665
- const state = tool === void 0 ? void 0 : STATE[tool];
5666
- return tool === void 0 || state === void 0 ? void 0 : { tool, ...state };
5667
- }, "messengerToolOf");
5668
- var syncProfileOf = /* @__PURE__ */ __name((value, previous = {}) => {
5669
- const [tool, profile] = value.split("=", 2);
5670
- const tools = Object.keys(STATE);
5671
- if (!tool || !profile || !tools.includes(tool))
5672
- throw new InvalidArgumentError(`<tool>=<profile>, where the tool is ${tools.join(", ")}: max=default`);
5673
- if (previous[tool] !== void 0 && previous[tool] !== profile)
5674
- throw new InvalidArgumentError(`one profile per tool; ${tool} is already ${previous[tool]}`);
5675
- return { ...previous, [tool]: profile };
5676
- }, "syncProfileOf");
5677
- var accountsDir = /* @__PURE__ */ __name((tool, env) => join4(resolvePaths({ appName: tool.appName, prefix: tool.envPrefix, env }).state, "accounts"), "accountsDir");
5678
- var signedInAccount = /* @__PURE__ */ __name((tool, profile, env) => {
5679
- try {
5680
- return JSON.parse(readFileSync5(join4(accountsDir(tool, env), `${profile}.json`), "utf8"))?.account;
5681
- } catch {
5682
- return void 0;
5683
- }
5684
- }, "signedInAccount");
5685
- var profileFor = /* @__PURE__ */ __name((provider, account2, env, chosen = {}) => {
5686
- const tool = messengerToolOf(provider);
5687
- if (!tool)
5406
+ // node_modules/.pnpm/@wirecat+cli-core@0.618.0_@wirecat+cli-base@0.91.0_commander@15.0.0_esbuild@0.28.2_type_a1c6dd927639f1a41fe5f9c53e24865e/node_modules/@wirecat/cli-core/dist/calendar/sync.js
5407
+ var DAY6 = 864e5;
5408
+ var SLOTS_EVERY = 7 * DAY6;
5409
+ var isWindow = /* @__PURE__ */ __name((value) => typeof value?.from === "number" && typeof value.to === "number", "isWindow");
5410
+ var isToken = /* @__PURE__ */ __name((value) => value === null || typeof value === "string", "isToken");
5411
+ var roundOf = /* @__PURE__ */ __name((value) => {
5412
+ if (value === null || value === void 0)
5413
+ return null;
5414
+ const round = value;
5415
+ if (typeof round.full !== "boolean" || !isToken(round.from) || !isWindow(round.window) || typeof round.startedAt !== "number" || typeof round.reset !== "boolean" || !isToken(round.page) || typeof round.listed !== "boolean" || !isToken(round.next) || !Array.isArray(round.series) || round.series.some((id) => typeof id !== "string") || round.everySeries !== void 0 && typeof round.everySeries !== "boolean")
5688
5416
  return void 0;
5689
- const dir = accountsDir(tool, env);
5690
- const profiles = existsSync4(dir) ? readdirSync2(dir).filter((name2) => name2.endsWith(".json")).map((name2) => name2.slice(0, -".json".length)).sort() : [];
5691
- const accountOf = /* @__PURE__ */ __name((profile) => signedInAccount(tool, profile, env), "accountOf");
5692
- const all = profiles.join(", ") || "none";
5693
- const pick = chosen[tool.tool];
5694
- if (pick !== void 0) {
5695
- if (!profiles.includes(pick))
5696
- return { why: `${tool.tool} profile ${pick} is not signed in here; profiles: ${all}`, profiles };
5697
- const held = accountOf(pick);
5698
- return held === account2 ? { profile: pick } : { why: `${tool.tool} profile ${pick} is signed in to account ${String(held)}, not ${account2}`, profiles: [] };
5699
- }
5700
- const matching = profiles.filter((profile) => accountOf(profile) === account2);
5701
- if (matching.length === 1 && matching[0])
5702
- return { profile: matching[0] };
5703
- if (matching.length > 1)
5417
+ const { full, from, window, startedAt, reset, page, listed: listed2, next, series, everySeries, unchanged } = round;
5418
+ return {
5419
+ full,
5420
+ from,
5421
+ window,
5422
+ startedAt,
5423
+ reset,
5424
+ page,
5425
+ listed: listed2,
5426
+ next,
5427
+ series,
5428
+ ...everySeries === false ? { everySeries } : {},
5429
+ ...unchanged === true ? { unchanged } : {}
5430
+ };
5431
+ }, "roundOf");
5432
+ var parseCalendarCursor = /* @__PURE__ */ __name((value) => {
5433
+ if (value === null)
5434
+ return null;
5435
+ try {
5436
+ const cursor = JSON.parse(value);
5437
+ if (cursor?.v !== 1 && cursor?.v !== 2 || !isToken(cursor.token) || typeof cursor.fullAt !== "number" || !isWindow(cursor.window))
5438
+ return null;
5439
+ const { token, window, fullAt } = cursor;
5440
+ if (cursor.v === 1)
5441
+ return { v: 2, token, window, fullAt, updatedAt: null, done: 0, round: null };
5442
+ const round = roundOf(cursor.round);
5443
+ if (round === void 0 || cursor.updatedAt !== null && typeof cursor.updatedAt !== "number" || typeof cursor.done !== "number")
5444
+ return null;
5704
5445
  return {
5705
- why: `${matching.length} ${tool.tool} profiles are signed in to account ${account2}: ${matching.join(", ")} \u2014 choose one with --sync-profile ${tool.tool}=<profile>`,
5706
- profiles: matching
5446
+ v: 2,
5447
+ token,
5448
+ window,
5449
+ fullAt,
5450
+ updatedAt: cursor.updatedAt ?? null,
5451
+ done: cursor.done,
5452
+ round,
5453
+ ...cursor.restored === true ? { restored: true } : {},
5454
+ ...typeof cursor.slotsTo === "number" ? { slotsTo: cursor.slotsTo } : {}
5707
5455
  };
5708
- return { why: `no ${tool.tool} profile here is signed in to account ${account2}; profiles: ${all}`, profiles: [] };
5709
- }, "profileFor");
5710
- var onPath = /* @__PURE__ */ __name((name2, env) => (env.PATH ?? "").split(delimiter).filter(Boolean).map((dir) => join4(dir, name2)).find((path) => {
5711
- try {
5712
- return statSync2(path).isFile();
5713
5456
  } catch {
5714
- return false;
5457
+ return null;
5715
5458
  }
5716
- }), "onPath");
5717
- var runChild = /* @__PURE__ */ __name((command, args, { env, signal, onStderr }) => new Promise((resolve6, reject) => {
5718
- const child = spawn(command, args, { env, stdio: ["ignore", "pipe", "pipe"] });
5719
- let stdout = "";
5720
- let pending2 = "";
5721
- child.stdout.setEncoding("utf8").on("data", (chunk) => {
5722
- stdout += chunk;
5723
- });
5724
- child.stderr.setEncoding("utf8").on("data", (chunk) => {
5725
- const lines2 = (pending2 + chunk).split("\n");
5726
- pending2 = lines2.pop() ?? "";
5727
- for (const line3 of lines2)
5728
- if (line3)
5729
- onStderr?.(line3);
5730
- });
5731
- let hard;
5732
- const stop = /* @__PURE__ */ __name(() => {
5733
- child.kill("SIGINT");
5734
- hard = setTimeout(() => child.kill("SIGTERM"), 1e4);
5735
- }, "stop");
5736
- signal?.addEventListener("abort", stop, { once: true });
5737
- child.on("error", reject);
5738
- child.on("close", (code) => {
5739
- clearTimeout(hard);
5740
- signal?.removeEventListener("abort", stop);
5741
- if (pending2)
5742
- onStderr?.(pending2);
5743
- resolve6({ code, stdout });
5744
- });
5745
- }), "runChild");
5746
- var binaryOf = /* @__PURE__ */ __name(async (provider, env, run) => {
5747
- const tool = messengerToolOf(provider);
5748
- if (!tool)
5749
- return void 0;
5750
- const umbrella = onPath(`wcat-${tool.tool}`, env);
5751
- if (umbrella) {
5752
- const described = await run(umbrella, ["--wcat-describe"], { env }).catch(() => void 0);
5753
- try {
5754
- if (described?.code === 0 && JSON.parse(described.stdout)?.name === tool.tool)
5755
- return umbrella;
5756
- } catch {
5459
+ }, "parseCalendarCursor");
5460
+ var calendarStatus = /* @__PURE__ */ __name(async (store, accountId, { profile, now }) => {
5461
+ const items = [];
5462
+ for (const calendar of await store.calendars(accountId)) {
5463
+ if (!calendar.synced || calendar.deletedAt !== null)
5464
+ continue;
5465
+ const cursor = parseCalendarCursor(await store.cursor(calendar.id));
5466
+ const progress = progressOf({
5467
+ job: `calendar ${calendar.id}`,
5468
+ profile,
5469
+ done: cursor?.done ?? 0,
5470
+ complete: cursor !== null && cursor.round === null,
5471
+ ...cursor?.updatedAt == null ? {} : { updatedAt: new Date(cursor.updatedAt).toISOString() },
5472
+ now: new Date(now)
5473
+ });
5474
+ items.push({
5475
+ calendar_id: String(calendar.id),
5476
+ external_id: calendar.externalId,
5477
+ title: calendar.title,
5478
+ round: cursor?.round ? cursor.round.full ? "full" : "changes" : null,
5479
+ ...progress,
5480
+ line: progressLine(progress)
5481
+ });
5482
+ }
5483
+ return { items, page: 1, limit: Math.max(1, items.length), hasMore: false };
5484
+ }, "calendarStatus");
5485
+
5486
+ // node_modules/.pnpm/@wirecat+cli-core@0.618.0_@wirecat+cli-base@0.91.0_commander@15.0.0_esbuild@0.28.2_type_a1c6dd927639f1a41fe5f9c53e24865e/node_modules/@wirecat/cli-core/dist/calendar/feeds.js
5487
+ var FEED_LIMITS = { maxBytes: 10 * 1024 * 1024, timeoutMs: 3e4, redirects: 5 };
5488
+
5489
+ // node_modules/.pnpm/@wirecat+cli-core@0.618.0_@wirecat+cli-base@0.91.0_commander@15.0.0_esbuild@0.28.2_type_a1c6dd927639f1a41fe5f9c53e24865e/node_modules/@wirecat/cli-core/dist/calendar/reads.js
5490
+ var LIST = "events list";
5491
+ var EVENT_TYPES = [
5492
+ "default",
5493
+ "out-of-office",
5494
+ "focus",
5495
+ "working-location",
5496
+ "birthday",
5497
+ "from-mail",
5498
+ "call"
5499
+ ];
5500
+ var oneSided = /* @__PURE__ */ __name((field2, example) => (node) => {
5501
+ if (node.operator !== "range" || node.upper !== "*")
5502
+ queryError("query.unsupported-operator", node.span, `${field2} takes only an earliest moment, such as ${example}`, {
5503
+ command: LIST,
5504
+ guide: LIST_QUERY_GUIDE
5505
+ });
5506
+ }, "oneSided");
5507
+ var EVENTS_QUERY = {
5508
+ entity: "event",
5509
+ backend: "native",
5510
+ fields: queryFieldsOf(event, {
5511
+ native: ["id", "starts_at", "ends_at", "changed_at", "overlapping", "calendar", "person", "event_type"],
5512
+ negatable: ["event_type"],
5513
+ relations: {
5514
+ event_type: {
5515
+ type: "enum",
5516
+ values: EVENT_TYPES,
5517
+ example: "event_type:default",
5518
+ relation: "events with a copy of this kind; -event_type:<kind> leaves out events with one"
5519
+ },
5520
+ calendar: {
5521
+ type: "kw",
5522
+ example: "calendar:3",
5523
+ relation: "one calendar, by its id, the provider's id or its title"
5524
+ },
5525
+ person: {
5526
+ type: "kw",
5527
+ example: "person:ana@example.com",
5528
+ relation: "events one person attends: an address, or a person id from search"
5529
+ }
5530
+ },
5531
+ examples: {
5532
+ id: "id:42",
5533
+ starts_at: "starts_at:[2026-11-02 TO 2026-11-07}",
5534
+ ends_at: "ends_at>=2026-11-02",
5535
+ changed_at: "changed_at>=2026-10-01",
5536
+ overlapping: "overlapping:true"
5537
+ },
5538
+ validate: {
5539
+ ends_at: oneSided("ends_at", "ends_at>=2026-11-02; for the latest, write starts_at<2026-11-07"),
5540
+ changed_at: oneSided("changed_at", "changed_at>=2026-10-01")
5757
5541
  }
5542
+ }).map((field2) => field2.native ? {
5543
+ ...field2,
5544
+ operators: field2.type === "time" ? ["term", "range"] : field2.type === "bool" ? ["term"] : ["term", "phrase"]
5545
+ } : field2)
5546
+ };
5547
+ var nameOf2 = /* @__PURE__ */ __name(({ provider, account: account2 }) => `${provider}:${account2}`, "nameOf");
5548
+ var accountsMeta = /* @__PURE__ */ __name(async (store, accounts, skipped, now) => {
5549
+ const issues = [...skipped];
5550
+ for (const { id, key } of accounts) {
5551
+ if (isImportedProvider(key.provider))
5552
+ continue;
5553
+ const { items } = await calendarStatus(store, id, { profile: nameOf2(key), now });
5554
+ if (items.length === 0)
5555
+ issues.push({ code: "not_found", reason: "calendar.unsynced", source: nameOf2(key) });
5556
+ else if (items.some(({ state }) => state !== "complete"))
5557
+ issues.push({ code: "not_found", reason: "calendar.sync-incomplete", source: nameOf2(key) });
5758
5558
  }
5759
- const short = onPath(tool.tool, env);
5760
- if (!short)
5761
- return void 0;
5762
- const mark = `@wirecat/${tool.appName}`;
5763
- const real = realpathSync(short);
5764
- if (real.includes(`/${mark}/`))
5765
- return short;
5766
- return statSync2(real).size < 65536 && readFileSync5(real, "utf8").includes(mark) ? short : void 0;
5767
- }, "binaryOf");
5768
- var answerOf = /* @__PURE__ */ __name((stdout) => {
5769
- try {
5770
- return JSON.parse(stdout.trim().split("\n").at(-1) ?? "");
5771
- } catch {
5772
- return {};
5773
- }
5774
- }, "answerOf");
5775
- var MARGIN_MS = 5e3;
5776
- var messengerFetcher = /* @__PURE__ */ __name(({ env, note, chosen = {}, run = runChild, now = Date.now }) => {
5777
- const binaries = /* @__PURE__ */ new Map();
5778
5559
  return {
5779
- fetch: /* @__PURE__ */ __name(async ({ account: account2, chatId, after, until, signal }) => {
5780
- const tool = messengerToolOf(account2.provider);
5781
- if (!tool)
5782
- return { state: "failed", reason: `no WireCat messenger CLI syncs ${account2.provider}` };
5783
- const found = profileFor(account2.provider, account2.account, env, chosen);
5784
- if (!found || !("profile" in found))
5785
- return { state: "failed", reason: found?.why ?? "no profile" };
5786
- if (!binaries.has(tool.tool))
5787
- binaries.set(tool.tool, binaryOf(account2.provider, env, run));
5788
- const bin = await binaries.get(tool.tool);
5789
- if (!bin)
5790
- return { state: "failed", reason: `${tool.tool} is not installed, or the one on PATH is not WireCat's` };
5791
- const seconds = Math.max(1, Math.floor((until - now() - MARGIN_MS) / 1e3));
5792
- const args = [
5793
- found.profile,
5794
- "messages",
5795
- "sync",
5796
- "--since-time",
5797
- new Date(after).toISOString(),
5798
- "--limit",
5799
- "1000000",
5800
- "--timeout",
5801
- `${seconds}s`,
5802
- "--json",
5803
- chatId
5804
- ];
5805
- const done = await run(bin, args, {
5806
- env,
5807
- signal,
5808
- onStderr: /* @__PURE__ */ __name((line3) => {
5809
- if (!isEvent(line3))
5810
- note(`${tool.tool}: ${line3}`);
5811
- }, "onStderr")
5812
- });
5813
- const answer = answerOf(done.stdout);
5814
- if (done.code !== 0) {
5815
- const why = answer.error?.message?.split("\n")[0];
5816
- const code = answer.error?.code ?? `exit ${done.code ?? "signal"}`;
5817
- return {
5818
- state: "partial",
5819
- reason: `${tool.tool} ${found.profile} messages sync: ${code}${why ? ` \u2014 ${why}` : ""}`
5820
- };
5821
- }
5822
- const item = answer.item ?? {};
5823
- if (item.issue?.code)
5824
- return { state: "partial", reason: item.issue.code };
5825
- if (item.stopped || signal.aborted)
5826
- return { state: "partial", reason: "fetch-budget" };
5827
- return item.complete || item.reached_since ? { state: "complete" } : { state: "partial", reason: "limit" };
5828
- }, "fetch")
5560
+ accounts: accounts.map(({ key }) => nameOf2(key)),
5561
+ complete: issues.length === 0,
5562
+ ...issues.length ? { issues } : {}
5829
5563
  };
5830
- }, "messengerFetcher");
5831
- var isEvent = /* @__PURE__ */ __name((line3) => {
5832
- if (!line3.startsWith("{"))
5833
- return false;
5834
- try {
5835
- return typeof JSON.parse(line3)?.event === "string";
5836
- } catch {
5837
- return false;
5838
- }
5839
- }, "isEvent");
5840
- var chatCommand = /* @__PURE__ */ __name((chat, words, env, chosen) => {
5841
- const tool = messengerToolOf(chat.provider);
5842
- if (!tool)
5843
- return null;
5844
- const found = profileFor(chat.provider, chat.account, env, chosen);
5845
- const line3 = /* @__PURE__ */ __name((profile) => `${tool.tool} ${profile} ${words}`, "line");
5846
- if (found && "profile" in found)
5847
- return { command: line3(found.profile) };
5848
- return { command: null, line: line3("<profile>"), why: found?.why ?? "no profile" };
5849
- }, "chatCommand");
5850
- var chatSync = /* @__PURE__ */ __name((chat, since, env, chosen = {}) => (
5851
- // A negative group id needs no `--`: no WireCat CLI has a digit short option.
5852
- chatCommand(chat, `messages sync ${chat.chat} --since-time ${since}`, env, chosen)
5853
- ), "chatSync");
5854
- var chatRead = /* @__PURE__ */ __name((chat, since, env, chosen = {}) => chatCommand(chat, `messages list --compact ${chat.chat} "date>=${since}"`, env, chosen), "chatRead");
5855
- var syncHint = /* @__PURE__ */ __name((chat, since, env, chosen = {}) => {
5856
- const sync = chatSync(chat, since, env, chosen);
5857
- if (!sync)
5858
- return null;
5859
- return sync.command ?? `${sync.line} (${sync.why})`;
5860
- }, "syncHint");
5564
+ }, "accountsMeta");
5861
5565
 
5862
- // dist/digest/sync-health.js
5863
- var HEALTH_REASONS = ["sync-failed", "no-schedule", "quiet"];
5864
- var MAIL2 = { appName: "mail-cli", envPrefix: "MAIL" };
5865
- var SOURCE_WORDS = {
5866
- telegram: "Telegram",
5867
- max: "MAX",
5868
- whatsapp: "WhatsApp",
5869
- email: "mail"
5566
+ // node_modules/.pnpm/@wirecat+cli-core@0.618.0_@wirecat+cli-base@0.91.0_commander@15.0.0_esbuild@0.28.2_type_a1c6dd927639f1a41fe5f9c53e24865e/node_modules/@wirecat/cli-core/dist/calendar/writer.js
5567
+ var EVENTS_NOTIFY_KEY = "events.notify";
5568
+ var EVENTS_DELETE_SERIES_KEY = "events.delete.series";
5569
+ var EVENTS_REMIND_HOOK_KEY = "events.remind.hook";
5570
+ var CALENDAR_WRITE_DEFAULTS = {
5571
+ "events.create": "allow",
5572
+ // One import writes a whole file of events, and may change events the calendar already holds under their UIDs.
5573
+ "events.import": "ask",
5574
+ "events.update": "allow",
5575
+ "events.respond": "allow",
5576
+ "events.move": "allow",
5577
+ "events.delete": "ask",
5578
+ [EVENTS_DELETE_SERIES_KEY]: "deny",
5579
+ [EVENTS_NOTIFY_KEY]: "deny",
5580
+ // Not ask: `--yes` answers ask, and anything that can type a gcal command can add it.
5581
+ [EVENTS_REMIND_HOOK_KEY]: "deny"
5870
5582
  };
5871
- var toolOf = /* @__PURE__ */ __name((provider) => provider === "email" ? MAIL2 : messengerToolOf(provider), "toolOf");
5872
- var stateDirs = /* @__PURE__ */ __name((tool, env) => resolvePaths({ appName: tool.appName, prefix: tool.envPrefix, env }), "stateDirs");
5873
- var schedulesOf = /* @__PURE__ */ __name((state) => {
5874
- const dir = join5(state, "schedules");
5875
- if (!existsSync5(dir))
5876
- return [];
5877
- const last = new Map(scheduledRuns(state).map((one2) => [one2.job, one2.last]));
5878
- return readdirSync3(dir).flatMap((name2) => {
5879
- try {
5880
- const schedule = JSON.parse(readFileSync6(join5(dir, name2, "schedule.json"), "utf8"));
5881
- return [
5882
- {
5883
- name: String(schedule.name),
5884
- kind: String(schedule.kind),
5885
- argv: Array.isArray(schedule.argv) ? schedule.argv.map(String) : [],
5886
- environment: schedule.environment ?? {},
5887
- last: last.get(String(schedule.name))
5888
- }
5889
- ];
5890
- } catch {
5891
- return [];
5892
- }
5893
- });
5894
- }, "schedulesOf");
5895
- var signedIn = /* @__PURE__ */ __name((dir) => existsSync5(dir) ? readdirSync3(dir).filter((name2) => name2.endsWith(".json")).map((name2) => name2.slice(0, -".json".length)) : [], "signedIn");
5896
- var profilesOf = /* @__PURE__ */ __name((provider, tool, env) => {
5897
- if (provider !== "email")
5898
- return new Map(signedIn(join5(stateDirs(tool, env).state, "accounts")).map((profile) => [
5899
- profile,
5900
- signedInAccount(tool, profile, env)
5901
- ]));
5902
- try {
5903
- const config = JSON.parse(readFileSync6(configFilePath(stateDirs(MAIL2, env).config), "utf8"));
5904
- return new Map(Object.entries(config?.profiles ?? {}).map(([profile, one2]) => [
5905
- profile,
5906
- one2?.address
5907
- ]));
5908
- } catch {
5909
- return /* @__PURE__ */ new Map();
5583
+
5584
+ // dist/events/events.js
5585
+ var LIST2 = "events list";
5586
+ var EVENT_TYPES2 = [
5587
+ "default",
5588
+ "out-of-office",
5589
+ "focus",
5590
+ "working-location",
5591
+ "birthday",
5592
+ "from-mail",
5593
+ "call"
5594
+ ];
5595
+ var EVENTS_QUERY2 = {
5596
+ entity: "event",
5597
+ backend: "native",
5598
+ fields: [
5599
+ nativeField({
5600
+ name: "starts_at",
5601
+ type: "time",
5602
+ operators: ["term", "range"],
5603
+ example: "starts_at:[2026-11-02 TO 2026-11-07}"
5604
+ }),
5605
+ termField("account", "kw", 'account:"phone:pixel"', {
5606
+ relation: "one calendar account, provider:account, or every account of a provider"
5607
+ }),
5608
+ termField("calendar", "kw", "calendar:Calls", { relation: "one calendar, by its id or its title" }),
5609
+ termField("event_type", "enum", "event_type:call", {
5610
+ values: EVENT_TYPES2,
5611
+ relation: "events with a copy of this kind; a phone's imported calls are call"
5612
+ })
5613
+ ]
5614
+ };
5615
+ var RECENT_DAYS = 30;
5616
+ var AHEAD_DAYS = 7;
5617
+ var EVENTS_NOTE = `
5618
+ Every calendar account in the store is read, phone imports included. Without starts_at: the last ${RECENT_DAYS} days and the next ${AHEAD_DAYS}. Latest start first.`;
5619
+ var UNTRUSTED = ["title", "description", "location", "in"];
5620
+ var readAccounts = /* @__PURE__ */ __name(async (store) => {
5621
+ const held = await calendarAccounts(store);
5622
+ const stored = await store.accounts.storedAccounts();
5623
+ const read = [];
5624
+ for (const key of held) {
5625
+ const one2 = stored.find(({ provider, account: account2 }) => provider === key.provider && account2 === key.account);
5626
+ if (one2)
5627
+ read.push({ id: one2.id, key, calendars: await store.calendar.calendars(one2.id) });
5910
5628
  }
5911
- }, "profilesOf");
5912
- var profileRun = /* @__PURE__ */ __name((schedule, tool, profiles) => {
5913
- const first = schedule.argv[0];
5914
- return first !== void 0 && profiles.has(first) ? first : schedule.environment[`${tool.envPrefix}_PROFILE`] ?? "default";
5915
- }, "profileRun");
5916
- var syncRunsOf = /* @__PURE__ */ __name((env) => {
5917
- const cache = /* @__PURE__ */ new Map();
5918
- const read = /* @__PURE__ */ __name((provider, tool) => {
5919
- const known2 = cache.get(tool.appName);
5920
- if (known2)
5921
- return known2;
5922
- const app = { ...APP, appName: tool.appName, envPrefix: tool.envPrefix };
5923
- const fresh = {
5924
- schedules: schedulesOf(stateDirs(tool, env).state).filter((one2) => one2.kind === "command" && one2.argv.includes("sync")),
5925
- serving: servingProfiles(app, env),
5926
- profiles: profilesOf(provider, tool, env)
5927
- };
5928
- cache.set(tool.appName, fresh);
5929
- return fresh;
5930
- }, "read");
5931
- return (key) => {
5932
- const tool = toolOf(key.provider);
5933
- if (!tool)
5934
- return { runs: [], scheduled: false, serving: false, known: false };
5935
- const { schedules, serving, profiles } = read(key.provider, tool);
5936
- const mine = new Set([...profiles].filter(([, account2]) => account2 === key.account).map(([profile]) => profile));
5937
- const runs = schedules.filter((one2) => mine.has(profileRun(one2, tool, profiles)));
5938
- return {
5939
- runs: runs.flatMap((one2) => one2.last ? [one2.last] : []),
5940
- scheduled: runs.length > 0,
5941
- serving: serving.some((profile) => mine.has(profile)),
5942
- known: mine.size > 0
5943
- };
5629
+ return read;
5630
+ }, "readAccounts");
5631
+ var narrowed = /* @__PURE__ */ __name((accounts, value) => {
5632
+ const chosen = accounts.filter(({ key }) => accountName(key) === value || key.provider === value);
5633
+ if (chosen.length === 0)
5634
+ throw new CliError("not_found", `account:${value} names no calendar account \u2014 the store holds ${accounts.map(({ key }) => accountName(key)).join(", ") || "none"}`, { reason: "events.account-unknown" });
5635
+ return chosen;
5636
+ }, "narrowed");
5637
+ var calendarOf = /* @__PURE__ */ __name((accounts, value) => {
5638
+ const all = accounts.flatMap((account2) => account2.calendars.map((calendar) => ({ account: account2, calendar })));
5639
+ const byId = all.filter(({ calendar }) => String(calendar.id) === value);
5640
+ const found = byId.length ? byId : all.filter(({ calendar }) => calendar.title === value);
5641
+ if (found.length === 1)
5642
+ return found[0];
5643
+ if (found.length === 0)
5644
+ throw new CliError("not_found", `calendar:${value} names no calendar in the accounts read`);
5645
+ throw new CliError("validation_error", `calendar:${value} names ${found.length} calendars; give one by its id: ` + found.map(({ account: account2, calendar }) => `calendar:${calendar.id} (${accountName(account2.key)})`).join(", "));
5646
+ }, "calendarOf");
5647
+ var localDate2 = /* @__PURE__ */ __name((at, zone) => new Intl.DateTimeFormat("en-CA", { timeZone: zone, year: "numeric", month: "2-digit", day: "2-digit" }).format(at), "localDate");
5648
+ var plusDays = /* @__PURE__ */ __name((date, days2) => {
5649
+ const [year, month, day2] = date.split("-").map(Number);
5650
+ return new Date(Date.UTC(year, month - 1, day2 + days2)).toISOString().slice(0, 10);
5651
+ }, "plusDays");
5652
+ var midnight = /* @__PURE__ */ __name((date, zone) => dayBoundary(date, zone, false, { start: 0, end: date.length }), "midnight");
5653
+ var startsWindow = /* @__PURE__ */ __name((node, zone, now) => {
5654
+ const range = node.operator === "range" ? dateRange(node.value, node.upper ?? "*", node.lower_inclusive !== false, node.upper_inclusive !== false, zone, node.span, now) : dateRange(node.value, node.value, true, true, zone, node.span, now);
5655
+ return {
5656
+ ...range.lower === void 0 ? {} : { lower: range.lower_inclusive ? range.lower : range.lower + 1 },
5657
+ ...range.upper === void 0 ? {} : { upper: range.upper_inclusive ? range.upper + 1 : range.upper }
5944
5658
  };
5945
- }, "syncRunsOf");
5946
- var HOUR = 36e5;
5947
- var DAY4 = 24 * HOUR;
5948
- var QUIET_AFTER = 3 * DAY4;
5949
- var QUIET_GAPS = 3;
5950
- var hours = /* @__PURE__ */ __name((from, now) => Math.max(0, Math.floor((now - from) / HOUR)), "hours");
5951
- var healthOf = /* @__PURE__ */ __name((sync, activity, now) => {
5952
- const recent = /* @__PURE__ */ __name((at) => at !== null && at >= now - DAY4, "recent");
5953
- if (recent(activity.storedAt) || sync.runs.some((run) => run.exitCode === 0 && recent(Date.parse(run.at))))
5954
- return void 0;
5955
- if (!sync.serving && sync.runs.length > 0 && sync.runs.every((run) => run.exitCode !== 0)) {
5956
- const first = [...sync.runs].sort((a, b) => (a.failingSince ?? a.at).localeCompare(b.failingSince ?? b.at))[0];
5957
- const since = first?.failingSince ?? first?.at ?? new Date(now).toISOString();
5958
- return {
5959
- reason: "sync-failed",
5960
- since,
5961
- staleHours: hours(Date.parse(since), now),
5962
- ...first?.reason ? { failure: first.reason } : {}
5963
- };
5659
+ }, "startsWindow");
5660
+ var dated = /* @__PURE__ */ __name((filter, zone) => {
5661
+ if (filter.from !== void 0)
5662
+ filter.fromDate = localDate2(filter.from, zone);
5663
+ if (filter.to !== void 0)
5664
+ filter.toDate = plusDays(localDate2(filter.to - 1, zone), 1);
5665
+ if (filter.startsFrom !== void 0) {
5666
+ const date = localDate2(filter.startsFrom, zone);
5667
+ filter.startsFromDate = midnight(date, zone) < filter.startsFrom ? plusDays(date, 1) : date;
5964
5668
  }
5965
- if (!sync.scheduled && !sync.serving) {
5966
- if (!sync.known)
5967
- return void 0;
5968
- const last = activity.newest ?? activity.storedAt;
5969
- return { reason: "no-schedule", since: null, staleHours: last === null ? 0 : hours(last, now) };
5669
+ return filter;
5670
+ }, "dated");
5671
+ var windowMeta = /* @__PURE__ */ __name((filter, zone) => {
5672
+ const since = filter.startsFrom ?? filter.from;
5673
+ return {
5674
+ timezone: zone,
5675
+ window: {
5676
+ ...since === void 0 ? {} : { since: new Date(since).toISOString() },
5677
+ ...filter.to === void 0 ? {} : { until: new Date(filter.to - 1).toISOString() }
5678
+ }
5679
+ };
5680
+ }, "windowMeta");
5681
+ var rowOf = /* @__PURE__ */ __name((details, accounts) => {
5682
+ const live = details.entries.filter(({ deletedAt }) => deletedAt === null);
5683
+ const copies = live.length ? live : details.entries;
5684
+ const held = accounts.flatMap((account2) => account2.calendars.filter(({ id }) => copies.some(({ calendarId }) => calendarId === id)).map((calendar) => ({ account: accountName(account2.key), calendar: calendar.title ?? String(calendar.id) })));
5685
+ return {
5686
+ ...eventItem(details.event),
5687
+ ref: `event:${details.event.id}`,
5688
+ event_type: copies.find(({ eventType }) => eventType !== null)?.eventType ?? "default",
5689
+ accounts: [...new Set(held.map(({ account: account2 }) => account2))],
5690
+ in: held.map(({ account: account2, calendar }) => `${account2}/${calendar}`).join(", "),
5691
+ untrusted: UNTRUSTED
5692
+ };
5693
+ }, "rowOf");
5694
+ var listEvents = /* @__PURE__ */ __name(async (store, words, limit, { now = Date.now(), zone = timezoneOf() } = {}) => {
5695
+ const terms2 = nativeTerms(listQuery(typedQuery(words), EVENTS_QUERY2, LIST2), LIST2);
5696
+ const every = await readAccounts(store);
5697
+ const account2 = terms2.get("account");
5698
+ let accounts = account2 ? narrowed(every, account2.value) : every;
5699
+ const filter = { newest: true };
5700
+ const calendar = terms2.get("calendar");
5701
+ if (calendar) {
5702
+ const found = calendarOf(accounts, calendar.value);
5703
+ accounts = [found.account];
5704
+ filter.calendarId = found.calendar.id;
5970
5705
  }
5971
- if (activity.newest === null || activity.moved30d === 0)
5972
- return void 0;
5973
- const usual = Math.max(QUIET_AFTER, QUIET_GAPS * 30 * DAY4 / activity.moved30d);
5974
- if (activity.newest < now - usual)
5975
- return { reason: "quiet", since: new Date(activity.newest).toISOString(), staleHours: hours(activity.newest, now) };
5976
- return void 0;
5977
- }, "healthOf");
5706
+ const kind = terms2.get("event_type");
5707
+ if (kind)
5708
+ filter.eventType = kind.value.toLowerCase();
5709
+ const starts = terms2.get("starts_at");
5710
+ if (starts) {
5711
+ const { lower, upper } = startsWindow(starts, zone, now);
5712
+ if (lower !== void 0)
5713
+ filter.startsFrom = lower;
5714
+ if (upper !== void 0)
5715
+ filter.to = upper;
5716
+ } else {
5717
+ const today2 = localDate2(now, zone);
5718
+ filter.from = midnight(plusDays(today2, -RECENT_DAYS), zone);
5719
+ filter.to = midnight(plusDays(today2, AHEAD_DAYS + 1), zone);
5720
+ }
5721
+ dated(filter, zone);
5722
+ const read = accounts.map(({ id, key }) => ({ id, key }));
5723
+ const events = read.length ? await store.calendar.events({ ...filter, accountIds: read.map(({ id }) => id), limit: limit + 1 }) : [];
5724
+ const rows = [];
5725
+ for (const event2 of events.slice(0, limit)) {
5726
+ const details = await store.calendar.event(event2.id);
5727
+ if (details)
5728
+ rows.push(rowOf(details, accounts));
5729
+ }
5730
+ const body = listAnswer(LIST2, { items: rows, limit, has_more: events.length > limit });
5731
+ body.meta = {
5732
+ ...body.meta,
5733
+ ...await accountsMeta(store.calendar, read, [], now),
5734
+ ...windowMeta(filter, zone)
5735
+ };
5736
+ return body;
5737
+ }, "listEvents");
5738
+ var showEvent = /* @__PURE__ */ __name(async (store, reference2) => {
5739
+ const text3 = reference2.replace(/^event:/u, "");
5740
+ if (!/^[1-9]\d*$/u.test(text3))
5741
+ throw new CliError("validation_error", "an event is event:<id>");
5742
+ const details = await store.calendar.event(Number(text3));
5743
+ const accounts = await readAccounts(store);
5744
+ const row = details && rowOf(details, accounts);
5745
+ if (!details || !row?.accounts.length)
5746
+ throw new CliError("not_found", `no event ${text3} in any calendar account`);
5747
+ return {
5748
+ ...row,
5749
+ attendees: details.participants.map(({ email, name: name2, response, organizer }) => ({
5750
+ email,
5751
+ name: name2,
5752
+ response,
5753
+ organizer
5754
+ })),
5755
+ untrusted: [...UNTRUSTED, "attendees.name"]
5756
+ };
5757
+ }, "showEvent");
5758
+ var accountsLine = /* @__PURE__ */ __name((accounts) => accounts.length ? `calendar accounts read: ${accounts.join(", ")}` : "no calendar account in the store", "accountsLine");
5978
5759
 
5979
- // dist/tasks/list.js
5980
- import { createHash as createHash2 } from "node:crypto";
5981
- var OPEN_WORK = "state:(open OR todo OR in_progress)";
5982
- var TASKS_LIST_QUERY = {
5983
- entity: "task",
5984
- backend: "sql",
5985
- fields: [
5986
- ...PROJECT_TASK_FIELDS,
5987
- {
5988
- name: "source",
5989
- type: "ref",
5990
- operators: ["term", "phrase"],
5991
- example: 'source:"msg:telegram/1/7/1"',
5992
- relation: "tasks from this message, note or document; an account's tasks only"
5993
- }
5760
+ // dist/events/command.js
5761
+ var upTo1000 = /* @__PURE__ */ __name((value) => {
5762
+ const number2 = Number(value);
5763
+ if (!Number.isInteger(number2) || number2 < 1 || number2 > 1e3)
5764
+ throw new InvalidArgumentError("a whole number, 1 to 1000");
5765
+ return number2;
5766
+ }, "upTo1000");
5767
+ var eventsCommand = /* @__PURE__ */ __name((program, streams, env) => {
5768
+ const { print } = answers(program, streams, env);
5769
+ const events = program.command("events").description("calendar events of every account in the store, phone calls included").summary("calendar events and phone calls of every account");
5770
+ withQuery(annotate(events.command("list"), { effects: LOCAL_READ, partial: true }).description("events of every calendar account in the store, latest start first: calendars and imported phone calls").summary("events and phone calls of every account, latest first"), EVENTS_QUERY2).addHelpText("after", EVENTS_NOTE).option("--limit <n>", "events at most, 1\u20131000", upTo1000, 100).option("--json", "print JSON; --json=ref,title keeps only those fields").action(async (words, { json: json5, limit }, command) => {
5771
+ const body = await withStore(env, (store) => listEvents(store, words, limit));
5772
+ const accounts = body.meta?.accounts ?? [];
5773
+ print(json5, body, `${accountsLine(accounts)}
5774
+ ${plainText(body, command)}`);
5775
+ });
5776
+ annotate(events.command("show"), { effects: LOCAL_READ, output: { entity: "event" } }).description("one event: its time, the accounts and calendars that hold it, and its attendees").argument("<ref>", "event:<id>").option("--json", "print JSON; --json=ref,title keeps only those fields").action(async (ref, { json: json5 }) => print(json5, itemAnswer(await withStore(env, (store) => showEvent(store, ref)))));
5777
+ }, "eventsCommand");
5778
+
5779
+ // dist/examples.js
5780
+ var EXAMPLES = {
5781
+ context: ["memo context telegram:'Kai Sample'", "memo context email:kai@example.com --limit 5 --json"],
5782
+ show: [
5783
+ "memo show chat:max/500/-1001 document:413",
5784
+ "memo show note:12 msg:telegram/1/77/42 --json",
5785
+ "memo show contract:2 invoice:4 --json"
5786
+ ],
5787
+ "notes show": ["memo notes show note:12", "memo notes show ~/notes/People/Kai.md --json"],
5788
+ "notes list": [
5789
+ `memo notes list 'about:"telegram:Kai Sample"'`,
5790
+ "memo notes list source:internal text:budget --json",
5791
+ "memo notes list source:internal --limit 50"
5792
+ ],
5793
+ "notes add": [
5794
+ "memo notes add 'Prefers calls in the morning' --subject telegram:'Kai Sample'",
5795
+ "memo notes add --text-file draft.md --title 'Harbour kickoff' --subject project:2 --subject organization:1"
5796
+ ],
5797
+ "notes edit": ["memo notes edit note:12 --revision 2 --text 'Prefers calls after ten'"],
5798
+ "notes remove": ["memo notes remove note:12"],
5799
+ "notes export": ["memo notes export --output-dir ~/notes/memo --format obsidian"],
5800
+ "sources add": [
5801
+ "memo sources add ~/Downloads/runner-notes.pdf --subject project:3 --keep-copy",
5802
+ "memo sources add https://example.org/release-notes --title 'Release notes' --json",
5803
+ "memo sources add - --title 'Call with Kai' --subject person:12"
5804
+ ],
5805
+ "sources show": ["memo sources show document:42", "memo sources show document:42 --bytes 65536 --json"],
5806
+ "sources mentions": ["memo sources mentions document:42 --json"],
5807
+ "sources remove": ["memo sources remove document:42"],
5808
+ "folders add": ["memo folders add ~/notes"],
5809
+ "folders update": ["memo folders update 1 ~/notes"],
5810
+ "folders remove": ["memo folders remove 1", "memo folders remove 1 --allow-delete --yes"],
5811
+ "folders list": ["memo folders list --json"],
5812
+ "config show": ["memo config show", "memo config show --json"],
5813
+ "config migrate": ["memo config migrate"],
5814
+ "config unset": ["memo config unset permissions.notes.edit"],
5815
+ "mentions list": [
5816
+ "memo mentions list kind:phone --source notes",
5817
+ "memo mentions list domain:example.com --source email:personal,email:work --json",
5818
+ "memo mentions list kind:place --source mail"
5819
+ ],
5820
+ "mentions show": ["memo mentions show 12", "memo mentions show 40 --source email:personal --json"],
5821
+ "mentions confirm": ["memo mentions confirm 12 person:7", "memo mentions confirm 40 --source email:personal"],
5822
+ "mentions reject": ["memo mentions reject 12", "memo mentions reject 40 --source email:personal"],
5823
+ "mail review": [
5824
+ "memo mail review --holder mail-triage --source email:work --json",
5825
+ "memo mail review --unanswered 1d --source email:personal",
5826
+ "memo mail review 'date>2h' --source email:me@example.com"
5827
+ ],
5828
+ "messages acknowledge": [
5829
+ "memo messages acknowledge --holder mail-triage --cursor 9123.1762090000000 --source email:work",
5830
+ "memo messages acknowledge --holder mail-triage --cursor 9123.1762090000000 --source email:me@example.com --json",
5831
+ "memo messages acknowledge --holder backfill --cursor 870.1762090000000 --source email:personal"
5832
+ ],
5833
+ "messages reset": [
5834
+ "memo messages reset --holder mail-triage --last 8 --dry-run --source email:me@example.com",
5835
+ "memo messages reset --holder mail-triage --since-time 2026-11-04T00:00:00Z --source email:work --json"
5836
+ ],
5837
+ "mail findings add": [
5838
+ "memo mail findings add findings.json --source email:work",
5839
+ "memo mail findings add findings.json --source email:work --key mail-triage:9123 --dry-run --json",
5840
+ `memo mail findings add '{"schema_version":1,"key":"mail-triage:9123","extractor":"mail-triage/1","findings":[\u2026]}' --source email:me@example.com --json`
5841
+ ],
5842
+ "mail findings show": [
5843
+ "memo mail findings show 41",
5844
+ "memo mail findings show 41 --json",
5845
+ "memo mail findings show 41 --json=id,input.findings"
5846
+ ],
5847
+ "mail findings list": [
5848
+ "memo mail findings list status:proposed",
5849
+ "memo mail findings list --source email:work --json",
5850
+ "memo mail findings list status:reviewed --limit 50 --page 2"
5851
+ ],
5852
+ "messages show": ["memo messages show 'msg:email/me%40example.com/thread/42' --before-n 2 --after-n 2"],
5853
+ "projects actions add": ["memo projects actions add findings.json"],
5854
+ "projects actions list": [
5855
+ "memo projects actions list SYH",
5856
+ "memo projects actions list SYH status:reviewed --brief --json"
5857
+ ],
5858
+ "projects actions confirm": [
5859
+ "memo projects actions confirm SYH 12 budget accountant --fingerprint 3f9a0c --reason 'both are stated plainly'",
5860
+ "memo projects actions confirm SYH key:4"
5861
+ ],
5862
+ "projects actions reject": [
5863
+ "memo projects actions reject SYH 12 --fingerprint 3f9a0c --reason 'not about this project'"
5864
+ ],
5865
+ "projects evidence": [
5866
+ "memo projects evidence SYH --source telegram:1,email:personal --contact 'Alice Example'",
5867
+ "memo projects evidence SYH --source all --context-query 'crane tender' --since-time 180d --json"
5868
+ ],
5869
+ "models list": ["memo models list kind:text --json"],
5870
+ "commands info": ["memo commands info --json"],
5871
+ "models download": ["memo models download e5-small", "memo models download e5-large"],
5872
+ "models set": ["memo models set e5-large"],
5873
+ "models consents show": ["memo models consents show --json"],
5874
+ "models consents set": [
5875
+ "memo models consents set openai --model gpt-4o-mini",
5876
+ "memo models consents set openai --model gpt-4o-mini --yes"
5877
+ ],
5878
+ "models consents revoke": ["memo models consents revoke"],
5879
+ "models add": [
5880
+ `memo models add e5-base --payload '{"repository":"Xenova/multilingual-e5-base","onnx":"onnx/model_quantized.onnx","dims":768,"pooling":"mean","prefix":{"query":"query: ","passage":"passage: "}}'`
5881
+ ],
5882
+ "notes links show": ["memo notes links show telegram:101 --json"],
5883
+ "notes links add": ["memo notes links add telegram:101 ~/notes/People/Kai.md"],
5884
+ "notes links clear": ["memo notes links clear telegram:101"],
5885
+ "tags add": ["memo tags add follow-up --note note:12", "memo tags add harbour --project HRB"],
5886
+ "tags remove": ["memo tags remove follow-up --note note:12"],
5887
+ "tags list": ["memo tags list tag:follow-up", "memo tags list --source email:me@example.com --json"],
5888
+ "tasks link": ["memo tasks link HRB-2 HRB-1 --kind blocked-by"],
5889
+ "tasks unlink": ["memo tasks unlink HRB-2 HRB-1 --kind blocked-by"],
5890
+ "tasks links": ["memo tasks links HRB-2 --json"],
5891
+ "tasks status": ["memo tasks status HRB-2"],
5892
+ "tasks add": [
5893
+ "memo tasks add 'msg:telegram/1/77/42' --account telegram:1 --type request",
5894
+ "memo tasks add --project HRB --title 'Draft the budget' --type feature",
5895
+ "memo tasks add --project INBOX --title 'Reply on Avito to Ivan' --external-id avito:4512345678 --source-url https://www.avito.ru/4512345678"
5896
+ ],
5897
+ "tasks list": [
5898
+ "memo tasks list state:done --source telegram:1",
5899
+ "memo tasks list project:HRB ready:true --json",
5900
+ 'memo tasks list project:* priority:[high TO critical] "invoice" --sort due --json'
5901
+ ],
5902
+ "tasks counts": ["memo tasks counts project:* --json"],
5903
+ "tasks history": ["memo tasks history HRB-1 --json"],
5904
+ "tasks comments add": ["memo tasks comments add HRB-1 Booked the hall for Friday"],
5905
+ "tasks comments list": ["memo tasks comments list HRB-1 --json"],
5906
+ "tasks move": ["memo tasks move HRB-3 --status in_progress --before HRB-1 --revision 4"],
5907
+ "people list": ["memo people list --json"],
5908
+ "people add": ["memo people add Lena Example"],
5909
+ "tasks agenda": [
5910
+ "memo tasks agenda --source telegram:1",
5911
+ "memo tasks agenda --source max:1 --waiting 3d --within 90d --json"
5912
+ ],
5913
+ "tasks claim": ["memo tasks claim HRB-1 --lease 2h --account telegram:1"],
5914
+ "tasks release": ["memo tasks release HRB-1 --account telegram:1"],
5915
+ "tasks show": ["memo tasks show 1a2b3c4d --source telegram:1 --json"],
5916
+ "stats tasks show": [
5917
+ "memo stats tasks show --source telegram:1",
5918
+ "memo stats tasks show --type question --source telegram:1 --json"
5919
+ ],
5920
+ "tasks close": ["memo tasks close 7 --account telegram:1 --as dismissed --reason 'answered by phone'"],
5921
+ "tasks update": [
5922
+ "memo tasks update HRB-1 --title 'Send the budget' --priority high --account telegram:1",
5923
+ "memo tasks update 1a2b3c4d --due 2d --title 'Send the revised budget' --account telegram:1"
5924
+ ],
5925
+ "tasks assign": ["memo tasks assign 7 person:3", "memo tasks assign HRB-1 person:3 --clear --revision 4"],
5926
+ "organizations add": [
5927
+ "memo organizations add 'Harbour Studio' --kind company --scope work",
5928
+ "memo organizations add 'Harbour Family' --kind family"
5929
+ ],
5930
+ "organizations rename": [
5931
+ "memo organizations rename organization:1 'Harbour Studio Ltd'",
5932
+ "memo organizations rename 1 'Harbour Studio Ltd' --json"
5933
+ ],
5934
+ "organizations show": ["memo organizations show organization:1"],
5935
+ "organizations list": ["memo organizations list name:*harbour*", "memo organizations list --json"],
5936
+ "organizations context": ["memo organizations context organization:1"],
5937
+ "projects meetings list": ["memo projects meetings list HRB --source zoom:me@example.com"],
5938
+ "projects meetings link": ["memo projects meetings link HRB 31 --account zoom:me@example.com"],
5939
+ "projects add": ["memo projects add 'Harbour' --key HRB --type client --organization 1"],
5940
+ "projects list": ["memo projects list"],
5941
+ "projects context": ["memo projects context HRB", "memo projects context project:2 --json"],
5942
+ "projects status": ["memo projects status HRB --context-query 1C", "memo projects status HRB --compact"],
5943
+ "projects annotations show": ["memo projects annotations show HRB --max-read-bytes 1048576 --json"],
5944
+ "projects annotations export": ["memo projects annotations export HRB --output harbour-annotations.json --json"],
5945
+ "projects annotations check": [
5946
+ "memo projects annotations check harbour-annotations.json --target-store ./trial/wirecat.db --project project:2 --bindings bindings.json --json"
5947
+ ],
5948
+ "projects annotations restore": [
5949
+ "memo projects annotations restore harbour-annotations.json --target-store ./trial/wirecat.db --project project:2 --bindings bindings.json --fingerprint aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa --json"
5950
+ ],
5951
+ "projects key-documents add": ["memo projects key-documents add HRB note:12 --reason 'the brief'"],
5952
+ "projects key-documents remove": ["memo projects key-documents remove HRB note:12"],
5953
+ "projects key-documents list": ["memo projects key-documents list HRB"],
5954
+ "relationships update": [
5955
+ "memo relationships update 12 --purpose sales",
5956
+ "memo relationships update 12 --engagement low",
5957
+ "memo relationships update 12 --role approver --role-note 'signs the acts'"
5958
+ ],
5959
+ "relationships add": [
5960
+ "memo relationships add person:3 organization:1 --role employee",
5961
+ "memo relationships add person:3 project:1 --role client-contact --engagement low",
5962
+ "memo relationships add project:2 organization:1 --kind related-to --reason 'same client'"
5963
+ ],
5964
+ "relationships list": ['memo relationships list about:"person:3"', "memo relationships list confirmed:false"],
5965
+ "relationships remove": ["memo relationships remove 4"],
5966
+ "relationships confirm": ["memo relationships confirm 4"],
5967
+ "relationships scan": [
5968
+ "memo relationships scan HRB --source all",
5969
+ "memo relationships scan HRB --source telegram:1,email:personal --yes",
5970
+ "memo relationships scan --source email:me@example.com"
5971
+ ],
5972
+ "reminders add": [
5973
+ "memo reminders add 7 --at-time 2026-10-20T09:00:00+02:00 --timezone Europe/Madrid --account telegram:1"
5974
+ ],
5975
+ "reminders list": ["memo reminders list --source telegram:1"],
5976
+ "reminders claim": ["memo reminders claim --account telegram:1 --json"],
5977
+ "reminders acknowledge": ["memo reminders acknowledge 3 3:1 --account telegram:1"],
5978
+ "reminders cancel": ["memo reminders cancel 3 --account telegram:1"],
5979
+ "reminders update": ["memo reminders update 3 --revision 1 --at-time 2026-10-21T09:00:00+02:00 --account telegram:1"],
5980
+ "vocabulary list": ["memo vocabulary list kind:tag"],
5981
+ "vocabulary show": ["memo vocabulary show Harbour --kind project"],
5982
+ "vocabulary check": ["memo vocabulary check on-boarding --kind tag", "memo vocabulary check --limit 20 --json"],
5983
+ "vocabulary inspect": [
5984
+ "memo vocabulary inspect tag:1 tag:2 --fingerprint v1:all:all:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa --limit 5 --json"
5985
+ ],
5986
+ "vocabulary create": ["memo vocabulary create onboarding --kind tag --fingerprint 3f9a"],
5987
+ "vocabulary aliases add": ["memo vocabulary aliases add project:2 Harbor --fingerprint 3f9a"],
5988
+ "vocabulary aliases remove": ["memo vocabulary aliases remove project:2 5 --fingerprint 3f9a"],
5989
+ "memories add": ["memo memories add 'Kai prefers Friday meetings' --kind preference --scope work --evidence note:12"],
5990
+ "memories list": ["memo memories list status:proposed kind:preference"],
5991
+ "memories show": ["memo memories show memory:4 --sources"],
5992
+ "memories update": ["memo memories update memory:4 --project SYH", "memo memories update memory:4 --project none"],
5993
+ "memories confirm": ["memo memories confirm memory:4"],
5994
+ "memories invalidate": ["memo memories invalidate memory:4"],
5995
+ "memories remove": ["memo memories remove memory:4"],
5996
+ "memories check": ["memo memories check", "memo memories check --limit 2000 --json"],
5997
+ "memories challenge": [
5998
+ "memo memories challenge memory:4 --evidence note:15 --reason 'the later note moves delivery to the 30th'",
5999
+ "memo memories challenge memory:4 --evidence note:15 --reason 'moved' --suggest 'Delivery is on the 30th' --json"
6000
+ ],
6001
+ "facts add": [
6002
+ "memo facts add 'We moved the launch to spring' --kind decision --evidence 'msg:telegram/1/77/42' --project HRB"
6003
+ ],
6004
+ "facts list": ["memo facts list kind:decision project:HRB"],
6005
+ "orders list": ["memo orders list status:shipped", `memo orders list number:A-1042 'supplier:"Shop One"' --json`],
6006
+ "orders show": ["memo orders show order:3"],
6007
+ "pipelines list": ["memo pipelines list", "memo pipelines list kind:lead --json"],
6008
+ "pipelines show": ["memo pipelines show pipeline:2", "memo pipelines show Sales --since-time 2026-09-01"],
6009
+ "pipelines add": [
6010
+ "memo pipelines add Sales Call Quote Contract won lost",
6011
+ "memo pipelines add Inbound new called --kind lead"
6012
+ ],
6013
+ "deals list": ["memo deals list status:open project:HRB", "memo deals list stage:Quote --json"],
6014
+ "deals show": ["memo deals show deal:5"],
6015
+ "deals add": [
6016
+ "memo deals add 'Site for the clinic' Call 500000.00 RUB --organization organization:3 --project HRB",
6017
+ "memo deals add 'Support contract' Sales/Quote --person person:7 --role decision-maker --due 2026-12-01"
6018
+ ],
6019
+ "deals move": ["memo deals move deal:5 Quote"],
6020
+ "deals close": ["memo deals close deal:5 won", "memo deals close deal:6 lost --reason 'chose a cheaper studio'"],
6021
+ "leads list": ["memo leads list status:open"],
6022
+ "leads show": ["memo leads show lead:3"],
6023
+ "leads add": ["memo leads add 'Call from the site'", "memo leads add 'Clinic in Kazan' contacted --person person:9"],
6024
+ "leads move": ["memo leads move lead:3 qualified"],
6025
+ "leads close": ["memo leads close lead:3 lost --reason 'no budget'"],
6026
+ "orders add": [
6027
+ "memo orders add 'Shop One' 2499.00 RUB --kind personal --title A-1042",
6028
+ "memo orders add 'Office Supply' --kind client --organization organization:3"
6029
+ ],
6030
+ "events list": [
6031
+ "memo events list event_type:call",
6032
+ "memo events list account:phone:pixel 'starts_at:[2026-11-02 TO 2026-11-07}' --json"
6033
+ ],
6034
+ "events show": ["memo events show event:42"],
6035
+ "invoices list": ["memo invoices list project:HRB open:true", "memo invoices list review:proposed --json"],
6036
+ "invoices show": ["memo invoices show invoice:4"],
6037
+ "invoices add": [
6038
+ "memo invoices add 50000.00 RUB Bed rental, November --kind receivable --organization organization:3 --project HRB",
6039
+ "memo invoices add 1200.50 EUR Hosting --kind payable --person person:7 --title 2026-118 --due 2026-11-15"
6040
+ ],
6041
+ "payments list": ["memo payments list project:HRB direction:in", "memo payments list review:proposed --json"],
6042
+ "payments show": ["memo payments show payment:9"],
6043
+ "payments add": [
6044
+ "memo payments add 25000.00 RUB --kind in --organization organization:3 --occurred-time 2026-11-01 --project HRB",
6045
+ "memo payments add 5000.00 RUB --kind out --organization organization:3 --refund-of payment:9"
6046
+ ],
6047
+ "records import": [
6048
+ "memo records import payments.jsonl --account rentals --dry-run",
6049
+ "memo records import envelope.json --account rentals --json",
6050
+ "memo records import contacts.vcf --account phone:pixel --region gb"
6051
+ ],
6052
+ "records check": ["memo records check - --account rentals"],
6053
+ "records list": [
6054
+ "memo records list waiting:true --account sheet:rentals",
6055
+ "memo records list type:organization --account sheet:rentals --json",
6056
+ `memo records list 'entity:"organization:3"' deleted:false --account sheet:rentals`
6057
+ ],
6058
+ "records link": ["memo records link organization:client organization:3 --account sheet:rentals"],
6059
+ "payments allocate": ["memo payments allocate payment:9 invoice:4 25000.00"],
6060
+ "products list": ["memo products list kind:service", "memo products list archived:true --json"],
6061
+ "products show": ["memo products show product:4"],
6062
+ "products add": [
6063
+ "memo products add 'Bike rental, month' 10000.00 RUB --kind service --unit month",
6064
+ "memo products add 'Helmet' 2500.00 RUB --sku H-12 --supplier organization:3"
6065
+ ],
6066
+ "products update": [
6067
+ "memo products update product:4 --amount 12000.00 --currency RUB",
6068
+ "memo products update product:4 --archived on"
6069
+ ],
6070
+ "orders lines add": [
6071
+ "memo orders lines add order:3 12 --product product:4",
6072
+ "memo orders lines add order:3 1 Rental, first week --product product:4 --amount 2000.00",
6073
+ "memo orders lines add order:3 2 Helmet --amount 2500.00"
6074
+ ],
6075
+ "orders lines remove": ["memo orders lines remove order:3 2"],
6076
+ "invoices lines add": [
6077
+ "memo invoices lines add invoice:4 1 --product product:4",
6078
+ "memo invoices lines add invoice:4 2 Delivery --amount 1500.00"
6079
+ ],
6080
+ "invoices lines remove": ["memo invoices lines remove invoice:4 2"],
6081
+ "contracts list": ["memo contracts list status:active", "memo contracts list expires:2026-12-31 --json"],
6082
+ "contracts show": ["memo contracts show contract:2"],
6083
+ "contracts add": [
6084
+ "memo contracts add 'Rental agreement' --kind service --start-date 2026-11-01 --end-date 2027-10-31 --auto-renew on --notice-days 30 --amount 120000.00 --currency RUB",
6085
+ "memo contracts add 'NDA with the studio' --kind nda --project HRB"
6086
+ ],
6087
+ "contracts update": [
6088
+ "memo contracts update contract:2 --status terminated",
6089
+ "memo contracts update contract:2 --end-date 2027-12-31"
6090
+ ],
6091
+ "subscriptions list": [
6092
+ "memo subscriptions list status:active direction:payable",
6093
+ "memo subscriptions list renews:2026-12-01 --json"
6094
+ ],
6095
+ "subscriptions show": ["memo subscriptions show subscription:5"],
6096
+ "subscriptions add": [
6097
+ "memo subscriptions add Hosting 990.00 RUB --kind payable --supplier organization:3 --period 1-month --start-date 2026-11-01",
6098
+ "memo subscriptions add 'Bike rental' 10000.00 RUB --kind receivable --customer organization:3 --period 1-month --trial-end-date 2026-11-14 --status trial"
6099
+ ],
6100
+ "subscriptions update": [
6101
+ "memo subscriptions update subscription:5 --status paused",
6102
+ "memo subscriptions update subscription:5 --period 1-year"
6103
+ ],
6104
+ "subscriptions cancel": ["memo subscriptions cancel subscription:5 --occurred-time 2026-11-30"],
6105
+ "subscriptions prices add": ["memo subscriptions prices add subscription:5 1190.00 RUB --start-date 2027-01-01"],
6106
+ "wallets list": ["memo wallets list", "memo wallets list kind:card --json"],
6107
+ "wallets show": ["memo wallets show wallet:2", "memo wallets show wallet:2 --until-time 2026-10-31"],
6108
+ "wallets add": ["memo wallets add 'Main card' RUB --kind card --institution 'Bank One'"],
6109
+ "wallets update": ["memo wallets update wallet:2 --opening-balance 15000.00 --opening-date 2026-01-01"],
6110
+ "transactions list": [
6111
+ `memo transactions list 'wallet:"wallet:2"' status:pending`,
6112
+ "memo transactions list uncategorized:true --json"
6113
+ ],
6114
+ "transactions show": ["memo transactions show transaction:12"],
6115
+ "transactions add": [
6116
+ "memo transactions add wallet:2 500.00 --kind out --occurred-time 2026-11-14 --description 'Coffee shop'",
6117
+ "memo transactions add wallet:2 10000.00 --kind in --payment payment:9"
5994
6118
  ],
5995
- defaults: [OPEN_WORK]
5996
- };
5997
- var ACCOUNT_FIELDS = /* @__PURE__ */ new Set(["state", "source"]);
5998
- var taskItem = /* @__PURE__ */ __name((task, extra = {}) => {
5999
- const { held, readiness } = extra;
6000
- return {
6001
- ref: task.ref,
6002
- id: task.id,
6003
- key: task.key,
6004
- title: task.title,
6005
- description: task.description,
6006
- kind: task.type,
6007
- state: task.status,
6008
- project: task.project,
6009
- project_key: task.projectKey,
6010
- project_name: task.projectName,
6011
- ...held ? {
6012
- origin: held.origin,
6013
- account: held.account,
6014
- chat: held.sourceKind === "note" ? null : formatReference({
6015
- type: "chat",
6016
- provider: held.account.split(":")[0] ?? "",
6017
- account: held.account.split(":").slice(1).join(":"),
6018
- chat: held.group
6019
- })
6020
- } : {},
6021
- source_message: task.source,
6022
- external_id: task.externalId,
6023
- source_url: task.sourceUrl,
6024
- priority: task.priority,
6025
- priority_label: task.priorityLabel,
6026
- agent: task.agent,
6027
- revision: task.revision,
6028
- parent: task.parent,
6029
- due_at: task.dueAt,
6030
- closed_at: task.closedAt,
6031
- reason: task.reason,
6032
- category: task.category,
6033
- claimed_by: task.claimedBy,
6034
- claimed_until: task.claimedUntil,
6035
- ...readiness ? { assignments: task.assignments, ready: readiness.ready, blocked_by: readiness.blockers } : {},
6036
- created_by: task.createdBy,
6037
- created_at: task.createdAt,
6038
- updated_at: task.updatedAt
6039
- };
6040
- }, "taskItem");
6041
- var accountTasks = /* @__PURE__ */ __name(async (store, keys, options) => {
6042
- const pages = await Promise.all(keys.map((key) => store.knowledge.taskIds(key, options)));
6043
- const items = [];
6044
- for (const id of pages.flatMap((page) => page.items)) {
6045
- const held = await store.tasks.get(id);
6046
- if (held)
6047
- items.push(taskItem(await store.tasks.details(id), { held }));
6048
- }
6049
- items.sort((a, b) => a.created_at.localeCompare(b.created_at));
6050
- return listAnswer("tasks list", {
6051
- items: items.slice(0, options.limit),
6052
- limit: options.limit,
6053
- has_more: items.length > options.limit || pages.some((page) => page.hasMore),
6054
- meta: { accounts: keys.map(accountName) }
6055
- });
6056
- }, "accountTasks");
6057
- var accountTerms = /* @__PURE__ */ __name((query) => {
6058
- const leaves2 = query ? walkQuery(query.root) : [];
6059
- const other = leaves2.find(({ field: field2 }) => !ACCOUNT_FIELDS.has(field2));
6060
- if (other)
6061
- throw new CliError("validation_error", `${other.field}: narrows project tasks: add project:<key>, or project:* for every project`);
6062
- const states = new Set(leaves2.filter(({ field: field2 }) => field2 === "state").map(({ value }) => value.toLowerCase()));
6063
- if ((states.has("todo") || states.has("in_progress")) && !states.has("open"))
6064
- throw new CliError("validation_error", "todo and in_progress are steps of a project task: add project:");
6065
- const closed = [...states].filter(isClosedState);
6066
- if (closed.length > 1 || closed.length === 1 && states.size > 1)
6067
- throw new CliError("validation_error", "an account's tasks are listed in one state: state:open, done or dismissed");
6068
- const sources = leaves2.filter(({ field: field2 }) => field2 === "source").map(({ value }) => value);
6069
- if (sources.length > 1)
6070
- throw new CliError("validation_error", "source: only once here");
6071
- return { state: closed[0] ?? "open", source: sources[0] };
6072
- }, "accountTerms");
6073
- var resolveProjects = /* @__PURE__ */ __name(async (store, query) => {
6074
- for (const node of query ? walkQuery(query.root) : [])
6075
- if (node.field === "project" && node.operator !== "wildcard" && node.operator !== "range")
6076
- node.value = await canonicalProject(store, node.value);
6077
- return query;
6078
- }, "resolveProjects");
6079
- var offsetOf = /* @__PURE__ */ __name((cursor, key) => {
6080
- if (cursor === void 0)
6081
- return 0;
6082
- let read;
6083
- try {
6084
- read = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
6085
- } catch {
6086
- read = void 0;
6087
- }
6088
- if (read?.version !== 1 || read.key !== key || !Number.isSafeInteger(read.offset) || read.offset < 0)
6089
- throw new CliError("validation_error", "the cursor belongs to another query or order; list again without it");
6090
- return read.offset;
6091
- }, "offsetOf");
6092
- var projectTasks = /* @__PURE__ */ __name(async (store, typed, query, options) => {
6093
- const { limit, sort = "priority", desc = false } = options;
6094
- const key = createHash2("sha256").update(JSON.stringify([typed, sort, desc])).digest("base64url");
6095
- const offset = offsetOf(options.cursor, key);
6096
- const page = await store.tasks.board(query, { limit, offset, sort, descending: desc, zone: timezoneOf() });
6097
- const items = [];
6098
- for (const task of page.items)
6099
- items.push(taskItem(task, { readiness: await store.tasks.taskReadiness(task.id) }));
6100
- const next = offset + items.length;
6101
- const more = next < page.total;
6102
- return listAnswer("tasks list", {
6103
- items,
6104
- limit,
6105
- has_more: more,
6106
- paging: "cursor",
6107
- ...more ? {
6108
- cursor: Buffer.from(JSON.stringify({ version: 1, key, offset: next })).toString("base64url")
6109
- } : {},
6110
- total: { value: page.total, relation: "eq" },
6111
- args: { query: typed, ...options.sort ? { sort } : {}, ...desc ? { desc: true } : {} }
6112
- });
6113
- }, "projectTasks");
6114
- var listTasks = /* @__PURE__ */ __name(async (store, words, options, accounts) => {
6115
- const flag = Array.isArray(words) ? words.findIndex((word) => /^--[a-z]+$/.test(word)) : -1;
6116
- if (flag >= 0) {
6117
- const field2 = words[flag]?.slice(2) ?? "";
6118
- const value = words[flag + 1] ?? "<value>";
6119
- const fields = TASKS_LIST_QUERY.fields.map(({ name: name2 }) => name2);
6120
- throw new CliError("validation_error", fields.includes(field2) ? `tasks list takes ${field2} as a query word: memo tasks list ${field2}:${value}` : `tasks list has no --${field2}; its query words are ${fields.map((name2) => `${name2}:`).join(", ")}`);
6121
- }
6122
- const typed = typedQuery(words);
6123
- const query = listQuery(typed, TASKS_LIST_QUERY, "tasks list", { defaultField: "text" });
6124
- const leaves2 = query ? walkQuery(query.root) : [];
6125
- if (leaves2.some(({ field: field2 }) => field2 === "project")) {
6126
- if (leaves2.some(({ field: field2 }) => field2 === "source"))
6127
- throw new CliError("validation_error", "source: narrows an account's tasks, not a project's");
6128
- return projectTasks(store, typed, await resolveProjects(store, query), options);
6119
+ "transactions update": [
6120
+ "memo transactions update transaction:12 --status booked",
6121
+ "memo transactions update transaction:12 --payment payment:9"
6122
+ ],
6123
+ "transactions splits add": ["memo transactions splits add transaction:12 300.00 Rent --project HRB"],
6124
+ "transactions splits remove": ["memo transactions splits remove transaction:12 1"],
6125
+ "records show": [
6126
+ "memo records show custom:quote:Q-7 --account example-erp:demo",
6127
+ "memo records show organization:client --account sheet:rentals --json"
6128
+ ],
6129
+ "records delete": ["memo records delete payment:sheet-12 --account sheet:rentals"],
6130
+ "records match": ["memo records match --account sheet:rentals"],
6131
+ "payments unlink": ["memo payments unlink payment:9 invoice:4"],
6132
+ "facts show": ["memo facts show fact:4 --sources"],
6133
+ "facts confirm": ["memo facts confirm fact:4"],
6134
+ "facts revoke": ["memo facts revoke fact:4"],
6135
+ "facts reverse": ["memo facts reverse fact:4"],
6136
+ "facts reject": ["memo facts reject fact:4"],
6137
+ "facts remove": ["memo facts remove fact:4"],
6138
+ "facts challenge": ["memo facts challenge fact:4 --evidence 'msg:telegram/1/77/50' --reason 'reversed in chat'"],
6139
+ "actions add": [
6140
+ `memo actions add reply --reason 'Kai asked twice' --account telegram:1 --target 'msg:telegram/1/77/42' --payload '{"text":"Friday works"}'`
6141
+ ],
6142
+ "meetings actions add": ["memo meetings actions add findings.json --account zoom:me@example.com"],
6143
+ "meetings passage": [
6144
+ "memo meetings passage meeting:1/1/1/0 --source zoom:me@example.com --before-n 1 --after-n 1 --json"
6145
+ ],
6146
+ "meetings prepare": [
6147
+ "memo meetings prepare example-series --source zoom:me@example.com --json",
6148
+ "memo meetings prepare example-series --source zoom:me@example.com --context-source tg.personal --context-query Harbour --json"
6149
+ ],
6150
+ "search meetings": [
6151
+ "memo search meetings budget --source zoom:me@example.com --project Harbour --since-time 2026-10-01 --limit 10 --json"
6152
+ ],
6153
+ "meetings actions list": ["memo meetings actions list status:proposed --source zoom:me@example.com"],
6154
+ "meetings actions show": ["memo meetings actions show 5 --source zoom:me@example.com"],
6155
+ "meetings actions confirm": [
6156
+ "memo meetings actions confirm 5 decision --fingerprint aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa --account zoom:me@example.com"
6157
+ ],
6158
+ "meetings actions reject": [
6159
+ "memo meetings actions reject 5 --fingerprint aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa --account zoom:me@example.com"
6160
+ ],
6161
+ "actions list": ["memo actions list status:proposed"],
6162
+ "actions show": ["memo actions show 5"],
6163
+ "actions confirm": ["memo actions confirm 5"],
6164
+ "actions reject": ["memo actions reject 5"],
6165
+ "review list": [
6166
+ "memo review list project:HRB",
6167
+ "memo review list kind:relationship,fact by:agent",
6168
+ "memo review list via:chat:max/1/-100"
6169
+ ],
6170
+ "review show": ["memo review show 17079", "memo review show 17080 --brief --json"],
6171
+ "review confirm": [
6172
+ "memo review confirm 17078-17081",
6173
+ "memo review confirm 17080/f1,17080/f3",
6174
+ "memo review confirm close:Q-15"
6175
+ ],
6176
+ "review reject": [
6177
+ 'memo review reject 17081 --reason "not this project"',
6178
+ "memo review reject close:Q-15 --reason 'still open'"
6179
+ ],
6180
+ "review agenda": ["memo review agenda --compact", "memo review agenda --since-time 3d --json"],
6181
+ "review status": ["memo review status", "memo review status --since-time 7d --json"],
6182
+ "schedule command": [
6183
+ "memo schedule command digest --at 08:30 -- review status",
6184
+ "memo schedule command mail-sync --every 1h -- sync --only mail"
6185
+ ],
6186
+ "schedule agent": [
6187
+ "memo schedule agent triage claude 'read memo review status and list what needs me today' --at 08:45 --max-cost 1",
6188
+ "memo schedule agent mail-triage claude 'Run memo skill show mail-triage and follow it.' --every 5m --on-new-mail mail-triage --mail-wait 1h --mail-batch 20 --max-cost 1 --max-cost-per-day 5",
6189
+ "memo schedule agent weekly claude 'read memo review status and sum up the week' --at 09:00 --model opus"
6190
+ ],
6191
+ "schedule list": ["memo schedule list", "memo schedule list kind:agent --json"],
6192
+ "schedule run": ["memo schedule run digest"],
6193
+ "digest backfill": [
6194
+ "memo digest backfill --from 2026-11-01 --to 2026-11-07",
6195
+ "memo digest backfill --from 2026-11-01 --to 2026-11-07 --yes --json"
6196
+ ],
6197
+ "schedule remove": ["memo schedule remove digest"],
6198
+ "schedule exec": ["memo schedule exec ~/.local/state/cli-memo/schedules/digest/schedule.json"],
6199
+ "templates list": ["memo templates list", "memo templates list channel:telegram source:stored --json"],
6200
+ "templates show": [
6201
+ "memo templates show report/morning-digest --channel telegram",
6202
+ "memo templates show report/weekly-gaps --channel email --locale en --json"
6203
+ ],
6204
+ "templates edit": [
6205
+ "memo templates edit report/morning-digest --channel telegram",
6206
+ "memo templates edit report/morning-digest --channel text --file digest.text.liquid",
6207
+ "memo templates edit report/morning-digest --channel telegram --undo"
6208
+ ],
6209
+ "templates preview": [
6210
+ "memo templates preview report/morning-digest --channel telegram",
6211
+ "memo templates preview report/morning-digest --channel email --locale en --file draft.liquid"
6212
+ ],
6213
+ "templates reset": ["memo templates reset report/morning-digest --channel telegram"],
6214
+ "agents list": ["memo agents list agent:claude-code --limit 20"],
6215
+ "mcp serve": ["memo mcp serve", "memo mcp serve --http --public-url https://laptop.example.ts.net"],
6216
+ "mcp revoke": ["memo mcp revoke --json"],
6217
+ "mcp server start": ["memo mcp server start --public-url https://laptop.example.ts.net"],
6218
+ "mcp server status": ["memo mcp server status --json"],
6219
+ "mcp server stop": ["memo mcp server stop"],
6220
+ "search all": [
6221
+ "memo search all 'budget harbour'",
6222
+ "memo search all 'content:invoice' --only mail,notes --json",
6223
+ "memo search all 'Rental agreement' --only business"
6224
+ ],
6225
+ "search messages": ["memo search messages 'friday meeting has:voice'"],
6226
+ "search mail": ["memo search mail 'mailbox:important budget' --source email:me@example.com"],
6227
+ "search notes": ["memo search notes budget notes:2", "memo search notes kickoff --project HRB --json"],
6228
+ "search memories": ["memo search memories friday meetings scope:work"],
6229
+ "search people": ["memo search people ana", "memo search people Ana Example --source telegram --json"],
6230
+ "answers create": [
6231
+ "memo answers create 'what did Kai say about the budget?' --all",
6232
+ "memo answers create 'when is the launch?' --source telegram:1"
6233
+ ],
6234
+ sync: ["memo sync", "memo sync --only mail", "memo sync --only notes --folder ~/notes --no-embed"],
6235
+ "jobs start": ["memo jobs start --every 10m"],
6236
+ "jobs stop": ["memo jobs stop"],
6237
+ "jobs status": ["memo jobs status --json"],
6238
+ doctor: ["memo doctor", "memo doctor --json"],
6239
+ "runs list": ["memo runs list status:failed --limit 10", "memo runs list --page 2 --json"],
6240
+ "runs show": ["memo runs show 20261025T090000Z-mail-sync-a81f2c --json"],
6241
+ "runs path": ["memo runs path 20261025T090000Z-mail-sync-a81f2c"],
6242
+ "store info": ["memo store info", "memo store info --json"],
6243
+ "store recover": ["memo store recover", "memo store recover --json"],
6244
+ "store recover run": ["memo store recover run"],
6245
+ upgrade: ["memo upgrade --check"],
6246
+ commands: ["memo commands --json", "memo commands tags add --json"],
6247
+ "skill show": ["memo skill show"],
6248
+ "skill install": ["memo skill install --for all"],
6249
+ "skill status": ["memo skill status", "memo skill status --json"],
6250
+ guide: ["memo guide", "memo guide person-brief --json"],
6251
+ help: ["memo help options", "memo help tasks agenda"]
6252
+ };
6253
+ var leaves = /* @__PURE__ */ __name((command, path = []) => command.commands.flatMap((child) => {
6254
+ const at = [...path, child.name()];
6255
+ return child.commands.length ? leaves(child, at) : [[at.join(" "), child]];
6256
+ }), "leaves");
6257
+ var addExamples = /* @__PURE__ */ __name((program) => {
6258
+ for (const [path, command] of leaves(program)) {
6259
+ const examples = EXAMPLES[path];
6260
+ if (!examples || JSON.stringify(metaOf(command).examples) === JSON.stringify(examples))
6261
+ continue;
6262
+ if (hasSharedContract(command))
6263
+ overrideSharedContract(command, "Memo examples use explicit stored-account scope rather than messenger profile words.", (one2) => {
6264
+ annotate(one2, { examples });
6265
+ });
6266
+ else
6267
+ annotate(command, { examples });
6129
6268
  }
6130
- if (options.cursor !== void 0 || options.sort !== void 0 || options.desc)
6131
- throw new CliError("validation_error", "--cursor, --sort and --desc page and order project tasks: add project:");
6132
- const { state, source } = accountTerms(query);
6133
- return accountTasks(store, await accounts(), {
6134
- state,
6135
- limit: options.limit,
6136
- ...source === void 0 ? {} : { sources: [source] }
6137
- });
6138
- }, "listTasks");
6139
- var countTasks = /* @__PURE__ */ __name(async (store, words) => {
6140
- const query = listQuery(typedQuery(words), TASKS_LIST_QUERY, "tasks counts", { defaultField: "text" });
6141
- if (query && walkQuery(query.root).some(({ field: field2 }) => field2 === "source"))
6142
- throw new CliError("validation_error", "source: narrows an account's tasks; tasks counts counts project tasks");
6143
- return store.tasks.boardCounts(await resolveProjects(store, query), { zone: timezoneOf() });
6144
- }, "countTasks");
6269
+ }, "addExamples");
6145
6270
 
6146
- // dist/digest/tasks.js
6147
- var DIGEST_SCHEDULE = "morning-digest";
6148
- var DAY5 = 864e5;
6149
- var previousDigestAt = /* @__PURE__ */ __name((env, now) => {
6150
- const state = resolvePaths({ appName: APP.appName, prefix: APP.envPrefix, env }).state;
6151
- const digest = schedulesOf(state).find((one2) => one2.name === DIGEST_SCHEDULE || one2.kind === "agent" && one2.argv.join(" ").includes(DIGEST_SCHEDULE));
6152
- const last = digest?.last;
6153
- if (!last)
6154
- return new Date(now - DAY5).toISOString();
6155
- return last.exitCode === 0 ? last.at : last.failingSince ?? last.at;
6156
- }, "previousDigestAt");
6157
- var dayOf = /* @__PURE__ */ __name((zone) => {
6158
- const format = new Intl.DateTimeFormat("en-CA", { timeZone: zone, year: "numeric", month: "2-digit", day: "2-digit" });
6159
- return (ms) => format.format(ms);
6160
- }, "dayOf");
6161
- var PAGE3 = 500;
6162
- var openAccountTasks = /* @__PURE__ */ __name(async (store, key) => {
6163
- const ids = [];
6164
- for (let offset = 0; ; offset += PAGE3) {
6165
- const page = await store.knowledge.taskIds(key, { state: "open", limit: PAGE3, offset });
6166
- for (const id of page.items)
6167
- ids.push(id);
6168
- if (!page.hasMore)
6169
- break;
6170
- }
6171
- return Promise.all(ids.map((id) => store.tasks.details(id)));
6172
- }, "openAccountTasks");
6173
- var openProjectTasks = /* @__PURE__ */ __name(async (store, zone) => {
6174
- const query = listQuery(typedQuery("project:* state:(open OR todo OR in_progress)"), TASKS_LIST_QUERY, "review agenda", {
6175
- defaultField: "text"
6176
- });
6177
- const items = [];
6178
- for (let offset = 0; ; offset += PAGE3) {
6179
- const page = await store.tasks.board(query, { limit: PAGE3, offset, sort: "priority", descending: false, zone });
6180
- for (const task of page.items)
6181
- items.push(task);
6182
- if (page.items.length < PAGE3 || items.length >= page.total)
6183
- break;
6184
- }
6185
- return items;
6186
- }, "openProjectTasks");
6187
- var personName = /* @__PURE__ */ __name(async (store, ref) => {
6188
- if (!ref?.startsWith("person:"))
6189
- return null;
6190
- return (await store.people.personByUid(ref.slice("person:".length)))?.name ?? null;
6191
- }, "personName");
6192
- var gatherTasks = /* @__PURE__ */ __name(async (store, keys, { now, newSince }) => {
6193
- const zone = timezoneOf();
6194
- const day2 = dayOf(zone);
6195
- const today2 = day2(now);
6196
- const tomorrow = day2(now + DAY5);
6197
- const since = Date.parse(newSince);
6198
- const seen = /* @__PURE__ */ new Map();
6199
- let accounts = 0;
6200
- for (const key of keys) {
6201
- const held = await openAccountTasks(store, key);
6202
- if (held.length)
6203
- accounts++;
6204
- for (const task of held)
6205
- seen.set(task.id, task);
6271
+ // dist/memories/memories.js
6272
+ var addMemory = /* @__PURE__ */ __name(async (store, memory, by) => store.memories.add({
6273
+ kind: memory.kind,
6274
+ body: memory.text,
6275
+ scope: memory.scope,
6276
+ evidence: memory.evidence,
6277
+ author: by,
6278
+ status: by === "owner" ? "confirmed" : "proposed",
6279
+ ...memory.subject === void 0 ? {} : { subject: memory.subject },
6280
+ ...memory.project === void 0 ? {} : { project: await canonicalProject(store, memory.project) },
6281
+ ...memory.supersedes === void 0 ? {} : { supersedes: memory.supersedes }
6282
+ }), "addMemory");
6283
+
6284
+ // dist/memories/memory.js
6285
+ var MEMORY_QUERY = {
6286
+ entity: "memory",
6287
+ backend: "native",
6288
+ fields: [
6289
+ termField("kind", "enum", "kind:preference", { values: MEMORY_KINDS }),
6290
+ termField("status", "enum", "status:proposed", { values: MEMORY_STATUSES }),
6291
+ termField("scope", "enum", "scope:work", { values: MEMORY_SCOPES }),
6292
+ termField("subject", "ref", 'subject:"person:42"'),
6293
+ termField("project", "kw", "project:SYH")
6294
+ ]
6295
+ };
6296
+ var memoryItem = /* @__PURE__ */ __name(({ body, ...memory }) => ({
6297
+ ref: memory.ref,
6298
+ id: memory.id,
6299
+ kind: memory.kind,
6300
+ text: body,
6301
+ subject: memory.subject,
6302
+ subjectName: memory.subjectName,
6303
+ subjectIsOwner: memory.subjectIsOwner,
6304
+ project: memory.project,
6305
+ projectName: memory.projectName,
6306
+ author: memory.author,
6307
+ model: memory.model,
6308
+ confidence: memory.confidence,
6309
+ status: memory.status,
6310
+ scope: memory.scope,
6311
+ supersedes: memory.supersedes,
6312
+ evidence: memory.evidence,
6313
+ lastVerifiedAt: memory.lastVerifiedAt,
6314
+ sourceReview: memory.sourceReview,
6315
+ createdAt: memory.createdAt,
6316
+ updatedAt: memory.updatedAt
6317
+ }), "memoryItem");
6318
+ var listMemories = /* @__PURE__ */ __name(async (store, query, limit) => {
6319
+ const terms2 = queryTerms(query, MEMORY_QUERY, "memories list");
6320
+ const project = terms2.get("project");
6321
+ const filters = {
6322
+ ...terms2.has("kind") ? { kind: terms2.get("kind") } : {},
6323
+ ...terms2.has("status") ? { status: terms2.get("status") } : {},
6324
+ ...terms2.has("scope") ? { scope: terms2.get("scope") } : {},
6325
+ ...terms2.has("subject") ? { subject: terms2.get("subject") } : {},
6326
+ ...project === void 0 ? {} : { project: await canonicalProject(store, project) }
6327
+ };
6328
+ return pageOf("memories list", limit, async (most) => (await store.memories.list({ ...filters, limit: most })).map(memoryItem));
6329
+ }, "listMemories");
6330
+ var SEARCH = { command: "search memories", guide: LIST_QUERY_GUIDE };
6331
+ var MEMORY_FIELDS = /* @__PURE__ */ new Set(["text", ...MEMORY_QUERY.fields.map(({ name: name2 }) => name2)]);
6332
+ var searchMemories = /* @__PURE__ */ __name((store, query, limit) => {
6333
+ const { root } = parseLucene(plainQuery(typedQuery(query), MEMORY_FIELDS), { ...SEARCH, defaultField: "text" });
6334
+ const clauses = [];
6335
+ if (root.kind === "predicate")
6336
+ clauses.push(root);
6337
+ else
6338
+ for (const { occur, node } of root.clauses) {
6339
+ if (occur !== "must" || node.kind !== "predicate")
6340
+ queryError("query.unsupported-operator", node.span, "join words and scope: with AND only", SEARCH);
6341
+ clauses.push(node);
6342
+ }
6343
+ const words = [];
6344
+ let scope;
6345
+ for (const term of clauses) {
6346
+ if (term.field === "text" && (term.operator === "term" || term.operator === "phrase"))
6347
+ words.push(term.value);
6348
+ else if (term.field === "scope" && term.operator === "term" && scope === void 0) {
6349
+ const value = term.value.toLowerCase();
6350
+ if (!MEMORY_SCOPES.includes(value))
6351
+ queryError("query.invalid-value", term.span, `scope is ${MEMORY_SCOPES.join(" or ")}`, SEARCH);
6352
+ scope = value;
6353
+ } else
6354
+ queryError("query.unsupported-field", term.span, "words, and scope:personal or scope:work once", SEARCH);
6206
6355
  }
6207
- const projects = await openProjectTasks(store, zone);
6208
- if (projects.length)
6209
- accounts++;
6210
- for (const task of projects)
6211
- seen.set(task.id, task);
6212
- const counts = { overdue: 0, today: 0, tomorrow: 0, new: 0, newSince, undated: 0 };
6213
- const tasks = [];
6214
- for (const task of seen.values()) {
6215
- const groups = [];
6216
- const due = task.dueAt === null ? null : day2(Date.parse(task.dueAt));
6217
- if (due === null)
6218
- counts.undated++;
6219
- if (due !== null && due < today2)
6220
- groups.push("overdue");
6221
- if (due === today2)
6222
- groups.push("today");
6223
- if (due === tomorrow)
6224
- groups.push("tomorrow");
6225
- if (Date.parse(task.createdAt) >= since)
6226
- groups.push("new");
6227
- if (groups.length === 0)
6356
+ return pageOf("search memories", limit, async (most) => (await store.memories.search(words.join(" "), { limit: most, ...scope === void 0 ? {} : { scope } })).map(memoryItem), { fallback: 20, cap: 200 });
6357
+ }, "searchMemories");
6358
+
6359
+ // dist/digest/known.js
6360
+ var knownSenders = /* @__PURE__ */ __name(async (store, accountId, externalIds) => {
6361
+ if (externalIds.length === 0)
6362
+ return /* @__PURE__ */ new Set();
6363
+ const [facts, marks] = await Promise.all([
6364
+ store.mail.emailFacts({ accountId, externalIds }),
6365
+ store.mail.emailMarks(accountId, externalIds)
6366
+ ]);
6367
+ const known2 = /* @__PURE__ */ new Set();
6368
+ const unlinked = /* @__PURE__ */ new Map();
6369
+ for (const fact of facts) {
6370
+ const address = fact.fromAddress?.toLowerCase();
6371
+ if (!address)
6228
6372
  continue;
6229
- for (const group of groups)
6230
- counts[group]++;
6231
- const assignees = await Promise.all(task.assignments.filter((one2) => one2.role === "assignee").map((one2) => personName(store, one2.to)));
6232
- tasks.push({
6233
- task,
6234
- groups,
6235
- askedBy: await personName(store, await store.tasks.sourceSender(task.id)),
6236
- for: assignees.filter(Boolean).join(", ") || null
6237
- });
6373
+ if ((marks.get(fact.externalId)?.senderIdentities ?? 0) > 1)
6374
+ known2.add(fact.externalId);
6375
+ else
6376
+ unlinked.set(address, [...unlinked.get(address) ?? [], fact.externalId]);
6238
6377
  }
6239
- return { tasks, counts, accounts };
6240
- }, "gatherTasks");
6378
+ for (const address of await store.mail.writtenTo([...unlinked.keys()]))
6379
+ for (const id of unlinked.get(address) ?? [])
6380
+ known2.add(id);
6381
+ return known2;
6382
+ }, "knownSenders");
6241
6383
 
6242
6384
  // dist/digest/digest.js
6243
6385
  var WATCH_TAG = "watch";
6244
- var DAY6 = 864e5;
6386
+ var DAY7 = 864e5;
6245
6387
  var MESSENGERS2 = /* @__PURE__ */ new Set(["telegram", "max", "whatsapp"]);
6246
6388
  var HEALTH_ORDER = HEALTH_REASONS;
6247
6389
  var UNNAMED = "unnamed account";
@@ -6325,8 +6467,11 @@ var statusLine = /* @__PURE__ */ __name((data) => {
6325
6467
  return "";
6326
6468
  }, "statusLine");
6327
6469
  var personal = /* @__PURE__ */ __name((newest) => !newest.bulk && !newest.list && !newest.envelope && !newest.demoted, "personal");
6328
- var gatherDigest = /* @__PURE__ */ __name(async (store, env, { since, now = Date.now(), limit, excerpt: excerpt2, owedWithin, newSince }) => {
6329
- const window = (now - since) / DAY6;
6470
+ var gatherDigest = /* @__PURE__ */ __name(async (store, env, { since, now: clock = Date.now(), until, limit, excerpt: excerpt2, owedWithin, newSince }) => {
6471
+ const now = until ?? clock;
6472
+ const by = /* @__PURE__ */ __name((at) => until === void 0 || at <= until, "by");
6473
+ const upTo = /* @__PURE__ */ __name((found) => found.filter((one2) => by(Date.parse(one2.item.at))), "upTo");
6474
+ const window = (now - since) / DAY7;
6330
6475
  const days2 = `${Math.max(1, Math.ceil(window))}d`;
6331
6476
  const accounts = await store.accounts.accounts();
6332
6477
  const messengers2 = accounts.filter((key) => MESSENGERS2.has(key.provider));
@@ -6383,14 +6528,14 @@ var gatherDigest = /* @__PURE__ */ __name(async (store, env, { since, now = Date
6383
6528
  let mailMoved = 0;
6384
6529
  for (const key of messengers2) {
6385
6530
  const { chats, storedAt } = await store.chats.chatActivity(key, owedSince);
6386
- const month = (await store.chats.chatActivity(key, now - 30 * DAY6)).chats;
6531
+ const month = (await store.chats.chatActivity(key, now - 30 * DAY7)).chats;
6387
6532
  checkHealth(key, {
6388
6533
  newest: month.reduce((most, chat) => Math.max(most ?? 0, Date.parse(chat.lastMessageAt)), null),
6389
6534
  moved30d: month.length,
6390
6535
  storedAt: storedAt === null ? null : Date.parse(storedAt)
6391
6536
  });
6392
6537
  const ref = /* @__PURE__ */ __name((chat) => formatReference({ type: "chat", provider: key.provider, account: key.account, chat }), "ref");
6393
- chatsMoved += chats.filter((chat) => Date.parse(chat.lastMessageAt) >= since).length;
6538
+ chatsMoved += chats.filter((chat) => Date.parse(chat.lastMessageAt) >= since && by(Date.parse(chat.lastMessageAt))).length;
6394
6539
  coverage.push({
6395
6540
  account: name(key),
6396
6541
  storedAt,
@@ -6410,9 +6555,13 @@ var gatherDigest = /* @__PURE__ */ __name(async (store, env, { since, now = Date
6410
6555
  owedHere++;
6411
6556
  }
6412
6557
  if (owedHere)
6413
- owedNext.push(`memo tasks agenda --source ${name(key)} --unanswered 1m --within ${Math.ceil(owedWithin / DAY6)}d`);
6558
+ owedNext.push(`memo tasks agenda --source ${name(key)} --unanswered 1m --within ${Math.ceil(owedWithin / DAY7)}d`);
6414
6559
  for (const chatId of watched.chats.get(name(key)) ?? []) {
6415
- const page = await store.messages.messages(key, chatId, { limit, since: iso(since) });
6560
+ const page = await store.messages.messages(key, chatId, {
6561
+ limit,
6562
+ since: iso(since),
6563
+ ...until === void 0 ? {} : { until }
6564
+ });
6416
6565
  for (const message of page.items)
6417
6566
  watch.push({
6418
6567
  item: {
@@ -6433,15 +6582,15 @@ var gatherDigest = /* @__PURE__ */ __name(async (store, env, { since, now = Date
6433
6582
  }
6434
6583
  for (const key of mailboxes) {
6435
6584
  const stored = await store.accounts.storedAccount(key);
6436
- const threads = await store.mail.threadActivity(stored.id, Math.min(owedSince, now - OWED_DAYS * DAY6));
6585
+ const threads = await store.mail.threadActivity(stored.id, Math.min(owedSince, now - OWED_DAYS * DAY7));
6437
6586
  const ref = /* @__PURE__ */ __name((thread) => formatReference({ type: "chat", provider: "email", account: key.account, chat: thread }), "ref");
6438
6587
  const newest = threads.reduce((most, thread) => Math.max(most, thread.lastEmailAt), 0);
6439
6588
  checkHealth(key, {
6440
6589
  newest: newest || null,
6441
- moved30d: threads.filter((thread) => thread.lastEmailAt >= now - 30 * DAY6).length,
6590
+ moved30d: threads.filter((thread) => thread.lastEmailAt >= now - 30 * DAY7).length,
6442
6591
  storedAt: newest || null
6443
6592
  });
6444
- mailMoved += threads.filter((thread) => thread.lastEmailAt >= since && thread.newest !== null && personal(thread.newest)).length;
6593
+ mailMoved += threads.filter((thread) => thread.lastEmailAt >= since && by(thread.lastEmailAt) && thread.newest !== null && personal(thread.newest)).length;
6445
6594
  coverage.push({
6446
6595
  account: name(key),
6447
6596
  storedAt: newest ? iso(newest) : null,
@@ -6549,7 +6698,8 @@ var gatherDigest = /* @__PURE__ */ __name(async (store, env, { since, now = Date
6549
6698
  }
6550
6699
  const hot = await gatherTasks(store, [...messengers2, ...mailboxes], {
6551
6700
  now,
6552
- newSince: newSince ?? previousDigestAt(env, now)
6701
+ newSince: newSince ?? (until === void 0 ? previousDigestAt(env, now) : digestRunBefore(env, until)),
6702
+ ...until === void 0 ? {} : { until }
6553
6703
  });
6554
6704
  const tasks = hot.tasks.map(({ task, groups, askedBy, for: doer }) => ({
6555
6705
  item: {
@@ -6587,7 +6737,7 @@ var gatherDigest = /* @__PURE__ */ __name(async (store, env, { since, now = Date
6587
6737
  });
6588
6738
  }
6589
6739
  const reviews = await listReview(store, env, [`kind:${SOURCE_REVIEW_KIND}`], { limit: 50, page: 1 });
6590
- const sourceItems = reviews.items.filter((row) => Date.parse(row.createdAt) >= since).map((row) => ({
6740
+ const sourceItems = reviews.items.filter((row) => Date.parse(row.createdAt) >= since && by(Date.parse(row.createdAt))).map((row) => ({
6591
6741
  item: {
6592
6742
  ref: row.subject,
6593
6743
  who: row.proposer,
@@ -6599,7 +6749,7 @@ var gatherDigest = /* @__PURE__ */ __name(async (store, env, { since, now = Date
6599
6749
  const reviewCount = sourceItems.length;
6600
6750
  const reviewed = new Set(reviews.items.flatMap((row) => row.evidence));
6601
6751
  for (const changed of await store.knowledge.changedSources({ since }))
6602
- if (!reviewed.has(changed.ref))
6752
+ if (!reviewed.has(changed.ref) && by(Date.parse(changed.checkedAt)))
6603
6753
  sourceItems.push({
6604
6754
  item: {
6605
6755
  ref: changed.ref,
@@ -6621,27 +6771,30 @@ var gatherDigest = /* @__PURE__ */ __name(async (store, env, { since, now = Date
6621
6771
  }
6622
6772
  }));
6623
6773
  const sections = [
6624
- await section("watch", watch, limit, [
6774
+ await section("watch", upTo(watch), limit, [
6625
6775
  "memo tags list tag:watch",
6626
6776
  ...[...watched.people].map((uid) => `memo context person:${uid}`)
6627
6777
  ]),
6628
6778
  {
6629
- ...await section("owed", owed, limit, [
6779
+ ...await section("owed", upTo(owed), limit, [
6630
6780
  ...owedNext,
6631
6781
  ...olderMail ? [`# ${olderMail} mail threads owed from before the window left out`] : []
6632
6782
  ]),
6633
6783
  ...unknown2.count ? { unknown: unknown2 } : {}
6634
6784
  },
6635
- await section("mail", mail, limit, [...mailNext, ...bulk ? [`# ${bulk} bulk or list threads left out`] : []]),
6636
- await section("meetings", meetings2, limit, ["memo search meetings <words>"]),
6785
+ await section("mail", upTo(mail), limit, [
6786
+ ...mailNext,
6787
+ ...bulk ? [`# ${bulk} bulk or list threads left out`] : []
6788
+ ]),
6789
+ await section("meetings", upTo(meetings2), limit, ["memo search meetings <words>"]),
6637
6790
  await section("tasks", tasks, limit, ["memo tasks list"]),
6638
- await section("projects", projects, limit, projectNext),
6791
+ await section("projects", upTo(projects), limit, projectNext),
6639
6792
  await section("sources", sourceItems, limit, [
6640
6793
  REVIEW_SOURCES,
6641
6794
  ...reviewCount > 0 ? [`# decide them: ${REVIEW_SOURCES}`] : [],
6642
6795
  ...sourceItems.length > reviewCount ? [`# read changed sources again: memo memories check --since-time ${days2} --json`] : []
6643
6796
  ]),
6644
- await section("proposals", proposals, limit, ["memo review list"], waiting2.total)
6797
+ await section("proposals", upTo(proposals), limit, ["memo review list"], waiting2.total)
6645
6798
  ];
6646
6799
  const providerCount = /* @__PURE__ */ __name((provider) => accounts.filter((key) => key.provider === provider).length, "providerCount");
6647
6800
  const meetingAccounts = accounts.filter((key) => kindOf(key.provider) === "meetings").length;
@@ -6660,7 +6813,7 @@ var gatherDigest = /* @__PURE__ */ __name(async (store, env, { since, now = Date
6660
6813
  from: iso(since),
6661
6814
  to: iso(now),
6662
6815
  sources: sources.filter((one2) => one2.accounts > 0),
6663
- counts: { chats: chatsMoved, mail: mailMoved, tasks: hot.tasks.length, meetings: meetings2.length }
6816
+ counts: { chats: chatsMoved, mail: mailMoved, tasks: hot.tasks.length, meetings: upTo(meetings2).length }
6664
6817
  },
6665
6818
  tasks: hot.counts,
6666
6819
  health: health.sort((a, b) => HEALTH_ORDER.indexOf(a.reason) - HEALTH_ORDER.indexOf(b.reason)),
@@ -6725,16 +6878,30 @@ var sinceOf = /* @__PURE__ */ __name((value, now = Date.now()) => {
6725
6878
  });
6726
6879
  return since;
6727
6880
  }, "sinceOf");
6881
+ var beforeOf = /* @__PURE__ */ __name((value, now = Date.now()) => {
6882
+ const at = /^\d{4}-\d{2}-\d{2}$/.test(value) ? (/* @__PURE__ */ new Date(`${value}T00:00:00`)).getTime() : Date.parse(value);
6883
+ if (!/^\d{4}-\d{2}-\d{2}/.test(value) || Number.isNaN(at))
6884
+ throw new CliError("validation_error", `--before-time takes an ISO time or a date, not "${value}"`, {
6885
+ param: "before-time"
6886
+ });
6887
+ if (at > now)
6888
+ throw new CliError("validation_error", "--before-time is in the future; leave it out for now", {
6889
+ param: "before-time"
6890
+ });
6891
+ return at;
6892
+ }, "beforeOf");
6728
6893
  var reviewAgendaCommand = /* @__PURE__ */ __name((review, print, streams, env) => {
6729
- annotate(review.command("agenda"), { effects: LOCAL_READ }).summary("the morning digest: watch list, owed replies, mail, meetings, tasks, projects").description(`the morning digest, from the store alone with no model call: everything new in what you tagged ${WATCH_TAG} (chats, mail threads, people, projects, a meeting's series), replies you owe to people in messengers and personal mail (not bots or service chats), new personal mail, meetings that ended, tasks, projects that moved, memories and facts whose sources changed (source reviews raised since), proposals waiting. Tasks are the open ones created since the last morning-digest run (a day back when none ran) and those overdue, due today or due tomorrow, each with who asked and who does it. Replies owed reach back 7 days; owed mail lists only senders you know (linked to a person with other identities, or written to before), and mail from new senders is one count with the command that lists it. Each item has its reference, who, when, one line of text and why it is there; a section cut by --limit or --max-output-bytes says how many it left out and the command that reads them. \`scope\` says what was read (the window, sources, counts), \`tasks\` counts each task group and, as \`undated\`, every open task with no due date, and \`health\` names each account whose sync failed, that nothing syncs, or that is quiet for longer than usual, so an unsynced account does not read as a quiet day; an account stored or synced within a day is never named`).option("--since-time <time>", "how far back: hours or days (12h, 7d) or a start date (2026-11-01)", "1d").option("--limit <n>", "items per section before the size budget", positive, 25).option("--max-output-bytes <bytes>", "the text answer's size budget in bytes; 40000 with --compact, else 80000", positive).option("--compact", "one line per item, shorter excerpts, the smaller budget: for an agent").option("--json", "print JSON").action(async (given2) => {
6894
+ annotate(review.command("agenda"), { effects: LOCAL_READ }).summary("the morning digest: watch list, owed replies, mail, meetings, tasks, projects").description(`the morning digest, from the store alone with no model call: everything new in what you tagged ${WATCH_TAG} (chats, mail threads, people, projects, a meeting's series), replies you owe to people in messengers and personal mail (not bots or service chats), new personal mail, meetings that ended, tasks, projects that moved, memories and facts whose sources changed (source reviews raised since), proposals waiting. Tasks are the open ones created since the last morning-digest run (a day back when none ran) and those overdue, due today or due tomorrow, each with who asked and who does it. Replies owed reach back 7 days; owed mail lists only senders you know (linked to a person with other identities, or written to before), and mail from new senders is one count with the command that lists it. Each item has its reference, who, when, one line of text and why it is there; a section cut by --limit or --max-output-bytes says how many it left out and the command that reads them. \`scope\` says what was read (the window, sources, counts), \`tasks\` counts each task group and, as \`undated\`, every open task with no due date, and \`health\` names each account whose sync failed, that nothing syncs, or that is quiet for longer than usual, so an unsynced account does not read as a quiet day; an account stored or synced within a day is never named`).option("--since-time <time>", "how far back: hours or days (12h, 7d) or a start date (2026-11-01)", "1d").option("--before-time <time>", "build it as of this past ISO time or date, for a past day's digest: --since-time counts back from it, and what came later is left out; a chat or thread answered since reads as answered").option("--limit <n>", "items per section before the size budget", positive, 25).option("--max-output-bytes <bytes>", "the text answer's size budget in bytes; 40000 with --compact, else 80000", positive).option("--compact", "one line per item, shorter excerpts, the smaller budget: for an agent").option("--json", "print JSON").action(async (given2) => {
6730
6895
  if (given2.compact && given2.json)
6731
6896
  throw new CliError("validation_error", "--compact and --json ask for different output \u2014 pick one", {
6732
6897
  reason: "output.conflict",
6733
6898
  param: "--compact"
6734
6899
  });
6735
- const since = sinceOf(given2.sinceTime);
6900
+ const until = given2.beforeTime === void 0 ? void 0 : beforeOf(given2.beforeTime);
6901
+ const since = sinceOf(given2.sinceTime, until);
6736
6902
  const gathered = await withStore(env, (store) => gatherDigest(store, env, {
6737
6903
  since,
6904
+ ...until === void 0 ? {} : { until },
6738
6905
  limit: given2.limit,
6739
6906
  excerpt: given2.compact ? 100 : 200,
6740
6907
  owedWithin: OWED_WITHIN
@@ -8278,7 +8445,7 @@ var projectFindings = /* @__PURE__ */ __name(async (store, project, links) => {
8278
8445
  // dist/knowledge/project-context.js
8279
8446
  var projectContext = /* @__PURE__ */ __name(async (store, env, reference2) => {
8280
8447
  const project = await projectOf(store, reference2);
8281
- const relationships = (await store.notes.links({ to: project.ref })).concat(await store.notes.links({ from: project.ref })).filter((link2) => link2.kind !== "about" && link2.kind !== "links-to" && link2.role !== "key");
8448
+ const relationships = byEngagement((await store.notes.links({ to: project.ref })).concat(await store.notes.links({ from: project.ref })).filter((link2) => link2.kind !== "about" && link2.kind !== "links-to" && link2.role !== "key"));
8282
8449
  const documents = await keyDocuments(store, project);
8283
8450
  const pending2 = /* @__PURE__ */ __name((kind) => proposalRegistry(store).list({ project: project.ref, kinds: [kind], status: "proposed", limit: 50, offset: 0 }), "pending");
8284
8451
  const [facts, tasks, findings, keys, links, waiting2] = await Promise.all([
@@ -8351,7 +8518,7 @@ var projectContextText = /* @__PURE__ */ __name((context) => {
8351
8518
  }, "projectContextText");
8352
8519
 
8353
8520
  // dist/knowledge/relationships-scan.js
8354
- var DAY7 = 864e5;
8521
+ var DAY8 = 864e5;
8355
8522
  var SCAN_STEP = {
8356
8523
  id: "relationships.scan",
8357
8524
  kind: "command",
@@ -8425,7 +8592,7 @@ var relationshipsScanCommand = /* @__PURE__ */ __name((relations, print, env, wa
8425
8592
  if (options.limit > 50)
8426
8593
  throw new CliError("validation_error", "--limit is at most 50", { param: "limit" });
8427
8594
  const window = projectWindowOf(options.sinceTime);
8428
- const since = new Date("days" in window ? Date.now() - window.days * DAY7 : window.after).toISOString();
8595
+ const since = new Date("days" in window ? Date.now() - window.days * DAY8 : window.after).toISOString();
8429
8596
  const written2 = options.yes === true;
8430
8597
  const { body, text: text3 } = await withStore(env, async (store) => {
8431
8598
  const sources = await namedAccounts(store, env, options, warn);
@@ -8735,7 +8902,7 @@ var listRelationships = /* @__PURE__ */ __name(async (store, words) => {
8735
8902
  const confirmed = terms2.get("confirmed");
8736
8903
  const found = confirmed === "false" ? await pending(store, about) : {
8737
8904
  hasMore: false,
8738
- items: (await store.knowledge.relations(null, about)).filter((one2) => confirmed === void 0 || one2.confirmed).map((one2) => ({ ...one2, status: one2.confirmed ? "confirmed" : "proposed" }))
8905
+ items: byEngagement(await store.knowledge.relations(null, about)).filter((one2) => confirmed === void 0 || one2.confirmed).map((one2) => ({ ...one2, status: one2.confirmed ? "confirmed" : "proposed" }))
8739
8906
  };
8740
8907
  const label = await labelsFor(store, found.items.flatMap((one2) => [one2.from, one2.to]));
8741
8908
  return {
@@ -8747,7 +8914,13 @@ var listRelationships = /* @__PURE__ */ __name(async (store, words) => {
8747
8914
  var relationshipsText = /* @__PURE__ */ __name((items) => items.length ? items.map((one2) => [
8748
8915
  one2.id,
8749
8916
  `${one2.fromTitle} \u2192 ${one2.toTitle}`,
8750
- [one2.kind, one2.role && `as ${one2.role}`, one2.purpose && `for ${one2.purpose}`].filter(Boolean).join(" "),
8917
+ [
8918
+ one2.kind,
8919
+ one2.role && `as ${one2.role}`,
8920
+ "roleNote" in one2 && one2.roleNote && `(${one2.roleNote})`,
8921
+ one2.purpose && `for ${one2.purpose}`,
8922
+ "engagement" in one2 && one2.engagement && `engagement ${one2.engagement}`
8923
+ ].filter(Boolean).join(" "),
8751
8924
  "proposer" in one2 ? `proposed by ${one2.proposer}` : one2.confirmed ? "" : "suggested"
8752
8925
  ].filter(Boolean).join(" ")).join("\n") : "no relationships", "relationshipsText");
8753
8926
 
@@ -8761,6 +8934,7 @@ var pendingLink = /* @__PURE__ */ __name(async (store, id) => {
8761
8934
  throw error;
8762
8935
  }
8763
8936
  }, "pendingLink");
8937
+ var ENGAGEMENT_HELP = "a person on a project: how much you deal with them yourself, not how active they are: none, low, medium or high";
8764
8938
  var knowledgeCommands = /* @__PURE__ */ __name((program, streams, env) => {
8765
8939
  const { run, print } = answers(program, streams, env);
8766
8940
  const warn = /* @__PURE__ */ __name((text3) => streams.diagnostic(text3), "warn");
@@ -8838,7 +9012,7 @@ var knowledgeCommands = /* @__PURE__ */ __name((program, streams, env) => {
8838
9012
  return ownedList("projects key-documents list", options, async (store) => keyDocuments(store, await projectOf(store, project), id === void 0 ? {} : { id }));
8839
9013
  });
8840
9014
  const relations = program.command("relationships").description("relations between people, organizations and projects; never between identities");
8841
- byOption(annotate(relations.command("add"), { effects: LOCAL_WRITE }).description("state that a person, organization or project belongs to or relates to another, or that a chat is a project's (member-of only); an agent's waits for you to confirm it").summary("state that a person, organization, project or chat belongs or relates to another")).argument("<from>", "the person:, organization: or project: that belongs or relates, or chat:<provider>/<account>/<chat> of a project").argument("<to>", "the person:, organization: or project: it belongs or relates to").option("--json", "print JSON; --json=id,title keeps only those fields").option("--kind <kind>", "member-of or related-to", "member-of").option("--role <role>", "the role it has there; a person's role in an organization or a project is one from its list, such as employee or contributor").option("--reason <text>", "why you think so, in a sentence").action(async (from, to, options, command) => {
9015
+ byOption(annotate(relations.command("add"), { effects: LOCAL_WRITE }).description("state that a person, organization or project belongs to or relates to another, or that a chat is a project's (member-of only); an agent's waits for you to confirm it").summary("state that a person, organization, project or chat belongs or relates to another")).argument("<from>", "the person:, organization: or project: that belongs or relates, or chat:<provider>/<account>/<chat> of a project").argument("<to>", "the person:, organization: or project: it belongs or relates to").option("--json", "print JSON; --json=id,title keeps only those fields").option("--kind <kind>", "member-of or related-to", "member-of").option("--role <role>", "the role it has there; a person's role in an organization or a project is one from its list, such as employee or contributor").option("--role-note <text>", "what the role means here, in your words, beside a role from its list").option("--engagement <level>", ENGAGEMENT_HELP, oneOf(ENGAGEMENT_LEVELS)).option("--reason <text>", "why you think so, in a sentence").action(async (from, to, options, command) => {
8842
9016
  const by = await whoRuns(command, env, { asOwner: options.asOwner, question: "state it as yours?" });
8843
9017
  return owned(options, async (store) => {
8844
9018
  const input = {
@@ -8846,6 +9020,8 @@ var knowledgeCommands = /* @__PURE__ */ __name((program, streams, env) => {
8846
9020
  to: await withOwner(store, to),
8847
9021
  kind: options.kind,
8848
9022
  ...options.role === void 0 ? {} : { role: options.role },
9023
+ ...options.roleNote === void 0 ? {} : { roleNote: options.roleNote },
9024
+ ...options.engagement === void 0 ? {} : { engagement: options.engagement },
8849
9025
  ...options.reason === void 0 ? {} : { evidence: options.reason }
8850
9026
  };
8851
9027
  return by === "owner" ? store.knowledge.relate(null, { ...input, by }) : store.knowledge.relate(null, { ...input, by });
@@ -8855,15 +9031,18 @@ var knowledgeCommands = /* @__PURE__ */ __name((program, streams, env) => {
8855
9031
  const found = await withStore(env, (store) => listRelationships(store, words));
8856
9032
  print(options.json, listAnswer("relationships list", { items: found.items, limit: found.limit, has_more: found.hasMore }), relationshipsText(found.items));
8857
9033
  });
8858
- byOption(annotate(relations.command("update"), { effects: LOCAL_WRITE }).description("set what a relationship is: its role, or what a chat is for in a project (work, sales, support, other); an agent's change waits for you in memo review").summary("set a relationship's role or a chat's purpose in a project").argument("<id>", "the relationship's id, as relationships list gives it").option("--role <role>", "the role it has there, from the same list as relationships add; an empty value clears it").option("--purpose <purpose>", "what the chat is for in the project: work, sales, support or other").option("--reason <text>", "why, in a sentence").option("--json", "print JSON; --json=id,title keeps only those fields")).action(async (id, options, command) => {
9034
+ byOption(annotate(relations.command("update"), { effects: LOCAL_WRITE }).description("set what a relationship is: its role and a note beside it, how much you deal with a person on a project, or what a chat is for in a project (work, sales, support, other); an agent's change waits for you in memo review").summary("set a relationship's role, engagement or a chat's purpose in a project").argument("<id>", "the relationship's id, as relationships list gives it").option("--role <role>", "the role it has there, from the same list as relationships add; an empty value clears it").option("--role-note <text>", "what the role means here, in your words; an empty value clears it").option("--engagement <level>", `${ENGAGEMENT_HELP}; an empty value clears it`, (value) => value === "" ? "" : oneOf(ENGAGEMENT_LEVELS)(value)).option("--purpose <purpose>", "what the chat is for in the project: work, sales, support or other").option("--reason <text>", "why, in a sentence").option("--json", "print JSON; --json=id,title keeps only those fields")).action(async (id, options, command) => {
9035
+ const cleared = /* @__PURE__ */ __name((value) => value === "" ? null : value, "cleared");
8859
9036
  const change = {
8860
- ...options.role === void 0 ? {} : { role: options.role === "" ? null : options.role },
9037
+ ...options.role === void 0 ? {} : { role: cleared(options.role) },
9038
+ ...options.roleNote === void 0 ? {} : { roleNote: cleared(options.roleNote) },
9039
+ ...options.engagement === void 0 ? {} : { engagement: cleared(options.engagement) },
8861
9040
  ...options.purpose === void 0 ? {} : { purpose: options.purpose }
8862
9041
  };
8863
9042
  const by = await whoRuns(command, env, { asOwner: options.asOwner, question: `change relationship ${id}?` });
8864
9043
  return owned(options, (store) => {
8865
- if (options.role === void 0 && options.purpose === void 0)
8866
- throw new CliError("validation_error", "say what to change: --role, --purpose or both", {
9044
+ if (!Object.keys(change).length)
9045
+ throw new CliError("validation_error", "say what to change: --role, --role-note, --engagement or --purpose", {
8867
9046
  reason: "input.invalid",
8868
9047
  param: "--purpose"
8869
9048
  });
@@ -9327,9 +9506,9 @@ var messagesCommand = /* @__PURE__ */ __name((program, streams, env) => {
9327
9506
  }, "messagesCommand");
9328
9507
 
9329
9508
  // dist/mcp/command.js
9330
- import { join as join6 } from "node:path";
9509
+ import { join as join8 } from "node:path";
9331
9510
  var DEFAULT_PORT = 8767;
9332
- var httpTokenFile = /* @__PURE__ */ __name((env) => join6(resolvePaths({ appName: APP.appName, prefix: APP.envPrefix, env }).state, "mcp-http", "memo.json"), "httpTokenFile");
9511
+ var httpTokenFile = /* @__PURE__ */ __name((env) => join8(resolvePaths({ appName: APP.appName, prefix: APP.envPrefix, env }).state, "mcp-http", "memo.json"), "httpTokenFile");
9333
9512
  var mcpCommandDefinition = /* @__PURE__ */ __name((program, streams, env, createProgram2) => {
9334
9513
  const out = answers(program, streams, env);
9335
9514
  const mcp2 = annotate(program.command("mcp"), { effects: LOCAL_READ, mcp: false }).summary("serve this profile's commands to an AI agent, and set that up").description("serve memo commands over MCP on stdio or authenticated HTTP, limited by saved permissions \u2014 `claude mcp add memo -- memo mcp serve`");
@@ -9341,10 +9520,10 @@ var mcpCommandDefinition = /* @__PURE__ */ __name((program, streams, env, create
9341
9520
  }).summary("serve this profile's commands to an AI agent").description("serve memo commands over MCP on stdio or authenticated HTTP, limited by saved permissions").option("--permission <key=level>", "tighten one saved permission for this server; repeat for more keys", (value, previous = []) => [...previous, value]).option("--http", "serve on 127.0.0.1 behind an HTTPS tunnel with an owner access-code login").option("--port <port>", `local HTTP port (default ${DEFAULT_PORT}); requires --http`).option("--public-url <url>", "tunnel's HTTPS address without /mcp, or a path of it such as https://<name>.ts.net/memo; requires --http").action(async (options) => {
9342
9521
  if (!options.http && (options.port !== void 0 || options.publicUrl !== void 0))
9343
9522
  throw new CliError("validation_error", "--port and --public-url require --http");
9344
- const { createMemoServer } = await import("./server-NKCRFK26.js");
9523
+ const { createMemoServer } = await import("./server-JLPZUC42.js");
9345
9524
  const build = /* @__PURE__ */ __name(() => createMemoServer(env, createProgram2, options.permission), "build");
9346
9525
  if (options.http) {
9347
- const { httpLockFile, mcpUrlOf, portOf, publicUrlOf, serveOverHttp } = await import("./mcp-6RDHWRXG.js");
9526
+ const { httpLockFile, mcpUrlOf, portOf, publicUrlOf, serveOverHttp } = await import("./mcp-CFIL7WZU.js");
9348
9527
  const publicUrl = publicUrlOf(options.publicUrl, {
9349
9528
  missing: "--http",
9350
9529
  reaches: "the browser apps reach memo",
@@ -9396,7 +9575,7 @@ var mcpCommandDefinition = /* @__PURE__ */ __name((program, streams, env, create
9396
9575
  });
9397
9576
  });
9398
9577
  annotate(mcp2.command("revoke"), { effects: LOCAL_WRITE, mcp: false, examples: ["memo mcp revoke --json"] }).summary("forget every login given to a browser app").description("forget every login `memo mcp serve --http` gave a browser app; each app must log in again").option("--json", "print JSON").action(async function() {
9399
- const { revokeAll } = await import("./mcp-6RDHWRXG.js");
9578
+ const { revokeAll } = await import("./mcp-CFIL7WZU.js");
9400
9579
  revokeAll(httpTokenFile(env));
9401
9580
  out.print(this.opts().json, itemAnswer({ revoked: true }));
9402
9581
  });
@@ -9725,8 +9904,8 @@ import { resolve as resolve3 } from "node:path";
9725
9904
 
9726
9905
  // dist/notes/export.js
9727
9906
  import { createHash as createHash3 } from "node:crypto";
9728
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync7, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "node:fs";
9729
- import { join as join7 } from "node:path";
9907
+ import { existsSync as existsSync8, mkdirSync as mkdirSync3, readdirSync as readdirSync6, readFileSync as readFileSync7, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "node:fs";
9908
+ import { join as join9 } from "node:path";
9730
9909
  var HASH_LINE = /^memo-hash: .*\r?\n/m;
9731
9910
  var PLACEHOLDER = "f".repeat(64);
9732
9911
  var UNSAFE = /[\\/:*?"<>|#^[\]\p{Cc}]/gu;
@@ -9747,10 +9926,10 @@ var fileName = /* @__PURE__ */ __name((title, id, taken) => {
9747
9926
  }, "fileName");
9748
9927
  var exportedIn = /* @__PURE__ */ __name((dir) => {
9749
9928
  const found = /* @__PURE__ */ new Map();
9750
- for (const entry of readdirSync4(dir, { withFileTypes: true })) {
9929
+ for (const entry of readdirSync6(dir, { withFileTypes: true })) {
9751
9930
  if (!entry.isFile() || !/\.(md|markdown)$/i.test(entry.name))
9752
9931
  continue;
9753
- const id = readMemoId(join7(dir, entry.name));
9932
+ const id = readMemoId(join9(dir, entry.name));
9754
9933
  if (id !== null)
9755
9934
  found.set(id, entry.name);
9756
9935
  }
@@ -9767,9 +9946,9 @@ var exportNotes = /* @__PURE__ */ __name(({ dir, dialect, notes: notes2 }) => {
9767
9946
  frontMatter["memo-hash"] = PLACEHOLDER;
9768
9947
  const content = withHash(dialect.render({ ...note, frontMatter }));
9769
9948
  const known2 = existing.get(id);
9770
- const name2 = known2 ?? fileName(note.title, id, (candidate) => claimed.has(candidate) || existsSync6(join7(dir, candidate)));
9949
+ const name2 = known2 ?? fileName(note.title, id, (candidate) => claimed.has(candidate) || existsSync8(join9(dir, candidate)));
9771
9950
  claimed.add(name2);
9772
- const path = join7(dir, name2);
9951
+ const path = join9(dir, name2);
9773
9952
  if (known2 !== void 0) {
9774
9953
  const current = readFileSync7(path, "utf8");
9775
9954
  if (editedSinceExport(current)) {
@@ -9781,7 +9960,7 @@ var exportNotes = /* @__PURE__ */ __name(({ dir, dialect, notes: notes2 }) => {
9781
9960
  continue;
9782
9961
  }
9783
9962
  }
9784
- const partial = join7(dir, `.${name2}.memo-tmp`);
9963
+ const partial = join9(dir, `.${name2}.memo-tmp`);
9785
9964
  writeFileSync3(partial, content);
9786
9965
  renameSync2(partial, path);
9787
9966
  result.written.push(path);
@@ -10038,9 +10217,9 @@ import { statSync as statSync4 } from "node:fs";
10038
10217
  import { basename as basename2, resolve as resolve4 } from "node:path";
10039
10218
 
10040
10219
  // dist/run/lock.js
10041
- import { join as join8 } from "node:path";
10220
+ import { join as join10 } from "node:path";
10042
10221
  var withImportLock = /* @__PURE__ */ __name(async (env, work) => {
10043
- const lock = join8(resolvePaths({ appName: "cli-memo", prefix: "MEMO", env }).state, "import.lock");
10222
+ const lock = join10(resolvePaths({ appName: "cli-memo", prefix: "MEMO", env }).state, "import.lock");
10044
10223
  const holder = takeLock(lock, { pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() });
10045
10224
  if (holder !== void 0)
10046
10225
  throw new CliError("rate_limited", `another memo sync is running (process ${holder.pid}); try again when it ends`, {
@@ -10171,7 +10350,7 @@ var foldersCommand = /* @__PURE__ */ __name((program, streams, env) => {
10171
10350
  }, "foldersCommand");
10172
10351
 
10173
10352
  // dist/people/command.js
10174
- import { join as join9 } from "node:path";
10353
+ import { join as join11 } from "node:path";
10175
10354
  var noteCommand = /* @__PURE__ */ __name((program, streams, env) => {
10176
10355
  const notes2 = program.commands.find((command) => command.name() === "notes") ?? program.command("notes");
10177
10356
  const links = notes2.command("links").description("imported note files linked to the person behind an identity");
@@ -10224,7 +10403,7 @@ var noteCommand = /* @__PURE__ */ __name((program, streams, env) => {
10224
10403
  }
10225
10404
  const paths = (await files()).map(({ note }) => {
10226
10405
  const folder = folders.find((candidate) => candidate.id === note.folderId);
10227
- return folder ? join9(folder.path, note.path) : note.ref;
10406
+ return folder ? join11(folder.path, note.path) : note.ref;
10228
10407
  });
10229
10408
  return { person: to, name: person.name ?? person.uid, files: paths };
10230
10409
  });
@@ -10667,8 +10846,8 @@ ${SOURCE_REVIEW_HELP}`);
10667
10846
  }, "reviewCommand");
10668
10847
 
10669
10848
  // dist/notes/embed.js
10670
- import { existsSync as existsSync7, readdirSync as readdirSync5 } from "node:fs";
10671
- import { join as join10 } from "node:path";
10849
+ import { existsSync as existsSync9, readdirSync as readdirSync7 } from "node:fs";
10850
+ import { join as join12 } from "node:path";
10672
10851
  var EMBED_PER_RUN = 600;
10673
10852
  var SLOWEST_KNOWN = 2;
10674
10853
  var embedPerRun = /* @__PURE__ */ __name((env = process.env) => {
@@ -10685,8 +10864,8 @@ var missingModel = /* @__PURE__ */ __name((error, env) => {
10685
10864
  }, "missingModel");
10686
10865
  var searchModelPresent = /* @__PURE__ */ __name((env) => {
10687
10866
  try {
10688
- const path = join10(sharedModelsDirectory("text", env), chosenTextModel(env).id);
10689
- return existsSync7(path) && readdirSync5(path).length > 0;
10867
+ const path = join12(sharedModelsDirectory("text", env), chosenTextModel(env).id);
10868
+ return existsSync9(path) && readdirSync7(path).length > 0;
10690
10869
  } catch {
10691
10870
  return false;
10692
10871
  }
@@ -11127,10 +11306,12 @@ var mailTriageInput = /* @__PURE__ */ __name((mailboxes) => async (schedule, run
11127
11306
  }, "mailTriageInput");
11128
11307
 
11129
11308
  // dist/schedule/command.js
11309
+ var MAIL_CLI = { command: "mail", appName: "mail-cli", envPrefix: "MAIL" };
11130
11310
  var scheduleCommand = /* @__PURE__ */ __name((env) => {
11131
11311
  const schedule = scheduleCommandFor({
11132
11312
  app: APP,
11133
11313
  context: /* @__PURE__ */ __name((command) => ({ ...outputFor(command), env, profile: "" }), "context"),
11314
+ agentRuns: [MAIL_CLI],
11134
11315
  newMail: /* @__PURE__ */ __name((_command, holder) => withStore(env, async (store) => {
11135
11316
  const triage = bulkTriage(env);
11136
11317
  await advancePastSkipped(store, holder, Date.now(), triage);
@@ -11144,7 +11325,7 @@ var scheduleCommand = /* @__PURE__ */ __name((env) => {
11144
11325
  command.option("--json", "print JSON");
11145
11326
  });
11146
11327
  for (const one2 of schedule.commands.filter((command) => ["run", "exec"].includes(command.name())))
11147
- overrideSharedContract(one2, "A memo schedule runs memo, which writes to no remote service, or an agent allowed only memo under MEMO_READ_ONLY.", (command) => {
11328
+ overrideSharedContract(one2, "A memo schedule runs memo, which writes to no remote service, or an agent allowed only memo and mail, each read-only.", (command) => {
11148
11329
  annotate(command, { effects: LOCAL_WRITE });
11149
11330
  });
11150
11331
  return schedule;
@@ -11587,17 +11768,17 @@ var showCommand = /* @__PURE__ */ __name((program, streams, env) => {
11587
11768
  }, "showCommand");
11588
11769
 
11589
11770
  // dist/sources/command.js
11590
- import { existsSync as existsSync9 } from "node:fs";
11771
+ import { existsSync as existsSync11 } from "node:fs";
11591
11772
 
11592
11773
  // dist/sources/copy.js
11593
11774
  import { randomUUID } from "node:crypto";
11594
- import { existsSync as existsSync8, mkdirSync as mkdirSync4, renameSync as renameSync3, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "node:fs";
11595
- import { isAbsolute, join as join11, relative as relative2, resolve as resolve5, sep as sep2 } from "node:path";
11596
- var copiesDir = /* @__PURE__ */ __name((env) => join11(resolvePaths({ appName: APP.appName, prefix: APP.envPrefix, env }).state, "sources"), "copiesDir");
11597
- var copyPath = /* @__PURE__ */ __name((env, hash) => join11(copiesDir(env), hash), "copyPath");
11775
+ import { existsSync as existsSync10, mkdirSync as mkdirSync4, renameSync as renameSync3, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "node:fs";
11776
+ import { isAbsolute, join as join13, relative as relative2, resolve as resolve5, sep as sep2 } from "node:path";
11777
+ var copiesDir = /* @__PURE__ */ __name((env) => join13(resolvePaths({ appName: APP.appName, prefix: APP.envPrefix, env }).state, "sources"), "copiesDir");
11778
+ var copyPath = /* @__PURE__ */ __name((env, hash) => join13(copiesDir(env), hash), "copyPath");
11598
11779
  var keepCopy = /* @__PURE__ */ __name((env, hash, bytes) => {
11599
11780
  const path = copyPath(env, hash);
11600
- if (existsSync8(path))
11781
+ if (existsSync10(path))
11601
11782
  return { path, wrote: false };
11602
11783
  mkdirSync4(copiesDir(env), { recursive: true, mode: 448 });
11603
11784
  const partial = `${path}.${randomUUID()}.part`;
@@ -11607,7 +11788,7 @@ var keepCopy = /* @__PURE__ */ __name((env, hash, bytes) => {
11607
11788
  }, "keepCopy");
11608
11789
  var dropCopy = /* @__PURE__ */ __name((env, path) => {
11609
11790
  const inside2 = relative2(resolve5(copiesDir(env)), resolve5(path));
11610
- if (!inside2 || isAbsolute(inside2) || inside2.startsWith("..") || inside2.includes(sep2) || !existsSync8(path))
11791
+ if (!inside2 || isAbsolute(inside2) || inside2.startsWith("..") || inside2.includes(sep2) || !existsSync10(path))
11611
11792
  return false;
11612
11793
  rmSync2(path);
11613
11794
  return true;
@@ -12003,7 +12184,7 @@ var sourcesCommand = /* @__PURE__ */ __name((program, streams, env, { fetch: fet
12003
12184
  mime: source?.mime ?? null,
12004
12185
  bytes: source?.size ?? null,
12005
12186
  // The row says a copy was kept; only the file itself says it still is.
12006
- kept: source === void 0 ? null : source.copy !== null && existsSync9(source.copy) ? "copy" : "text",
12187
+ kept: source === void 0 ? null : source.copy !== null && existsSync11(source.copy) ? "copy" : "text",
12007
12188
  chars: note.text.length,
12008
12189
  text: text3,
12009
12190
  textTruncated: truncated,
@@ -13127,6 +13308,7 @@ var createProgram = /* @__PURE__ */ __name(({ streams: raw = processStreams, env
13127
13308
  doctorCommand(program, streams, env);
13128
13309
  program.addCommand(upgradeCommand(streams));
13129
13310
  program.addCommand(scheduleCommand(env));
13311
+ digestCommand(program, streams, env);
13130
13312
  commandsCommand(program, streams);
13131
13313
  const skill2 = customizeSharedCommand(skillCommand2(APP, new URL("../skills/cli-memo/SKILL.md", new URL("../program.js", import.meta.url).href)), "Memo installs its own stored-data instructions through its environment, prints the packaged primary skill without filesystem effects, and takes --json on each skill command because it has no global one.", () => {
13132
13314
  const out = answers(program, streams, env);