@wirecat/cli-core 0.544.0 → 0.546.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 (114) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/dist/calendar/busy.js +4 -1
  3. package/dist/calendar/cli/answers.d.ts +31 -0
  4. package/dist/calendar/cli/answers.js +8 -0
  5. package/dist/calendar/cli/index.d.ts +6 -0
  6. package/dist/calendar/cli/index.js +43 -4
  7. package/dist/calendar/cli/remind.js +2 -1
  8. package/dist/calendar/expand.d.ts +17 -0
  9. package/dist/calendar/expand.js +340 -0
  10. package/dist/calendar/feeds.d.ts +96 -0
  11. package/dist/calendar/feeds.js +324 -0
  12. package/dist/calendar/ics.d.ts +15 -4
  13. package/dist/calendar/ics.js +18 -7
  14. package/dist/calendar/index.d.ts +3 -1
  15. package/dist/calendar/index.js +2 -0
  16. package/dist/calendar/server-search.js +3 -1
  17. package/dist/calendar/writes.js +15 -4
  18. package/dist/cli/after-dashes.d.ts +16 -0
  19. package/dist/cli/after-dashes.js +41 -0
  20. package/dist/cli/bot/context.d.ts +1 -1
  21. package/dist/cli/bot/context.js +9 -1
  22. package/dist/cli/bot/messages.js +2 -0
  23. package/dist/cli/bot/setup.js +1 -0
  24. package/dist/cli/contract-templates.js +2 -2
  25. package/dist/cli/entry.d.ts +2 -0
  26. package/dist/cli/entry.js +3 -2
  27. package/dist/cli/index.d.ts +2 -1
  28. package/dist/cli/index.js +2 -1
  29. package/dist/cli/messenger/admin-commands.js +5 -1
  30. package/dist/cli/messenger/admin-contacts-command.js +5 -1
  31. package/dist/cli/messenger/admin-folders-command.js +5 -1
  32. package/dist/cli/messenger/archive-commands.js +20 -2
  33. package/dist/cli/messenger/backfill-command.js +13 -2
  34. package/dist/cli/messenger/complete-command.js +1 -0
  35. package/dist/cli/messenger/context.js +3 -0
  36. package/dist/cli/messenger/history-gaps.d.ts +12 -0
  37. package/dist/cli/messenger/history-gaps.js +41 -0
  38. package/dist/cli/messenger/messages-command.js +9 -22
  39. package/dist/cli/messenger/messages-edit-command.js +1 -0
  40. package/dist/cli/messenger/messages-press-command.js +1 -0
  41. package/dist/cli/messenger/messages-search-command.js +5 -1
  42. package/dist/cli/messenger/messages-send-command.js +1 -0
  43. package/dist/cli/messenger/observed.js +2 -2
  44. package/dist/cli/messenger/polls-command.js +1 -0
  45. package/dist/cli/messenger/private-people-command.js +5 -1
  46. package/dist/cli/messenger/search-answers.d.ts +3 -2
  47. package/dist/cli/messenger/search-answers.js +12 -5
  48. package/dist/cli/messenger/search-command.js +3 -1
  49. package/dist/cli/messenger/stored.d.ts +6 -1
  50. package/dist/cli/messenger/stored.js +94 -5
  51. package/dist/cli/messenger/topics-command.js +1 -1
  52. package/dist/cli/program.js +17 -6
  53. package/dist/cli/schedule/schedule-command.js +1 -0
  54. package/dist/cli/settings.d.ts +9 -0
  55. package/dist/cli/settings.js +20 -0
  56. package/dist/cli/shared-command-exports.d.ts +24 -0
  57. package/dist/cli/steps.js +1 -0
  58. package/dist/domain/message-link.d.ts +3 -0
  59. package/dist/domain/message-link.js +9 -14
  60. package/dist/domain/money-words.d.ts +11 -0
  61. package/dist/domain/money-words.js +38 -0
  62. package/dist/domain/providers.d.ts +2 -0
  63. package/dist/domain/providers.js +15 -3
  64. package/dist/domain/ref-arguments.d.ts +31 -0
  65. package/dist/domain/ref-arguments.js +79 -0
  66. package/dist/domain/references.d.ts +20 -2
  67. package/dist/domain/references.js +132 -47
  68. package/dist/entities/index.d.ts +1 -0
  69. package/dist/entities/message-transfer.d.ts +4 -0
  70. package/dist/entities/message-transfer.js +2 -0
  71. package/dist/errors/reasons.js +18 -0
  72. package/dist/index.d.ts +1 -1
  73. package/dist/index.js +1 -1
  74. package/dist/meetings/mcp/index.d.ts +15 -15
  75. package/dist/oauth/client-file.d.ts +14 -0
  76. package/dist/oauth/client-file.js +28 -0
  77. package/dist/oauth/index.d.ts +1 -0
  78. package/dist/oauth/index.js +1 -0
  79. package/dist/ports/messenger.d.ts +3 -2
  80. package/dist/resolve.d.ts +3 -1
  81. package/dist/resolve.js +8 -0
  82. package/dist/search/task-signature.d.ts +5 -1
  83. package/dist/search/task-signature.js +27 -9
  84. package/dist/services/agenda.d.ts +5 -0
  85. package/dist/services/agenda.js +3 -1
  86. package/dist/services/history-limits.d.ts +45 -0
  87. package/dist/services/history-limits.js +50 -0
  88. package/dist/services/index.d.ts +5 -1
  89. package/dist/services/index.js +5 -1
  90. package/dist/services/message-resolution.d.ts +4 -1
  91. package/dist/services/message-resolution.js +19 -3
  92. package/dist/services/messages.js +15 -13
  93. package/dist/services/money-words.d.ts +3 -0
  94. package/dist/services/money-words.js +5 -0
  95. package/dist/services/questions.d.ts +7 -2
  96. package/dist/services/questions.js +13 -4
  97. package/dist/services/stored-project-gathering.d.ts +9 -0
  98. package/dist/services/stored-project-gathering.js +9 -4
  99. package/dist/services/task-categories.d.ts +2 -0
  100. package/dist/services/task-categories.js +19 -8
  101. package/dist/store/chats.d.ts +10 -0
  102. package/dist/store/chats.js +25 -0
  103. package/dist/store/sqlite/chats.js +2 -1
  104. package/dist/store/sqlite/evidence-sources.js +2 -2
  105. package/dist/store/sqlite/history-skips.d.ts +24 -0
  106. package/dist/store/sqlite/history-skips.js +95 -0
  107. package/dist/store/sqlite/identities.js +1 -1
  108. package/dist/store/sqlite/knowledge.js +7 -2
  109. package/dist/store/sqlite/notes.js +4 -4
  110. package/dist/store/sqlite/things.d.ts +0 -1
  111. package/dist/store/sqlite/things.js +25 -24
  112. package/dist/store/store.d.ts +1 -0
  113. package/package.json +1 -1
  114. package/parity.json +4 -0
@@ -38,7 +38,6 @@ export declare const meetingTools: ({
38
38
  max_read_bytes?: undefined;
39
39
  cursor?: undefined;
40
40
  } | {
41
- limit?: undefined;
42
41
  event_id?: undefined;
43
42
  series_id?: undefined;
44
43
  page?: undefined;
@@ -54,11 +53,11 @@ export declare const meetingTools: ({
54
53
  format?: undefined;
55
54
  transcript_id?: undefined;
56
55
  query?: undefined;
56
+ limit?: undefined;
57
57
  scan_limit?: undefined;
58
58
  max_read_bytes?: undefined;
59
59
  cursor?: undefined;
60
60
  } | {
61
- limit?: undefined;
62
61
  event_id?: undefined;
63
62
  series_id?: undefined;
64
63
  page?: undefined;
@@ -76,11 +75,11 @@ export declare const meetingTools: ({
76
75
  format?: undefined;
77
76
  transcript_id?: undefined;
78
77
  query?: undefined;
78
+ limit?: undefined;
79
79
  scan_limit?: undefined;
80
80
  max_read_bytes?: undefined;
81
81
  cursor?: undefined;
82
82
  } | {
83
- limit?: undefined;
84
83
  event_id?: undefined;
85
84
  series_id?: undefined;
86
85
  page?: undefined;
@@ -102,6 +101,7 @@ export declare const meetingTools: ({
102
101
  minimum: number;
103
102
  };
104
103
  query?: undefined;
104
+ limit?: undefined;
105
105
  scan_limit?: undefined;
106
106
  max_read_bytes?: undefined;
107
107
  cursor?: undefined;
@@ -188,7 +188,6 @@ export declare const meetingTools: ({
188
188
  max_read_bytes?: undefined;
189
189
  cursor?: undefined;
190
190
  } | {
191
- limit?: undefined;
192
191
  event_id?: undefined;
193
192
  series_id?: undefined;
194
193
  page?: undefined;
@@ -204,11 +203,11 @@ export declare const meetingTools: ({
204
203
  format?: undefined;
205
204
  transcript_id?: undefined;
206
205
  query?: undefined;
206
+ limit?: undefined;
207
207
  scan_limit?: undefined;
208
208
  max_read_bytes?: undefined;
209
209
  cursor?: undefined;
210
210
  } | {
211
- limit?: undefined;
212
211
  event_id?: undefined;
213
212
  series_id?: undefined;
214
213
  page?: undefined;
@@ -226,11 +225,11 @@ export declare const meetingTools: ({
226
225
  format?: undefined;
227
226
  transcript_id?: undefined;
228
227
  query?: undefined;
228
+ limit?: undefined;
229
229
  scan_limit?: undefined;
230
230
  max_read_bytes?: undefined;
231
231
  cursor?: undefined;
232
232
  } | {
233
- limit?: undefined;
234
233
  event_id?: undefined;
235
234
  series_id?: undefined;
236
235
  page?: undefined;
@@ -252,6 +251,7 @@ export declare const meetingTools: ({
252
251
  minimum: number;
253
252
  };
254
253
  query?: undefined;
254
+ limit?: undefined;
255
255
  scan_limit?: undefined;
256
256
  max_read_bytes?: undefined;
257
257
  cursor?: undefined;
@@ -338,7 +338,6 @@ export declare const meetingTools: ({
338
338
  max_read_bytes?: undefined;
339
339
  cursor?: undefined;
340
340
  } | {
341
- limit?: undefined;
342
341
  event_id?: undefined;
343
342
  series_id?: undefined;
344
343
  page?: undefined;
@@ -354,11 +353,11 @@ export declare const meetingTools: ({
354
353
  format?: undefined;
355
354
  transcript_id?: undefined;
356
355
  query?: undefined;
356
+ limit?: undefined;
357
357
  scan_limit?: undefined;
358
358
  max_read_bytes?: undefined;
359
359
  cursor?: undefined;
360
360
  } | {
361
- limit?: undefined;
362
361
  event_id?: undefined;
363
362
  series_id?: undefined;
364
363
  page?: undefined;
@@ -376,11 +375,11 @@ export declare const meetingTools: ({
376
375
  format?: undefined;
377
376
  transcript_id?: undefined;
378
377
  query?: undefined;
378
+ limit?: undefined;
379
379
  scan_limit?: undefined;
380
380
  max_read_bytes?: undefined;
381
381
  cursor?: undefined;
382
382
  } | {
383
- limit?: undefined;
384
383
  event_id?: undefined;
385
384
  series_id?: undefined;
386
385
  page?: undefined;
@@ -402,6 +401,7 @@ export declare const meetingTools: ({
402
401
  minimum: number;
403
402
  };
404
403
  query?: undefined;
404
+ limit?: undefined;
405
405
  scan_limit?: undefined;
406
406
  max_read_bytes?: undefined;
407
407
  cursor?: undefined;
@@ -488,7 +488,6 @@ export declare const meetingTools: ({
488
488
  max_read_bytes?: undefined;
489
489
  cursor?: undefined;
490
490
  } | {
491
- limit?: undefined;
492
491
  event_id?: undefined;
493
492
  series_id?: undefined;
494
493
  page?: undefined;
@@ -504,11 +503,11 @@ export declare const meetingTools: ({
504
503
  format?: undefined;
505
504
  transcript_id?: undefined;
506
505
  query?: undefined;
506
+ limit?: undefined;
507
507
  scan_limit?: undefined;
508
508
  max_read_bytes?: undefined;
509
509
  cursor?: undefined;
510
510
  } | {
511
- limit?: undefined;
512
511
  event_id?: undefined;
513
512
  series_id?: undefined;
514
513
  page?: undefined;
@@ -526,11 +525,11 @@ export declare const meetingTools: ({
526
525
  format?: undefined;
527
526
  transcript_id?: undefined;
528
527
  query?: undefined;
528
+ limit?: undefined;
529
529
  scan_limit?: undefined;
530
530
  max_read_bytes?: undefined;
531
531
  cursor?: undefined;
532
532
  } | {
533
- limit?: undefined;
534
533
  event_id?: undefined;
535
534
  series_id?: undefined;
536
535
  page?: undefined;
@@ -552,6 +551,7 @@ export declare const meetingTools: ({
552
551
  minimum: number;
553
552
  };
554
553
  query?: undefined;
554
+ limit?: undefined;
555
555
  scan_limit?: undefined;
556
556
  max_read_bytes?: undefined;
557
557
  cursor?: undefined;
@@ -638,7 +638,6 @@ export declare const meetingTools: ({
638
638
  max_read_bytes?: undefined;
639
639
  cursor?: undefined;
640
640
  } | {
641
- limit?: undefined;
642
641
  event_id?: undefined;
643
642
  series_id?: undefined;
644
643
  page?: undefined;
@@ -654,11 +653,11 @@ export declare const meetingTools: ({
654
653
  format?: undefined;
655
654
  transcript_id?: undefined;
656
655
  query?: undefined;
656
+ limit?: undefined;
657
657
  scan_limit?: undefined;
658
658
  max_read_bytes?: undefined;
659
659
  cursor?: undefined;
660
660
  } | {
661
- limit?: undefined;
662
661
  event_id?: undefined;
663
662
  series_id?: undefined;
664
663
  page?: undefined;
@@ -676,11 +675,11 @@ export declare const meetingTools: ({
676
675
  format?: undefined;
677
676
  transcript_id?: undefined;
678
677
  query?: undefined;
678
+ limit?: undefined;
679
679
  scan_limit?: undefined;
680
680
  max_read_bytes?: undefined;
681
681
  cursor?: undefined;
682
682
  } | {
683
- limit?: undefined;
684
683
  event_id?: undefined;
685
684
  series_id?: undefined;
686
685
  page?: undefined;
@@ -702,6 +701,7 @@ export declare const meetingTools: ({
702
701
  minimum: number;
703
702
  };
704
703
  query?: undefined;
704
+ limit?: undefined;
705
705
  scan_limit?: undefined;
706
706
  max_read_bytes?: undefined;
707
707
  cursor?: undefined;
@@ -0,0 +1,14 @@
1
+ import { CliError } from "@wirecat/cli-base";
2
+ import type { OAuthClient } from "./provider.js";
3
+ export interface ClientFileOptions {
4
+ platform?: NodeJS.Platform;
5
+ /** Skip the owner-only check, for a file the package ships. Default: the file must be private to its owner. */
6
+ shipped?: boolean;
7
+ /** Thrown when the file cannot be read or has the wrong shape. */
8
+ invalid?: CliError;
9
+ }
10
+ /**
11
+ * Reads a Google Desktop OAuth client file (`{ "installed": { client_id, client_secret? } }`, the JSON Google's console
12
+ * downloads). Refuses a file other users can read, and never puts the file's contents in an error.
13
+ */
14
+ export declare const readClientFile: (location: URL | string, options?: ClientFileOptions) => OAuthClient;
@@ -0,0 +1,28 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { CliError } from "@wirecat/cli-base";
3
+ import { privateFiles } from "../cli/messenger/private-files.js";
4
+ // No schema library: the keeper-process test bundles this module without a node_modules next to it.
5
+ /**
6
+ * Reads a Google Desktop OAuth client file (`{ "installed": { client_id, client_secret? } }`, the JSON Google's console
7
+ * downloads). Refuses a file other users can read, and never puts the file's contents in an error.
8
+ */
9
+ export const readClientFile = (location, options = {}) => {
10
+ if (!options.shipped && typeof location === "string") {
11
+ const mode = privateFiles({ files: [location], dirs: [] }, options.platform);
12
+ if (mode.checked && !mode.ok)
13
+ throw new CliError("configuration_error", `the client file can be read by other users of this computer; run chmod 600 ${location} and try again`, { reason: "sign-in.client-file-open" });
14
+ }
15
+ try {
16
+ const { installed } = JSON.parse(readFileSync(location, "utf8"));
17
+ const { client_id: clientId, client_secret: clientSecret } = installed;
18
+ if (typeof clientId !== "string" || !clientId)
19
+ throw new TypeError("client_id");
20
+ if (clientSecret !== undefined && typeof clientSecret !== "string")
21
+ throw new TypeError("client_secret");
22
+ return { clientId, ...(clientSecret ? { clientSecret } : {}) };
23
+ }
24
+ catch {
25
+ throw (options.invalid ??
26
+ new CliError("configuration_error", "could not read a Desktop app OAuth client JSON from the client file; its contents were not logged", { reason: "sign-in.client-file-invalid" }));
27
+ }
28
+ };
@@ -1,4 +1,5 @@
1
1
  export { type Authorization, type AuthorizeOptions, authorize } from "./authorize.js";
2
+ export { type ClientFileOptions, readClientFile } from "./client-file.js";
2
3
  export { type SignInOptions, type TokenKeeper, type TokenKeeperOptions, type TokensOptions, tokenKeeper, } from "./keeper.js";
3
4
  export type { ExchangeContext, OAuthClient, OAuthProvider, RequestContext, TokenSet } from "./provider.js";
4
5
  export { requestTokens, revokeToken, TOKEN_LOG_ALLOW, TOKEN_LOG_EVENT } from "./requests.js";
@@ -1,4 +1,5 @@
1
1
  export { authorize } from "./authorize.js";
2
+ export { readClientFile } from "./client-file.js";
2
3
  export { tokenKeeper, } from "./keeper.js";
3
4
  export { requestTokens, revokeToken, TOKEN_LOG_ALLOW, TOKEN_LOG_EVENT } from "./requests.js";
4
5
  export { credentialStorage, driveSessionCodec, oauthAccount, standardCodec, zoomTokenCodec, } from "./storage.js";
@@ -401,9 +401,10 @@ export interface HistoryBatch {
401
401
  /**
402
402
  * Takes one pushed batch. The CLI's own handler resolves `true` once the batch is in the local store and `false` when it
403
403
  * could not be written, so a messenger that pushes history once can keep a batch until it is saved; a handler that
404
- * stores nothing returns nothing.
404
+ * stores nothing returns nothing. A command's own handler, called through the store, is handed that answer as `saved`,
405
+ * so what it reports as kept is what reached the store.
405
406
  */
406
- export type HistoryBatchHandler = (batch: HistoryBatch) => void | Promise<boolean>;
407
+ export type HistoryBatchHandler = (batch: HistoryBatch, saved?: Promise<boolean>) => void | Promise<boolean>;
407
408
  /** For a messenger that pushes its history to the client instead of answering for it (`Messenger.history: "store"`). */
408
409
  export interface PushedHistory {
409
410
  /** Chats, people and past messages as the messenger pushes them, until `signal` aborts. */
package/dist/resolve.d.ts CHANGED
@@ -1,6 +1,8 @@
1
- import type { Chat, Contact, Id } from "./domain/models.js";
1
+ import type { Chat, Contact, Id, Provider } from "./domain/models.js";
2
2
  /** The part of a store that `pickPerson` reads. */
3
3
  export interface PeopleLookup {
4
+ /** Whose identities these are; a `contact:` reference of another provider is refused when it is set. */
5
+ provider?: Provider;
4
6
  get(id: Id): Contact | undefined;
5
7
  /** Everyone known, in any order. */
6
8
  all(): Contact[];
package/dist/resolve.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { CliError, singleLine } from "@wirecat/cli-base";
2
+ import { contactReferenceOf } from "./domain/ref-arguments.js";
2
3
  /** Shaped like a Telegram or MAX id. Another messenger's ids may be any string, so a miss here is not "not an id". */
3
4
  export const isId = (reference) => /^-?\d+$/.test(reference.trim());
4
5
  /**
@@ -31,6 +32,13 @@ export const pickChat = (reference, chats) => {
31
32
  */
32
33
  export const pickPerson = (reference, people) => {
33
34
  const trimmed = reference.trim();
35
+ const contact = contactReferenceOf(trimmed, people.provider);
36
+ if (contact !== undefined) {
37
+ const named = people.get(contact);
38
+ if (named)
39
+ return named;
40
+ throw new CliError("not_found", `no person ${contact} in what this account has seen`);
41
+ }
34
42
  const known = people.get(trimmed);
35
43
  if (known)
36
44
  return known;
@@ -7,7 +7,11 @@ export type TaskAmount = {
7
7
  value: number;
8
8
  currency: string | null;
9
9
  };
10
- /** A bare number is not an amount: `верни 3000 за бензин` has none, so a reminder about it compares as missing. */
10
+ /**
11
+ * A bare number is not an amount: `верни 3000 за бензин` has none, so a reminder about it compares as missing.
12
+ * Takes the text as written; it is lower-cased here.
13
+ */
14
+ export declare const amountsOf: (text: string) => TaskAmount[];
11
15
  export declare const amountOf: (text: string) => TaskAmount | null;
12
16
  /** The creator's calendar date in the machine's time zone, as a day count: "tomorrow" late at night is local. */
13
17
  export declare const dayOf: (ms: number) => number;
@@ -10,18 +10,36 @@ const CURRENCY = [
10
10
  [/^(\$|usd|долл.*|dollars?)$/u, "USD"],
11
11
  ];
12
12
  const currencyOf = (word) => word ? (CURRENCY.find(([pattern]) => pattern.test(word))?.[1] ?? null) : null;
13
- const AMOUNT = /(?<![\p{L}\p{N}])([$€₽])?\s?(\d+(?:[.,]\d+)?)(?:([кk])(?![\p{L}])|\s?(тыс\.?|тысяч\p{L}*))?(?![\p{N}])\s?(₽|руб\p{L}*\.?|р\.|р(?![\p{L}])|rub|eur|евро|€|\$|usd|долл\p{L}*|dollars?|euros?)?(?![\p{L}])/gu;
14
- /** A bare number is not an amount: `верни 3000 за бензин` has none, so a reminder about it compares as missing. */
15
- export const amountOf = (text) => {
16
- for (const [, before, number = "", short, long, after] of text.matchAll(AMOUNT)) {
17
- const multiplier = short ?? long;
18
- if (!before && !after && !multiplier)
13
+ const SEPARATORS = String.raw `[ \u00a0\u2009\u202f.,]`;
14
+ /**
15
+ * Groups of three after a space (also no-break, thin, narrow no-break), a dot or a comma (`50 000`, `1.200`,
16
+ * `$1,200`) are one number, with an optional one or two digit decimal after (`5 000.50`). A lone `1,5` is a decimal.
17
+ */
18
+ const NUMBER = String.raw `\d{1,3}(?:${SEPARATORS}\d{3})+(?:[.,]\d{1,2})?|\d+(?:[.,]\d+)?`;
19
+ const AMOUNT = new RegExp(String.raw `(?<![\p{L}\p{N}])([$€₽])?\s?(${NUMBER})(?:([кk])(?![\p{L}])|\s?(тыс\.?|тысяч\p{L}*))?(?![\p{N}])\s?(₽|руб\p{L}*\.?|р\.|р(?![\p{L}])|rub|eur|евро|€|\$|usd|долл\p{L}*|dollars?|euros?)?(?![\p{L}])`, "gu");
20
+ const GROUPED = new RegExp(String.raw `^(\d{1,3}(?:${SEPARATORS}\d{3})+)([.,]\d{1,2})?$`, "u");
21
+ const amountValue = (number, thousands) => {
22
+ const grouped = GROUPED.exec(number);
23
+ const plain = grouped
24
+ ? `${(grouped[1] ?? "").replace(new RegExp(SEPARATORS, "gu"), "")}${(grouped[2] ?? "").replace(",", ".")}`
25
+ : number.replace(",", ".");
26
+ return Math.round(Number(plain) * (thousands ? 1000 : 1) * 100) / 100;
27
+ };
28
+ /**
29
+ * A bare number is not an amount: `верни 3000 за бензин` has none, so a reminder about it compares as missing.
30
+ * Takes the text as written; it is lower-cased here.
31
+ */
32
+ export const amountsOf = (text) => {
33
+ const found = [];
34
+ for (const [, before, number = "", short, long, after] of text.toLowerCase().matchAll(AMOUNT)) {
35
+ const thousands = (short ?? long) !== undefined;
36
+ if (!before && !after && !thousands)
19
37
  continue;
20
- const value = Math.round(Number(number.replace(",", ".")) * (multiplier ? 1000 : 1));
21
- return { value, currency: currencyOf(before ?? after?.replace(/\.$/u, "")) };
38
+ found.push({ value: amountValue(number, thousands), currency: currencyOf(before ?? after?.replace(/\.$/u, "")) });
22
39
  }
23
- return null;
40
+ return found;
24
41
  };
42
+ export const amountOf = (text) => amountsOf(text)[0] ?? null;
25
43
  const RU_WEEKDAYS = ["воскресен", "понедельник", "вторник", "сред", "четверг", "пятниц", "суббот"];
26
44
  const EN_WEEKDAYS = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
27
45
  const ES_WEEKDAYS = ["domingo", "lunes", "martes", "miercoles", "jueves", "viernes", "sabado"];
@@ -4,6 +4,11 @@ import { agenda } from "../entities/agenda.js";
4
4
  import { type ChatActivity, type TaskDetails } from "../store/store.js";
5
5
  import { type ChatCoverage } from "./chat-coverage.js";
6
6
  import type { ServiceDeps } from "./deps.js";
7
+ /** How long a chat must be silent before `tasks agenda` lists it, per side. */
8
+ export declare const AGENDA_DEFAULTS: {
9
+ readonly unanswered: 86400000;
10
+ readonly waiting: number;
11
+ };
7
12
  export interface AgendaOptions {
8
13
  /** ms: a task due within this is listed. */
9
14
  dueWithin?: number;
@@ -8,6 +8,8 @@ import { CHAT_KINDS } from "./chats.js";
8
8
  import { taskAccount } from "./task-rules.js";
9
9
  import { taskTagFilters } from "./task-tag-filters.js";
10
10
  const DAY = 86_400_000;
11
+ /** How long a chat must be silent before `tasks agenda` lists it, per side. */
12
+ export const AGENDA_DEFAULTS = { unanswered: DAY, waiting: 7 * DAY };
11
13
  const CLAIMS = ["none", "mine", "others", "any"];
12
14
  const TASK_FIELDS = new Set(["tag", "category", "claimed"]);
13
15
  export const AGENDA_QUERY = {
@@ -35,7 +37,7 @@ export const AGENDA_QUERY = {
35
37
  defaults: ["chat_kind:(dialog OR group)", "claimed:(none OR mine)"],
36
38
  };
37
39
  export const agendaService = (deps, now = Date.now) => ({
38
- show: async ({ dueWithin = DAY, waiting = 7 * DAY, unanswered = DAY, within = 30 * DAY, query, holder } = {}) => {
40
+ show: async ({ dueWithin = DAY, waiting = AGENDA_DEFAULTS.waiting, unanswered = AGENDA_DEFAULTS.unanswered, within = 30 * DAY, query, holder, } = {}) => {
39
41
  const [store, account] = [await deps.store(), await deps.account()];
40
42
  const at = now();
41
43
  const parts = query ? split(query) : { tasks: undefined, chats: undefined };
@@ -0,0 +1,45 @@
1
+ import type { Id } from "../domain/models.js";
2
+ import type { HistoryBatch } from "../ports/messenger.js";
3
+ /** How much pushed history the store keeps of each kind of chat. */
4
+ export interface HistoryLimits {
5
+ /** One-to-one chats: days back from now. */
6
+ dialogDays: number;
7
+ /** Groups of more than two people: days back from now. */
8
+ groupDays: number;
9
+ /** Above this many people a group's pushed history is not kept at all. */
10
+ groupMembersMax: number;
11
+ }
12
+ export declare const HISTORY_MAX_DAYS = 1095;
13
+ export declare const DEFAULT_HISTORY_LIMITS: HistoryLimits;
14
+ export type HistorySkipReason = "group_too_large" | "older_than_window";
15
+ /** What the limits left out of one chat, summed over every push. */
16
+ export interface HistorySkip {
17
+ reason: HistorySkipReason;
18
+ messages: number;
19
+ oldestAt: string;
20
+ newestAt: string;
21
+ /** For `group_too_large`: how many people the group had. */
22
+ members?: number;
23
+ /** For `older_than_window`: the window's start when the push was saved. */
24
+ keptFrom?: string;
25
+ }
26
+ /** The kind and size of a chat, as the batch names it or the store holds it. */
27
+ export interface ChatShape {
28
+ kind: string;
29
+ participantsCount: number | null;
30
+ }
31
+ /** Whether this chat's pushed history is kept, and from when. A chat the owner lifted keeps everything. */
32
+ export declare const historyWindow: (shape: ChatShape | undefined, limits: HistoryLimits, now: number) => {
33
+ keepFrom: number;
34
+ } | {
35
+ skip: "group_too_large";
36
+ members: number;
37
+ };
38
+ /**
39
+ * The batch without the messages its chats' limits leave out, and what was left out per chat. Chat rows, people and
40
+ * re-keys always stay: a skipped chat is still listed, so its history can be fetched by hand.
41
+ */
42
+ export declare const limitBatch: (batch: HistoryBatch, shapes: (chatId: Id) => ChatShape | undefined, included: (chatId: Id) => boolean, limits: HistoryLimits, now?: number) => {
43
+ batch: HistoryBatch;
44
+ skipped: Map<Id, HistorySkip>;
45
+ };
@@ -0,0 +1,50 @@
1
+ export const HISTORY_MAX_DAYS = 1095;
2
+ export const DEFAULT_HISTORY_LIMITS = { dialogDays: 1095, groupDays: 365, groupMembersMax: 50 };
3
+ const DAY_MS = 24 * 60 * 60 * 1000;
4
+ /** Whether this chat's pushed history is kept, and from when. A chat the owner lifted keeps everything. */
5
+ export const historyWindow = (shape, limits, now) => {
6
+ const group = shape !== undefined && shape.kind !== "dialog" && shape.kind !== "saved";
7
+ if (group && (shape.participantsCount ?? 0) > limits.groupMembersMax)
8
+ return { skip: "group_too_large", members: shape.participantsCount ?? 0 };
9
+ const days = group ? limits.groupDays : limits.dialogDays;
10
+ return { keepFrom: now - Math.min(days, HISTORY_MAX_DAYS) * DAY_MS };
11
+ };
12
+ /**
13
+ * The batch without the messages its chats' limits leave out, and what was left out per chat. Chat rows, people and
14
+ * re-keys always stay: a skipped chat is still listed, so its history can be fetched by hand.
15
+ */
16
+ export const limitBatch = (batch, shapes, included, limits, now = Date.now()) => {
17
+ const skipped = new Map();
18
+ if (!batch.messages?.length)
19
+ return { batch, skipped };
20
+ const named = new Map((batch.chats ?? []).map((chat) => [chat.id, chat]));
21
+ const windows = new Map();
22
+ const windowOf = (chatId) => {
23
+ if (!windows.has(chatId)) {
24
+ const chat = named.get(chatId);
25
+ const shape = chat ? { kind: chat.kind, participantsCount: chat.participantsCount ?? null } : shapes(chatId);
26
+ windows.set(chatId, included(chatId) ? undefined : historyWindow(shape, limits, now));
27
+ }
28
+ return windows.get(chatId);
29
+ };
30
+ const messages = batch.messages.filter((message) => {
31
+ const window = windowOf(message.chatId);
32
+ if (window === undefined)
33
+ return true;
34
+ const at = Date.parse(message.timestamp);
35
+ if ("keepFrom" in window && !(at < window.keepFrom))
36
+ return true;
37
+ const reason = "skip" in window ? window.skip : "older_than_window";
38
+ const held = skipped.get(message.chatId);
39
+ const iso = new Date(at).toISOString();
40
+ skipped.set(message.chatId, {
41
+ reason,
42
+ messages: (held?.messages ?? 0) + 1,
43
+ oldestAt: held && held.oldestAt < iso ? held.oldestAt : iso,
44
+ newestAt: held && held.newestAt > iso ? held.newestAt : iso,
45
+ ...("skip" in window ? { members: window.members } : { keptFrom: new Date(window.keepFrom).toISOString() }),
46
+ });
47
+ return false;
48
+ });
49
+ return { batch: { ...batch, messages }, skipped };
50
+ };
@@ -1,4 +1,6 @@
1
1
  import { type BotCheckService } from "../botcheck/service.js";
2
+ export { amountOf, amountsOf, type TaskAmount } from "../search/task-signature.js";
3
+ export { AGENDA_DEFAULTS } from "./agenda.js";
2
4
  export { type ChatCoverage, chatCoverage } from "./chat-coverage.js";
3
5
  export type { ContextEvidenceItem, ContextEvidencePage, ContextEvidenceReader, ContextEvidenceRequest, ContextEvidenceSender, ContextIndex, ContextReadLimits, ContextReadUsage, ContextRecordReader, ContextSource, ScopedContextEvidenceReader, } from "./context-evidence.js";
4
6
  export { contextEvidenceFingerprint, createContextEvidenceReader } from "./context-evidence.js";
@@ -9,6 +11,7 @@ export type { MediaArchiveEntry, MediaIdentity } from "./media-archive-manifest.
9
11
  export type { MoveMediaArchiveOptions } from "./media-archive-move.js";
10
12
  export { moveMediaArchive } from "./media-archive-move.js";
11
13
  export { type DownloadedModel, downloadImageModel, downloadTextModel, type ModelDownloadOptions, } from "./model-downloads.js";
14
+ export { MONEY_DONE, MONEY_REQUEST, MONEY_WORDS, moneyClassesOf, PROMISE } from "./money-words.js";
12
15
  export type { ProjectChatFetcher, ProjectChatFetchProgress, ProjectChatFetchRequest, ProjectEvidenceContext, ProjectEvidenceItem, ProjectEvidencePerson, ProjectGathering, ProjectGatheringPorts, ProjectGatheringStop, ProjectIndexCatchUp, } from "./project-gathering.js";
13
16
  export { gatherProjectEvidence } from "./project-gathering.js";
14
17
  export type { ProjectNeighbour, ProjectNeighbourReader } from "./project-gathering-around.js";
@@ -16,7 +19,8 @@ export { documentPassage, storedMessageNeighbours, storedNeighbours } from "./pr
16
19
  export type { EvidenceMention, EvidenceMentions } from "./project-gathering-mentions.js";
17
20
  export { findEvidenceMentions } from "./project-gathering-mentions.js";
18
21
  export type { ProjectChatFetched, ProjectGatheringBudgets, ProjectGatheringRequest, ProjectLane, ProjectLaneKind, ProjectLinkedChat, ProjectPerson, ProjectSender, ProjectUnitState, } from "./project-gathering-request.js";
19
- export { canonicalProjectRef, gatherStoredProject, type StoredProjectGathering, type StoredProjectInput, } from "./stored-project-gathering.js";
22
+ export { questions } from "./questions.js";
23
+ export { canonicalProjectRef, gatherStoredProject, type LinkedChat, linkedChatsOf, type StoredProjectGathering, type StoredProjectInput, } from "./stored-project-gathering.js";
20
24
  import { type AccountService } from "./account.js";
21
25
  import { type AdminService } from "./admin.js";
22
26
  import { type AdminStatisticsService } from "./admin-statistics.js";
@@ -1,14 +1,18 @@
1
1
  import { botCheckService } from "../botcheck/service.js";
2
+ export { amountOf, amountsOf } from "../search/task-signature.js";
3
+ export { AGENDA_DEFAULTS } from "./agenda.js";
2
4
  export { chatCoverage } from "./chat-coverage.js";
3
5
  export { contextEvidenceFingerprint, createContextEvidenceReader } from "./context-evidence.js";
4
6
  export { excerpt } from "./excerpt.js";
5
7
  export { archiveMedia, checkMediaArchive } from "./media-archive.js";
6
8
  export { moveMediaArchive } from "./media-archive-move.js";
7
9
  export { downloadImageModel, downloadTextModel, } from "./model-downloads.js";
10
+ export { MONEY_DONE, MONEY_REQUEST, MONEY_WORDS, moneyClassesOf, PROMISE } from "./money-words.js";
8
11
  export { gatherProjectEvidence } from "./project-gathering.js";
9
12
  export { documentPassage, storedMessageNeighbours, storedNeighbours } from "./project-gathering-around.js";
10
13
  export { findEvidenceMentions } from "./project-gathering-mentions.js";
11
- export { canonicalProjectRef, gatherStoredProject, } from "./stored-project-gathering.js";
14
+ export { questions } from "./questions.js";
15
+ export { canonicalProjectRef, gatherStoredProject, linkedChatsOf, } from "./stored-project-gathering.js";
12
16
  import { accountService } from "./account.js";
13
17
  import { adminService } from "./admin.js";
14
18
  import { adminStatisticsService } from "./admin-statistics.js";
@@ -1,4 +1,5 @@
1
- import type { Id } from "../domain/models.js";
1
+ import type { Id, Provider } from "../domain/models.js";
2
+ import { type ProfilesOn } from "../domain/ref-arguments.js";
2
3
  import type { MessengerTraits } from "../ports/traits.js";
3
4
  import type { AccountStore } from "../store/account-store.js";
4
5
  import type { AccountKey, MessageStore } from "../store/store.js";
@@ -21,6 +22,8 @@ export declare const storedChatId: (messenger: Saved, reference: string, store:
21
22
  export declare const chatIdIn: (messenger: Saved, reference: string, store: AccountStore, { includeLeft }?: {
22
23
  includeLeft?: boolean;
23
24
  }) => Promise<string>;
25
+ /** The profiles of `app` the store recorded on an account of this provider, for a refusal to name. */
26
+ export declare const profilesOnAccount: (store: Pick<MessageStore, "accounts">, app: string | undefined, provider: Provider) => ProfilesOn;
24
27
  /** A chat as `--chat` names it, in whichever of the accounts holds it — one of them, or it is an error. */
25
28
  export declare const chatAmong: (messenger: Saved, store: MessageStore, accounts: AccountKey[], reference: string) => Promise<{
26
29
  account: AccountKey;
@@ -1,4 +1,5 @@
1
1
  import { CliError, singleLine } from "@wirecat/cli-base";
2
+ import { chatReferenceOf, ensureAccount } from "../domain/ref-arguments.js";
2
3
  import { isId, pickChat, pickPerson } from "../resolve.js";
3
4
  import { codeOf } from "../sends/guarded.js";
4
5
  import { nothingStored } from "./deps.js";
@@ -28,10 +29,12 @@ const LEFT_EMPTY = "you left this chat, and the store holds none of its messages
28
29
  */
29
30
  export const storedChatId = (messenger, reference, store, account, { includeLeft = false } = {}) => {
30
31
  const renamed = (id) => store.chats.renamedChatId(account, id);
32
+ const profilesOn = profilesOnAccount(store, messenger.app?.command, account.provider);
31
33
  const among = chatIdAmong(messenger, reference, account, {
32
34
  held: () => store.chats.chats(account, {}),
33
35
  renamed,
34
36
  left: async (id) => leftIn(await store.chats.chats(account, byId(id))),
37
+ profilesOn,
35
38
  });
36
39
  if (!includeLeft)
37
40
  return among;
@@ -41,6 +44,7 @@ export const storedChatId = (messenger, reference, store, account, { includeLeft
41
44
  return chatIdAmong(messenger, reference, account, {
42
45
  held: () => store.chats.chats(account, { includeLeft: true }),
43
46
  renamed,
47
+ profilesOn,
44
48
  });
45
49
  });
46
50
  };
@@ -70,14 +74,26 @@ const byId = (id) => ({
70
74
  where: { sql: "chats.external_id = ?", params: [id] },
71
75
  });
72
76
  const leftIn = ({ items }) => items.some((one) => one.membershipState === "left");
77
+ /** The profiles of `app` the store recorded on an account of this provider, for a refusal to name. */
78
+ export const profilesOnAccount = (store, app, provider) => async (account) => {
79
+ if (app === undefined)
80
+ return undefined;
81
+ return Object.entries((await store.accounts.profiles())[app] ?? {})
82
+ .filter(([, key]) => key.provider === provider && key.account === account)
83
+ .map(([name]) => name);
84
+ };
73
85
  /** `left` is given where a left chat is refused; it lets a numeric id the store does not hold through as typed. */
74
- const chatIdAmong = async (messenger, reference, account, { held, renamed, left, }) => {
86
+ const chatIdAmong = async (messenger, reference, account, { held, renamed, left, profilesOn, }) => {
75
87
  const trimmed = reference.trim();
76
88
  if (messenger.savedChatId && ["me", "self", "saved"].includes(trimmed.toLowerCase())) {
77
89
  return messenger.savedChatId(account);
78
90
  }
79
- if (/^-?\d+$/.test(trimmed)) {
80
- const id = (await renamed(trimmed)) ?? trimmed;
91
+ const named = chatReferenceOf(trimmed, account.provider);
92
+ if (named)
93
+ await ensureAccount(named, { account: account.account }, profilesOn);
94
+ if (named || /^-?\d+$/.test(trimmed)) {
95
+ const typed = named?.chat ?? trimmed;
96
+ const id = (await renamed(typed)) ?? typed;
81
97
  if (await left?.(id))
82
98
  throw new CliError("not_found", `you left chat ${id}`);
83
99
  return id;