@wirecat/cli-memo 0.131.67 → 0.131.68

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