@wirecat/cli-core 0.555.0 → 0.557.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 (68) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/dist/cli/config-command.js +64 -2
  3. package/dist/cli/config-issues.d.ts +2 -0
  4. package/dist/cli/config-issues.js +11 -3
  5. package/dist/cli/config-repair.d.ts +26 -0
  6. package/dist/cli/config-repair.js +252 -0
  7. package/dist/cli/messenger/tasks-analysis-command.d.ts +8 -0
  8. package/dist/cli/messenger/tasks-analysis-command.js +184 -0
  9. package/dist/cli/messenger/tasks-command.js +11 -2
  10. package/dist/cli/steps.js +10 -0
  11. package/dist/domain/finance.d.ts +40 -0
  12. package/dist/domain/finance.js +39 -0
  13. package/dist/domain/money.d.ts +29 -0
  14. package/dist/domain/money.js +98 -0
  15. package/dist/domain/ref-summaries.js +2 -0
  16. package/dist/domain/references.d.ts +1 -1
  17. package/dist/domain/references.js +2 -0
  18. package/dist/entities/index.d.ts +64 -2
  19. package/dist/entities/index.js +5 -2
  20. package/dist/entities/task-results.d.ts +72 -0
  21. package/dist/entities/task-results.js +40 -1
  22. package/dist/errors/reasons.d.ts +1 -1
  23. package/dist/errors/reasons.js +24 -0
  24. package/dist/index.d.ts +2 -0
  25. package/dist/index.js +2 -0
  26. package/dist/mail/cli/index.d.ts +1 -0
  27. package/dist/mail/cli/index.js +3 -0
  28. package/dist/mail/cli/marks.d.ts +26 -0
  29. package/dist/mail/cli/marks.js +51 -0
  30. package/dist/mail/cli/review.js +70 -1
  31. package/dist/mail/index.d.ts +1 -0
  32. package/dist/mail/index.js +1 -0
  33. package/dist/mail/marks.d.ts +56 -0
  34. package/dist/mail/marks.js +178 -0
  35. package/dist/meetings/mcp/index.d.ts +15 -15
  36. package/dist/sends/permissions.js +1 -0
  37. package/dist/services/proposals/kinds/finance.d.ts +4 -0
  38. package/dist/services/proposals/kinds/finance.js +40 -0
  39. package/dist/services/proposals/registry.js +3 -0
  40. package/dist/services/task-analysis.d.ts +58 -0
  41. package/dist/services/task-analysis.js +125 -0
  42. package/dist/store/exports.d.ts +2 -2
  43. package/dist/store/index.d.ts +2 -1
  44. package/dist/store/sqlite/email-activity.d.ts +31 -0
  45. package/dist/store/sqlite/email-activity.js +38 -0
  46. package/dist/store/sqlite/emails.d.ts +2 -0
  47. package/dist/store/sqlite/emails.js +1 -0
  48. package/dist/store/sqlite/entity-types.d.ts +2 -0
  49. package/dist/store/sqlite/entity-types.js +2 -0
  50. package/dist/store/sqlite/finance.d.ts +199 -0
  51. package/dist/store/sqlite/finance.js +514 -0
  52. package/dist/store/sqlite/knowledge.js +3 -1
  53. package/dist/store/sqlite/manifest.js +2 -0
  54. package/dist/store/sqlite/migrations.generated.js +35 -0
  55. package/dist/store/sqlite/ref-summaries.js +33 -0
  56. package/dist/store/sqlite/schema.d.ts +1043 -0
  57. package/dist/store/sqlite/schema.js +98 -0
  58. package/dist/store/sqlite/task-analysis-marks.d.ts +7 -0
  59. package/dist/store/sqlite/task-analysis-marks.js +27 -0
  60. package/dist/store/sqlite/things.js +2 -0
  61. package/dist/store/store.d.ts +3 -0
  62. package/dist/store/store.js +5 -0
  63. package/dist/store/sync.d.ts +5 -1
  64. package/dist/store/sync.js +4 -0
  65. package/dist/store/types.d.ts +25 -0
  66. package/package.json +1 -1
  67. package/parity.json +29 -0
  68. package/skills/follow-ups/SKILL.md +22 -2
@@ -587,3 +587,75 @@ export declare const taskScan: v.StrictObjectSchema<{
587
587
  readonly category: v.NullableSchema<v.StringSchema<undefined>, undefined>;
588
588
  }, undefined>, undefined>;
589
589
  }, undefined>;
590
+ export declare const taskAnalysisMark: v.StrictObjectSchema<{
591
+ readonly chat: v.StringSchema<undefined>;
592
+ readonly from: 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`">]>;
593
+ readonly through: 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`">]>;
594
+ readonly through_id: v.StringSchema<undefined>;
595
+ readonly by: v.StringSchema<undefined>;
596
+ readonly checked_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`">]>;
597
+ readonly checked_through: 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`">]>;
598
+ 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`">]>;
599
+ }, undefined>;
600
+ export declare const taskAnalysisMarkMoved: v.StrictObjectSchema<{
601
+ readonly chat: v.StringSchema<undefined>;
602
+ readonly from: 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`">]>;
603
+ readonly through: 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`">]>;
604
+ readonly through_id: v.StringSchema<undefined>;
605
+ readonly by: v.StringSchema<undefined>;
606
+ readonly checked_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`">]>;
607
+ readonly checked_through: 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`">]>;
608
+ 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`">]>;
609
+ readonly moved: v.BooleanSchema<undefined>;
610
+ }, undefined>;
611
+ export declare const taskAnalysis: v.StrictObjectSchema<{
612
+ readonly chat: v.StringSchema<undefined>;
613
+ readonly title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
614
+ readonly kind: v.StringSchema<undefined>;
615
+ readonly newest_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>;
616
+ readonly mark: v.NullableSchema<v.StrictObjectSchema<{
617
+ readonly chat: v.StringSchema<undefined>;
618
+ readonly from: 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`">]>;
619
+ readonly through: 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`">]>;
620
+ readonly through_id: v.StringSchema<undefined>;
621
+ readonly by: v.StringSchema<undefined>;
622
+ readonly checked_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`">]>;
623
+ readonly checked_through: 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`">]>;
624
+ 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`">]>;
625
+ }, undefined>, undefined>;
626
+ readonly waiting: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
627
+ readonly late: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
628
+ readonly untrusted: v.OptionalSchema<v.ArraySchema<v.StringSchema<undefined>, undefined>, undefined>;
629
+ }, undefined>;
630
+ export declare const taskReview: v.StrictObjectSchema<{
631
+ readonly chat: v.StringSchema<undefined>;
632
+ readonly from: 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`">]>;
633
+ readonly mark: v.NullableSchema<v.StrictObjectSchema<{
634
+ readonly chat: v.StringSchema<undefined>;
635
+ readonly from: 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`">]>;
636
+ readonly through: 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`">]>;
637
+ readonly through_id: v.StringSchema<undefined>;
638
+ readonly by: v.StringSchema<undefined>;
639
+ readonly checked_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`">]>;
640
+ readonly checked_through: 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`">]>;
641
+ 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`">]>;
642
+ }, undefined>, undefined>;
643
+ readonly late: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
644
+ readonly has_more: v.BooleanSchema<undefined>;
645
+ readonly through: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
646
+ readonly messages: v.ArraySchema<v.StrictObjectSchema<{
647
+ readonly ref: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
648
+ 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`">]>;
649
+ readonly sender: v.NullableSchema<v.StringSchema<undefined>, undefined>;
650
+ readonly outgoing: v.BooleanSchema<undefined>;
651
+ readonly reply_to: v.NullableSchema<v.StringSchema<undefined>, undefined>;
652
+ readonly text: v.StringSchema<undefined>;
653
+ }, undefined>, undefined>;
654
+ readonly open_tasks: v.ArraySchema<v.StrictObjectSchema<{
655
+ readonly key: v.NullableSchema<v.StringSchema<undefined>, undefined>;
656
+ readonly kind: v.StringSchema<undefined>;
657
+ readonly title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
658
+ readonly source: v.NullableSchema<v.StringSchema<undefined>, undefined>;
659
+ }, undefined>, undefined>;
660
+ readonly untrusted: v.OptionalSchema<v.ArraySchema<v.StringSchema<undefined>, undefined>, undefined>;
661
+ }, undefined>;
@@ -2,7 +2,7 @@ import * as v from "valibot";
2
2
  import { TASK_TYPES } from "../domain/knowledge.js";
3
3
  import { LINK_KINDS, LINK_SOURCES } from "../store/sqlite/link-kinds.js";
4
4
  import { PRIORITY_LABELS, TASK_KINDS, TASK_ORIGINS, TASK_STATES, TASK_VERDICTS } from "../tasks/index.js";
5
- import { count, id, reference, text, time } from "./fields.js";
5
+ import { count, id, reference, text, time, untrusted } from "./fields.js";
6
6
  import { closeSuggestion } from "./task.js";
7
7
  import { taskPair } from "./task-pair.js";
8
8
  const nullableTime = v.nullable(time);
@@ -135,3 +135,42 @@ export const taskScan = v.strictObject({
135
135
  held_chats: v.optional(count),
136
136
  tasks: v.array(v.strictObject({ source: text, kind: v.picklist(TASK_KINDS), category: v.nullable(text) })),
137
137
  });
138
+ export const taskAnalysisMark = v.strictObject({
139
+ chat: text,
140
+ from: time,
141
+ through: time,
142
+ through_id: text,
143
+ by: text,
144
+ checked_at: time,
145
+ checked_through: time,
146
+ at: time,
147
+ });
148
+ export const taskAnalysisMarkMoved = v.strictObject({ ...taskAnalysisMark.entries, moved: v.boolean() });
149
+ export const taskAnalysis = v.strictObject({
150
+ chat: text,
151
+ title: v.nullable(text),
152
+ kind: text,
153
+ newest_at: nullableTime,
154
+ mark: v.nullable(taskAnalysisMark),
155
+ waiting: count,
156
+ late: count,
157
+ untrusted,
158
+ });
159
+ export const taskReview = v.strictObject({
160
+ chat: text,
161
+ from: time,
162
+ mark: v.nullable(taskAnalysisMark),
163
+ late: count,
164
+ has_more: v.boolean(),
165
+ through: v.nullable(reference),
166
+ messages: v.array(v.strictObject({
167
+ ref: reference,
168
+ at: time,
169
+ sender: v.nullable(text),
170
+ outgoing: v.boolean(),
171
+ reply_to: v.nullable(text),
172
+ text,
173
+ })),
174
+ open_tasks: v.array(v.strictObject({ key: v.nullable(text), kind: text, title: v.nullable(text), source: v.nullable(text) })),
175
+ untrusted,
176
+ });
@@ -7,7 +7,7 @@ export declare const REASONS: ErrorReason[];
7
7
  * Detail keys an error from this package may carry, until each is registered on its own reason. Not `cause`:
8
8
  * that can hold a provider's or SQLite's raw text.
9
9
  */
10
- export declare const DETAILS: readonly ["contracts", "calendars", "allowed", "alternative", "arguments", "at", "available", "batch", "budget", "built", "candidates", "chat", "chat_id", "cli", "complete", "emitted_bytes", "exact", "forum", "guide", "hint", "id", "index", "kind", "limit", "limit_source", "max_bytes", "model", "name", "operation_id", "operation_ids", "text_limit", "store_refused_in", "subtasks", "partial_output", "parent", "path", "permission", "person_ids", "previous_chat_id", "profiles", "provider_error", "provider", "recovery", "reference", "remembered", "retry_at", "retry_after_ms", "runtime", "saved", "scheduled_for", "script", "sent", "sent_count", "settlement_failed", "span", "sqlite", "stage", "standing", "state", "stderr", "term", "total", "upgraded", "wanted"];
10
+ export declare const DETAILS: readonly ["contracts", "calendars", "allowed", "alternative", "arguments", "at", "available", "batch", "budget", "built", "candidates", "chat", "chat_id", "cli", "column", "complete", "emitted_bytes", "exact", "file", "forum", "guide", "hint", "id", "index", "kind", "limit", "limit_source", "line", "max_bytes", "model", "name", "operation_id", "operation_ids", "text_limit", "store_refused_in", "subtasks", "partial_output", "parent", "path", "permission", "person_ids", "previous_chat_id", "profiles", "provider_error", "provider", "recovery", "reference", "remembered", "retry_at", "retry_after_ms", "runtime", "saved", "scheduled_for", "script", "sent", "sent_count", "settlement_failed", "span", "sqlite", "stage", "standing", "state", "stderr", "term", "total", "upgraded", "wanted"];
11
11
  /** What a CLI adds to the shared registry: its own reasons, and the detail keys its adapters raise. */
12
12
  export interface AppErrors {
13
13
  reasons?: readonly ErrorReason[];
@@ -57,6 +57,11 @@ const OTHERS = [
57
57
  validation("push.too-many-files", "the folder holds more files than one push takes"),
58
58
  validation("push.unreadable", "the folder to push could not be read"),
59
59
  validation("spreadsheet.unreadable", "the workbook could not be read"),
60
+ [
61
+ "config.unrepairable",
62
+ "configuration_error",
63
+ "the settings file's damage has more than one reading; nothing was changed, so it is fixed by hand",
64
+ ],
60
65
  ["state.invalid", "configuration_error", "the saved sync state is unreadable or invalid; it is kept, not reset"],
61
66
  ["sync.partial", "provider_error", "some calendars did not sync; the next sync retries them"],
62
67
  validation("event.conflict", "the event changed at the provider since the last sync; sync and try again"),
@@ -133,8 +138,23 @@ const OTHERS = [
133
138
  validation("input.required", "the command needs an answer and cannot ask for one"),
134
139
  validation("input.too-large", "the input is over its size limit"),
135
140
  ["meeting-project.stale", "configuration_error", "the meeting's project changed meanwhile"],
141
+ validation("finance.allocate", "the invoice or payment cannot take an allocation in its state"),
142
+ validation("finance.currency", "the payment and the invoice are in different currencies; nothing is converted"),
143
+ validation("finance.field", "a field of the invoice or payment is not valid"),
144
+ validation("finance.line-amount", "the line's amount is not its quantity times its unit price"),
145
+ validation("finance.lines", "the invoice's lines are not valid"),
146
+ validation("finance.over-invoice", "the allocation would settle more than the invoice's total"),
147
+ validation("finance.over-payment", "the allocation would use more than the payment's amount"),
148
+ ["finance.owner-only", "permission_error", "only the owner does this with money"],
149
+ validation("finance.party", "a party is a person: or organization: reference"),
150
+ validation("finance.ref", "the reference names no invoice or payment"),
151
+ validation("finance.status", "the invoice's status does not allow this"),
152
+ validation("finance.total", "the invoice's total is not the sum of its lines"),
136
153
  validation("memory.evidence-required", "a memory needs its evidence"),
137
154
  validation("memory.scope-required", "a memory needs its scope"),
155
+ validation("money.amount", "the amount is not a decimal string in the currency's scale"),
156
+ validation("money.currency", "the currency is not one the store keeps money in"),
157
+ validation("money.quantity", "the quantity is not a decimal above 0"),
138
158
  ["model.not-downloaded", "not_found", "the model is not downloaded on this machine"],
139
159
  ["person.no-owner", "not_found", "no person is marked as the owner in the store"],
140
160
  ["transcription.no-key", "authentication_error", "no OpenAI key is set for transcription"],
@@ -188,6 +208,7 @@ const OTHERS = [
188
208
  validation("task.has-subtasks", "the task has subtasks; move or detach them before it moves to another project"),
189
209
  validation("task.is-subtask", "a subtask moves to another project only when it is detached from its parent in the same write"),
190
210
  validation("task.duplicate-candidates", "open tasks may be the same job: attach to one, or add it as new"),
211
+ validation("task.already-read", "the message was already read for tasks: a read again only updates and closes tasks unless --open-new"),
191
212
  validation("task.certainty-required", "an agent closes a task with its certainty, 0 to 1"),
192
213
  ["task.pair-decided", "permission_error", "the owner already decided the pair; an agent does not change it"],
193
214
  validation("task.no-close-suggestion", "the task has no agent's close waiting on the owner"),
@@ -245,9 +266,11 @@ export const DETAILS = [
245
266
  "chat",
246
267
  "chat_id",
247
268
  "cli",
269
+ "column",
248
270
  "complete",
249
271
  "emitted_bytes",
250
272
  "exact",
273
+ "file",
251
274
  "forum",
252
275
  "guide",
253
276
  "hint",
@@ -256,6 +279,7 @@ export const DETAILS = [
256
279
  "kind",
257
280
  "limit",
258
281
  "limit_source",
282
+ "line",
259
283
  "max_bytes",
260
284
  "model",
261
285
  "name",
package/dist/index.d.ts CHANGED
@@ -1,12 +1,14 @@
1
1
  export { bareCheck, type CheckWords, checkWords } from "./domain/checks.js";
2
2
  export type { CounterField, CounterObservation, CounterObservations, CounterState } from "./domain/counters.js";
3
3
  export { COUNTER_FIELDS, counterValue, observedCounters } from "./domain/counters.js";
4
+ export { FINANCE_REVIEWS, type FinanceReview, INVOICE_DIRECTIONS, INVOICE_STATUSES, type InvoiceDirection, type InvoiceStatus, MONEY_DIRECTIONS, type MoneyDirection, } from "./domain/finance.js";
4
5
  export { type FormattedText, type HtmlFormatting, type MarkdownFormatting, type TextSpan, validateFormattedText, } from "./domain/formatting.js";
5
6
  export { formatLocator, isLocator, type MessageLocator, parseLocator } from "./domain/locator.js";
6
7
  export { type Markup, parseMarkdown } from "./domain/markdown.js";
7
8
  export { parseMarkdownSpans } from "./domain/markdown-spans.js";
8
9
  export type { MessageLink, MessagePermalink } from "./domain/message-link.js";
9
10
  export type * from "./domain/models.js";
11
+ export { CURRENCY_SCALES, currencyOf, type Money, minorUnits, moneyLabel, moneyOf, } from "./domain/money.js";
10
12
  export { countedPoll } from "./domain/polls.js";
11
13
  export { FOLDER_PROVIDERS, isFolderProvider, isKind, KINDS, type Kind, kindIn, kindOf, PROVIDERS, type ProviderEntry, providerNamed, providers, UNKNOWN_PROVIDER, useProviders, } from "./domain/providers.js";
12
14
  export type { RankingGraphEvidence, RankingGraphLink } from "./domain/rankings-graph.js";
package/dist/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  export { bareCheck, checkWords } from "./domain/checks.js";
2
2
  export { COUNTER_FIELDS, counterValue, observedCounters } from "./domain/counters.js";
3
+ export { FINANCE_REVIEWS, INVOICE_DIRECTIONS, INVOICE_STATUSES, MONEY_DIRECTIONS, } from "./domain/finance.js";
3
4
  export { validateFormattedText, } from "./domain/formatting.js";
4
5
  export { formatLocator, isLocator, parseLocator } from "./domain/locator.js";
5
6
  export { parseMarkdown } from "./domain/markdown.js";
6
7
  export { parseMarkdownSpans } from "./domain/markdown-spans.js";
8
+ export { CURRENCY_SCALES, currencyOf, minorUnits, moneyLabel, moneyOf, } from "./domain/money.js";
7
9
  export { countedPoll } from "./domain/polls.js";
8
10
  export { FOLDER_PROVIDERS, isFolderProvider, isKind, KINDS, kindIn, kindOf, PROVIDERS, providerNamed, providers, UNKNOWN_PROVIDER, useProviders, } from "./domain/providers.js";
9
11
  export { rankingOptions } from "./domain/rankings-options.js";
@@ -2,6 +2,7 @@ import { Command } from "commander";
2
2
  import { type MailServerAccess } from "../../mail/server-search.js";
3
3
  import type { AppIdentity } from "../../ports/app.js";
4
4
  import { type AccountKey, type MessageStore } from "../../store/store.js";
5
+ export { mailMark, markItem, markText } from "./marks.js";
5
6
  export { MAIL_REVIEW_QUERY, MAIL_REVIEW_STEPS } from "./review.js";
6
7
  /** What a mail CLI hands the shared commands: how to open the store for one run, and whose mail it reads. */
7
8
  export interface MailReads {
@@ -18,7 +18,9 @@ import { searchMailServer } from "../../mail/server-search.js";
18
18
  import { atDetail } from "../../output-detail.js";
19
19
  import { sourceKey } from "../../services/server-search.js";
20
20
  import { queryFilter, THREAD_COLUMNS } from "../../store/store.js";
21
+ import { acknowledgeCommand } from "./marks.js";
21
22
  import { mailReviewCommand } from "./review.js";
23
+ export { mailMark, markItem, markText } from "./marks.js";
22
24
  export { MAIL_REVIEW_QUERY, MAIL_REVIEW_STEPS } from "./review.js";
23
25
  /** A locked or broken store must fail the command, not read as "nothing stored yet". */
24
26
  const absentAccount = (error) => {
@@ -226,6 +228,7 @@ const messagesCommand = (mail) => {
226
228
  ? listOf("messages show", { items: shown, limit: Math.max(1, shown.length), has_more: false })
227
229
  : itemOf(shown.find((_, at) => emails[at]?.anchor) ?? shown[0]), () => shownText(emails));
228
230
  });
231
+ messages.addCommand(acknowledgeCommand(mail));
229
232
  return messages;
230
233
  };
231
234
  const mailThread = v.strictObject({
@@ -0,0 +1,26 @@
1
+ import { Command } from "commander";
2
+ import * as v from "valibot";
3
+ import { type MailMark } from "../marks.js";
4
+ import type { MailReads } from "./index.js";
5
+ /** A holder's mark as `--json`, `mail status` and MCP show it. */
6
+ export declare const mailMark: v.StrictObjectSchema<{
7
+ readonly holder: v.StringSchema<undefined>;
8
+ readonly through_id: v.NumberSchema<undefined>;
9
+ readonly through_received_at: v.NullableSchema<v.StringSchema<undefined>, undefined>;
10
+ readonly held: v.NumberSchema<undefined>;
11
+ readonly read: v.NumberSchema<undefined>;
12
+ readonly skipped: v.StrictObjectSchema<{
13
+ readonly bulk: v.NumberSchema<undefined>;
14
+ readonly envelope: v.NumberSchema<undefined>;
15
+ readonly partial: v.NumberSchema<undefined>;
16
+ readonly draft: v.NumberSchema<undefined>;
17
+ }, undefined>;
18
+ readonly updated_at: v.StringSchema<undefined>;
19
+ }, undefined>;
20
+ export declare const markItem: (mark: MailMark) => v.InferOutput<typeof mailMark>;
21
+ export declare const markText: (mark: MailMark) => string;
22
+ /**
23
+ * `messages acknowledge`: a holder says it is done with what `review --holder` gave it, and its mark moves there. It
24
+ * only moves the holder's own bookkeeping forward, so a read-only scheduled run may do it.
25
+ */
26
+ export declare const acknowledgeCommand: (mail: MailReads) => Command;
@@ -0,0 +1,51 @@
1
+ import { annotate } from "@wirecat/cli-base/commands";
2
+ import { Command } from "commander";
3
+ import * as v from "valibot";
4
+ import { answerEntity } from "../../cli/answer-entity.js";
5
+ import { outputFor } from "../../cli/context.js";
6
+ import { destroysNothing, LOCAL_WRITE } from "../../cli/effects.js";
7
+ import { itemOf, renderAnswer } from "../../cli/envelope.js";
8
+ import { acknowledgeMark } from "../marks.js";
9
+ const iso = (ms) => (ms === null ? null : new Date(ms).toISOString());
10
+ /** A holder's mark as `--json`, `mail status` and MCP show it. */
11
+ export const mailMark = v.strictObject({
12
+ holder: v.string(),
13
+ through_id: v.number(),
14
+ through_received_at: v.nullable(v.string()),
15
+ held: v.number(),
16
+ read: v.number(),
17
+ skipped: v.strictObject({ bulk: v.number(), envelope: v.number(), partial: v.number(), draft: v.number() }),
18
+ updated_at: v.string(),
19
+ });
20
+ export const markItem = (mark) => ({
21
+ holder: mark.holder,
22
+ through_id: mark.throughId,
23
+ through_received_at: iso(mark.throughAt),
24
+ held: mark.held.length,
25
+ read: mark.read,
26
+ skipped: mark.skipped,
27
+ updated_at: iso(mark.updatedAt),
28
+ });
29
+ export const markText = (mark) => `${mark.holder} through ${mark.throughId}${mark.throughAt === null ? "" : ` (${iso(mark.throughAt)})`} ` +
30
+ `read ${mark.read} held ${mark.held.length} skipped bulk ${mark.skipped.bulk}, envelope ${mark.skipped.envelope}, ` +
31
+ `partial ${mark.skipped.partial}, draft ${mark.skipped.draft} updated ${iso(mark.updatedAt)}\n`;
32
+ /**
33
+ * `messages acknowledge`: a holder says it is done with what `review --holder` gave it, and its mark moves there. It
34
+ * only moves the holder's own bookkeeping forward, so a read-only scheduled run may do it.
35
+ */
36
+ export const acknowledgeCommand = (mail) => answerEntity(destroysNothing(annotate(new Command("acknowledge"), {
37
+ effects: LOCAL_WRITE,
38
+ examples: [
39
+ `${mail.app.command} personal messages acknowledge --holder mail-triage --cursor 9123.1762090000000`,
40
+ ],
41
+ })), "mail_mark", mailMark)
42
+ .summary("move a holder's processing mark to what it has read")
43
+ .description("record that a holder — an agent or a pipeline — processed the mail `review --holder` gave it, with the cursor " +
44
+ "that review gave; the mark only moves forward, and mail still partial waits for the next review")
45
+ .requiredOption("--holder <name>", "whose mark: lower-case letters, digits and dashes, such as mail-triage")
46
+ .requiredOption("--cursor <cursor>", "the cursor `review --holder` gave")
47
+ .action(async function () {
48
+ const { holder, cursor } = this.opts();
49
+ const mark = await mail.read(this, (store, mailbox) => acknowledgeMark(store, mailbox, holder, cursor));
50
+ renderAnswer(this, outputFor(this), itemOf(markItem(mark)), () => markText(mark));
51
+ });
@@ -16,6 +16,7 @@ import { parseDuration } from "../../domain/duration.js";
16
16
  import { formatReference } from "../../domain/references.js";
17
17
  import { AGENDA_DEFAULTS } from "../../services/agenda.js";
18
18
  import { REVIEW_DAYS, reviewStart } from "../../services/inbox.js";
19
+ import { readPastMark } from "../marks.js";
19
20
  import { hitText } from "../read.js";
20
21
  import { OWED_DAYS, REVIEW_THREADS, reviewMail } from "../review.js";
21
22
  const DAY = 86_400_000;
@@ -30,6 +31,15 @@ export const MAIL_REVIEW_QUERY = defineQuery({
30
31
  });
31
32
  /** The mail review's steps; a host lists them beside its own so its step checks know them. */
32
33
  export const MAIL_REVIEW_STEPS = defineSteps([
34
+ {
35
+ id: "mail-messages.acknowledge",
36
+ command: "messages acknowledge",
37
+ args: [
38
+ { name: "holder", required: true },
39
+ { name: "cursor", required: true },
40
+ ],
41
+ why: "once these are processed, this moves the holder's mark past them",
42
+ },
33
43
  {
34
44
  id: "mail-review.next",
35
45
  command: "review",
@@ -113,6 +123,62 @@ const reviewAnswer = (found, mailbox, fresh) => {
113
123
  meta: found.complete ? {} : { complete: false },
114
124
  });
115
125
  };
126
+ const holderReview = async (command, mail, holder, query) => {
127
+ const { unanswered, waiting } = command.opts();
128
+ if (typedQuery(query).trim() || unanswered !== undefined || waiting !== undefined)
129
+ throw new CliError("validation_error", "--holder reads past its own mark — not with a query, --unanswered or --waiting");
130
+ const { read, mailbox, marks } = await mail.read(command, async (store, mailbox) => {
131
+ const read = await readPastMark(store, mailbox, holder);
132
+ const account = read.emails.length ? await store.accounts.storedAccount(mailbox) : undefined;
133
+ const marks = account
134
+ ? await store.mail.emailMarks(account.id, read.emails.map((one) => one.id))
135
+ : new Map();
136
+ return { read, mailbox, marks };
137
+ });
138
+ const chats = new Map();
139
+ for (const email of read.emails) {
140
+ const chat = chats.get(email.chatId) ?? {
141
+ id: email.chatId,
142
+ title: email.chatTitle,
143
+ kind: "dialog",
144
+ messages: [],
145
+ more: false,
146
+ };
147
+ const mark = marks.get(email.id);
148
+ chat.messages.push(mark ? { ...email, marks: mark } : email);
149
+ chats.set(email.chatId, chat);
150
+ }
151
+ const now = new Date().toISOString();
152
+ const found = {
153
+ since: read.mark?.throughAt ? new Date(read.mark.throughAt).toISOString() : now,
154
+ until: now,
155
+ complete: !read.more,
156
+ chats: [...chats.values()],
157
+ skipped: [],
158
+ partial: false,
159
+ quiet: 0,
160
+ };
161
+ const answer = reviewAnswer(found, mailbox, true);
162
+ const skipped = Object.entries(read.skipped).filter(([, count]) => count > 0);
163
+ answer.next = [
164
+ stepOf(step("mail-messages.acknowledge"), { args: { holder, cursor: read.cursor } }),
165
+ ...(answer.next ?? []),
166
+ ];
167
+ answer.missing = [
168
+ ...(answer.missing ?? []),
169
+ ...(read.held > 0 ? [{ field: "items", why: `${read.held} partial emails held until they are whole` }] : []),
170
+ ...(skipped.length
171
+ ? [{ field: "items", why: `left out: ${skipped.map(([reason, count]) => `${count} ${reason}`).join(", ")}` }]
172
+ : []),
173
+ ];
174
+ const output = outputFor(command);
175
+ renderAnswer(command, output, answer, () => {
176
+ output.renderer.note(read.mark
177
+ ? `${holder}: past ${read.mark.throughId}; acknowledge with --cursor ${read.cursor}${read.more ? "; more after these" : ""}`
178
+ : `${holder} has no mark yet: it starts from now — acknowledge with --cursor ${read.cursor}`);
179
+ return read.emails.map((hit) => `${hitText(hit)}${markText(marks.get(hit.id))}\n`).join("");
180
+ });
181
+ };
116
182
  /**
117
183
  * `review` over the stored mail of the profile's mailbox, with tg's words: what is new, what the owner owes, what
118
184
  * waits for an answer. It reads the store only and opens no task.
@@ -122,7 +188,6 @@ export const mailReviewCommand = (mail) => annotate(new Command("review"), {
122
188
  `${mail.app.command} personal review "date>2h"`,
123
189
  `${mail.app.command} personal review --new`,
124
190
  `${mail.app.command} personal review --unanswered 1d`,
125
- `${mail.app.command} personal review --waiting 7d`,
126
191
  ],
127
192
  effects: LOCAL_READ,
128
193
  partial: true,
@@ -136,6 +201,8 @@ export const mailReviewCommand = (mail) => annotate(new Command("review"), {
136
201
  .argument("[query...]", `where the last review ended: date>2h; ${REVIEW_DAYS} days ago if no date`)
137
202
  .option("--unanswered [duration]", "only threads whose newest email came to you, in To or Cc, with no answer for this long — 4h, 1d; 24h if not given")
138
203
  .option("--waiting [duration]", "only threads where you wrote last and nobody answered for this long — 3d; 7d if not given")
204
+ .option("--holder <name>", "read for this holder — an agent or a pipeline — what is stored past its processing mark, by store order; " +
205
+ "bulk, envelopes, drafts and partial mail left out; `messages acknowledge` moves the mark")
139
206
  .option("--new", "what changed since the last `review --new` of this mailbox, a point per thread — for scheduled runs")
140
207
  .addHelpText("after", `
141
208
  Without a date, --unanswered and --waiting look at threads active in the last ${OWED_DAYS} days.
@@ -145,6 +212,8 @@ Mail newer than the last finished sync waits for the next review. Each reader ke
145
212
  an agent over MCP, and each scheduled run.`)
146
213
  .action(async function (query) {
147
214
  const options = this.opts();
215
+ if (options.holder !== undefined)
216
+ return holderReview(this, mail, options.holder, query);
148
217
  const { since: from, meta } = catchUpQuery(query, MAIL_REVIEW_QUERY, "review");
149
218
  const bulk = nativeTerms(listQuery(typedQuery(query), MAIL_REVIEW_QUERY, "review"), "review").get("bulk")?.value;
150
219
  if (bulk !== undefined && bulk !== "false")
@@ -6,6 +6,7 @@ export { dropMissing, type Gone, tooManyGone } from "./gone.js";
6
6
  export { type AuthVerdicts, HEADER_FIELDS, headersOf, type MailHeaders, mailHeadersOf } from "./headers.js";
7
7
  export { type MailIdsInput, mailIds } from "./ids.js";
8
8
  export { decodedLabelName, decodeMailboxName, labelName, systemLabel } from "./labels.js";
9
+ export { acknowledgeMark, cursorOf, HOLD_PARTIAL_MS, HOLDER, MARK_BATCH, type MailMark, type MarkRead, markOf, marksOf, readPastMark, type SkipReason, } from "./marks.js";
9
10
  export { type MailTurn, mailTurn } from "./owed.js";
10
11
  export { type Address, htmlToText, looksLikeHtml, MAX_ATTACHMENTS, MAX_BODY_CHARS, type ParsedMail, parseMail, } from "./parse.js";
11
12
  export { PARTIAL, type PartialOutcome } from "./partial.js";
@@ -6,6 +6,7 @@ export { dropMissing, tooManyGone } from "./gone.js";
6
6
  export { HEADER_FIELDS, headersOf, mailHeadersOf } from "./headers.js";
7
7
  export { mailIds } from "./ids.js";
8
8
  export { decodedLabelName, decodeMailboxName, labelName, systemLabel } from "./labels.js";
9
+ export { acknowledgeMark, cursorOf, HOLD_PARTIAL_MS, HOLDER, MARK_BATCH, markOf, marksOf, readPastMark, } from "./marks.js";
9
10
  export { mailTurn } from "./owed.js";
10
11
  export { htmlToText, looksLikeHtml, MAX_ATTACHMENTS, MAX_BODY_CHARS, parseMail, } from "./parse.js";
11
12
  export { PARTIAL } from "./partial.js";
@@ -0,0 +1,56 @@
1
+ import type { AccountKey, MessageStore, StoredHit } from "../store/store.js";
2
+ /** A holder's name: what `--holder` takes and the mark's key ends with. */
3
+ export declare const HOLDER: RegExp;
4
+ /** At most this many emails are read past the mark in one go; the rest come with the next read. */
5
+ export declare const MARK_BATCH = 200;
6
+ /** A partial email waits this long to be stored whole, then is skipped. */
7
+ export declare const HOLD_PARTIAL_MS: number;
8
+ export type SkipReason = "bulk" | "envelope" | "partial" | "draft";
9
+ /**
10
+ * How far one holder — an agent, a pipeline — has processed an account's mail, in the shared store (`sync_cursors`,
11
+ * key `consumer:<holder>`). It counts by the store's insert order (`emails.id`), so mail stored late, older than what
12
+ * was read, is still after the mark. Partial emails are held until they are whole.
13
+ */
14
+ export interface MailMark {
15
+ holder: string;
16
+ throughId: number;
17
+ /** ms: when the email at `throughId` was received; for display. */
18
+ throughAt: number | null;
19
+ held: number[];
20
+ read: number;
21
+ skipped: Record<SkipReason, number>;
22
+ /** ms. */
23
+ updatedAt: number;
24
+ }
25
+ export declare const markOf: (store: MessageStore, mailbox: AccountKey, holder: string) => Promise<MailMark | undefined>;
26
+ /** Every holder's mark on the mailbox, by name. */
27
+ export declare const marksOf: (store: MessageStore, mailbox: AccountKey) => Promise<MailMark[]>;
28
+ /** The mark's state, as a cursor `acknowledge` takes back: the last id read, and when it was read. */
29
+ export declare const cursorOf: (throughId: number, readAt: number) => string;
30
+ export interface MarkRead {
31
+ /** The emails for the holder, oldest first; bulk, envelopes, drafts and partial mail left out. */
32
+ emails: StoredHit[];
33
+ /** What `acknowledge --cursor` takes once the holder is done with these. */
34
+ cursor: string;
35
+ /** More is stored after this batch: read again after acknowledging. */
36
+ more: boolean;
37
+ held: number;
38
+ skipped: Record<SkipReason, number>;
39
+ /** `undefined` before the holder's first acknowledge: it starts from the mail stored now. */
40
+ mark: MailMark | undefined;
41
+ }
42
+ /**
43
+ * What the holder has not processed: the emails stored after its mark, and the held ones that are whole now. Reads
44
+ * only; the mark moves when the holder acknowledges. A holder with no mark starts from now: its first read is
45
+ * empty and gives the cursor of the newest stored email.
46
+ */
47
+ export declare const readPastMark: (store: MessageStore, mailbox: AccountKey, holder: string, { now, limit }?: {
48
+ now?: number;
49
+ limit?: number;
50
+ }) => Promise<MarkRead>;
51
+ /**
52
+ * Moves the holder's mark to the cursor its read gave, never back. An email whole when it was read counts read; one
53
+ * still partial, or changed after the read (it may have become whole since), is held and comes with the next read.
54
+ * The same cursor twice changes nothing more.
55
+ */
56
+ export declare const acknowledgeMark: (store: MessageStore, mailbox: AccountKey, holder: string, cursor: string, now?: number) => Promise<MailMark>;