@wirecat/cli-core 0.377.0 → 0.379.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 (74) hide show
  1. package/CHANGELOG.md +37 -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-read.js +5 -5
  5. package/dist/cli/meeting-series-inventory-read.js +3 -4
  6. package/dist/cli/messenger/contacts-command.js +1 -1
  7. package/dist/cli/messenger/stats-command.js +3 -1
  8. package/dist/context/evidence-response.js +2 -2
  9. package/dist/context/evidence.d.ts +5 -2
  10. package/dist/context/evidence.js +21 -6
  11. package/dist/domain/moment.d.ts +8 -0
  12. package/dist/domain/moment.js +14 -0
  13. package/dist/domain/send-time.d.ts +2 -2
  14. package/dist/domain/send-time.js +4 -3
  15. package/dist/entities/account-session.d.ts +2 -2
  16. package/dist/entities/admin-report.d.ts +8 -8
  17. package/dist/entities/agenda.d.ts +12 -12
  18. package/dist/entities/chat-event.d.ts +1 -1
  19. package/dist/entities/chat-stats.d.ts +5 -5
  20. package/dist/entities/chat.d.ts +4 -4
  21. package/dist/entities/contact-note.d.ts +2 -2
  22. package/dist/entities/contact.d.ts +13 -13
  23. package/dist/entities/conversation.d.ts +3 -3
  24. package/dist/entities/counter-reading.d.ts +1 -1
  25. package/dist/entities/counters.d.ts +2 -2
  26. package/dist/entities/document.d.ts +2 -2
  27. package/dist/entities/evidence.d.ts +72 -72
  28. package/dist/entities/fact.d.ts +3 -3
  29. package/dist/entities/fields.d.ts +3 -1
  30. package/dist/entities/fields.js +2 -0
  31. package/dist/entities/hit.d.ts +1 -1
  32. package/dist/entities/index.d.ts +216 -215
  33. package/dist/entities/index.js +1 -0
  34. package/dist/entities/invite-link.d.ts +1 -1
  35. package/dist/entities/job.d.ts +2 -2
  36. package/dist/entities/join-request.d.ts +1 -1
  37. package/dist/entities/meeting-communications.d.ts +4 -4
  38. package/dist/entities/meeting-findings.d.ts +2 -2
  39. package/dist/entities/meeting-preparation-parts.d.ts +9 -9
  40. package/dist/entities/meeting-preparation.d.ts +23 -23
  41. package/dist/entities/meeting-series-inventory.d.ts +3 -3
  42. package/dist/entities/member.d.ts +3 -3
  43. package/dist/entities/memory.d.ts +3 -3
  44. package/dist/entities/message.d.ts +6 -6
  45. package/dist/entities/note.d.ts +3 -3
  46. package/dist/entities/official-stats.d.ts +2 -2
  47. package/dist/entities/poll-vote.d.ts +1 -1
  48. package/dist/entities/ranking.d.ts +2 -2
  49. package/dist/entities/relationship.d.ts +1 -1
  50. package/dist/entities/retention.d.ts +4 -4
  51. package/dist/entities/saved-search.d.ts +2 -2
  52. package/dist/entities/tagging.d.ts +1 -1
  53. package/dist/entities/task.d.ts +7 -7
  54. package/dist/entities/topic.d.ts +2 -2
  55. package/dist/mcp/tools/stats.js +3 -1
  56. package/dist/meetings/answers.d.ts +14 -14
  57. package/dist/meetings/cli/index.js +7 -4
  58. package/dist/meetings/cli/search.js +11 -13
  59. package/dist/meetings/item.d.ts +4 -4
  60. package/dist/meetings/project-evidence-answer.d.ts +3 -3
  61. package/dist/services/admin-statistics.js +2 -2
  62. package/dist/services/context-evidence.js +1 -0
  63. package/dist/services/index.d.ts +1 -1
  64. package/dist/services/meeting-communications-selection.d.ts +1 -0
  65. package/dist/services/messages-archive-search.js +1 -1
  66. package/dist/services/moment.d.ts +4 -2
  67. package/dist/services/moment.js +13 -4
  68. package/dist/services/project-gathering-request.d.ts +30 -8
  69. package/dist/services/project-gathering-request.js +61 -8
  70. package/dist/services/project-gathering.d.ts +5 -2
  71. package/dist/services/project-gathering.js +11 -5
  72. package/dist/store/sqlite/context-evidence.js +24 -12
  73. package/dist/store/sqlite/facts.js +2 -1
  74. package/package.json +3 -3
@@ -18,7 +18,7 @@ export declare const meetingHit: v.StrictObjectSchema<{
18
18
  readonly source: v.OptionalSchema<v.StrictObjectSchema<{
19
19
  readonly ref: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
20
20
  readonly title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
21
- readonly started_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>, undefined>;
21
+ readonly started_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>;
22
22
  readonly url: v.NullableSchema<v.StringSchema<undefined>, undefined>;
23
23
  }, undefined>, undefined>;
24
24
  }, undefined>;
@@ -29,8 +29,8 @@ export declare const meetingTranscript: v.StrictObjectSchema<{
29
29
  readonly source: v.StringSchema<undefined>;
30
30
  readonly language: v.NullableSchema<v.StringSchema<undefined>, undefined>;
31
31
  /** When a newer transcript of the same source replaced it; `meetings transcript --history` shows those too. */
32
- readonly superseded_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>, undefined>;
33
- readonly created_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>;
32
+ readonly superseded_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>;
33
+ 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`">]>;
34
34
  readonly lines: v.ArraySchema<v.StrictObjectSchema<{
35
35
  readonly position: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
36
36
  readonly offset_ms: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
@@ -53,8 +53,8 @@ export declare const meetingSummary: v.StrictObjectSchema<{
53
53
  readonly next_steps: v.ArraySchema<v.StringSchema<undefined>, undefined>;
54
54
  readonly content: v.NullableSchema<v.StringSchema<undefined>, undefined>;
55
55
  readonly url: v.NullableSchema<v.StringSchema<undefined>, undefined>;
56
- readonly created_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>;
57
- readonly updated_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>;
56
+ 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`">]>;
57
+ readonly updated_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`">]>;
58
58
  }, undefined>;
59
59
  /** `meetings show`: the meeting with who took part, what was said in its chat, and what it holds. */
60
60
  export declare const meetingDetails: v.StrictObjectSchema<{
@@ -66,13 +66,13 @@ export declare const meetingDetails: v.StrictObjectSchema<{
66
66
  readonly description: v.OptionalSchema<v.NullableSchema<v.StringSchema<undefined>, undefined>, undefined>;
67
67
  readonly location: v.OptionalSchema<v.NullableSchema<v.StringSchema<undefined>, undefined>, undefined>;
68
68
  readonly url: v.NullableSchema<v.StringSchema<undefined>, undefined>;
69
- readonly started_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>, undefined>;
70
- readonly ended_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>, undefined>;
69
+ readonly started_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>;
70
+ readonly ended_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>;
71
71
  readonly duration_ms: v.NullableSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
72
72
  readonly timezone: v.OptionalSchema<v.NullableSchema<v.StringSchema<undefined>, undefined>, undefined>;
73
73
  readonly participants_count: v.NullableSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
74
- readonly created_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>;
75
- readonly updated_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>;
74
+ 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`">]>;
75
+ readonly updated_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`">]>;
76
76
  /** The stored event and series it belongs to, as `meetings_list` takes them. */
77
77
  readonly event_id: v.NullableSchema<v.StringSchema<undefined>, undefined>;
78
78
  readonly series_id: v.NullableSchema<v.StringSchema<undefined>, undefined>;
@@ -81,8 +81,8 @@ export declare const meetingDetails: v.StrictObjectSchema<{
81
81
  readonly name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
82
82
  readonly email: v.NullableSchema<v.StringSchema<undefined>, undefined>;
83
83
  readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
84
- readonly joined_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>, undefined>;
85
- readonly left_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>, undefined>;
84
+ readonly joined_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>;
85
+ readonly left_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>;
86
86
  readonly duration_ms: v.NullableSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
87
87
  }, undefined>, undefined>;
88
88
  readonly transcripts: v.ArraySchema<v.StrictObjectSchema<{
@@ -92,7 +92,7 @@ export declare const meetingDetails: v.StrictObjectSchema<{
92
92
  readonly lines_count: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
93
93
  }, undefined>, undefined>;
94
94
  readonly chat: v.ArraySchema<v.StrictObjectSchema<{
95
- readonly sent_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>;
95
+ readonly sent_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`">]>;
96
96
  readonly sender: v.NullableSchema<v.StringSchema<undefined>, undefined>;
97
97
  readonly recipient: v.NullableSchema<v.StringSchema<undefined>, undefined>;
98
98
  readonly text: v.StringSchema<undefined>;
@@ -110,8 +110,8 @@ export declare const meetingDetails: v.StrictObjectSchema<{
110
110
  readonly next_steps: v.ArraySchema<v.StringSchema<undefined>, undefined>;
111
111
  readonly content: v.NullableSchema<v.StringSchema<undefined>, undefined>;
112
112
  readonly url: v.NullableSchema<v.StringSchema<undefined>, undefined>;
113
- readonly created_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>;
114
- readonly updated_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>;
113
+ 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`">]>;
114
+ readonly updated_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`">]>;
115
115
  }, undefined>, undefined>;
116
116
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
117
117
  readonly position: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
@@ -1,5 +1,5 @@
1
1
  import { annotate } from "@wirecat/cli-base/commands";
2
- import { defineQuery, fieldsHelp } from "@wirecat/cli-base/query";
2
+ import { defineQuery, fieldsHelp, timezoneOf, zonedMoment } from "@wirecat/cli-base/query";
3
3
  import { InvalidArgumentError, Option } from "commander";
4
4
  import { exportMeeting } from "../export.js";
5
5
  import { importFiles } from "../import.js";
@@ -19,9 +19,12 @@ const lookbackDays = (text) => {
19
19
  return Number(text);
20
20
  };
21
21
  const date = (text) => {
22
- if (!Number.isFinite(Date.parse(text)))
23
- throw new InvalidArgumentError("expected an ISO date or timestamp");
24
- return Date.parse(text);
22
+ try {
23
+ return zonedMoment(text, timezoneOf(), { start: 0, end: text.length });
24
+ }
25
+ catch {
26
+ throw new InvalidArgumentError("expected an ISO date, or a time with or without an offset");
27
+ }
25
28
  };
26
29
  const LOCAL_READ = { remote: "none", local: "read" };
27
30
  const LOCAL_WRITE = { remote: "none", local: "write" };
@@ -1,25 +1,23 @@
1
1
  import { CliError } from "@wirecat/cli-base";
2
2
  import { annotate } from "@wirecat/cli-base/commands";
3
+ import { timezoneOf, zonedMoment } from "@wirecat/cli-base/query";
3
4
  import { Command } from "commander";
4
5
  import { meetingHitsList } from "../answers.js";
5
6
  import { validateMeetingHitSearch } from "../search-validation.js";
6
7
  export { validateMeetingHitSearch } from "../search-validation.js";
8
+ const moment = (flag, end) => (value) => {
9
+ try {
10
+ return zonedMoment(value, timezoneOf(), { start: 0, end: value.length }, { end });
11
+ }
12
+ catch {
13
+ throw new CliError("validation_error", `${flag} takes an ISO date, or a time with or without an offset`);
14
+ }
15
+ };
7
16
  const integer = (value) => {
8
17
  if (!/^[1-9]\d*$/.test(value) || !Number.isSafeInteger(Number(value)))
9
18
  throw new CliError("validation_error", "expected a positive safe integer");
10
19
  return Number(value);
11
20
  };
12
- const timestamp = (value) => {
13
- const parsed = Date.parse(value);
14
- const date = value.slice(0, 10);
15
- const day = Date.parse(`${date}T00:00:00Z`);
16
- if (!/^\d{4}-\d{2}-\d{2}(?:T.+(?:Z|[+-]\d{2}:\d{2}))?$/.test(value) ||
17
- !Number.isFinite(parsed) ||
18
- !Number.isFinite(day) ||
19
- new Date(day).toISOString().slice(0, 10) !== date)
20
- throw new CliError("validation_error", "meeting search dates require a real ISO date or timestamp with offset");
21
- return parsed;
22
- };
23
21
  /** Shared grammar without an action: a host with other source reports supplies their transport routing. */
24
22
  export const createMeetingSearchCommand = (program) => {
25
23
  let search = program.commands.find((command) => command.name() === "search");
@@ -39,8 +37,8 @@ export const createMeetingSearchCommand = (program) => {
39
37
  .option("--project <name>", "meetings confirmed in this project, by name, key or alias")
40
38
  .option("--tag <name>", "meetings with this tag, by name or alias")
41
39
  .option("--topic <name>", "meetings with this topic, by name or alias")
42
- .option("--since-time <time>", "meetings starting at or after this ISO 8601 date or time", timestamp)
43
- .option("--until-time <time>", "meetings starting at or before this ISO 8601 date or time", timestamp)
40
+ .option("--since-time <time>", "meetings starting at or after this ISO 8601 date or time", moment("--since-time", false))
41
+ .option("--until-time <time>", "meetings starting at or before this ISO 8601 date or time; a date alone covers that whole local day", moment("--until-time", true))
44
42
  .option("--limit <n>", "how many hits, 1 through 1000 (default 100)", integer)
45
43
  .option("--cursor <cursor>", "continue from the previous page's page.next_cursor, with the same query and filters")
46
44
  .option("--max-read-bytes <bytes>", "most stored bytes to read, 1 through 67108864 (default 4194304)", integer);
@@ -11,13 +11,13 @@ export declare const meeting: v.StrictObjectSchema<{
11
11
  readonly location: v.OptionalSchema<v.NullableSchema<v.StringSchema<undefined>, undefined>, undefined>;
12
12
  /** The link that joins it. */
13
13
  readonly url: v.NullableSchema<v.StringSchema<undefined>, undefined>;
14
- readonly started_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>, undefined>;
15
- readonly ended_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>, undefined>;
14
+ readonly started_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>;
15
+ readonly ended_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>;
16
16
  readonly duration_ms: v.NullableSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
17
17
  readonly timezone: v.OptionalSchema<v.NullableSchema<v.StringSchema<undefined>, undefined>, undefined>;
18
18
  readonly participants_count: v.NullableSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
19
- readonly created_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>;
20
- readonly updated_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>;
19
+ 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`">]>;
20
+ readonly updated_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`">]>;
21
21
  }, undefined>;
22
22
  export type MeetingItem = v.InferOutput<typeof meeting>;
23
23
  /** Which provider and account a stored meeting account is, as `MeetingStore.account` answers it. */
@@ -27,7 +27,7 @@ export declare const meetingProjectEvidence: v.StrictObjectSchema<{
27
27
  readonly source: v.OptionalSchema<v.StrictObjectSchema<{
28
28
  readonly ref: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
29
29
  readonly title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
30
- readonly started_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>, undefined>;
30
+ readonly started_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>;
31
31
  readonly url: v.NullableSchema<v.StringSchema<undefined>, undefined>;
32
32
  }, undefined>, undefined>;
33
33
  }, undefined>, undefined>;
@@ -40,8 +40,8 @@ export declare const meetingProjectEvidence: v.StrictObjectSchema<{
40
40
  readonly tag_id: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>, undefined>;
41
41
  readonly event_id: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>, undefined>;
42
42
  readonly series_id: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>, undefined>;
43
- readonly since: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>, undefined>;
44
- readonly until: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>, undefined>;
43
+ 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>;
44
+ 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>;
45
45
  }, undefined>;
46
46
  readonly limits: v.StrictObjectSchema<{
47
47
  readonly limit: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
@@ -213,10 +213,10 @@ export const adminStatisticsService = (deps) => ({
213
213
  : duration(query.olderThan ?? "24h", "--older-than"),
214
214
  within: query.within === undefined && pinned ? pinned.options.within : duration(query.within ?? "7d", "--within"),
215
215
  joinSince: query.sinceTime
216
- ? momentOf(query.sinceTime, "--since-time", cutoff)
216
+ ? momentOf(query.sinceTime, "--since-time", cutoff, { zone: prepared.timezone })
217
217
  : (pinned?.options.joinSince ?? cutoff - 30 * 86_400_000),
218
218
  joinUntil: query.untilTime
219
- ? momentOf(query.untilTime, "--until-time", cutoff)
219
+ ? momentOf(query.untilTime, "--until-time", cutoff, { zone: prepared.timezone, end: true })
220
220
  : (pinned?.options.joinUntil ?? cutoff),
221
221
  minViews: count(query.minViews ?? pinned?.options.minViews ?? 1, "--min-views"),
222
222
  maxReplies: count(query.maxReplies ?? pinned?.options.maxReplies ?? 0, "--max-replies"),
@@ -23,6 +23,7 @@ export const createContextEvidenceReader = (port, allowed, limits) => {
23
23
  source: Object.freeze({ ...source }),
24
24
  ...(validated.query === undefined ? {} : { query: validated.query }),
25
25
  ...(validated.chat === undefined ? {} : { chat: validated.chat }),
26
+ ...(validated.senders === undefined ? {} : { senders: Object.freeze([...validated.senders]) }),
26
27
  after: validated.after,
27
28
  before: validated.before,
28
29
  limit: validated.limit,
@@ -9,7 +9,7 @@ export type { ProjectNeighbour, ProjectNeighbourReader } from "./project-gatheri
9
9
  export { documentPassage, storedMessageNeighbours, storedNeighbours } from "./project-gathering-around.js";
10
10
  export type { EvidenceMention, EvidenceMentions } from "./project-gathering-mentions.js";
11
11
  export { findEvidenceMentions } from "./project-gathering-mentions.js";
12
- export type { ProjectFetchState, ProjectGatheringBudgets, ProjectGatheringRequest, ProjectLane, ProjectLaneKind, ProjectLinkedChat, ProjectUnitState, } from "./project-gathering-request.js";
12
+ export type { ProjectFetchState, ProjectGatheringBudgets, ProjectGatheringRequest, ProjectLane, ProjectLaneKind, ProjectLinkedChat, ProjectPerson, ProjectSender, ProjectUnitState, } from "./project-gathering-request.js";
13
13
  import { type AccountService } from "./account.js";
14
14
  import { type AdminService } from "./admin.js";
15
15
  import { type AdminStatisticsService } from "./admin-statistics.js";
@@ -16,6 +16,7 @@ export declare const captureCommunicationSelections: (port: MeetingCommunication
16
16
  source: import("./context-evidence.js").ContextSource;
17
17
  query: string | undefined;
18
18
  chat: string | undefined;
19
+ senders: string[] | undefined;
19
20
  after: number;
20
21
  before: number;
21
22
  limit: number;
@@ -65,7 +65,7 @@ const searchPage = async (store, account, request, messenger) => {
65
65
  throw new CliError("validation_error", "discovery searches the local archive; use backend archive");
66
66
  await topUp(store);
67
67
  const found = request.discover
68
- ? await searchDiscovery(store, account, request, messenger)
68
+ ? await searchDiscovery(store, account, { ...request, thread: undefined }, messenger)
69
69
  : await searchLucene(store, account, request, messenger);
70
70
  return withThreads(store, found, request);
71
71
  };
@@ -1,12 +1,14 @@
1
+ import { type MomentZone } from "../domain/moment.js";
2
+ export type { MomentZone };
1
3
  /** Shaped like a moment `momentOf` reads, whether or not it is a valid one. */
2
4
  export declare const isMoment: (reference: string) => boolean;
3
5
  /**
4
6
  * `--since` as a moment: ISO 8601, or `30m`, `2h`, `1d` ago. Unlike max-cli, never a message id —
5
7
  * a MAX id carries its time, and a Telegram id is only a counter within one chat.
6
8
  */
7
- export declare const momentOf: (reference: string, flag?: string, now?: number) => number;
9
+ export declare const momentOf: (reference: string, flag?: string, now?: number, at?: MomentZone) => number;
8
10
  /**
9
11
  * A time ahead, for a due date or the next check: ISO 8601, or `30m`, `2h`, `3d` from now. `none`
10
12
  * is not a time; a caller that clears a date checks for it first.
11
13
  */
12
- export declare const aheadOf: (reference: string, flag: string, now?: number) => number;
14
+ export declare const aheadOf: (reference: string, flag: string, now?: number, at?: MomentZone) => number;
@@ -1,4 +1,5 @@
1
1
  import { CliError } from "@wirecat/cli-base";
2
+ import { calendarMoment } from "../domain/moment.js";
2
3
  const AGO = /^(\d+)(m|h|d)$/;
3
4
  const AGO_MS = { m: 60_000, h: 3_600_000, d: 86_400_000 };
4
5
  const DATE = /^\d{4}-\d{2}-\d{2}/;
@@ -11,11 +12,15 @@ export const isMoment = (reference) => {
11
12
  * `--since` as a moment: ISO 8601, or `30m`, `2h`, `1d` ago. Unlike max-cli, never a message id —
12
13
  * a MAX id carries its time, and a Telegram id is only a counter within one chat.
13
14
  */
14
- export const momentOf = (reference, flag = "--since", now = Date.now()) => {
15
+ export const momentOf = (reference, flag = "--since", now = Date.now(), at = {}) => {
15
16
  const wanted = reference.trim();
16
17
  const [, amount, unit] = AGO.exec(wanted) ?? [];
17
18
  // By shape: `Date.parse("12345")` is the year 12345, so a message id would pass as a date.
18
- const time = amount && unit ? now - Number(amount) * (AGO_MS[unit] ?? 0) : DATE.test(wanted) ? Date.parse(wanted) : Number.NaN;
19
+ const time = amount && unit
20
+ ? now - Number(amount) * (AGO_MS[unit] ?? 0)
21
+ : DATE.test(wanted)
22
+ ? calendarMoment(wanted, flag, at)
23
+ : Number.NaN;
19
24
  if (Number.isNaN(time)) {
20
25
  throw new CliError("validation_error", `${flag} takes an ISO 8601 time or 30m, 2h, 1d ago — not "${wanted}"`);
21
26
  }
@@ -25,10 +30,14 @@ export const momentOf = (reference, flag = "--since", now = Date.now()) => {
25
30
  * A time ahead, for a due date or the next check: ISO 8601, or `30m`, `2h`, `3d` from now. `none`
26
31
  * is not a time; a caller that clears a date checks for it first.
27
32
  */
28
- export const aheadOf = (reference, flag, now = Date.now()) => {
33
+ export const aheadOf = (reference, flag, now = Date.now(), at = {}) => {
29
34
  const wanted = reference.trim();
30
35
  const [, amount, unit] = AGO.exec(wanted) ?? [];
31
- const time = amount && unit ? now + Number(amount) * (AGO_MS[unit] ?? 0) : DATE.test(wanted) ? Date.parse(wanted) : Number.NaN;
36
+ const time = amount && unit
37
+ ? now + Number(amount) * (AGO_MS[unit] ?? 0)
38
+ : DATE.test(wanted)
39
+ ? calendarMoment(wanted, flag, at)
40
+ : Number.NaN;
32
41
  if (Number.isNaN(time)) {
33
42
  throw new CliError("validation_error", `${flag} takes an ISO 8601 time or 30m, 2h, 3d from now — not "${wanted}"`);
34
43
  }
@@ -7,10 +7,7 @@ export interface ProjectGatheringRequest {
7
7
  aliases?: string[];
8
8
  };
9
9
  keywords?: string[];
10
- people?: {
11
- name: string;
12
- reference?: string;
13
- }[];
10
+ people?: ProjectPerson[];
14
11
  sources: ContextSource[];
15
12
  /** Chats the host confirmed as the project's; each is read whole, through the source of its own account. */
16
13
  linkedChats?: ProjectLinkedChat[];
@@ -30,6 +27,20 @@ export interface ProjectGatheringRequest {
30
27
  checkpoint?: string;
31
28
  signal?: AbortSignal;
32
29
  }
30
+ /**
31
+ * `name` is searched as words; `senders` are the person's stored identities, and every message or mail one of them
32
+ * wrote in the window is read. A sender lane needs the person's `reference`.
33
+ */
34
+ export interface ProjectPerson {
35
+ name?: string;
36
+ reference?: string;
37
+ senders?: ProjectSender[];
38
+ }
39
+ /** One stored identity by its provider and the provider's own id; an email identity's id is its address. */
40
+ export interface ProjectSender {
41
+ provider: string;
42
+ id: string;
43
+ }
33
44
  export interface ProjectLinkedChat {
34
45
  provider: string;
35
46
  account: string;
@@ -42,13 +53,15 @@ export interface ProjectGatheringBudgets {
42
53
  maxElapsedMs?: number;
43
54
  maxItems?: number;
44
55
  }
45
- export type ProjectLaneKind = "project" | "key" | "alias" | "keyword" | "person" | "linked-chat";
56
+ export type ProjectLaneKind = "project" | "key" | "alias" | "keyword" | "person" | "sender" | "linked-chat";
46
57
  export interface ProjectLane {
47
58
  id: string;
48
59
  kinds: ProjectLaneKind[];
49
- /** Null for a linked chat's lane, which reads every message of the chat. */
60
+ /** Null for a linked chat's or a sender's lane, which read every message of the chat or of the sender. */
50
61
  query: string | null;
51
62
  people: string[];
63
+ /** A sender lane only: the identities whose messages and mail it reads. */
64
+ senders?: ProjectSender[];
52
65
  /** A linked chat's lane only: the chat's reference. */
53
66
  chat?: string;
54
67
  }
@@ -96,10 +109,18 @@ export declare const projectGatheringRequest: (input: ProjectGatheringRequest, n
96
109
  aliases: string[];
97
110
  };
98
111
  keywords: string[];
99
- people: {
112
+ people: ({
100
113
  name: string;
101
114
  reference: string | null;
102
- }[];
115
+ senders?: undefined;
116
+ } | {
117
+ name: string | null;
118
+ reference: string | null;
119
+ senders: {
120
+ provider: string;
121
+ id: string;
122
+ }[];
123
+ })[];
103
124
  lanes: ProjectLane[];
104
125
  sources: ContextSource[];
105
126
  linkedChats: {
@@ -148,6 +169,7 @@ export declare const projectUnits: (plan: ProjectGatheringPlan, window: {
148
169
  before: number;
149
170
  };
150
171
  chat: string | undefined;
172
+ senders: string[] | undefined;
151
173
  }[];
152
174
  export declare const seenKey: (ref: string) => string;
153
175
  export declare const encodeCheckpoint: (checkpoint: ProjectGatheringCheckpoint) => string;
@@ -6,7 +6,15 @@ import { formatReference } from "../domain/references.js";
6
6
  const DAY = 86400000;
7
7
  const MAX_TIME = 8_640_000_000_000_000;
8
8
  export const CONTEXT_CHUNK_DAYS = 90;
9
- const LIMITS = { lanes: 16, sources: 32, units: 512, lookbackDays: 730, people: 16, linkedChats: 16 };
9
+ const LIMITS = {
10
+ lanes: 16,
11
+ sources: 32,
12
+ units: 512,
13
+ lookbackDays: 730,
14
+ people: 16,
15
+ linkedChats: 16,
16
+ senders: 16,
17
+ };
10
18
  const BUDGETS = {
11
19
  maxReads: [48, 512],
12
20
  maxReadBytes: [2097152, 16777216],
@@ -77,11 +85,29 @@ export const projectGatheringRequest = (input, now) => {
77
85
  const people = list(input.people, "people", LIMITS.people).map((person, i) => {
78
86
  if (!person || typeof person !== "object")
79
87
  return fail(`people.${i} needs a name`, `people.${i}`);
80
- known(person, ["name", "reference"], `people.${i}.`);
88
+ known(person, ["name", "reference", "senders"], `people.${i}.`);
81
89
  const p = person;
90
+ const reference = p.reference === undefined ? null : text(p.reference, `people.${i}.reference`, 1024);
91
+ if (p.senders === undefined)
92
+ return { name: laneQuery(p.name, `people.${i}.name`), reference };
93
+ const senders = list(p.senders, `people.${i}.senders`, LIMITS.senders).map((value, j) => {
94
+ const name = `people.${i}.senders.${j}`;
95
+ if (!value || typeof value !== "object")
96
+ return fail(`${name} needs provider and id`, name);
97
+ known(value, ["provider", "id"], `${name}.`);
98
+ const given = value;
99
+ return { provider: text(given.provider, `${name}.provider`, 128), id: text(given.id, `${name}.id`, 1024) };
100
+ });
101
+ if (!senders.length)
102
+ fail(`people.${i}.senders needs at least one identity`, `people.${i}.senders`);
103
+ if (new Set(senders.map((one) => JSON.stringify([one.provider, one.id]))).size !== senders.length)
104
+ fail(`people.${i}.senders lists one identity twice`, `people.${i}.senders`);
105
+ if (reference === null)
106
+ fail(`people.${i} needs a reference to read by sender`, `people.${i}.reference`);
82
107
  return {
83
- name: laneQuery(p.name, `people.${i}.name`),
84
- reference: p.reference === undefined ? null : text(p.reference, `people.${i}.reference`, 1024),
108
+ name: p.name === undefined ? null : laneQuery(p.name, `people.${i}.name`),
109
+ reference,
110
+ senders,
85
111
  };
86
112
  });
87
113
  if (input.scope !== "explicit" && input.scope !== "all-connected")
@@ -139,7 +165,17 @@ export const projectGatheringRequest = (input, now) => {
139
165
  for (const k of keywords)
140
166
  lane("keyword", k);
141
167
  for (const p of people)
142
- lane("person", p.name, p.reference ?? p.name);
168
+ if (p.name !== null)
169
+ lane("person", p.name, p.reference ?? p.name);
170
+ for (const p of people)
171
+ if (p.senders && p.reference !== null)
172
+ lanes.push({
173
+ id: `l${lanes.length + 1}`,
174
+ kinds: ["sender"],
175
+ query: null,
176
+ people: [p.reference],
177
+ senders: p.senders,
178
+ });
143
179
  if (lanes.length > LIMITS.lanes)
144
180
  fail(`at most ${LIMITS.lanes} distinct search lanes`, "keywords");
145
181
  for (const linked of linkedChats) {
@@ -248,16 +284,33 @@ export const projectUnits = (plan, window) => {
248
284
  sourceIndex,
249
285
  chunk,
250
286
  chat: undefined,
287
+ senders: undefined,
251
288
  }))));
252
- // Appended after every literal unit: a checkpoint from a run without links keeps its unit order, and in each
289
+ // Only the sources of a sender's own provider: an identity of one messenger never wrote in another.
290
+ const bySender = chunks.flatMap((chunk) => plan.lanes.flatMap((lane) => plan.sources.flatMap((source, sourceIndex) => {
291
+ if (!lane.senders || (source.kind !== "message" && source.kind !== "mail"))
292
+ return [];
293
+ const ids = lane.senders.filter((one) => one.provider === source.provider).map((one) => one.id);
294
+ return ids.length ? [{ lane, source, sourceIndex, chunk, chat: undefined, senders: ids }] : [];
295
+ })));
296
+ // Appended after every literal and sender unit: a checkpoint from a run without links keeps its unit order, and in each
253
297
  // round a literal lane labels a shared hit before the linked chat does.
254
298
  const linked = chunks.flatMap((chunk) => plan.linkedChats.flatMap(({ lane, source, chat }) => {
255
299
  const found = plan.lanes.find((l) => l.id === lane);
256
300
  return found && source !== null
257
- ? [{ lane: found, source: plan.sources[source], sourceIndex: source, chunk, chat }]
301
+ ? [
302
+ {
303
+ lane: found,
304
+ source: plan.sources[source],
305
+ sourceIndex: source,
306
+ chunk,
307
+ chat,
308
+ senders: undefined,
309
+ },
310
+ ]
258
311
  : [];
259
312
  }));
260
- const units = [...literal, ...linked];
313
+ const units = [...literal, ...bySender, ...linked];
261
314
  if (units.length > LIMITS.units)
262
315
  fail(`${units.length} lane × source × 90-day reads exceed ${LIMITS.units}; narrow the lanes, sources or window`);
263
316
  return units;
@@ -39,8 +39,11 @@ export interface ProjectEvidenceItem extends Omit<ContextEvidenceItem, "text"> {
39
39
  /** A document hit with `around`: how many paragraphs after the one it centres on also name a lane word. */
40
40
  moreMatches?: number;
41
41
  }
42
- /** Strongest first: a hit that names the project, then one only from a linked chat, then one naming only a person. */
43
- export type ProjectEvidenceContext = "project" | "linked-chat" | "person-only";
42
+ /**
43
+ * Strongest first: a hit that names the project, then one only from a linked chat, then one a person wrote, then one
44
+ * naming only a person.
45
+ */
46
+ export type ProjectEvidenceContext = "project" | "linked-chat" | "sender" | "person-only";
44
47
  export type ProjectGatheringStop = "reads" | "read-bytes" | "elapsed" | "items" | null;
45
48
  export declare const gatherProjectEvidence: (ports: ProjectGatheringPorts, request: ProjectGatheringRequest) => Promise<{
46
49
  schemaVersion: 1;
@@ -5,12 +5,14 @@ import { excerpt } from "./excerpt.js";
5
5
  import { attachNeighbours, documentMatches, } from "./project-gathering-around.js";
6
6
  import { decodeCheckpoint, encodeCheckpoint, projectGatheringRequest, projectUnits, seenKey, } from "./project-gathering-request.js";
7
7
  const terminal = new Set(["done", "unavailable", "failed"]);
8
- const CONTEXTS = ["person-only", "linked-chat", "project"];
8
+ const CONTEXTS = ["person-only", "sender", "linked-chat", "project"];
9
9
  const contextOf = (lane) => lane.kinds.includes("linked-chat")
10
10
  ? "linked-chat"
11
- : lane.kinds.every((k) => k === "person")
12
- ? "person-only"
13
- : "project";
11
+ : lane.kinds.includes("sender")
12
+ ? "sender"
13
+ : lane.kinds.every((k) => k === "person")
14
+ ? "person-only"
15
+ : "project";
14
16
  export const gatherProjectEvidence = async (ports, request) => {
15
17
  if (!ports?.evidence || typeof ports.evidence.read !== "function")
16
18
  throw new CliError("configuration_error", "stored context reads are unavailable in this store");
@@ -164,7 +166,11 @@ export const gatherProjectEvidence = async (ports, request) => {
164
166
  try {
165
167
  const page = await reader.read({
166
168
  source: unit.source,
167
- ...(unit.chat === undefined ? { query: unit.lane.query ?? undefined } : { chat: unit.chat }),
169
+ ...(unit.chat !== undefined
170
+ ? { chat: unit.chat }
171
+ : unit.senders !== undefined
172
+ ? { senders: unit.senders }
173
+ : { query: unit.lane.query ?? undefined }),
168
174
  after: unit.chunk.after,
169
175
  before: unit.chunk.before,
170
176
  limit,