@wirecat/cli-core 0.549.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 (137) hide show
  1. package/CHANGELOG.md +223 -22
  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/errors/reasons.js +1 -0
  35. package/dist/mail/partial.d.ts +9 -3
  36. package/dist/mail/partial.js +35 -10
  37. package/dist/mail/source.d.ts +7 -1
  38. package/dist/mail/sync.js +1 -1
  39. package/dist/mcp/app-server.js +1 -0
  40. package/dist/mcp/http/oauth.d.ts +2 -1
  41. package/dist/mcp/http/oauth.js +2 -2
  42. package/dist/mcp/http/serve.d.ts +2 -0
  43. package/dist/mcp/http/serve.js +2 -1
  44. package/dist/mcp/server.js +1 -0
  45. package/dist/mcp/surface.js +1 -1
  46. package/dist/meetings/findings-rules.d.ts +14 -3
  47. package/dist/meetings/findings-rules.js +16 -2
  48. package/dist/meetings/index.d.ts +3 -2
  49. package/dist/meetings/index.js +2 -1
  50. package/dist/meetings/meeting-context.d.ts +2 -3
  51. package/dist/meetings/meeting-context.js +3 -4
  52. package/dist/meetings/payment.d.ts +27 -0
  53. package/dist/meetings/payment.js +81 -0
  54. package/dist/services/archive.d.ts +13 -0
  55. package/dist/services/archive.js +56 -5
  56. package/dist/services/deps.d.ts +3 -0
  57. package/dist/services/history-limits.d.ts +2 -0
  58. package/dist/services/index.d.ts +4 -1
  59. package/dist/services/index.js +4 -1
  60. package/dist/services/job-query.d.ts +5 -2
  61. package/dist/services/job-query.js +5 -2
  62. package/dist/services/link-scorers/alias-hits.d.ts +7 -0
  63. package/dist/services/link-scorers/alias-hits.js +54 -0
  64. package/dist/services/link-scorers/chat-senders.d.ts +6 -0
  65. package/dist/services/link-scorers/chat-senders.js +56 -0
  66. package/dist/services/link-scorers/chat-title.d.ts +6 -0
  67. package/dist/services/link-scorers/chat-title.js +44 -0
  68. package/dist/services/link-scorers/email-domain.d.ts +6 -0
  69. package/dist/services/link-scorers/email-domain.js +64 -0
  70. package/dist/services/link-scorers/index.d.ts +9 -0
  71. package/dist/services/link-scorers/index.js +8 -0
  72. package/dist/services/link-scorers/mail-thread.d.ts +3 -0
  73. package/dist/services/link-scorers/mail-thread.js +74 -0
  74. package/dist/services/link-scorers/member-chats.d.ts +3 -0
  75. package/dist/services/link-scorers/member-chats.js +40 -0
  76. package/dist/services/link-scorers/member-dialog.d.ts +3 -0
  77. package/dist/services/link-scorers/member-dialog.js +34 -0
  78. package/dist/services/link-scorers/scan.d.ts +41 -0
  79. package/dist/services/link-scorers/scan.js +244 -0
  80. package/dist/services/link-scorers/shared.d.ts +26 -0
  81. package/dist/services/link-scorers/shared.js +48 -0
  82. package/dist/services/link-scorers/types.d.ts +77 -0
  83. package/dist/services/link-scorers/types.js +1 -0
  84. package/dist/services/project-status/index.d.ts +71 -0
  85. package/dist/services/project-status/index.js +196 -0
  86. package/dist/services/project-status/items.d.ts +10 -0
  87. package/dist/services/project-status/items.js +23 -0
  88. package/dist/services/project-status/signals/amounts.d.ts +20 -0
  89. package/dist/services/project-status/signals/amounts.js +91 -0
  90. package/dist/services/project-status/signals/children.d.ts +13 -0
  91. package/dist/services/project-status/signals/children.js +28 -0
  92. package/dist/services/project-status/signals/coverage.d.ts +13 -0
  93. package/dist/services/project-status/signals/coverage.js +30 -0
  94. package/dist/services/project-status/signals/index.d.ts +3 -0
  95. package/dist/services/project-status/signals/index.js +21 -0
  96. package/dist/services/project-status/signals/last-message.d.ts +4 -0
  97. package/dist/services/project-status/signals/last-message.js +22 -0
  98. package/dist/services/project-status/signals/payments.d.ts +9 -0
  99. package/dist/services/project-status/signals/payments.js +24 -0
  100. package/dist/services/project-status/signals/promises.d.ts +17 -0
  101. package/dist/services/project-status/signals/promises.js +109 -0
  102. package/dist/services/project-status/signals/questions.d.ts +8 -0
  103. package/dist/services/project-status/signals/questions.js +38 -0
  104. package/dist/services/project-status/signals/reply-next.d.ts +19 -0
  105. package/dist/services/project-status/signals/reply-next.js +75 -0
  106. package/dist/services/project-status/signals/topics.d.ts +10 -0
  107. package/dist/services/project-status/signals/topics.js +50 -0
  108. package/dist/services/project-status/types.d.ts +84 -0
  109. package/dist/services/project-status/types.js +1 -0
  110. package/dist/services/proposals/index.d.ts +1 -0
  111. package/dist/services/proposals/index.js +1 -0
  112. package/dist/services/proposals/kinds/findings.d.ts +3 -0
  113. package/dist/services/proposals/kinds/findings.js +8 -1
  114. package/dist/store/decisions.d.ts +5 -1
  115. package/dist/store/decisions.js +5 -1
  116. package/dist/store/evidence-findings.js +1 -0
  117. package/dist/store/index.d.ts +2 -1
  118. package/dist/store/index.js +1 -0
  119. package/dist/store/project-transfer/request.d.ts +1 -1
  120. package/dist/store/project-transfer/schema.d.ts +2 -2
  121. package/dist/store/project-transfer/snapshot-model.d.ts +2 -2
  122. package/dist/store/sqlite/email-locations.d.ts +22 -1
  123. package/dist/store/sqlite/email-locations.js +29 -2
  124. package/dist/store/sqlite/emails.d.ts +3 -7
  125. package/dist/store/sqlite/emails.js +4 -2
  126. package/dist/store/sqlite/evidence-findings.js +5 -1
  127. package/dist/store/sqlite/facts.d.ts +13 -0
  128. package/dist/store/sqlite/facts.js +64 -6
  129. package/dist/store/sqlite/findings-core.js +14 -1
  130. package/dist/store/sqlite/knowledge.d.ts +16 -2
  131. package/dist/store/sqlite/knowledge.js +1 -0
  132. package/dist/store/sqlite/project-transfer/snapshot.d.ts +1 -1
  133. package/dist/store/sqlite/relation-proposals.d.ts +29 -2
  134. package/dist/store/sqlite/relation-proposals.js +75 -4
  135. package/dist/store/store.d.ts +1 -0
  136. package/package.json +3 -3
  137. package/parity.json +11 -0
package/CHANGELOG.md CHANGED
@@ -7,6 +7,229 @@ 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
+
186
+ ## 0.550.0 — 02.11.2026
187
+
188
+ ### Changed — may break callers
189
+
190
+ - **cli-core registers `mail.search-refused`.** A CLI that listed it in its own reasons gets "listed twice" from
191
+ `defineReasons` and must drop its entry; mail does.
192
+ - **`MailStore` has `markPartial(accountId, externalId, mark)`, and `partialEmails` answers `stuck` and leaves out an
193
+ email marked `partial.stuck`.** A store of its own must add both. `PartialOutcome` (`syncMail`'s `partial`) gains
194
+ `stuck`, and `partial` is answered whenever any of `completed`, `left` or `stuck` is not 0.
195
+ - **cli-core now needs `@wirecat/cli-base` 0.74.0**, which holds the `--compact` switch's name. A CLI that pins
196
+ cli-base bumps it to 0.74.0 together with cli-core.
197
+
198
+ ### Fixed
199
+
200
+ - **A partial email the server no longer gives at its stored place is given up on instead of read whole on every
201
+ sync.** `completePartial` reads it at its newest stored location under that location's UIDVALIDITY (the new optional
202
+ `Quick.validity`, which a source checks before reading), counts each failed read in `metadata.partial.tries`, and
203
+ after 3, or at once when the place holds another message, marks it `partial.stuck`: it stays readable as it is, is
204
+ counted in `partial.stuck`, and no longer takes one of the 100 places a run reads, so it never holds back newer ones.
205
+ An email with no stored location is given up on at once.
206
+ - **A server search never stores a partial copy over an email already stored whole.** A sync in another process could
207
+ save the email between the search's check and its write; the write then dropped every attachment. The store now
208
+ keeps a live whole email as it is when a copy marked `partial` is saved over it.
209
+ - **`--brief` now cuts a messenger search hit.** It only knew mail's camelCase hits, so `search messages --brief` in
210
+ tg, max and wa printed every field; it now keeps `ref`, `chat_title`, `timestamp`, `sender_name`, `excerpt` and
211
+ `score`. The default and `--full` answers are unchanged. Commands that print through `outputFor` now honour
212
+ `--brief` and `--full` too, and refuse them together as the others do.
213
+
214
+ ### Added
215
+
216
+ - **`--compact`: one line per item for an agent, from the same answer `--json` prints.** `messages list <chat>
217
+ --compact` prints `#<id> · 10-01 09:12 · Анна · <text>` under one header line that names the shared ref prefix
218
+ (`ref: msg:max/500/-1001/<id>`), what every row has in common (the chat's title), the date span and the zone. Rows
219
+ from several chats keep their whole refs. Text stays on one line with control and direction characters made
220
+ visible, and is cut at 400 characters with `…(+N)`; `--full` keeps it whole. The paging, `missing`, the next steps
221
+ and an incomplete answer follow as `# more:`, `# missing:`, `# next:` and `# complete: false` lines on stdout, and
222
+ each command line in them carries `--compact` again. On 1 000 messages of one chat it is about 48 % of the JSON
223
+ bytes; the texts alone are 40 %. `--compact` with `--json`, `--json=…`, `--jsonl` or `--brief` fails with
224
+ `validation_error` (`output.conflict`); it never prompts, and exits with the code JSON would. MCP keeps JSON.
225
+ - **Every command that answers through `renderAnswer` gets it.** A command draws with its own row
226
+ (`answerEntity(cmd, name, schema, { row })`), its app's (`rows: { name: shape }` in the program definition,
227
+ which also draws an app entity listed inside one thing), the shared one in `SHARED_ROWS` (`message`,
228
+ `message.summary`, `message.hit`, `chat`), or the generic row: key, ref or id · title, name or path · state,
229
+ 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,
230
+ options)` is pure, for an app that prints its own answers.
231
+ - What a command prints without `renderAnswer` stays JSON under `--compact`.
232
+
10
233
  ## 0.549.0 — 02.11.2026
11
234
 
12
235
  ### Added
@@ -293,22 +516,6 @@ shape of this file.
293
516
  **`knowledge.decideSuggestedLink(id, decision)`** an older unconfirmed link, each in one transaction: the link and
294
517
  the decision land together or not at all. A decision's `reason` is kept: on a rejected proposal as its result, on a
295
518
  fact in `metadata.decision`, on an evidence findings receipt.
296
- - **`--compact`: one line per item for an agent, from the same answer `--json` prints.** `messages list <chat>
297
- --compact` prints `#<id> · 10-01 09:12 · Анна · <text>` under one header line that names the shared ref prefix
298
- (`ref: msg:max/500/-1001/<id>`), what every row has in common (the chat's title), the date span and the zone. Rows
299
- from several chats keep their whole refs. Text stays on one line with control and direction characters made
300
- visible, and is cut at 400 characters with `…(+N)`; `--full` keeps it whole. The paging, `missing`, the next steps
301
- and an incomplete answer follow as `# more:`, `# missing:`, `# next:` and `# complete: false` lines on stdout, and
302
- each command line in them carries `--compact` again. On 1 000 messages of one chat it is about 48 % of the JSON
303
- bytes; the texts alone are 40 %. `--compact` with `--json`, `--json=…`, `--jsonl` or `--brief` fails with
304
- `validation_error` (`output.conflict`); it never prompts, and exits with the code JSON would. MCP keeps JSON.
305
- - **Every command that answers through `renderAnswer` gets it.** A command draws with its own row
306
- (`answerEntity(cmd, name, schema, { row })`), its app's (`rows: { name: shape }` in the program definition,
307
- which also draws an app entity listed inside one thing), the shared one in `SHARED_ROWS` (`message`,
308
- `message.summary`, `message.hit`, `chat`), or the generic row: key, ref or id · title, name or path · state,
309
- 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,
310
- options)` is pure, for an app that prints its own answers.
311
- - What a command prints without `renderAnswer` stays JSON under `--compact`.
312
519
 
313
520
  ### Changed — may break callers
314
521
 
@@ -351,8 +558,6 @@ shape of this file.
351
558
  the agent's bot and reason otherwise.
352
559
  An agent's confirmation of a relationship is recorded in the link's provenance, and of a fact or proposal as its
353
560
  decider.
354
- - **cli-core now needs `@wirecat/cli-base` 0.74.0**, which holds the `--compact` switch's name. A CLI that pins
355
- cli-base bumps it to 0.74.0 together with cli-core.
356
561
 
357
562
  ### Fixed
358
563
 
@@ -366,10 +571,6 @@ shape of this file.
366
571
  `starred:`.
367
572
  - **An inclusive start keeps a message sent exactly at it.** `messages list 'date>=2026-10-01'` skipped a message
368
573
  sent at the first millisecond of the day, and `date<="…"` one sent at that moment.
369
- - **`--brief` now cuts a messenger search hit.** It only knew mail's camelCase hits, so `search messages --brief` in
370
- tg, max and wa printed every field; it now keeps `ref`, `chat_title`, `timestamp`, `sender_name`, `excerpt` and
371
- `score`. The default and `--full` answers are unchanged. Commands that print through `outputFor` now honour
372
- `--brief` and `--full` too, and refuse them together as the others do.
373
574
 
374
575
  ### Security
375
576
 
@@ -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
  };