@wirecat/cli-core 0.378.0 → 0.380.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 (95) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/dist/cli/envelope.d.ts +10 -0
  3. package/dist/cli/list-query.js +2 -2
  4. package/dist/cli/meeting-preparation-answer.d.ts +5 -0
  5. package/dist/cli/meeting-preparation-projects.d.ts +5 -0
  6. package/dist/cli/meeting-preparation-read.d.ts +5 -0
  7. package/dist/cli/meeting-preparation-read.js +5 -5
  8. package/dist/cli/meeting-series-inventory-read.js +3 -4
  9. package/dist/cli/messenger/chats-stats-command.js +5 -1
  10. package/dist/cli/messenger/contacts-command.js +8 -4
  11. package/dist/cli/messenger/stats-answers.d.ts +2 -1
  12. package/dist/cli/messenger/stats-answers.js +1 -1
  13. package/dist/cli/messenger/stats-command.js +4 -7
  14. package/dist/domain/moment.d.ts +8 -0
  15. package/dist/domain/moment.js +14 -0
  16. package/dist/domain/send-time.d.ts +2 -2
  17. package/dist/domain/send-time.js +4 -3
  18. package/dist/entities/account-session.d.ts +2 -2
  19. package/dist/entities/admin-report.d.ts +8 -8
  20. package/dist/entities/agenda.d.ts +12 -12
  21. package/dist/entities/chat-event.d.ts +1 -1
  22. package/dist/entities/chat-stats.d.ts +5 -5
  23. package/dist/entities/chat.d.ts +4 -4
  24. package/dist/entities/contact-note.d.ts +2 -2
  25. package/dist/entities/contact.d.ts +13 -13
  26. package/dist/entities/conversation.d.ts +3 -3
  27. package/dist/entities/counter-reading.d.ts +1 -1
  28. package/dist/entities/counters.d.ts +2 -2
  29. package/dist/entities/document.d.ts +2 -2
  30. package/dist/entities/evidence.d.ts +72 -72
  31. package/dist/entities/fact.d.ts +3 -3
  32. package/dist/entities/fields.d.ts +3 -1
  33. package/dist/entities/fields.js +2 -0
  34. package/dist/entities/hit.d.ts +1 -1
  35. package/dist/entities/index.d.ts +231 -215
  36. package/dist/entities/index.js +1 -0
  37. package/dist/entities/invite-link.d.ts +1 -1
  38. package/dist/entities/job.d.ts +2 -2
  39. package/dist/entities/join-request.d.ts +1 -1
  40. package/dist/entities/meeting-communications.d.ts +4 -4
  41. package/dist/entities/meeting-findings.d.ts +2 -2
  42. package/dist/entities/meeting-preparation-parts.d.ts +9 -9
  43. package/dist/entities/meeting-preparation.d.ts +28 -23
  44. package/dist/entities/meeting-series-inventory.d.ts +3 -3
  45. package/dist/entities/member.d.ts +3 -3
  46. package/dist/entities/memory.d.ts +3 -3
  47. package/dist/entities/message.d.ts +6 -6
  48. package/dist/entities/note.d.ts +3 -3
  49. package/dist/entities/official-stats.d.ts +2 -2
  50. package/dist/entities/poll-vote.d.ts +1 -1
  51. package/dist/entities/ranking.d.ts +2 -2
  52. package/dist/entities/relationship.d.ts +1 -1
  53. package/dist/entities/retention.d.ts +4 -4
  54. package/dist/entities/saved-search.d.ts +2 -2
  55. package/dist/entities/tagging.d.ts +1 -1
  56. package/dist/entities/task.d.ts +7 -7
  57. package/dist/entities/topic.d.ts +2 -2
  58. package/dist/mcp/meeting-preparation.d.ts +5 -0
  59. package/dist/mcp/tools/stats.js +9 -4
  60. package/dist/meetings/answers.d.ts +20 -14
  61. package/dist/meetings/answers.js +5 -0
  62. package/dist/meetings/cli/index.d.ts +2 -0
  63. package/dist/meetings/cli/index.js +12 -6
  64. package/dist/meetings/cli/search.js +12 -14
  65. package/dist/meetings/hit-query.d.ts +12 -0
  66. package/dist/meetings/hit-query.js +58 -0
  67. package/dist/meetings/import.d.ts +2 -0
  68. package/dist/meetings/import.js +8 -2
  69. package/dist/meetings/index.d.ts +2 -0
  70. package/dist/meetings/index.js +2 -0
  71. package/dist/meetings/item.d.ts +4 -4
  72. package/dist/meetings/mcp/search.d.ts +6 -0
  73. package/dist/meetings/mcp/search.js +5 -1
  74. package/dist/meetings/project-evidence-answer.d.ts +8 -3
  75. package/dist/meetings/pull.d.ts +2 -0
  76. package/dist/meetings/recording-schedule.js +6 -5
  77. package/dist/meetings/search-store.d.ts +3 -1
  78. package/dist/meetings/store.d.ts +7 -0
  79. package/dist/meetings/transcript-text.d.ts +20 -0
  80. package/dist/meetings/transcript-text.js +59 -0
  81. package/dist/render/markdown.js +2 -2
  82. package/dist/search/garbled.d.ts +29 -0
  83. package/dist/search/garbled.js +125 -0
  84. package/dist/search/russian-dictionary.d.ts +5 -0
  85. package/dist/search/russian-dictionary.js +66 -0
  86. package/dist/services/admin-statistics.js +2 -2
  87. package/dist/services/meeting-project-discovery.js +2 -2
  88. package/dist/services/moment.d.ts +25 -2
  89. package/dist/services/moment.js +32 -4
  90. package/dist/services/retention.js +6 -3
  91. package/dist/store/sqlite/facts.js +2 -1
  92. package/dist/store/sqlite/meeting-hit-query.d.ts +60 -0
  93. package/dist/store/sqlite/meeting-hit-query.js +262 -0
  94. package/dist/store/sqlite/meeting-hit-search.js +28 -45
  95. package/package.json +4 -3
package/CHANGELOG.md CHANGED
@@ -5,6 +5,79 @@ Notable changes to `@wirecat/cli-core` (`@wirecat/cli-messaging` up to 0.261.0,
5
5
  minor version may break callers, and says how under "Changed — may break callers". `pnpm docs:check` checks the
6
6
  shape of this file.
7
7
 
8
+ ## Unreleased
9
+
10
+ ## 0.380.0 — 21.10.2026
11
+
12
+ ### Added
13
+
14
+ - **Meetings import text transcripts.** `parseTranscriptText` reads the text form zoom-searcher wrote: the header
15
+ (`Встреча:` title, `Дата:` start as written, `Zoom ID:`, `Говорят:` speakers) and `[hh:mm:ss] Speaker: text`
16
+ blocks, a block running over several lines. `ImportFile.format: "text"` makes `importFiles` use it; `vtt` stays
17
+ the default. The file keeps start times only, so a line ends where the next begins.
18
+ - **An import says the zone it read file times in, and a host can explain its file names.** `RunReport.timezone` is
19
+ set when every imported file's meeting carries one zone; `MeetingCommandDeps.importHelp` adds the host's note
20
+ (file name patterns, how their time is read) under `meetings import --help`.
21
+ - **Meeting search finds words spread over neighbouring lines.** `"циан риелтор"~3` in a meeting query finds a
22
+ transcript line with one of the words when the others are at most three lines away in the same transcript; `~0`
23
+ is one line, any order; at most 20. The words widen to garbled spellings as single words do. It needs
24
+ `@wirecat/cli-base` 0.49.0 (`parseLucene({ proximity: true })`), now the pinned version and the peer range.
25
+ See [query language](docs/search/query-language.md#встречи).
26
+
27
+ - **Meeting search finds words speech recognition garbled.** `meetings.searchHits` widens each query word of four
28
+ letters or more to stored words one stem edit away that a Russian dictionary does not know, and to stems one side
29
+ extends, so «риелтор» finds «реелтора», «рилторов» and «дриелтора», «циана» finds «цианом», and «договор» does not
30
+ find «поговорить». Hits come typed form first, then other forms, then garbled spellings; a widened hit carries `match: "corrected"` and `corrections`, and `meeting_hit` items show both. Number words never
31
+ widen, and match only their own forms («семь» finds «семи», not «семьдесят»). See [meetings](docs/dev/MEETINGS.md).
32
+ - **Answers say which zone a typed date was read in, and the UTC window it gave** (cli-base `WC-TIME-3`, `WC-TIME-5`).
33
+ `stats charts`, the MCP `stats` tool and `contacts timeline` put `meta.timezone` (only when a value had no zone of
34
+ its own) and `meta.window: { since?, until? }` in UTC in their answers. `dateWindow` in `./cli`'s moment helpers
35
+ builds both from `--since-time`/`--until-time`. Other date filters follow.
36
+
37
+ ### Changed — may break callers
38
+
39
+ - **Meeting search reads the messages query language.** `meetings.searchHits`, `search meetings` and MCP
40
+ `search_meetings` take `OR`, `(groups)`, `NOT`/`-word`, `"phrases"` (neighbouring words, any form), `exact:` and
41
+ `from:"Name"` — who said the line or wrote the chat message, a person through all their linked identities; an
42
+ ambiguous name is an error with the candidates. `:`, `(`, `"`, `-` and `AND`/`OR`/`NOT` in a meeting query are
43
+ now syntax, and a query of punctuation alone is a `validation_error`. `searchHits` also takes a `QueryAst`;
44
+ `meetingWordsQuery(text)` builds one from text that must stay plain words, as project discovery now does.
45
+ `MEETING_HIT_QUERY` and `parseMeetingQuery` are exported. See [query language](docs/search/query-language.md#встречи).
46
+
47
+ - **Meeting search reads `exact:` and has new cursors.** `exact:word` and `exact:"two words"` in a
48
+ `meetings.searchHits` query keep those words to their typed form, where `exact` was a search word before. Exact
49
+ hits now come before widened ones, so cursors from earlier versions are refused; start the search again.
50
+
51
+ ### Fixed
52
+
53
+ - **A recording archive schedule counts "today" in its own zone.** The window's end, its ten-year floor and its
54
+ look-back started from the UTC date, so a run between local midnight and UTC midnight worked on yesterday. The
55
+ schedule's `timeZone` was stored and never used for this.
56
+ - **A Markdown chat archive says which zone its times are in.** The file printed local wall times with no zone; it
57
+ now has `Times are in <zone>.` under the title.
58
+ - **`chats stats show` and `stats chats retention` read `--since-time`/`--until-time` in their `--timezone`.** They
59
+ used the computer's zone even when `--timezone` was given, and retention's `--until-time` dropped the day it named;
60
+ it now covers that whole local day.
61
+
62
+ ## 0.379.0 — 21.10.2026
63
+
64
+ ### Changed — may break callers
65
+
66
+ - **A typed date or time is read in a zone, and a date alone covers the whole local day** (cli-base `WC-TIME-3`,
67
+ `WC-TIME-5`). `--since-time`, `--until-time`, `--at-time`, `--due`, `--remind-time`, `chats mute --until`, fact
68
+ `occurredAt`, `meetings sync --since-time` and `search meetings --since-time`/`--until-time` used `Date.parse`: a date
69
+ alone was UTC midnight, a time without a zone was the process zone, and `--timezone` was ignored. Now a zone the
70
+ value names wins; otherwise the command's `--timezone` (stats, admin statistics, the MCP `stats` tool), else the
71
+ computer's IANA zone. A date alone starts at local midnight; as an end (`--until-time`) it is the last millisecond
72
+ of that local day, so the day is no longer dropped. A local time that daylight saving skips or repeats is refused.
73
+ List-query `time` terms (`timeBounds`) default to the computer's zone, not UTC. What to watch for: `momentOf` and
74
+ `aheadOf` keep their signature and take an optional `{ zone, end }`; a caller that passed a date alone and expected
75
+ UTC midnight now gets local midnight. `search meetings` now takes a time without an offset instead of refusing it.
76
+ The answers do not name the zone used yet (`meta.timezone`); that follows.
77
+ - **cli-base 0.48.0.** `isoTime` refuses offsets other than `Z`; two input schemas that take a caller's time with any
78
+ offset (meeting preparation, meeting series inventory) use the new `zonedTime` instead. The peer range moves to
79
+ `>=0.48.0 <0.49.0`, and `node-ci.yml` runs the tests in `Asia/Vladivostok`.
80
+
8
81
  ## 0.378.0 — 21.10.2026
9
82
 
10
83
  ### Added
@@ -84,6 +84,11 @@ export declare const answerSchema: (item?: v.GenericSchema) => v.UnionSchema<[v.
84
84
  readonly source: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "must not be empty">]>;
85
85
  }, undefined>, undefined>, undefined>;
86
86
  readonly truncated: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
87
+ readonly timezone: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "must not be empty">]>, undefined>;
88
+ readonly window: v.OptionalSchema<v.StrictObjectSchema<{
89
+ readonly since: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
90
+ readonly until: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
91
+ }, undefined>, undefined>;
87
92
  }, undefined>;
88
93
  }, undefined>, v.StrictObjectSchema<{
89
94
  readonly item: v.GenericSchema;
@@ -132,6 +137,11 @@ export declare const answerSchema: (item?: v.GenericSchema) => v.UnionSchema<[v.
132
137
  readonly source: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "must not be empty">]>;
133
138
  }, undefined>, undefined>, undefined>;
134
139
  readonly truncated: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
140
+ readonly timezone: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "must not be empty">]>, undefined>;
141
+ readonly window: v.OptionalSchema<v.StrictObjectSchema<{
142
+ readonly since: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
143
+ readonly until: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
144
+ }, undefined>, undefined>;
135
145
  }, undefined>;
136
146
  }, undefined>], undefined>;
137
147
  /**
@@ -1,5 +1,5 @@
1
1
  import { annotate } from "@wirecat/cli-base/commands";
2
- import { dateRange, defineQuery, fieldsHelp, fieldsNamed, OPERATORS_BY_TYPE, parseLucene, QUERY_LIMITS, queryError, queryFieldsOf, validateQuery, } from "@wirecat/cli-base/query";
2
+ import { dateRange, defineQuery, fieldsHelp, fieldsNamed, OPERATORS_BY_TYPE, parseLucene, QUERY_LIMITS, queryError, queryFieldsOf, timezoneOf, validateQuery, } from "@wirecat/cli-base/query";
3
3
  import * as v from "valibot";
4
4
  import { queryWordsAfterOptions } from "../meetings/cli/index.js";
5
5
  import { listOf } from "./envelope.js";
@@ -131,7 +131,7 @@ export const queryTerms = (words, registry, command) => {
131
131
  return terms;
132
132
  };
133
133
  /** A `time` term as epoch ms bounds: `time>=7d`, `time:[2026-10-01 TO 2026-10-07]`, `time:today`. */
134
- export const timeBounds = (term, zone = "UTC") => {
134
+ export const timeBounds = (term, zone = timezoneOf()) => {
135
135
  if (!term)
136
136
  return {};
137
137
  const upper = term.operator === "range" ? (term.upper ?? "*") : term.value;
@@ -309,6 +309,11 @@ export declare const meetingPreparationAnswer: (packet: MeetingPreparation) => i
309
309
  id: string;
310
310
  excerpt: string;
311
311
  offset_ms: number | null;
312
+ match?: "corrected" | "words" | undefined;
313
+ corrections?: {
314
+ from: string;
315
+ to: string[];
316
+ }[] | undefined;
312
317
  source?: {
313
318
  ref: string;
314
319
  title: string | null;
@@ -29,6 +29,11 @@ export declare const mapPreparationProjectEvidence: (section: NonNullable<Meetin
29
29
  id: string;
30
30
  excerpt: string;
31
31
  offset_ms: number | null;
32
+ match?: "corrected" | "words" | undefined;
33
+ corrections?: {
34
+ from: string;
35
+ to: string[];
36
+ }[] | undefined;
32
37
  source?: {
33
38
  ref: string;
34
39
  title: string | null;
@@ -338,6 +338,11 @@ export declare const readMeetingPreparation: (read: MeetingPreparationReader, in
338
338
  id: string;
339
339
  excerpt: string;
340
340
  offset_ms: number | null;
341
+ match?: "corrected" | "words" | undefined;
342
+ corrections?: {
343
+ from: string;
344
+ to: string[];
345
+ }[] | undefined;
341
346
  source?: {
342
347
  ref: string;
343
348
  title: string | null;
@@ -1,7 +1,7 @@
1
1
  import { CliError } from "@wirecat/cli-base";
2
- import { isoTime } from "@wirecat/cli-base/standards";
3
2
  import * as v from "valibot";
4
3
  import { communicationFields } from "../context/communication-selection.js";
4
+ import { zonedTime } from "../entities/index.js";
5
5
  import { validateMeetingChange } from "../meetings/index.js";
6
6
  import { MEETING_PREPARATION_LIMITS, validateMeetingPreparationRequest, } from "../services/index.js";
7
7
  import { validatePreparationCommunications } from "../services/meeting-communications-selection.js";
@@ -12,11 +12,11 @@ const bound = (name) => {
12
12
  };
13
13
  export const meetingPreparationInput = v.strictObject({
14
14
  series_id: v.pipe(v.string(), v.minLength(1), v.maxLength(1000)),
15
- communications: v.optional(v.strictObject({ ...communicationFields, after: v.optional(isoTime), before: v.optional(isoTime) })),
15
+ communications: v.optional(v.strictObject({ ...communicationFields, after: v.optional(zonedTime), before: v.optional(zonedTime) })),
16
16
  project: v.optional(v.pipe(v.string(), v.minLength(1), v.maxLength(200))),
17
- after: v.optional(isoTime),
18
- until: v.optional(isoTime),
19
- history_after: v.optional(isoTime),
17
+ after: v.optional(zonedTime),
18
+ until: v.optional(zonedTime),
19
+ history_after: v.optional(zonedTime),
20
20
  meetings: bound("meetings"),
21
21
  cues: bound("cues"),
22
22
  context_limit: bound("contextLimit"),
@@ -1,13 +1,12 @@
1
1
  import { CliError } from "@wirecat/cli-base";
2
- import { isoTime } from "@wirecat/cli-base/standards";
3
2
  import * as v from "valibot";
4
- import { meetingSeriesInventory } from "../entities/index.js";
3
+ import { meetingSeriesInventory, zonedTime } from "../entities/index.js";
5
4
  import { validateMeetingChange, validateMeetingSeriesInventoryCursor, } from "../meetings/index.js";
6
5
  import { itemOf, metaNow, sealed, snakeKeys } from "./envelope.js";
7
6
  const bounded = (min, max) => v.optional(v.pipe(v.number(), v.integer(), v.minValue(min), v.maxValue(max)));
8
7
  export const meetingSeriesInventoryInput = v.strictObject({
9
- after: v.optional(isoTime),
10
- until: v.optional(isoTime),
8
+ after: v.optional(zonedTime),
9
+ until: v.optional(zonedTime),
11
10
  limit: bounded(1, 100),
12
11
  max_read_bytes: bounded(1, 1048576),
13
12
  bytes: bounded(2048, 1048576),
@@ -21,7 +21,11 @@ export const statsCommand = (messenger) => annotate(new Command("show"), { effec
21
21
  refuseJsonl(context.format);
22
22
  const { stats, account } = await context.withServices(async (services) => ({
23
23
  stats: await services.chats.stats(chat, {
24
- ...(sinceTime === undefined ? {} : { since: momentOf(sinceTime, "--since-time") }),
24
+ ...(sinceTime === undefined
25
+ ? {}
26
+ : {
27
+ since: momentOf(sinceTime, "--since-time", Date.now(), timezone === undefined ? {} : { zone: timezone }),
28
+ }),
25
29
  ...(by ? { by } : {}),
26
30
  ...(timezone === undefined ? {} : { timezone }),
27
31
  }),
@@ -4,7 +4,7 @@ import { Command } from "commander";
4
4
  import { REGISTRIES, registriesCover } from "../../botcheck/registries.js";
5
5
  import { levelFor } from "../../sends/permissions.js";
6
6
  import { phoneOf } from "../../services/index.js";
7
- import { momentOf } from "../../services/moment.js";
7
+ import { dateWindow, momentOf } from "../../services/moment.js";
8
8
  import { maskedAccount } from "../../services/people.js";
9
9
  import { CHAT_MESSAGES, CONTEXT_BYTES, CONTEXT_MESSAGES } from "../../services/person-context.js";
10
10
  import { SCOPES, TIMELINE_ITEMS } from "../../services/person-timeline.js";
@@ -150,10 +150,14 @@ export const contactsCommand = (messenger) => {
150
150
  if (scope !== undefined && !SCOPES.includes(scope))
151
151
  throw new CliError("validation_error", `--scope is personal or work, not "${scope}"`);
152
152
  const context = messengerContext(this, messenger);
153
+ const window = dateWindow({
154
+ ...(sinceTime === undefined ? {} : { sinceTime }),
155
+ ...(untilTime === undefined ? {} : { untilTime }),
156
+ });
153
157
  const found = await context.withServices((services) => services.people.timeline(person, {
154
158
  ...(scope === undefined ? {} : { scope: scope }),
155
- ...(sinceTime === undefined ? {} : { since: momentOf(sinceTime, "--since-time") }),
156
- ...(untilTime === undefined ? {} : { until: momentOf(untilTime, "--until-time") }),
159
+ ...(window.since === undefined ? {} : { since: window.since }),
160
+ ...(window.until === undefined ? {} : { until: window.until }),
157
161
  ...(limit === undefined ? {} : { limit }),
158
162
  ...(cursor === undefined ? {} : { cursor }),
159
163
  }));
@@ -170,7 +174,7 @@ export const contactsCommand = (messenger) => {
170
174
  ...(sinceTime === undefined ? {} : { since_time: sinceTime }),
171
175
  ...(untilTime === undefined ? {} : { until_time: untilTime }),
172
176
  },
173
- ...(pending > 0 ? { meta: { truncated: true } } : {}),
177
+ meta: { ...window.meta, ...(pending > 0 ? { truncated: true } : {}) },
174
178
  }));
175
179
  });
176
180
  annotate(contacts.command("check"), { effects: REMOTE_READ })
@@ -1,3 +1,4 @@
1
+ import type { Meta } from "@wirecat/cli-base/standards";
1
2
  import type { InferOutput } from "valibot";
2
3
  import type { ChartData } from "../../charts/model.js";
3
4
  import type { CounterState } from "../../domain/counters.js";
@@ -62,7 +63,7 @@ export declare const chartAnswer: (chart: ChartData, file?: {
62
63
  format: "svg" | "png";
63
64
  width: number;
64
65
  height: number;
65
- }) => ItemBody<Of<"chart">>;
66
+ }, meta?: Partial<Meta>) => ItemBody<Of<"chart">>;
66
67
  /** A report or a chart is one object: `--jsonl` would print one line pretending to be a stream. */
67
68
  export declare const refuseJsonl: (format: string) => void;
68
69
  /** `stats chats show`, CLI and MCP alike; a chat the store does not hold whole names the sync that fills it. */
@@ -326,7 +326,7 @@ export const officialAnswer = (stats, provider) => {
326
326
  graphs: stats.graphs,
327
327
  }));
328
328
  };
329
- export const chartAnswer = (chart, file) => itemOf(defined({ ...chart, file }));
329
+ export const chartAnswer = (chart, file, meta) => itemOf(defined({ ...chart, file }), meta ? { meta } : {});
330
330
  /** A report or a chart is one object: `--jsonl` would print one line pretending to be a stream. */
331
331
  export const refuseJsonl = (format) => {
332
332
  if (format === "jsonl")
@@ -9,7 +9,7 @@ import { CHART_SIZE } from "../../charts/model.js";
9
9
  import { chartPng } from "../../charts/png.js";
10
10
  import { chartRenderer } from "../../charts/render.js";
11
11
  import { EVENTS_DAYS } from "../../services/chats.js";
12
- import { momentOf } from "../../services/moment.js";
12
+ import { dateWindow } from "../../services/moment.js";
13
13
  import { destroysNothing, LOCAL_WRITE } from "../effects.js";
14
14
  import { renderAnswer } from "../envelope.js";
15
15
  import { adminStatisticsCommand } from "./admin-statistics-command.js";
@@ -61,11 +61,8 @@ export const statsCommand = (messenger, loadRenderer = chartRenderer) => {
61
61
  throw new CliError("validation_error", "--output takes a .svg or .png file; image output to stdout is unavailable");
62
62
  }
63
63
  const timezone = timezoneOf(given);
64
- const found = await context.withServices((services) => services.chats.stats(chat, {
65
- by,
66
- timezone,
67
- ...(sinceTime === undefined ? {} : { since: momentOf(sinceTime, "--since-time") }),
68
- }));
64
+ const window = dateWindow({ ...(sinceTime === undefined ? {} : { sinceTime }), zone: timezone });
65
+ const found = await context.withServices((services) => services.chats.stats(chat, { by, timezone, ...(window.since === undefined ? {} : { since: window.since }) }));
69
66
  const chart = chatChart(found, { kind: chartKind, by, timezone });
70
67
  let chartFile;
71
68
  if (output !== undefined) {
@@ -85,7 +82,7 @@ export const statsCommand = (messenger, loadRenderer = chartRenderer) => {
85
82
  }
86
83
  if (chart.partial)
87
84
  context.renderer.note("partial data — the chart shows lower bounds");
88
- renderAnswer(this, context, chartAnswer(chart, chartFile));
85
+ renderAnswer(this, context, chartAnswer(chart, chartFile, window.meta));
89
86
  });
90
87
  return stats;
91
88
  };
@@ -0,0 +1,8 @@
1
+ export interface MomentZone {
2
+ /** The IANA zone a date or a time without a zone is read in; the computer's zone when left out. */
3
+ zone?: string;
4
+ /** A date alone means the last millisecond of that local day, not its first: the end of a range. */
5
+ end?: boolean;
6
+ }
7
+ /** A typed date or time as epoch ms, with the flag named when it is not one. */
8
+ export declare const calendarMoment: (wanted: string, flag: string, { zone, end }?: MomentZone) => number;
@@ -0,0 +1,14 @@
1
+ import { CliError } from "@wirecat/cli-base";
2
+ import { timezoneOf, zonedMoment } from "@wirecat/cli-base/query";
3
+ /** A typed date or time as epoch ms, with the flag named when it is not one. */
4
+ export const calendarMoment = (wanted, flag, { zone, end = false } = {}) => {
5
+ try {
6
+ return zonedMoment(wanted, timezoneOf(zone), { start: 0, end: wanted.length }, { end });
7
+ }
8
+ catch (error) {
9
+ if (!(error instanceof CliError))
10
+ throw error;
11
+ const alternative = error.details?.alternative;
12
+ throw new CliError("validation_error", `${flag}: "${wanted}" is not a time${alternative ? ` — ${alternative}` : ""}`);
13
+ }
14
+ };
@@ -2,6 +2,6 @@
2
2
  * `--at-time`: a time like `2026-09-25T09:00`, or a delay like `30m`, `2h`, `1d`, as an ISO time — the
3
3
  * same words max-cli takes. Rounded down to the minute, because messengers send at the start of it
4
4
  * (MAX measured, max-cli `FIND-141`), so the time printed is the time it goes. A time without an
5
- * offset is local, which is how `Date.parse` reads one with a clock and no zone.
5
+ * offset is read in `zone`, the computer's zone when none is given.
6
6
  */
7
- export declare const sendTime: (value: string, now?: number, flag?: string) => string;
7
+ export declare const sendTime: (value: string, now?: number, flag?: string, zone?: string) => string;
@@ -1,4 +1,5 @@
1
1
  import { CliError } from "@wirecat/cli-base";
2
+ import { calendarMoment } from "./moment.js";
2
3
  const MINUTE = 60_000;
3
4
  const YEAR = 365 * 24 * 60 * MINUTE;
4
5
  const DELAY = /^(\d+)(m|h|d)$/;
@@ -7,13 +8,13 @@ const UNIT_MS = { m: MINUTE, h: 60 * MINUTE, d: 24 * 60 * MINUTE };
7
8
  * `--at-time`: a time like `2026-09-25T09:00`, or a delay like `30m`, `2h`, `1d`, as an ISO time — the
8
9
  * same words max-cli takes. Rounded down to the minute, because messengers send at the start of it
9
10
  * (MAX measured, max-cli `FIND-141`), so the time printed is the time it goes. A time without an
10
- * offset is local, which is how `Date.parse` reads one with a clock and no zone.
11
+ * offset is read in `zone`, the computer's zone when none is given.
11
12
  */
12
- export const sendTime = (value, now = Date.now(), flag = "--at-time") => {
13
+ export const sendTime = (value, now = Date.now(), flag = "--at-time", zone) => {
13
14
  const trimmed = value.trim();
14
15
  const [, amount, unit] = DELAY.exec(trimmed) ?? [];
15
16
  const at = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}/.test(trimmed)
16
- ? Date.parse(trimmed.replace(" ", "T"))
17
+ ? calendarMoment(trimmed.replace(" ", "T"), flag, zone === undefined ? {} : { zone })
17
18
  : amount && unit
18
19
  ? now + Number(amount) * (UNIT_MS[unit] ?? 0)
19
20
  : Number.NaN;
@@ -5,6 +5,6 @@ export declare const accountSession: v.StrictObjectSchema<{
5
5
  readonly client: v.NullableSchema<v.StringSchema<undefined>, undefined>;
6
6
  readonly device: v.NullableSchema<v.StringSchema<undefined>, undefined>;
7
7
  readonly location: v.NullableSchema<v.StringSchema<undefined>, undefined>;
8
- readonly last_active_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>, undefined>;
9
- readonly created_at: v.OptionalSchema<v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>, undefined>, undefined>;
8
+ readonly last_active_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
9
+ readonly created_at: v.OptionalSchema<v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>, undefined>;
10
10
  }, undefined>;
@@ -13,8 +13,8 @@ export declare const adminRow: v.StrictObjectSchema<{
13
13
  readonly median_ms: v.OptionalSchema<v.NullableSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>, undefined>;
14
14
  readonly p90_ms: v.OptionalSchema<v.NullableSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>, undefined>;
15
15
  readonly age_ms: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
16
- readonly joined_at: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>, undefined>;
17
- readonly window_end: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>, undefined>;
16
+ readonly joined_at: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
17
+ readonly window_end: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
18
18
  readonly pending: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
19
19
  readonly views: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
20
20
  readonly observed_replies: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
@@ -22,7 +22,7 @@ export declare const adminRow: v.StrictObjectSchema<{
22
22
  readonly counters: v.OptionalSchema<v.ArraySchema<v.StrictObjectSchema<{
23
23
  readonly counter: v.PicklistSchema<["views", "reactions", "comments"], undefined>;
24
24
  readonly value: v.NullableSchema<v.NumberSchema<undefined>, undefined>;
25
- readonly observed_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>, undefined>;
25
+ readonly observed_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
26
26
  readonly source: v.NullableSchema<v.StringSchema<undefined>, undefined>;
27
27
  readonly age_ms: v.NullableSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
28
28
  readonly freshness: v.PicklistSchema<["fresh", "stale", "unknown"], undefined>;
@@ -32,7 +32,7 @@ export declare const adminRow: v.StrictObjectSchema<{
32
32
  /** One administrator report over stored history: unanswered questions, responses, newcomers or discussion. */
33
33
  export declare const adminReport: v.StrictObjectSchema<{
34
34
  readonly report: v.PicklistSchema<["unanswered", "responses", "newcomers", "discussion"], undefined>;
35
- readonly cutoff: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>;
35
+ readonly cutoff: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
36
36
  /** Rows the report found; `rows` holds the first `--limit` of them. */
37
37
  readonly total: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
38
38
  readonly summary: v.StrictObjectSchema<{
@@ -54,7 +54,7 @@ export declare const adminReport: v.StrictObjectSchema<{
54
54
  readonly archives: v.ArraySchema<v.StrictObjectSchema<{
55
55
  readonly chat_ref: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
56
56
  readonly state: v.StringSchema<undefined>;
57
- readonly fetched_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>, undefined>;
57
+ readonly fetched_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
58
58
  }, undefined>, undefined>;
59
59
  readonly archives_total: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
60
60
  readonly membership: v.StringSchema<undefined>;
@@ -74,8 +74,8 @@ export declare const adminReport: v.StrictObjectSchema<{
74
74
  readonly median_ms: v.OptionalSchema<v.NullableSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>, undefined>;
75
75
  readonly p90_ms: v.OptionalSchema<v.NullableSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>, undefined>;
76
76
  readonly age_ms: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
77
- readonly joined_at: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>, undefined>;
78
- readonly window_end: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>, undefined>;
77
+ readonly joined_at: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
78
+ readonly window_end: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
79
79
  readonly pending: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
80
80
  readonly views: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
81
81
  readonly observed_replies: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
@@ -83,7 +83,7 @@ export declare const adminReport: v.StrictObjectSchema<{
83
83
  readonly counters: v.OptionalSchema<v.ArraySchema<v.StrictObjectSchema<{
84
84
  readonly counter: v.PicklistSchema<["views", "reactions", "comments"], undefined>;
85
85
  readonly value: v.NullableSchema<v.NumberSchema<undefined>, undefined>;
86
- readonly observed_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>, undefined>;
86
+ readonly observed_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
87
87
  readonly source: v.NullableSchema<v.StringSchema<undefined>, undefined>;
88
88
  readonly age_ms: v.NullableSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
89
89
  readonly freshness: v.PicklistSchema<["fresh", "stale", "unknown"], undefined>;
@@ -1,9 +1,9 @@
1
1
  import * as v from "valibot";
2
2
  /** What needs a look today, from the store alone and without one message's text. */
3
3
  export declare const agenda: v.StrictObjectSchema<{
4
- readonly at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>;
4
+ readonly at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
5
5
  readonly account: v.StringSchema<undefined>;
6
- readonly stored_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>, undefined>;
6
+ readonly stored_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
7
7
  /** The store saved nothing for a day: a chat may look quiet only because nothing was synced. */
8
8
  readonly stale: v.BooleanSchema<undefined>;
9
9
  readonly tasks: v.ArraySchema<v.StrictObjectSchema<{
@@ -14,20 +14,20 @@ export declare const agenda: v.StrictObjectSchema<{
14
14
  readonly category: v.NullableSchema<v.StringSchema<undefined>, undefined>;
15
15
  /** The message or note it came from. */
16
16
  readonly source: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
17
- readonly due_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>, undefined>;
18
- readonly next_check_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>, undefined>;
17
+ readonly due_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
18
+ readonly next_check_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
19
19
  /** Who works on it now; only the asker's own claims are listed unless the query names claimed:. */
20
20
  readonly claimed_by: v.NullableSchema<v.StringSchema<undefined>, undefined>;
21
- readonly claimed_until: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>, undefined>;
21
+ readonly claimed_until: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
22
22
  /** An agent's close waiting on the owner: `tasks close` confirms it, `tasks reject` refuses it. */
23
23
  readonly close_suggestion: v.NullableSchema<v.StrictObjectSchema<{
24
24
  readonly as: v.PicklistSchema<["done", "dismissed"], undefined>;
25
25
  readonly by: v.StringSchema<undefined>;
26
26
  readonly certainty: v.NumberSchema<undefined>;
27
27
  readonly reason: v.NullableSchema<v.StringSchema<undefined>, undefined>;
28
- readonly at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>;
28
+ readonly at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
29
29
  }, undefined>, undefined>;
30
- readonly created_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>;
30
+ readonly created_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
31
31
  readonly reasons: v.ArraySchema<v.PicklistSchema<["overdue", "due", "check", "unplanned", "close-suggested"], undefined>, undefined>;
32
32
  }, undefined>, undefined>;
33
33
  /** Someone else wrote last, and the owner has not answered. */
@@ -37,7 +37,7 @@ export declare const agenda: v.StrictObjectSchema<{
37
37
  readonly kind: v.PicklistSchema<["dialog", "group", "channel", "saved", "unknown"], undefined>;
38
38
  /** The newest message, never its text; `null` while it is not stored. */
39
39
  readonly last_message: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
40
- readonly last_message_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>;
40
+ readonly last_message_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
41
41
  /** `null` when the newest message is not stored, or the messenger did not say who sent it. */
42
42
  readonly last_from_owner: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
43
43
  /** Open tasks whose source is in this chat. */
@@ -50,7 +50,7 @@ export declare const agenda: v.StrictObjectSchema<{
50
50
  readonly kind: v.PicklistSchema<["dialog", "group", "channel", "saved", "unknown"], undefined>;
51
51
  /** The newest message, never its text; `null` while it is not stored. */
52
52
  readonly last_message: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
53
- readonly last_message_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>;
53
+ readonly last_message_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
54
54
  /** `null` when the newest message is not stored, or the messenger did not say who sent it. */
55
55
  readonly last_from_owner: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
56
56
  /** Open tasks whose source is in this chat. */
@@ -63,7 +63,7 @@ export declare const agenda: v.StrictObjectSchema<{
63
63
  readonly kind: v.PicklistSchema<["dialog", "group", "channel", "saved", "unknown"], undefined>;
64
64
  /** The newest message, never its text; `null` while it is not stored. */
65
65
  readonly last_message: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
66
- readonly last_message_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>;
66
+ readonly last_message_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
67
67
  /** `null` when the newest message is not stored, or the messenger did not say who sent it. */
68
68
  readonly last_from_owner: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
69
69
  /** Open tasks whose source is in this chat. */
@@ -77,8 +77,8 @@ export declare const agenda: v.StrictObjectSchema<{
77
77
  readonly kind: v.PicklistSchema<readonly ["bug", "feature", "chore", "question", "request", "mention", "promise"], undefined>;
78
78
  readonly category: v.NullableSchema<v.StringSchema<undefined>, undefined>;
79
79
  readonly source: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
80
- readonly due_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>, undefined>;
81
- readonly created_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>;
80
+ readonly due_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
81
+ readonly created_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
82
82
  /** `person:<id>`, each one who does it. */
83
83
  readonly assignees: v.ArraySchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
84
84
  }, undefined>, undefined>;
@@ -2,7 +2,7 @@ import * as v from "valibot";
2
2
  /** Somebody joined, left, was added or removed, or changed their profile, in one chat. */
3
3
  export declare const chatEvent: v.StrictObjectSchema<{
4
4
  readonly chat_id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
5
- readonly time: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>;
5
+ readonly time: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
6
6
  /** `join`, `leave`, `add`, `remove`, `create`, `title`, `pin`, `profile`; a messenger may pass others through. */
7
7
  readonly type: v.StringSchema<undefined>;
8
8
  /** The service message that says so, where there is one. */
@@ -5,8 +5,8 @@ import * as v from "valibot";
5
5
  */
6
6
  export declare const chatStats: v.StrictObjectSchema<{
7
7
  readonly chat: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
8
- readonly since: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>;
9
- readonly until: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>;
8
+ readonly since: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
9
+ readonly until: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
10
10
  /** The store holds the chat whole and the messenger read every join and leave; otherwise lower bounds. */
11
11
  readonly complete: v.BooleanSchema<undefined>;
12
12
  readonly completeness: v.StrictObjectSchema<{
@@ -14,7 +14,7 @@ export declare const chatStats: v.StrictObjectSchema<{
14
14
  readonly up_to_date: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
15
15
  readonly gaps: v.BooleanSchema<undefined>;
16
16
  readonly reaches_start: v.BooleanSchema<undefined>;
17
- readonly fetched_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>, undefined>;
17
+ readonly fetched_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
18
18
  }, undefined>;
19
19
  readonly messages: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
20
20
  readonly senders: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
@@ -26,7 +26,7 @@ export declare const chatStats: v.StrictObjectSchema<{
26
26
  readonly comments: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
27
27
  readonly top_posts: v.ArraySchema<v.StrictObjectSchema<{
28
28
  readonly ref: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
29
- readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>;
29
+ readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
30
30
  readonly reactions: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
31
31
  readonly views: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
32
32
  readonly forwards: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
@@ -69,7 +69,7 @@ export declare const taskStats: v.StrictObjectSchema<{
69
69
  readonly rows: v.ArraySchema<v.StrictObjectSchema<{
70
70
  readonly chat: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
71
71
  readonly open: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
72
- readonly oldest_open_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>, undefined>;
72
+ readonly oldest_open_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
73
73
  readonly median_close_ms: v.NullableSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
74
74
  }, undefined>, undefined>;
75
75
  }, undefined>;