@wirecat/cli-core 0.550.0 → 0.551.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/CHANGELOG.md +176 -0
  2. package/THIRD_PARTY_NOTICES +41 -0
  3. package/dist/calendar/ics.d.ts +1 -1
  4. package/dist/calendar/ics.js +5 -2
  5. package/dist/calendar/windows-zones.generated.d.ts +1 -0
  6. package/dist/calendar/windows-zones.generated.js +146 -0
  7. package/dist/cli/compact.js +2 -0
  8. package/dist/cli/index.d.ts +3 -0
  9. package/dist/cli/index.js +3 -0
  10. package/dist/cli/meeting-findings-input.js +4 -3
  11. package/dist/cli/messenger/backfill-command.js +28 -1
  12. package/dist/cli/messenger/context.js +1 -0
  13. package/dist/cli/messenger/history-gaps.js +4 -4
  14. package/dist/cli/messenger/mcp-command.js +1 -0
  15. package/dist/cli/messenger/messages-delete-command.js +7 -8
  16. package/dist/cli/messenger/projects-command.js +47 -2
  17. package/dist/cli/payment-query.d.ts +8 -0
  18. package/dist/cli/payment-query.js +27 -0
  19. package/dist/cli/project-status-rows.d.ts +8 -0
  20. package/dist/cli/project-status-rows.js +102 -0
  21. package/dist/cli/project-status.d.ts +132 -0
  22. package/dist/cli/project-status.js +248 -0
  23. package/dist/cli/steps.js +4 -1
  24. package/dist/domain/knowledge.d.ts +1 -1
  25. package/dist/domain/knowledge.js +1 -1
  26. package/dist/domain/status-words.d.ts +3 -0
  27. package/dist/domain/status-words.js +44 -0
  28. package/dist/entities/fact.d.ts +12 -1
  29. package/dist/entities/fact.js +13 -0
  30. package/dist/entities/index.d.ts +18 -2
  31. package/dist/entities/meeting-findings.d.ts +1 -1
  32. package/dist/entities/message-transfer.d.ts +16 -0
  33. package/dist/entities/message-transfer.js +6 -0
  34. package/dist/mcp/app-server.js +1 -0
  35. package/dist/mcp/http/oauth.d.ts +2 -1
  36. package/dist/mcp/http/oauth.js +2 -2
  37. package/dist/mcp/http/serve.d.ts +2 -0
  38. package/dist/mcp/http/serve.js +2 -1
  39. package/dist/mcp/server.js +1 -0
  40. package/dist/mcp/surface.js +1 -1
  41. package/dist/meetings/findings-rules.d.ts +14 -3
  42. package/dist/meetings/findings-rules.js +16 -2
  43. package/dist/meetings/index.d.ts +3 -2
  44. package/dist/meetings/index.js +2 -1
  45. package/dist/meetings/meeting-context.d.ts +2 -3
  46. package/dist/meetings/meeting-context.js +3 -4
  47. package/dist/meetings/payment.d.ts +27 -0
  48. package/dist/meetings/payment.js +81 -0
  49. package/dist/services/archive.d.ts +13 -0
  50. package/dist/services/archive.js +56 -5
  51. package/dist/services/deps.d.ts +3 -0
  52. package/dist/services/history-limits.d.ts +2 -0
  53. package/dist/services/index.d.ts +4 -1
  54. package/dist/services/index.js +4 -1
  55. package/dist/services/job-query.d.ts +5 -2
  56. package/dist/services/job-query.js +5 -2
  57. package/dist/services/link-scorers/alias-hits.d.ts +7 -0
  58. package/dist/services/link-scorers/alias-hits.js +54 -0
  59. package/dist/services/link-scorers/chat-senders.d.ts +6 -0
  60. package/dist/services/link-scorers/chat-senders.js +56 -0
  61. package/dist/services/link-scorers/chat-title.d.ts +6 -0
  62. package/dist/services/link-scorers/chat-title.js +44 -0
  63. package/dist/services/link-scorers/email-domain.d.ts +6 -0
  64. package/dist/services/link-scorers/email-domain.js +64 -0
  65. package/dist/services/link-scorers/index.d.ts +9 -0
  66. package/dist/services/link-scorers/index.js +8 -0
  67. package/dist/services/link-scorers/mail-thread.d.ts +3 -0
  68. package/dist/services/link-scorers/mail-thread.js +74 -0
  69. package/dist/services/link-scorers/member-chats.d.ts +3 -0
  70. package/dist/services/link-scorers/member-chats.js +40 -0
  71. package/dist/services/link-scorers/member-dialog.d.ts +3 -0
  72. package/dist/services/link-scorers/member-dialog.js +34 -0
  73. package/dist/services/link-scorers/scan.d.ts +41 -0
  74. package/dist/services/link-scorers/scan.js +244 -0
  75. package/dist/services/link-scorers/shared.d.ts +26 -0
  76. package/dist/services/link-scorers/shared.js +48 -0
  77. package/dist/services/link-scorers/types.d.ts +77 -0
  78. package/dist/services/link-scorers/types.js +1 -0
  79. package/dist/services/project-status/index.d.ts +71 -0
  80. package/dist/services/project-status/index.js +196 -0
  81. package/dist/services/project-status/items.d.ts +10 -0
  82. package/dist/services/project-status/items.js +23 -0
  83. package/dist/services/project-status/signals/amounts.d.ts +20 -0
  84. package/dist/services/project-status/signals/amounts.js +91 -0
  85. package/dist/services/project-status/signals/children.d.ts +13 -0
  86. package/dist/services/project-status/signals/children.js +28 -0
  87. package/dist/services/project-status/signals/coverage.d.ts +13 -0
  88. package/dist/services/project-status/signals/coverage.js +30 -0
  89. package/dist/services/project-status/signals/index.d.ts +3 -0
  90. package/dist/services/project-status/signals/index.js +21 -0
  91. package/dist/services/project-status/signals/last-message.d.ts +4 -0
  92. package/dist/services/project-status/signals/last-message.js +22 -0
  93. package/dist/services/project-status/signals/payments.d.ts +9 -0
  94. package/dist/services/project-status/signals/payments.js +24 -0
  95. package/dist/services/project-status/signals/promises.d.ts +17 -0
  96. package/dist/services/project-status/signals/promises.js +109 -0
  97. package/dist/services/project-status/signals/questions.d.ts +8 -0
  98. package/dist/services/project-status/signals/questions.js +38 -0
  99. package/dist/services/project-status/signals/reply-next.d.ts +19 -0
  100. package/dist/services/project-status/signals/reply-next.js +75 -0
  101. package/dist/services/project-status/signals/topics.d.ts +10 -0
  102. package/dist/services/project-status/signals/topics.js +50 -0
  103. package/dist/services/project-status/types.d.ts +84 -0
  104. package/dist/services/project-status/types.js +1 -0
  105. package/dist/services/proposals/index.d.ts +1 -0
  106. package/dist/services/proposals/index.js +1 -0
  107. package/dist/services/proposals/kinds/findings.d.ts +3 -0
  108. package/dist/services/proposals/kinds/findings.js +8 -1
  109. package/dist/store/decisions.d.ts +5 -1
  110. package/dist/store/decisions.js +5 -1
  111. package/dist/store/evidence-findings.js +1 -0
  112. package/dist/store/index.d.ts +2 -1
  113. package/dist/store/index.js +1 -0
  114. package/dist/store/project-transfer/request.d.ts +1 -1
  115. package/dist/store/project-transfer/schema.d.ts +2 -2
  116. package/dist/store/project-transfer/snapshot-model.d.ts +2 -2
  117. package/dist/store/sqlite/evidence-findings.js +5 -1
  118. package/dist/store/sqlite/facts.d.ts +13 -0
  119. package/dist/store/sqlite/facts.js +64 -6
  120. package/dist/store/sqlite/findings-core.js +14 -1
  121. package/dist/store/sqlite/knowledge.d.ts +16 -2
  122. package/dist/store/sqlite/knowledge.js +1 -0
  123. package/dist/store/sqlite/project-transfer/snapshot.d.ts +1 -1
  124. package/dist/store/sqlite/relation-proposals.d.ts +29 -2
  125. package/dist/store/sqlite/relation-proposals.js +75 -4
  126. package/dist/store/store.d.ts +1 -0
  127. package/package.json +3 -3
  128. package/parity.json +11 -0
package/CHANGELOG.md CHANGED
@@ -7,6 +7,182 @@ shape of this file.
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ ## 0.551.0 — 02.11.2026
11
+
12
+ ### Added
13
+
14
+ - **`messages sync` keeps to the history limits too**, for every messenger: it does not read a group above
15
+ `history_group_members_max` people and stops a chat at its window (`history_dialog_days`, `history_group_days`). The
16
+ answer says `limited` (`group_too_large`, `older_than_window`; `--all` counts both), the skip is recorded for
17
+ `store status` and the search answers' `missing.history_limits`, and `--full-history` reads past it. Live messages
18
+ are never dropped.
19
+ - **`readClientFile` (from `@wirecat/cli-core/oauth`) reads a user's own Google Desktop OAuth client file** (the
20
+ `{ "installed": { client_id, client_secret } }` JSON from Google's console). It refuses a file other users can read
21
+ (`sign-in.client-file-open`) and a file of the wrong shape (`sign-in.client-file-invalid`), and never puts the file's
22
+ contents in an error. gcal, drive and mail each kept a copy of this reader.
23
+ - **`openBrowser(url, browser?)` is exported from `@wirecat/cli-core/cli`**, so a CLI that does not start through
24
+ `startCli` opens the sign-in page the same way: `open`, `rundll32` or `xdg-open`, or the named browser.
25
+ - **iCalendar feeds as read-only calendars.** With `deps.feeds` (`icsFeeds`), `calendars add <url> [--title]` follows a
26
+ public holiday calendar, a sports schedule or a published `.ics` address, and `calendars remove <calendar>` stops.
27
+ `sync` reads each feed after the provider (`syncWithFeeds`), so its events show in `events list`,
28
+ `search events`, `overlapping:` and `events remind`. Repeating events are expanded here (`expandSeries`: RRULE in
29
+ the event's own zone, RDATE, EXDATE and overrides). The address is a secret input, kept by the CLI and never
30
+ printed; a fetch follows redirects only to https and stops at 10 MB and 30 s. `readIcs` takes
31
+ `{ overrides: true }` and returns the calendar's `name`. See `docs/dev/CALENDARS.md`, "Feeds".
32
+ - **History limits for pushed history, per profile.** A messenger whose history is pushed (`history: "store"`,
33
+ WhatsApp) saves one-to-one chats back `history_dialog_days` (1095 by default), groups of more than two people back
34
+ `history_group_days` (365) and nothing of a group above `history_group_members_max` people (50); at most 1095 days.
35
+ The chat row stays, so the chat is still listed. Live messages, `messages sync` and what the store already holds are
36
+ never dropped. `messages sync <chat> --full-history` fetches a chat and keeps its pushed history in full from then on
37
+ (`history_included` in the answer). `ChatsStore` gains `historySkips`, `historyShapes`, `recordHistorySkips` and
38
+ `setHistoryIncluded`; `Saving` takes `historyLimits`.
39
+ - **Helpers for reading money and questions in a chat, for the project status work.** `amountsOf(text)` returns
40
+ every amount in a text, in order (`amountOf` is its first). `MONEY_REQUEST`, `MONEY_DONE` and `PROMISE` word lists
41
+ (ru, en, es) with `moneyClassesOf(text)` say which a text uses. `questions(messages, { asker: "owner" })` lists the
42
+ owner's own questions and whether anyone but the owner answered them, by a reply or by speaking next; the default,
43
+ `"others"`, is what it did before. `linkedChatsOf` is exported, now with each link's `role`, and so is
44
+ `AGENDA_DEFAULTS`, the silence `tasks agenda` waits before it lists a chat.
45
+ - **A command's own history handler learns whether each batch reached the store**: `HistoryBatchHandler` takes
46
+ `saved`, a promise of the same `true` or `false` the messenger gets, so a link can count only what was kept.
47
+ - **Every messenger command that takes a chat or a message also takes the reference other WireCat CLIs print**:
48
+ `chat:<provider>/<account>/<chat>` wherever a chat is named (`messages list chat:max/34871122/63488582`, bot
49
+ commands too), and `msg:<provider>/<account>/<chat>/<message>` wherever a chat or a message is, online and offline. A person lookup takes `contact:<provider>/<id>`. A reference of another
50
+ account is refused, naming the account it belongs to and the profiles of this CLI the store saw on it; it never
51
+ switches account. One of another messenger is refused with the command that opens it (`open it with tg <profile>
52
+ messages list chat:telegram/…`), and another kind (`document:413` given as a chat) with what the argument takes.
53
+ - **`event:<id>` and `meeting:<account>/<meeting>[/<transcript>[/<cue>]]` parse with `parseReference`** and
54
+ `canonicalReference`, as every other kind: `event:N` was printed by the calendar and person answers and could not be
55
+ read back. The kinds are one table, `REF_KINDS`; `looksLikeReference(text)` tells a reference from a title such as
56
+ `Note: plan`. Reference errors carry a reason: `ref.malformed`, `ref.unknown-kind` (with the kind meant:
57
+ `"doc:413" … did you mean document:413?`), `ref.wrong-kind`, `ref.wrong-provider`, `ref.wrong-account`.
58
+ - **`messages list` reads a window bounded on both sides, offline too.** The query takes a start and an end,
59
+ `'date>=2026-10-01 date<2026-10-20'` or `'date:[2026-10-01 TO 2026-10-19]'`, and `id>` with `date<`. A start reads
60
+ forward, oldest first, and stops at the end; an end alone reads back from it, newest page first, as before.
61
+ `--offline` and a messenger whose history is read from the store (WhatsApp) now answer both ways from the store
62
+ instead of refusing. A forward cursor carries the end, `after:<id>~<epoch ms>`, so the next page stops at the same
63
+ place. `--since`, `--since-time`, `--until`, `--until-time`, `--after-time` and `--before-time` on `messages list`
64
+ fail with the query form to type instead. The store's `messages()` window takes `after` (a message or a moment)
65
+ and `until`.
66
+ - **`projects status <project>`: where a project stands, from the store alone, with no model call.** For each chat
67
+ confirmed as the project's or a child project's, in this profile's account: the last message, who replies next
68
+ (you, them, a member a message names, or nobody), open questions both ways, promises (stored tasks, promise facts,
69
+ and first person future words such as "пришлю" or "I'll send"), amounts with their direction and whether a later
70
+ message says paid, payment facts, how long each `--context-query` (words that all appear in one text) has been
71
+ quiet on their side, and how much of the chat the store holds. Open amounts are summed per direction and currency. Word matches are marked `basis: pattern`;
72
+ a message saying "paid" is `paid-mentioned`, never paid. At most two steps: sync the chat held in part, then read
73
+ the chat that needs you most. The other accounts' linked chats are named in `out_of_scope`. tg and max get it with
74
+ the `projects` group; `--compact` draws each signal with its own row.
75
+ - **For an app: `projectStatus(store, input)`, a list of signals in `STATUS_SIGNALS`.** A signal is one file with an
76
+ id, a version and `read`; one that throws is named in `failed` and the others still answer. Each built in signal
77
+ has its compact row in `STATUS_SIGNAL_ROWS`, also in `SHARED_ROWS`. `projectStatusAnswer` and `projectStatusText`
78
+ build the answer and the human form. `input.purposes` picks which chat purposes are read (`["work"]` until links
79
+ carry one). The `messages.list` step takes a `query`.
80
+ - **`--compact`: one line per item for an agent, from the same answer `--json` prints.** `messages list <chat>
81
+ --compact` prints `#<id> · 10-01 09:12 · Анна · <text>` under one header line that names the shared ref prefix
82
+ (`ref: msg:max/500/-1001/<id>`), what every row has in common (the chat's title), the date span and the zone. Rows
83
+ from several chats keep their whole refs. Text stays on one line with control and direction characters made
84
+ visible, and is cut at 400 characters with `…(+N)`; `--full` keeps it whole. The paging, `missing`, the next steps
85
+ and an incomplete answer follow as `# more:`, `# missing:`, `# next:` and `# complete: false` lines on stdout, and
86
+ each command line in them carries `--compact` again. On 1 000 messages of one chat it is about 48 % of the JSON
87
+ bytes; the texts alone are 40 %. `--compact` with `--json`, `--json=…`, `--jsonl` or `--brief` fails with
88
+ `validation_error` (`output.conflict`); it never prompts, and exits with the code JSON would. MCP keeps JSON.
89
+ - **Every command that answers through `renderAnswer` gets it.** A command draws with its own row
90
+ (`answerEntity(cmd, name, schema, { row })`), its app's (`rows: { name: shape }` in the program definition,
91
+ which also draws an app entity listed inside one thing), the shared one in `SHARED_ROWS` (`message`,
92
+ `message.summary`, `message.hit`, `chat`), or the generic row: key, ref or id · title, name or path · state,
93
+ status or due date · text. A record or a list is never printed as a column's value. `RowShape` is data, and `compactText(body, shape,
94
+ options)` is pure, for an app that prints its own answers.
95
+ - What a command prints without `renderAnswer` stays JSON under `--compact`.
96
+ - **`scanProjectLinks(store, { project, sources, since, write, scorers?, threshold?, limit?, purposes? })` and
97
+ `LinkScorer` (`@wirecat/cli-core/services`): memo proposes a project's chats, mail threads and people itself.**
98
+ Six scorers in `SCORERS` (`chat-title`, `alias-hits`, `member-chats`, `member-dialog`, `chat-senders`,
99
+ `mail-thread`) and the shared mail domain rule as `email-domain` in `PERSON_SCORERS`. Signals combine as
100
+ 1 − Π(1 − w); at most 20 new proposals a run by the rule bot `memo-scan`, never confirmed by themselves. Each
101
+ carries `score` and `signals` (scorer, weight, why: ids and counts only). Linked and rejected pairs are skipped,
102
+ a pending one is updated in place; people are matched by identity only, never by name. `write: false` writes nothing.
103
+ - **`knowledge.relationState(scope, { from, to, kind })`** says whether a link joins a pair, whether a proposal for it
104
+ was rejected, and which one is pending; `proposeRelation` takes `score` and `signals`.
105
+ - **Payments are facts of type `payment`.** The amount (text, two decimals), ISO 4217 currency, direction (`to_us` or
106
+ `from_us`), counterparty ref or name, due date, state (`expected`, `paid`, `cancelled`) and item ride in
107
+ `metadata.payment`, read back as `Fact.payment`; no migration. A payment fact cites 1 to 8 references. Paid is a new
108
+ fact that supersedes the expected one, so the history stays. Only the owner accepts or rejects a payment:
109
+ `mayDecide` takes `ownerOnly`. `facts.list({ payment: { open, direction, state } })` filters them; `open` is
110
+ expected and still holding. `paymentTitle(fact)` reads "1 200 EUR to us from Ana, due 31.10 (expected)".
111
+ - **Agents stage payments as findings of kind `payment`** in `projects actions add`; confirming one writes and
112
+ accepts the fact, and a batch holding one is the owner's to decide. The inbox summarises each finding through
113
+ `FACT_SUMMARIES`, one row per kind. `projects status` lists payment facts with their title and adds the expected
114
+ ones to `money.open`, counting an ask the fact cites once.
115
+ - **For an app's `facts list`: `PAYMENT_QUERY_FIELDS` (`open:true`, `direction:to_us`, `state:paid`) and
116
+ `paymentFilterOf(terms)`.**
117
+
118
+ ### Changed — may break callers
119
+
120
+ - **An option after `--` is read as the option.** `messages sync -- -100 --json` now syncs `-100` and answers in JSON;
121
+ it failed with "too many arguments". After `--`, a word that is exactly one of the command's options, its own or a
122
+ global one (`--json`, `--limit 5`, `--json=id`), moves in front of `--`; every other word stays a positional. A
123
+ negative id needs no `--` at all. Commands that take free text keep every word after `--` literal: `messages send` and
124
+ `edit` (also the bot's), `polls create`, `topics create`, `chats create`, `chats folders create`, `contacts rename`,
125
+ `contacts aliases set`, `messages press`, `bot commands set`, `schedule command`, `events remind`, `complete`, and the
126
+ notes templates. Mark such a command with `annotate(command, { literal_after_dashes: true })`; MCP calls are never
127
+ lifted. A script that passed the literal word `--json` as a positional of another command now gets the option instead.
128
+ - **A write to a calendar the account may only read refuses before anything is asked or journaled**
129
+ (`calendar.read-only`): create, quick add, import, update, delete, respond and move, for a feed and for a provider's
130
+ `reader` or `free-busy` calendar alike. Before, only the target of a move was checked; the provider refused the rest.
131
+ `calendars busy` and `search --backend server` leave feed calendars out.
132
+ - **A locator of another account or messenger is refused with a new text and a reason.** `messages show`, `context`,
133
+ `link` and `download` said "that locator belongs to another account; select its profile first" or "…belongs to
134
+ another messenger"; they now say `<ref> belongs to account <id>; profile <name> is account <id>` (`ref.wrong-account`)
135
+ or `<ref> is a <messenger> message; open it with …` (`ref.wrong-provider`). A script matching the old text matches
136
+ the reason instead.
137
+ - **A text of the form `<kind>:<no spaces>` given as a chat is read as a reference**, so a chat titled `note:plan` is
138
+ no longer found by that title; give its id or a part of the title.
139
+ - The provider seeds of `telegram`, `max` and `whatsapp` name their CLI (`command: tg`, `max`, `wa`), which a
140
+ provider clash message now shows (`telegram (messages, from tg)`).
141
+ - **cli-core now needs `@wirecat/cli-base` 0.75.0**, which holds the `--full-history` switch's name and the help
142
+ without `OR` for a list its provider filters. A CLI that pins cli-base bumps it to 0.75.0 together with cli-core.
143
+ - **`FACT_TYPES` gains `payment`, and a `Finding` may be a `PaymentFinding`.** A caller that lists facts with a fixed
144
+ type list, or reads `finding.title` on every finding that is not a fact, narrows by `kind` first. A memo older than
145
+ this release refuses a payment fact in `facts list`; ship memo's bump the same day. `MEETING_FACT_TYPES` is gone:
146
+ `FACT_TYPES` is the one list.
147
+
148
+ ### Fixed
149
+
150
+ - **`messages delete` with no message id refuses with `validation_error`** before it connects. An empty id (`""`
151
+ or a lone comma) used to reach the messenger as a deletion of nothing and answer success.
152
+ - **An MCP read or write whose answer named a step only the person can take (`user_action`) failed as a bug.** The
153
+ router dropped next steps this server does not offer by their command, and such a step has none. It is kept now;
154
+ a command step this server does not offer is still dropped.
155
+ - **A sum written in groups of three read as the wrong amount: `50 000 ₽` was `0` and `4 300 ₽` was `300`**, because
156
+ only the last group was read. Groups after a space, a no-break or thin space, a dot or a comma (`50 000`,
157
+ `1.200 €`, `$1,200`) are one number now, a decimal part after them is kept (`5 000.50 ₽`), and amounts keep two
158
+ decimals instead of a whole number (`15.5 eur` was 16). This is the amount the duplicate check compares between
159
+ two tasks, so a reminder worded with a grouped sum is no longer compared on the wrong figure.
160
+ - **`page.next` of `messages list` and `messages comments` runs as written.** It repeated the query's `id`/`date`
161
+ bound beside `--cursor`, which the command refuses as two starting points; it now keeps only `topic:` and
162
+ `starred:`.
163
+ - **An inclusive start keeps a message sent exactly at it.** `messages list 'date>=2026-10-01'` skipped a message
164
+ sent at the first millisecond of the day, and `date<="…"` one sent at that moment.
165
+ - **`--brief` now cuts a messenger search hit.** It only knew mail's camelCase hits, so `search messages --brief` in
166
+ tg, max and wa printed every field; it now keeps `ref`, `chat_title`, `timestamp`, `sender_name`, `excerpt` and
167
+ `score`. The default and `--full` answers are unchanged. Commands that print through `outputFor` now honour
168
+ `--brief` and `--full` too, and refuse them together as the others do.
169
+ - **An Outlook or Exchange feed's events are read, no longer skipped.** Those feeds name time zones the Windows way,
170
+ `TZID:W. Europe Standard Time`, and the iCalendar reader took only IANA names (`Europe/Berlin`), so `calendars add`
171
+ and `sync` skipped every timed event of such a feed and counted it under `skipped`. A Windows name is now read as
172
+ the IANA zone Unicode CLDR gives it worldwide (territory `001`), in DTSTART, DTEND, RECURRENCE-ID, and in the TZID of
173
+ RDATE and EXDATE, which are stored with the IANA name. The 142 names are a table generated from CLDR release-49
174
+ (`scripts/windows-zones.ts` regenerates it); no new dependency. A TZID that is neither, such as Outlook's
175
+ `(UTC+01:00) Amsterdam, Berlin, Bern`, is still skipped.
176
+ - **A list whose provider filters it no longer says in `--help` that its query takes `OR`.** With
177
+ `@wirecat/cli-base` 0.74.0 → 0.75.0, its `Syntax:` line names only what it takes: `field:value` terms joined by AND,
178
+ and the phrase, `word*` and range forms where a field takes them. Every CLI picks this up at its next cli-core bump.
179
+ - **The browser login page of `mcp serve --http` names what the app asks to reach and the right command.** It always
180
+ said "reach your messages through `<app> mcp --http`"; a CLI that is not a messenger now says what it serves (gcal:
181
+ "your Google Calendar"), and every CLI names `<app> mcp serve --http`. `HttpOptions` takes `reaches` for the
182
+ wording; unset, the page says "your `<app>` data".
183
+ - **`store jobs list` says in its help and its MCP description that its query takes `OR`.** The list reads every job
184
+ file and takes the whole query language, but its registry said the source filters it (`native`).
185
+
10
186
  ## 0.550.0 — 02.11.2026
11
187
 
12
188
  ### Changed — may break callers
@@ -758,3 +758,44 @@ Licensed under SIL Open Font License 1.1; see dist/charts/fonts/OFL.txt and SOUR
758
758
 
759
759
  PNG rasterization uses @resvg/resvg-js under MPL-2.0. Its original sources are
760
760
  available at https://github.com/thx/resvg-js/tree/v2.6.2.
761
+
762
+ Windows time zone names mapped to IANA zones (dist/calendar/windows-zones.generated.js), generated from
763
+ Unicode CLDR release-49, common/supplemental/windowsZones.xml (https://github.com/unicode-org/cldr).
764
+
765
+ Copyright © 2001–2026 Unicode, Inc.
766
+
767
+ NOTICE TO USER: Carefully read the following legal agreement. BY
768
+ DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR
769
+ SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE
770
+ TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT
771
+ DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE.
772
+
773
+ Permission is hereby granted, free of charge, to any person obtaining a
774
+ copy of data files and any associated documentation (the "Data Files") or
775
+ software and any associated documentation (the "Software") to deal in the
776
+ Data Files or Software without restriction, including without limitation
777
+ the rights to use, copy, modify, merge, publish, distribute, and/or sell
778
+ copies of the Data Files or Software, and to permit persons to whom the
779
+ Data Files or Software are furnished to do so, provided that either (a)
780
+ this copyright and permission notice appear with all copies of the Data
781
+ Files or Software, or (b) this copyright and permission notice appear in
782
+ associated Documentation.
783
+
784
+ THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
785
+ KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
786
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
787
+ THIRD PARTY RIGHTS.
788
+
789
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
790
+ BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
791
+ OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
792
+ WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
793
+ ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA
794
+ FILES OR SOFTWARE.
795
+
796
+ Except as contained in this notice, the name of a copyright holder shall
797
+ not be used in advertising or otherwise to promote the sale, use or other
798
+ dealings in these Data Files or Software without prior written
799
+ authorization of the copyright holder.
800
+
801
+ SPDX-License-Identifier: Unicode-3.0
@@ -2,7 +2,7 @@ import type { EntryStatus, EntryTransparency, ParticipantResponse } from "../mee
2
2
  /**
3
3
  * The part of iCalendar (RFC 5545, https://www.rfc-editor.org/rfc/rfc5545) the calendar commands read and write: one
4
4
  * VCALENDAR of VEVENTs, with no VTIMEZONE. Times are written in UTC; a time read with a TZID keeps that zone's name,
5
- * which the provider takes as it is.
5
+ * which the provider takes as it is. A Windows zone name, as Outlook and Exchange write it, is read as its IANA zone.
6
6
  */
7
7
  export interface IcsEvent {
8
8
  uid: string;
@@ -1,5 +1,6 @@
1
1
  import { CliError } from "@wirecat/cli-base";
2
2
  import { zonedMoment } from "@wirecat/cli-base/query";
3
+ import { WINDOWS_ZONES } from "./windows-zones.generated.js";
3
4
  const CRLF = "\r\n";
4
5
  const LINE_OCTETS = 75;
5
6
  const escapeText = (text) => text
@@ -129,6 +130,8 @@ const contentLine = (line) => {
129
130
  const unescapeText = (text) => text.replace(/\\([\\;,nN])/gu, (_match, char) => (char === "n" || char === "N" ? "\n" : char));
130
131
  const DATE = /^(\d{4})(\d{2})(\d{2})$/u;
131
132
  const DATE_TIME = /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(Z?)$/u;
133
+ /** `W. Europe Standard Time` → `Europe/Berlin`, CLDR's zone for the name worldwide; any other TZID as written. */
134
+ const ianaZone = (tzid) => (Object.hasOwn(WINDOWS_ZONES, tzid) ? WINDOWS_ZONES[tzid] : tzid);
132
135
  /** A DATE or DATE-TIME: UTC with `Z`, in its TZID, or floating, read in `zone`. */
133
136
  const momentOf = (line, zone) => {
134
137
  const value = line.value.trim();
@@ -146,7 +149,7 @@ const momentOf = (line, zone) => {
146
149
  if (utc)
147
150
  return { at: Date.parse(`${local}Z`), zone: "UTC" };
148
151
  const named = line.params.TZID;
149
- const where = named ?? zone;
152
+ const where = named === undefined ? zone : ianaZone(named);
150
153
  try {
151
154
  return { at: zonedMoment(local, where, { start: 0, end: local.length }), zone: where };
152
155
  }
@@ -231,7 +234,7 @@ const eventOf = (lines, zone, overrides = false) => {
231
234
  const recurrence = lines
232
235
  .filter(({ name }) => ["RRULE", "RDATE", "EXDATE"].includes(name))
233
236
  .map(({ name, params, value }) => {
234
- const kept = Object.entries(params).map(([key, param]) => `;${key}=${paramValue(param)}`);
237
+ const kept = Object.entries(params).map(([key, param]) => `;${key}=${paramValue(key === "TZID" ? ianaZone(param) : param)}`);
235
238
  return `${name}${kept.join("")}:${value.trim()}`;
236
239
  });
237
240
  if (recurrence.length)
@@ -0,0 +1 @@
1
+ export declare const WINDOWS_ZONES: Readonly<Record<string, string>>;
@@ -0,0 +1,146 @@
1
+ // Generated by scripts/windows-zones.ts from CLDR release-49 (Unicode License v3, THIRD_PARTY_NOTICES); do not edit by hand.
2
+ // https://raw.githubusercontent.com/unicode-org/cldr/release-49/common/supplemental/windowsZones.xml
3
+ export const WINDOWS_ZONES = {
4
+ "Afghanistan Standard Time": "Asia/Kabul",
5
+ "Alaskan Standard Time": "America/Anchorage",
6
+ "Alberta Standard Time": "America/Edmonton",
7
+ "Aleutian Standard Time": "America/Adak",
8
+ "Altai Standard Time": "Asia/Barnaul",
9
+ "Arab Standard Time": "Asia/Riyadh",
10
+ "Arabian Standard Time": "Asia/Dubai",
11
+ "Arabic Standard Time": "Asia/Baghdad",
12
+ "Argentina Standard Time": "America/Buenos_Aires",
13
+ "Astrakhan Standard Time": "Europe/Astrakhan",
14
+ "Atlantic Standard Time": "America/Halifax",
15
+ "AUS Central Standard Time": "Australia/Darwin",
16
+ "Aus Central W. Standard Time": "Australia/Eucla",
17
+ "AUS Eastern Standard Time": "Australia/Sydney",
18
+ "Azerbaijan Standard Time": "Asia/Baku",
19
+ "Azores Standard Time": "Atlantic/Azores",
20
+ "Bahia Standard Time": "America/Bahia",
21
+ "Bangladesh Standard Time": "Asia/Dhaka",
22
+ "Belarus Standard Time": "Europe/Minsk",
23
+ "Bougainville Standard Time": "Pacific/Bougainville",
24
+ "British Columbia Standard Time": "America/Vancouver",
25
+ "Canada Central Standard Time": "America/Regina",
26
+ "Cape Verde Standard Time": "Atlantic/Cape_Verde",
27
+ "Caucasus Standard Time": "Asia/Yerevan",
28
+ "Cen. Australia Standard Time": "Australia/Adelaide",
29
+ "Central America Standard Time": "America/Guatemala",
30
+ "Central Asia Standard Time": "Asia/Bishkek",
31
+ "Central Brazilian Standard Time": "America/Cuiaba",
32
+ "Central Europe Standard Time": "Europe/Budapest",
33
+ "Central European Standard Time": "Europe/Warsaw",
34
+ "Central Pacific Standard Time": "Pacific/Guadalcanal",
35
+ "Central Standard Time": "America/Chicago",
36
+ "Central Standard Time (Mexico)": "America/Mexico_City",
37
+ "Chatham Islands Standard Time": "Pacific/Chatham",
38
+ "China Standard Time": "Asia/Shanghai",
39
+ "Cuba Standard Time": "America/Havana",
40
+ "Dateline Standard Time": "Etc/GMT+12",
41
+ "E. Africa Standard Time": "Africa/Nairobi",
42
+ "E. Australia Standard Time": "Australia/Brisbane",
43
+ "E. Europe Standard Time": "Europe/Chisinau",
44
+ "E. South America Standard Time": "America/Sao_Paulo",
45
+ "Easter Island Standard Time": "Pacific/Easter",
46
+ "Eastern Standard Time": "America/New_York",
47
+ "Eastern Standard Time (Mexico)": "America/Cancun",
48
+ "Egypt Standard Time": "Africa/Cairo",
49
+ "Ekaterinburg Standard Time": "Asia/Yekaterinburg",
50
+ "Fiji Standard Time": "Pacific/Fiji",
51
+ "FLE Standard Time": "Europe/Kiev",
52
+ "Georgian Standard Time": "Asia/Tbilisi",
53
+ "GMT Standard Time": "Europe/London",
54
+ "Greenland Standard Time": "America/Godthab",
55
+ "Greenwich Standard Time": "Atlantic/Reykjavik",
56
+ "GTB Standard Time": "Europe/Bucharest",
57
+ "Haiti Standard Time": "America/Port-au-Prince",
58
+ "Hawaiian Standard Time": "Pacific/Honolulu",
59
+ "India Standard Time": "Asia/Calcutta",
60
+ "Iran Standard Time": "Asia/Tehran",
61
+ "Israel Standard Time": "Asia/Jerusalem",
62
+ "Jordan Standard Time": "Asia/Amman",
63
+ "Kaliningrad Standard Time": "Europe/Kaliningrad",
64
+ "Korea Standard Time": "Asia/Seoul",
65
+ "Libya Standard Time": "Africa/Tripoli",
66
+ "Line Islands Standard Time": "Pacific/Kiritimati",
67
+ "Lord Howe Standard Time": "Australia/Lord_Howe",
68
+ "Magadan Standard Time": "Asia/Magadan",
69
+ "Magallanes Standard Time": "America/Punta_Arenas",
70
+ "Manitoba Standard Time": "America/Winnipeg",
71
+ "Marquesas Standard Time": "Pacific/Marquesas",
72
+ "Mauritius Standard Time": "Indian/Mauritius",
73
+ "Middle East Standard Time": "Asia/Beirut",
74
+ "Montevideo Standard Time": "America/Montevideo",
75
+ "Morocco Standard Time": "Africa/Casablanca",
76
+ "Mountain Standard Time": "America/Denver",
77
+ "Mountain Standard Time (Mexico)": "America/Mazatlan",
78
+ "Myanmar Standard Time": "Asia/Rangoon",
79
+ "N. Central Asia Standard Time": "Asia/Novosibirsk",
80
+ "Namibia Standard Time": "Africa/Windhoek",
81
+ "Nepal Standard Time": "Asia/Katmandu",
82
+ "New Zealand Standard Time": "Pacific/Auckland",
83
+ "Newfoundland Standard Time": "America/St_Johns",
84
+ "Norfolk Standard Time": "Pacific/Norfolk",
85
+ "North Asia East Standard Time": "Asia/Irkutsk",
86
+ "North Asia Standard Time": "Asia/Krasnoyarsk",
87
+ "North Korea Standard Time": "Asia/Pyongyang",
88
+ "Omsk Standard Time": "Asia/Omsk",
89
+ "Pacific SA Standard Time": "America/Santiago",
90
+ "Pacific Standard Time": "America/Los_Angeles",
91
+ "Pacific Standard Time (Mexico)": "America/Tijuana",
92
+ "Pakistan Standard Time": "Asia/Karachi",
93
+ "Paraguay Standard Time": "America/Asuncion",
94
+ "Qyzylorda Standard Time": "Asia/Qyzylorda",
95
+ "Romance Standard Time": "Europe/Paris",
96
+ "Russia Time Zone 10": "Asia/Srednekolymsk",
97
+ "Russia Time Zone 11": "Asia/Kamchatka",
98
+ "Russia Time Zone 3": "Europe/Samara",
99
+ "Russian Standard Time": "Europe/Moscow",
100
+ "SA Eastern Standard Time": "America/Cayenne",
101
+ "SA Pacific Standard Time": "America/Bogota",
102
+ "SA Western Standard Time": "America/La_Paz",
103
+ "Saint Pierre Standard Time": "America/Miquelon",
104
+ "Sakhalin Standard Time": "Asia/Sakhalin",
105
+ "Samoa Standard Time": "Pacific/Apia",
106
+ "Sao Tome Standard Time": "Africa/Sao_Tome",
107
+ "Saratov Standard Time": "Europe/Saratov",
108
+ "SE Asia Standard Time": "Asia/Bangkok",
109
+ "Singapore Standard Time": "Asia/Singapore",
110
+ "South Africa Standard Time": "Africa/Johannesburg",
111
+ "South Sudan Standard Time": "Africa/Juba",
112
+ "Sri Lanka Standard Time": "Asia/Colombo",
113
+ "Sudan Standard Time": "Africa/Khartoum",
114
+ "Syria Standard Time": "Asia/Damascus",
115
+ "Taipei Standard Time": "Asia/Taipei",
116
+ "Tasmania Standard Time": "Australia/Hobart",
117
+ "Tocantins Standard Time": "America/Araguaina",
118
+ "Tokyo Standard Time": "Asia/Tokyo",
119
+ "Tomsk Standard Time": "Asia/Tomsk",
120
+ "Tonga Standard Time": "Pacific/Tongatapu",
121
+ "Transbaikal Standard Time": "Asia/Chita",
122
+ "Turkey Standard Time": "Europe/Istanbul",
123
+ "Turks And Caicos Standard Time": "America/Grand_Turk",
124
+ "Ulaanbaatar Standard Time": "Asia/Ulaanbaatar",
125
+ "US Eastern Standard Time": "America/Indianapolis",
126
+ "US Mountain Standard Time": "America/Phoenix",
127
+ UTC: "Etc/UTC",
128
+ "UTC-02": "Etc/GMT+2",
129
+ "UTC-08": "Etc/GMT+8",
130
+ "UTC-09": "Etc/GMT+9",
131
+ "UTC-11": "Etc/GMT+11",
132
+ "UTC+12": "Etc/GMT-12",
133
+ "UTC+13": "Etc/GMT-13",
134
+ "Venezuela Standard Time": "America/Caracas",
135
+ "Vladivostok Standard Time": "Asia/Vladivostok",
136
+ "Volgograd Standard Time": "Europe/Volgograd",
137
+ "W. Australia Standard Time": "Australia/Perth",
138
+ "W. Central Africa Standard Time": "Africa/Lagos",
139
+ "W. Europe Standard Time": "Europe/Berlin",
140
+ "W. Mongolia Standard Time": "Asia/Hovd",
141
+ "West Asia Standard Time": "Asia/Tashkent",
142
+ "West Bank Standard Time": "Asia/Hebron",
143
+ "West Pacific Standard Time": "Pacific/Port_Moresby",
144
+ "Yakutsk Standard Time": "Asia/Yakutsk",
145
+ "Yukon Standard Time": "America/Whitehorse",
146
+ };
@@ -2,6 +2,7 @@ import { visibleControls } from "@wirecat/cli-base";
2
2
  import { metaOf } from "@wirecat/cli-base/commands";
3
3
  import { ENTITIES } from "../entities/index.js";
4
4
  import { answerRow } from "./answer-entity.js";
5
+ import { STATUS_ROWS } from "./project-status-rows.js";
5
6
  export const TEXT_CHARS = 400;
6
7
  const sender = { field: ["sender_is_me", "sender_name"], tag: "me" };
7
8
  const messageRow = {
@@ -39,6 +40,7 @@ export const SHARED_ROWS = {
39
40
  { field: "last_message_at", as: "time" },
40
41
  ],
41
42
  },
43
+ ...STATUS_ROWS,
42
44
  };
43
45
  /** Any record: what names it, what it is called, what state it is in, then what it says. */
44
46
  export const GENERIC_ROW = {
@@ -111,10 +111,13 @@ export { type Saving, stored } from "./messenger/stored.js";
111
111
  export { dateOrNone } from "./messenger/tasks-command.js";
112
112
  export { MODEL_CATALOGUE_QUERY, type ModelCatalogueOptions } from "./model-catalogue-command.js";
113
113
  export { positiveCount, window, withPaging } from "./paging.js";
114
+ export { PAYMENT_QUERY_FIELDS, paymentFilterOf } from "./payment-query.js";
114
115
  export { type CommandPermissions, commandPathKey, commandPermissions, knownBeside, knownPermissionKeys, type PermissionKeyOf, type PermissionKeySource, refuseUnknownPermissionKeys, unknownPermissionIssues, unknownPermissionKeys, } from "./permission-keys.js";
115
116
  export { asFirstWord, commandWords, liftProfile, profileFree, refuseCommandName, rootOf, takesProfile, usableProfileName, } from "./profile.js";
116
117
  export { createProgram, type ProgramDefinition, type ProgramOptions, type RunOptions, run } from "./program.js";
117
118
  export { type ProjectTextHints, projectAnswerParts, projectCutoffTime, projectEvidenceText, projectWindowOf, withSyncCommands, } from "./project-evidence.js";
119
+ export { type ProjectStatusHints, type ProjectStatusItem, projectStatusAnswer, projectStatusRow, projectStatusSchema, projectStatusText, } from "./project-status.js";
120
+ export { STATUS_ROWS, STATUS_SIGNAL_ROWS } from "./project-status-rows.js";
118
121
  export { fieldsOf, projectFields } from "./result-fields.js";
119
122
  export type { ScheduleHost } from "./schedule/schedule-command.js";
120
123
  export { type Config, type Configuration, count, first, flag, fromFile, type GlobalFlags, NONE_NAMED, namedProfile, type ProfileKind, plain, type ResolveOptions, type Settings, type SettingsExtension, type SettingsExtensionContext, type Source, settingsFor, } from "./settings.js";
package/dist/cli/index.js CHANGED
@@ -101,10 +101,13 @@ export { stored } from "./messenger/stored.js";
101
101
  export { dateOrNone } from "./messenger/tasks-command.js";
102
102
  export { MODEL_CATALOGUE_QUERY } from "./model-catalogue-command.js";
103
103
  export { positiveCount, window, withPaging } from "./paging.js";
104
+ export { PAYMENT_QUERY_FIELDS, paymentFilterOf } from "./payment-query.js";
104
105
  export { commandPathKey, commandPermissions, knownBeside, knownPermissionKeys, refuseUnknownPermissionKeys, unknownPermissionIssues, unknownPermissionKeys, } from "./permission-keys.js";
105
106
  export { asFirstWord, commandWords, liftProfile, profileFree, refuseCommandName, rootOf, takesProfile, usableProfileName, } from "./profile.js";
106
107
  export { createProgram, run } from "./program.js";
107
108
  export { projectAnswerParts, projectCutoffTime, projectEvidenceText, projectWindowOf, withSyncCommands, } from "./project-evidence.js";
109
+ export { projectStatusAnswer, projectStatusRow, projectStatusSchema, projectStatusText, } from "./project-status.js";
110
+ export { STATUS_ROWS, STATUS_SIGNAL_ROWS } from "./project-status-rows.js";
108
111
  export { fieldsOf, projectFields } from "./result-fields.js";
109
112
  export { count, first, flag, fromFile, NONE_NAMED, namedProfile, plain, settingsFor, } from "./settings.js";
110
113
  export { readSetupAnswers, setupPermissionCeiling } from "./setup-answers.js";
@@ -17,7 +17,8 @@ const parseFindingsJson = (text, label, validate) => {
17
17
  catch {
18
18
  throw new CliError("validation_error", `${label} input must be valid JSON`);
19
19
  }
20
- const decode = (value, depth = 0) => {
20
+ // A payment keeps its snake_case fields: it is stored and printed as written.
21
+ const decode = (value, depth = 0, verbatim = false) => {
21
22
  if (depth > 16)
22
23
  throw new CliError("validation_error", `${label} JSON is nested too deeply`);
23
24
  if (Array.isArray(value))
@@ -28,10 +29,10 @@ const parseFindingsJson = (text, label, validate) => {
28
29
  for (const [key, item] of Object.entries(value)) {
29
30
  if (/[A-Z]/.test(key))
30
31
  throw new CliError("validation_error", `${label} JSON fields use snake_case`);
31
- const name = keys[key] ?? key;
32
+ const name = verbatim ? key : (keys[key] ?? key);
32
33
  if (Object.hasOwn(output, name))
33
34
  throw new CliError("validation_error", `duplicate ${label} field`);
34
- Object.defineProperty(output, name, { value: decode(item, depth + 1), enumerable: true });
35
+ Object.defineProperty(output, name, { value: decode(item, depth + 1, key === "payment"), enumerable: true });
35
36
  }
36
37
  return output;
37
38
  };
@@ -33,6 +33,21 @@ import { stopOnSignal } from "./patience.js";
33
33
  const ALL_SINCE = "90d";
34
34
  /** What a background `--all` job is listed under; no chat argument is spelled like it. */
35
35
  const ALL_CHATS = "--all";
36
+ /** Says where the history limits (history_* settings) stopped the read, so an agent does not take it for all. */
37
+ const limitedLine = (result, status) => {
38
+ if ("items" in result) {
39
+ const { groupTooLarge = 0, olderThanWindow = 0 } = result.limited ?? {};
40
+ if (groupTooLarge + olderThanWindow === 0)
41
+ return undefined;
42
+ return (`the history limits (history_* settings) left ${groupTooLarge} big group(s) unread and read ${olderThanWindow} ` +
43
+ `chat(s) only to the window's start — \`${status}\` lists them; \`messages sync <chat> --full-history\` reads one in full`);
44
+ }
45
+ if (result.limited === "group_too_large")
46
+ return "not read: a group above history_group_members_max — `--full-history` reads it and keeps it in full";
47
+ if (result.limited === "older_than_window")
48
+ return "read to the history window's start (history_dialog_days / history_group_days) — `--full-history` reads further";
49
+ return undefined;
50
+ };
36
51
  export const syncCommand = (messenger) => {
37
52
  const fetching = messenger.fetching ?? FETCHING;
38
53
  return annotate(new Command("sync"), {
@@ -58,7 +73,7 @@ export const syncCommand = (messenger) => {
58
73
  .option("--catch-up-chunks <n>", "at most this many local vector chunks", wholeNumber)
59
74
  .option("--catch-up-messages <n>", "skip a graph rebuild larger than this many messages", wholeNumber)
60
75
  .option("--catch-up-time <duration>", "local preparation time budget, 30s by default")
61
- .option("--full-history", "keep this chat's pushed history in full from now on, past the history limits (history_* settings)")
76
+ .option("--full-history", "read and keep this chat's history in full from now on, past the history limits (history_* settings)")
62
77
  .option("--background", "run as a job that outlives this command; `store jobs show` follows it")
63
78
  .option("--dry-run", "only estimate how many messages, requests and minutes a full fetch would still take — from the store, no request")
64
79
  .action(async function (chat) {
@@ -151,6 +166,7 @@ export const syncCommand = (messenger) => {
151
166
  limit,
152
167
  pageSize,
153
168
  pauseMs,
169
+ ...(include ? { fullHistory: true } : {}),
154
170
  catchUp: prepare ? preparation : false,
155
171
  ...(sinceMs === undefined ? {} : { sinceMs }),
156
172
  ...(last === undefined ? {} : { last }),
@@ -191,9 +207,20 @@ export const syncCommand = (messenger) => {
191
207
  ]
192
208
  : []),
193
209
  ...(toReview > 0 ? [nextStep("mentions.review", { args: { new: true }, values: { shown } })] : []),
210
+ ...("limited" in result && result.limited && "chat" in result && result.chat
211
+ ? [
212
+ {
213
+ ...nextStep("messages.sync", { args: { chat: result.chat, full_history: true } }),
214
+ why: "reads this chat past the history limits and keeps it in full from now on",
215
+ },
216
+ ]
217
+ : []),
194
218
  ],
195
219
  meta: window.meta,
196
220
  }));
221
+ const limitedNote = limitedLine(result, lineOf(this, "store status"));
222
+ if (limitedNote)
223
+ context.renderer.note(limitedNote);
197
224
  if (tasks?.held)
198
225
  context.renderer.note(`the first sync left ${tasks.held} possible task(s) in ${tasks.heldChats ?? 0} chat(s) unopened ` +
199
226
  `(config task_first_pass_per_chat) — ask the owner, then \`${lineOf(this, "tasks scan <chat>")}\``);
@@ -422,6 +422,7 @@ export const messengerContext = (command, messenger, resolving = {}) => {
422
422
  // Turning recording off by name (--no-record, or record: false) keeps no search history either.
423
423
  history: base.settings.record,
424
424
  searchCatchUp: base.settings.searchCatchUp,
425
+ ...(base.settings.historyLimits ? { historyLimits: base.settings.historyLimits } : {}),
425
426
  pictures: base.settings.pictureSearch,
426
427
  reranker: base.settings.searchReranker,
427
428
  guard,
@@ -1,7 +1,7 @@
1
1
  import { nextStep } from "../steps.js";
2
2
  const SHOWN = 5;
3
3
  const reasonOf = (skip) => skip.reason === "group_too_large"
4
- ? `a group of ${skip.members ?? "many"} people, not saved`
4
+ ? `a group of ${skip.members ?? "many"} people, not ${skip.unread ? "read" : "saved"}`
5
5
  : `only from ${skip.keptFrom?.slice(0, 10) ?? "the window's start"}`;
6
6
  const nameOf = (skip) => skip.title ?? skip.chatId;
7
7
  /**
@@ -15,7 +15,7 @@ export const historyLimitGaps = (skips) => {
15
15
  const messages = skips.reduce((sum, skip) => sum + skip.messages, 0);
16
16
  const listed = skips
17
17
  .slice(0, SHOWN)
18
- .map((skip) => `${nameOf(skip)} (${skip.messages}, ${reasonOf(skip)})`)
18
+ .map((skip) => `${nameOf(skip)} (${skip.messages > 0 ? `${skip.messages}, ` : ""}${reasonOf(skip)})`)
19
19
  .join("; ");
20
20
  const more = skips.length > SHOWN ? `; and ${skips.length - SHOWN} more — \`store status\` lists them` : "";
21
21
  return {
@@ -28,8 +28,8 @@ export const historyLimitGaps = (skips) => {
28
28
  missing: [
29
29
  {
30
30
  field: "history_limits",
31
- why: `the history limits (history_* settings) left ${messages} pushed messages of ${skips.length} chats out of ` +
32
- `the store: ${listed}${more}`,
31
+ why: `the history limits (history_* settings) left history of ${skips.length} chats out of the store` +
32
+ `${messages > 0 ? ` (${messages} pushed messages counted)` : ""}: ${listed}${more}`,
33
33
  step: "messages.sync",
34
34
  },
35
35
  ],
@@ -355,6 +355,7 @@ const serveProfiles = async (self, context, host, flags, set) => {
355
355
  tokenFile,
356
356
  lockFile: httpLockFile(tokenFile),
357
357
  version: app.version,
358
+ reaches: app.messenger === false ? `your ${host.name}` : "your messages",
358
359
  onCode: (code, expires) => context.renderer.note(env[mcpHttpMarker(app.envPrefix)] === undefined
359
360
  ? `login code for a new browser app: ${code} (until ${expires.toTimeString().slice(0, 5)}; a new one after each login)`
360
361
  : `a new login code for a browser app: \`${app.command} mcp server status --profiles ${set.option}\``),