@wirecat/cli-core 0.574.0 → 0.576.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 (50) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/calendar/mcp/index.d.ts +12 -12
  3. package/dist/cli/findings-input.js +6 -0
  4. package/dist/domain/orders.d.ts +19 -0
  5. package/dist/domain/orders.js +12 -0
  6. package/dist/domain/ref-summaries.js +1 -0
  7. package/dist/domain/references.d.ts +1 -1
  8. package/dist/domain/references.js +1 -0
  9. package/dist/entities/index.d.ts +75 -0
  10. package/dist/entities/mail-findings.d.ts +75 -0
  11. package/dist/entities/mail-findings.js +62 -0
  12. package/dist/errors/reasons.js +5 -0
  13. package/dist/index.d.ts +1 -0
  14. package/dist/index.js +1 -0
  15. package/dist/mail/cli/bulk-settings.d.ts +6 -0
  16. package/dist/mail/cli/bulk-settings.js +65 -0
  17. package/dist/mail/cli/index.d.ts +4 -0
  18. package/dist/mail/cli/index.js +1 -0
  19. package/dist/mail/cli/review.js +34 -13
  20. package/dist/mail/findings.d.ts +73 -1
  21. package/dist/mail/findings.js +164 -1
  22. package/dist/mail/index.d.ts +2 -1
  23. package/dist/mail/index.js +1 -0
  24. package/dist/mail/marks.d.ts +19 -7
  25. package/dist/mail/marks.js +72 -16
  26. package/dist/mail/newsletter.d.ts +52 -0
  27. package/dist/mail/newsletter.js +189 -0
  28. package/dist/mail/review.d.ts +2 -0
  29. package/dist/services/proposals/kinds/mail-findings.js +9 -0
  30. package/dist/services/proposals/kinds/orders.d.ts +4 -0
  31. package/dist/services/proposals/kinds/orders.js +32 -0
  32. package/dist/services/proposals/registry.js +2 -0
  33. package/dist/store/index.d.ts +1 -0
  34. package/dist/store/sqlite/entity-types.d.ts +1 -0
  35. package/dist/store/sqlite/entity-types.js +1 -0
  36. package/dist/store/sqlite/finance.d.ts +40 -2
  37. package/dist/store/sqlite/finance.js +48 -42
  38. package/dist/store/sqlite/knowledge.js +2 -1
  39. package/dist/store/sqlite/mail-findings.js +81 -1
  40. package/dist/store/sqlite/manifest.js +2 -0
  41. package/dist/store/sqlite/migrations.generated.js +20 -0
  42. package/dist/store/sqlite/orders.d.ts +84 -0
  43. package/dist/store/sqlite/orders.js +186 -0
  44. package/dist/store/sqlite/ref-summaries.js +10 -0
  45. package/dist/store/sqlite/schema.d.ts +440 -0
  46. package/dist/store/sqlite/schema.js +38 -0
  47. package/dist/store/sqlite/things.js +1 -0
  48. package/dist/store/store.d.ts +3 -0
  49. package/dist/store/store.js +5 -0
  50. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,39 @@ shape of this file.
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ ## 0.576.0 — 05.11.2026
11
+
12
+ ### Added
13
+
14
+ - **`review --holder` gives an agent the bulk mail that is not surely a newsletter.** Shops, banks and site forms put
15
+ List-Unsubscribe on orders, invoices and lead forms too, and the holder read left all of it out. Now
16
+ `newsletterSorter` scores each bulk email by words in its subject and first lines (Russian, English, Spanish), its
17
+ sender's address, an emoji or an unsubscribe link: a newsletter is left out and counted under `missing` as
18
+ `newsletter`; an order word in the subject, an order's or a request's number, or a reply keeps it. A kept email
19
+ comes after the other mail, as its subject and first lines, with `email.bulk: true` and `email.excerpt_only: true`;
20
+ `messages show <ref>` reads it whole. At most 30 excerpts a read; the read then ends early and the next gives the
21
+ rest. `mailWaiting` and `advancePastSkipped` count and stop at excerpts the same way. A host sets it from its config
22
+ file: `MAIL_BULK_SETTINGS` (`mail_bulk_excerpts`, `mail_bulk_excerpts_per_read`, `mail_bulk_excerpt_chars`,
23
+ `mail_bulk_newsletter_score`, `mail_bulk_newsletter_words`, `mail_bulk_newsletter_senders`, `mail_bulk_order_words`),
24
+ read by `bulkTriageOf` into `MailReads.bulkTriage`, and passed to `mailWaiting` and `advancePastSkipped` as their
25
+ new last argument; without it the defaults apply.
26
+
27
+ ## 0.575.0 — 05.11.2026
28
+
29
+ ### Added
30
+
31
+ - **Orders in the store (store version 50).** `store.orders` keeps what the owner, his business or a client ordered:
32
+ for whom (`customer`, the owner when left out) and why (`purpose`: `personal`, `business`, `client`, `other`), the
33
+ shop as its mail names it with an optional `supplier` reference, the shop's number, amount and currency, the status
34
+ (`placed`, `shipped`, `delivered`, `cancelled`, `returned`), the order and delivery days, and links to an invoice and
35
+ a payment. The owner's entry is confirmed at once; an agent's is an `order` proposal (`order:<id>`) only the owner
36
+ decides. One shop's number is stored once (`order.duplicate`), and only the owner moves an order's status on.
37
+ - **Mail findings take invoices, payments and orders.** A `mail_findings` batch now takes the kinds `invoice`,
38
+ `payment`, `order` and `order_update` (a later mail's status for a stored order, found by `supplier_name` and
39
+ `number`). Staging checks each the way `store.finance` and `store.orders` check the owner's own entry and refuses
40
+ the batch with that reason; nothing is written until the owner accepts, which records the confirmed row with the
41
+ cited mail as its evidence. The agent still only proposes.
42
+
10
43
  ## 0.574.0 — 05.11.2026
11
44
 
12
45
  ### Fixed
@@ -10,13 +10,13 @@ export declare const calendarTools: ({
10
10
  inputSchema: {
11
11
  type: string;
12
12
  properties: {
13
+ limit?: undefined;
13
14
  account_id: {
14
15
  type: string;
15
16
  minimum: number;
16
17
  };
17
18
  query?: undefined;
18
19
  timezone?: undefined;
19
- limit?: undefined;
20
20
  page?: undefined;
21
21
  event?: undefined;
22
22
  } | {
@@ -45,9 +45,9 @@ export declare const calendarTools: ({
45
45
  };
46
46
  event?: undefined;
47
47
  } | {
48
+ limit?: undefined;
48
49
  query?: undefined;
49
50
  timezone?: undefined;
50
- limit?: undefined;
51
51
  page?: undefined;
52
52
  event: {
53
53
  type: string;
@@ -58,9 +58,9 @@ export declare const calendarTools: ({
58
58
  minimum: number;
59
59
  };
60
60
  } | {
61
+ limit?: undefined;
61
62
  query?: undefined;
62
63
  timezone?: undefined;
63
- limit?: undefined;
64
64
  page?: undefined;
65
65
  event?: undefined;
66
66
  account_id?: undefined;
@@ -80,13 +80,13 @@ export declare const calendarTools: ({
80
80
  inputSchema: {
81
81
  type: string;
82
82
  properties: {
83
+ limit?: undefined;
83
84
  account_id: {
84
85
  type: string;
85
86
  minimum: number;
86
87
  };
87
88
  query?: undefined;
88
89
  timezone?: undefined;
89
- limit?: undefined;
90
90
  page?: undefined;
91
91
  event?: undefined;
92
92
  } | {
@@ -115,9 +115,9 @@ export declare const calendarTools: ({
115
115
  };
116
116
  event?: undefined;
117
117
  } | {
118
+ limit?: undefined;
118
119
  query?: undefined;
119
120
  timezone?: undefined;
120
- limit?: undefined;
121
121
  page?: undefined;
122
122
  event: {
123
123
  type: string;
@@ -128,9 +128,9 @@ export declare const calendarTools: ({
128
128
  minimum: number;
129
129
  };
130
130
  } | {
131
+ limit?: undefined;
131
132
  query?: undefined;
132
133
  timezone?: undefined;
133
- limit?: undefined;
134
134
  page?: undefined;
135
135
  event?: undefined;
136
136
  account_id?: undefined;
@@ -150,13 +150,13 @@ export declare const calendarTools: ({
150
150
  inputSchema: {
151
151
  type: string;
152
152
  properties: {
153
+ limit?: undefined;
153
154
  account_id: {
154
155
  type: string;
155
156
  minimum: number;
156
157
  };
157
158
  query?: undefined;
158
159
  timezone?: undefined;
159
- limit?: undefined;
160
160
  page?: undefined;
161
161
  event?: undefined;
162
162
  } | {
@@ -185,9 +185,9 @@ export declare const calendarTools: ({
185
185
  };
186
186
  event?: undefined;
187
187
  } | {
188
+ limit?: undefined;
188
189
  query?: undefined;
189
190
  timezone?: undefined;
190
- limit?: undefined;
191
191
  page?: undefined;
192
192
  event: {
193
193
  type: string;
@@ -198,9 +198,9 @@ export declare const calendarTools: ({
198
198
  minimum: number;
199
199
  };
200
200
  } | {
201
+ limit?: undefined;
201
202
  query?: undefined;
202
203
  timezone?: undefined;
203
- limit?: undefined;
204
204
  page?: undefined;
205
205
  event?: undefined;
206
206
  account_id?: undefined;
@@ -220,13 +220,13 @@ export declare const calendarTools: ({
220
220
  inputSchema: {
221
221
  type: string;
222
222
  properties: {
223
+ limit?: undefined;
223
224
  account_id: {
224
225
  type: string;
225
226
  minimum: number;
226
227
  };
227
228
  query?: undefined;
228
229
  timezone?: undefined;
229
- limit?: undefined;
230
230
  page?: undefined;
231
231
  event?: undefined;
232
232
  } | {
@@ -255,9 +255,9 @@ export declare const calendarTools: ({
255
255
  };
256
256
  event?: undefined;
257
257
  } | {
258
+ limit?: undefined;
258
259
  query?: undefined;
259
260
  timezone?: undefined;
260
- limit?: undefined;
261
261
  page?: undefined;
262
262
  event: {
263
263
  type: string;
@@ -268,9 +268,9 @@ export declare const calendarTools: ({
268
268
  minimum: number;
269
269
  };
270
270
  } | {
271
+ limit?: undefined;
271
272
  query?: undefined;
272
273
  timezone?: undefined;
273
- limit?: undefined;
274
274
  page?: undefined;
275
275
  event?: undefined;
276
276
  account_id?: undefined;
@@ -8,6 +8,12 @@ const keys = {
8
8
  due_evidence: "dueEvidence",
9
9
  assignee_evidence: "assigneeEvidence",
10
10
  memory_kind: "memoryKind",
11
+ issue_date: "issueDate",
12
+ due_date: "dueDate",
13
+ unit_price: "unitPrice",
14
+ supplier_name: "supplierName",
15
+ order_date: "orderDate",
16
+ fulfilled_date: "fulfilledDate",
11
17
  };
12
18
  const parseFindingsJson = (text, label, validate) => {
13
19
  if (Buffer.byteLength(text, "utf8") > 65536)
@@ -0,0 +1,19 @@
1
+ import type { FinanceReview } from "./finance.js";
2
+ export declare const ORDER_STATUSES: readonly ["placed", "shipped", "delivered", "cancelled", "returned"];
3
+ /** Why it was ordered: for the owner himself, for his business, for a client, or anything else. */
4
+ export declare const ORDER_PURPOSES: readonly ["personal", "business", "client", "other"];
5
+ export type OrderStatus = (typeof ORDER_STATUSES)[number];
6
+ export type OrderPurpose = (typeof ORDER_PURPOSES)[number];
7
+ export interface OrderTitleFields {
8
+ id: number;
9
+ number: string | null;
10
+ supplierName: string;
11
+ total: number | null;
12
+ currency: string | null;
13
+ status: OrderStatus;
14
+ review: FinanceReview;
15
+ }
16
+ /** "12345 · Ozon, 2 499.00 RUB (shipped)": a ref summary's title, which `refLabel` prefixes. */
17
+ export declare const orderLine: (order: OrderTitleFields) => string;
18
+ /** "Order 12345 · Ozon, 2 499.00 RUB (shipped)". */
19
+ export declare const orderTitle: (order: OrderTitleFields) => string;
@@ -0,0 +1,12 @@
1
+ import { moneyLabel } from "./money.js";
2
+ export const ORDER_STATUSES = ["placed", "shipped", "delivered", "cancelled", "returned"];
3
+ /** Why it was ordered: for the owner himself, for his business, for a client, or anything else. */
4
+ export const ORDER_PURPOSES = ["personal", "business", "client", "other"];
5
+ /** "12345 · Ozon, 2 499.00 RUB (shipped)": a ref summary's title, which `refLabel` prefixes. */
6
+ export const orderLine = (order) => {
7
+ const money = order.total === null || order.currency === null ? "" : `, ${moneyLabel(order.total, order.currency)}`;
8
+ const notes = [order.status, order.review === "confirmed" ? null : order.review].filter(Boolean).join(", ");
9
+ return `${order.number ?? `#${order.id}`} · ${order.supplierName}${money} (${notes})`;
10
+ };
11
+ /** "Order 12345 · Ozon, 2 499.00 RUB (shipped)". */
12
+ export const orderTitle = (order) => `Order ${orderLine(order)}`;
@@ -51,6 +51,7 @@ const WORDS = {
51
51
  bot: "Bot",
52
52
  invoice: "Invoice",
53
53
  payment: "Payment",
54
+ order: "Order",
54
55
  email: "Email",
55
56
  email_thread: "Email thread",
56
57
  unknown: "Unknown",
@@ -33,7 +33,7 @@ export type Reference = {
33
33
  * Things named by their store id (a task also by its key or the task package's id). `entity` is the name
34
34
  * before organizations and projects: it still parses, so an old file's reference resolves as not found.
35
35
  */
36
- export declare const SIMPLE_REFERENCES: readonly ["note", "document", "person", "organization", "project", "task", "memory", "fact", "bot", "event", "invoice", "payment", "entity"];
36
+ export declare const SIMPLE_REFERENCES: readonly ["note", "document", "person", "organization", "project", "task", "memory", "fact", "bot", "event", "invoice", "payment", "order", "entity"];
37
37
  export type SimpleReference = (typeof SIMPLE_REFERENCES)[number];
38
38
  export type RefKindName = Exclude<Reference["type"], "entity">;
39
39
  export interface RefKindSpec<R extends Reference = Reference> {
@@ -18,6 +18,7 @@ export const SIMPLE_REFERENCES = [
18
18
  "event",
19
19
  "invoice",
20
20
  "payment",
21
+ "order",
21
22
  "entity",
22
23
  ];
23
24
  const malformed = (text, shape) => new CliError("validation_error", `"${singleLine(text)}" is not a reference — expected ${shape}`, {
@@ -5966,6 +5966,81 @@ export declare const ENTITIES: {
5966
5966
  readonly address: v.StringSchema<undefined>;
5967
5967
  readonly name: v.StringSchema<undefined>;
5968
5968
  readonly reason: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
5969
+ }, undefined>, v.StrictObjectSchema<{
5970
+ readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
5971
+ readonly evidence: v.ArraySchema<v.StrictObjectSchema<{
5972
+ readonly reference: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
5973
+ readonly fingerprint: v.StringSchema<undefined>;
5974
+ }, undefined>, undefined>;
5975
+ readonly kind: v.LiteralSchema<"invoice", undefined>;
5976
+ readonly direction: v.PicklistSchema<readonly ["receivable", "payable"], undefined>;
5977
+ readonly counterparty: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
5978
+ readonly currency: v.StringSchema<undefined>;
5979
+ readonly lines: v.ArraySchema<v.StrictObjectSchema<{
5980
+ readonly description: v.StringSchema<undefined>;
5981
+ readonly quantity: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
5982
+ readonly unit_price: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
5983
+ readonly amount: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
5984
+ }, undefined>, undefined>;
5985
+ readonly total: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
5986
+ readonly number: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
5987
+ readonly issue_date: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
5988
+ readonly due_date: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
5989
+ readonly project: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
5990
+ readonly reason: v.StringSchema<undefined>;
5991
+ }, undefined>, v.StrictObjectSchema<{
5992
+ readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
5993
+ readonly evidence: v.ArraySchema<v.StrictObjectSchema<{
5994
+ readonly reference: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
5995
+ readonly fingerprint: v.StringSchema<undefined>;
5996
+ }, undefined>, undefined>;
5997
+ readonly kind: v.LiteralSchema<"payment", undefined>;
5998
+ readonly direction: v.PicklistSchema<readonly ["in", "out"], undefined>;
5999
+ readonly payer: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
6000
+ readonly payee: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
6001
+ readonly amount: v.StringSchema<undefined>;
6002
+ readonly currency: v.StringSchema<undefined>;
6003
+ readonly date: v.StringSchema<undefined>;
6004
+ readonly reference: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
6005
+ readonly allocations: v.OptionalSchema<v.ArraySchema<v.StrictObjectSchema<{
6006
+ readonly invoice: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
6007
+ readonly amount: v.StringSchema<undefined>;
6008
+ }, undefined>, undefined>, undefined>;
6009
+ readonly project: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
6010
+ readonly reason: v.StringSchema<undefined>;
6011
+ }, undefined>, v.StrictObjectSchema<{
6012
+ readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
6013
+ readonly evidence: v.ArraySchema<v.StrictObjectSchema<{
6014
+ readonly reference: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
6015
+ readonly fingerprint: v.StringSchema<undefined>;
6016
+ }, undefined>, undefined>;
6017
+ readonly kind: v.LiteralSchema<"order", undefined>;
6018
+ readonly purpose: v.PicklistSchema<readonly ["personal", "business", "client", "other"], undefined>;
6019
+ readonly customer: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
6020
+ readonly supplier: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
6021
+ readonly supplier_name: v.StringSchema<undefined>;
6022
+ readonly number: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
6023
+ readonly total: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
6024
+ readonly currency: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
6025
+ readonly status: v.OptionalSchema<v.PicklistSchema<readonly ["placed", "shipped", "delivered", "cancelled", "returned"], undefined>, undefined>;
6026
+ readonly order_date: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
6027
+ readonly fulfilled_date: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
6028
+ readonly invoice: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
6029
+ readonly payment: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
6030
+ readonly project: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
6031
+ readonly reason: v.StringSchema<undefined>;
6032
+ }, undefined>, v.StrictObjectSchema<{
6033
+ readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
6034
+ readonly evidence: v.ArraySchema<v.StrictObjectSchema<{
6035
+ readonly reference: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
6036
+ readonly fingerprint: v.StringSchema<undefined>;
6037
+ }, undefined>, undefined>;
6038
+ readonly kind: v.LiteralSchema<"order_update", undefined>;
6039
+ readonly supplier_name: v.StringSchema<undefined>;
6040
+ readonly number: v.StringSchema<undefined>;
6041
+ readonly status: v.PicklistSchema<readonly ["placed", "shipped", "delivered", "cancelled", "returned"], undefined>;
6042
+ readonly date: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
6043
+ readonly reason: v.StringSchema<undefined>;
5969
6044
  }, undefined>], undefined>, undefined>;
5970
6045
  }, undefined>;
5971
6046
  readonly fingerprint: v.StringSchema<undefined>;
@@ -81,6 +81,81 @@ export declare const mailFindings: v.StrictObjectSchema<{
81
81
  readonly address: v.StringSchema<undefined>;
82
82
  readonly name: v.StringSchema<undefined>;
83
83
  readonly reason: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
84
+ }, undefined>, v.StrictObjectSchema<{
85
+ readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
86
+ readonly evidence: v.ArraySchema<v.StrictObjectSchema<{
87
+ readonly reference: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
88
+ readonly fingerprint: v.StringSchema<undefined>;
89
+ }, undefined>, undefined>;
90
+ readonly kind: v.LiteralSchema<"invoice", undefined>;
91
+ readonly direction: v.PicklistSchema<readonly ["receivable", "payable"], undefined>;
92
+ readonly counterparty: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
93
+ readonly currency: v.StringSchema<undefined>;
94
+ readonly lines: v.ArraySchema<v.StrictObjectSchema<{
95
+ readonly description: v.StringSchema<undefined>;
96
+ readonly quantity: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
97
+ readonly unit_price: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
98
+ readonly amount: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
99
+ }, undefined>, undefined>;
100
+ readonly total: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
101
+ readonly number: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
102
+ readonly issue_date: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
103
+ readonly due_date: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
104
+ readonly project: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
105
+ readonly reason: v.StringSchema<undefined>;
106
+ }, undefined>, v.StrictObjectSchema<{
107
+ readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
108
+ readonly evidence: v.ArraySchema<v.StrictObjectSchema<{
109
+ readonly reference: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
110
+ readonly fingerprint: v.StringSchema<undefined>;
111
+ }, undefined>, undefined>;
112
+ readonly kind: v.LiteralSchema<"payment", undefined>;
113
+ readonly direction: v.PicklistSchema<readonly ["in", "out"], undefined>;
114
+ readonly payer: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
115
+ readonly payee: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
116
+ readonly amount: v.StringSchema<undefined>;
117
+ readonly currency: v.StringSchema<undefined>;
118
+ readonly date: v.StringSchema<undefined>;
119
+ readonly reference: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
120
+ readonly allocations: v.OptionalSchema<v.ArraySchema<v.StrictObjectSchema<{
121
+ readonly invoice: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
122
+ readonly amount: v.StringSchema<undefined>;
123
+ }, undefined>, undefined>, undefined>;
124
+ readonly project: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
125
+ readonly reason: v.StringSchema<undefined>;
126
+ }, undefined>, v.StrictObjectSchema<{
127
+ readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
128
+ readonly evidence: v.ArraySchema<v.StrictObjectSchema<{
129
+ readonly reference: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
130
+ readonly fingerprint: v.StringSchema<undefined>;
131
+ }, undefined>, undefined>;
132
+ readonly kind: v.LiteralSchema<"order", undefined>;
133
+ readonly purpose: v.PicklistSchema<readonly ["personal", "business", "client", "other"], undefined>;
134
+ readonly customer: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
135
+ readonly supplier: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
136
+ readonly supplier_name: v.StringSchema<undefined>;
137
+ readonly number: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
138
+ readonly total: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
139
+ readonly currency: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
140
+ readonly status: v.OptionalSchema<v.PicklistSchema<readonly ["placed", "shipped", "delivered", "cancelled", "returned"], undefined>, undefined>;
141
+ readonly order_date: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
142
+ readonly fulfilled_date: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
143
+ readonly invoice: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
144
+ readonly payment: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
145
+ readonly project: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
146
+ readonly reason: v.StringSchema<undefined>;
147
+ }, undefined>, v.StrictObjectSchema<{
148
+ readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
149
+ readonly evidence: v.ArraySchema<v.StrictObjectSchema<{
150
+ readonly reference: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
151
+ readonly fingerprint: v.StringSchema<undefined>;
152
+ }, undefined>, undefined>;
153
+ readonly kind: v.LiteralSchema<"order_update", undefined>;
154
+ readonly supplier_name: v.StringSchema<undefined>;
155
+ readonly number: v.StringSchema<undefined>;
156
+ readonly status: v.PicklistSchema<readonly ["placed", "shipped", "delivered", "cancelled", "returned"], undefined>;
157
+ readonly date: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
158
+ readonly reason: v.StringSchema<undefined>;
84
159
  }, undefined>], undefined>, undefined>;
85
160
  }, undefined>;
86
161
  readonly fingerprint: v.StringSchema<undefined>;
@@ -1,5 +1,7 @@
1
1
  import * as v from "valibot";
2
+ import { INVOICE_DIRECTIONS, MONEY_DIRECTIONS } from "../domain/finance.js";
2
3
  import { FACT_TYPES, MEMORY_KINDS, MEMORY_SCOPES, WORK_TASK_TYPES } from "../domain/knowledge.js";
4
+ import { ORDER_PURPOSES, ORDER_STATUSES } from "../domain/orders.js";
3
5
  import { author, id, reference, text, time } from "./fields.js";
4
6
  const evidence = v.strictObject({ reference, fingerprint: text });
5
7
  const base = { id, evidence: v.array(evidence) };
@@ -45,6 +47,66 @@ const finding = v.variant("kind", [
45
47
  }),
46
48
  v.strictObject({ ...base, kind: v.literal("link"), from: reference, to: reference, reason: text }),
47
49
  v.strictObject({ ...base, kind: v.literal("new_contact"), address: text, name: text, reason: v.optional(text) }),
50
+ v.strictObject({
51
+ ...base,
52
+ kind: v.literal("invoice"),
53
+ direction: v.picklist(INVOICE_DIRECTIONS),
54
+ counterparty: reference,
55
+ currency: text,
56
+ lines: v.array(v.strictObject({
57
+ description: text,
58
+ quantity: v.optional(text),
59
+ unit_price: v.optional(text),
60
+ amount: v.optional(text),
61
+ })),
62
+ total: v.optional(text),
63
+ number: v.optional(text),
64
+ issue_date: v.optional(text),
65
+ due_date: v.optional(text),
66
+ project: v.optional(reference),
67
+ reason: text,
68
+ }),
69
+ v.strictObject({
70
+ ...base,
71
+ kind: v.literal("payment"),
72
+ direction: v.picklist(MONEY_DIRECTIONS),
73
+ payer: v.optional(reference),
74
+ payee: v.optional(reference),
75
+ amount: text,
76
+ currency: text,
77
+ date: text,
78
+ reference: v.optional(text),
79
+ allocations: v.optional(v.array(v.strictObject({ invoice: reference, amount: text }))),
80
+ project: v.optional(reference),
81
+ reason: text,
82
+ }),
83
+ v.strictObject({
84
+ ...base,
85
+ kind: v.literal("order"),
86
+ purpose: v.picklist(ORDER_PURPOSES),
87
+ customer: v.optional(reference),
88
+ supplier: v.optional(reference),
89
+ supplier_name: text,
90
+ number: v.optional(text),
91
+ total: v.optional(text),
92
+ currency: v.optional(text),
93
+ status: v.optional(v.picklist(ORDER_STATUSES)),
94
+ order_date: v.optional(text),
95
+ fulfilled_date: v.optional(text),
96
+ invoice: v.optional(reference),
97
+ payment: v.optional(reference),
98
+ project: v.optional(reference),
99
+ reason: text,
100
+ }),
101
+ v.strictObject({
102
+ ...base,
103
+ kind: v.literal("order_update"),
104
+ supplier_name: text,
105
+ number: text,
106
+ status: v.picklist(ORDER_STATUSES),
107
+ date: v.optional(text),
108
+ reason: text,
109
+ }),
48
110
  ]);
49
111
  const outcome = v.strictObject({
50
112
  id,
@@ -197,6 +197,11 @@ const OTHERS = [
197
197
  validation("money.amount", "the amount is not a decimal string in the currency's scale"),
198
198
  validation("money.currency", "the currency is not one the store keeps money in"),
199
199
  validation("money.quantity", "the quantity is not a decimal above 0"),
200
+ validation("order.duplicate", "the shop's order with this number is already stored; change its status instead"),
201
+ validation("order.field", "a field of the order is not valid"),
202
+ ["order.not-found", "not_found", "no stored order of that shop has that number"],
203
+ validation("order.ref", "the reference names no order, or a shop's number names more than one"),
204
+ validation("order.status", "a rejected or deleted order's status does not change"),
200
205
  ["model.not-downloaded", "not_found", "the model is not downloaded on this machine"],
201
206
  ["person.no-owner", "not_found", "no person is marked as the owner in the store"],
202
207
  ["transcription.no-key", "authentication_error", "no OpenAI key is set for transcription"],
package/dist/index.d.ts CHANGED
@@ -9,6 +9,7 @@ export { parseMarkdownSpans } from "./domain/markdown-spans.js";
9
9
  export type { MessageLink, MessagePermalink } from "./domain/message-link.js";
10
10
  export type * from "./domain/models.js";
11
11
  export { CURRENCY_SCALES, currencyOf, type Money, minorUnits, moneyLabel, moneyOf, } from "./domain/money.js";
12
+ export { ORDER_PURPOSES, ORDER_STATUSES, type OrderPurpose, type OrderStatus, } from "./domain/orders.js";
12
13
  export { countedPoll } from "./domain/polls.js";
13
14
  export { FOLDER_PROVIDERS, isFolderProvider, isKind, KINDS, type Kind, kindIn, kindOf, PROVIDERS, type ProviderEntry, providerNamed, providers, UNKNOWN_PROVIDER, useProviders, } from "./domain/providers.js";
14
15
  export type { RankingGraphEvidence, RankingGraphLink } from "./domain/rankings-graph.js";
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ export { formatLocator, isLocator, parseLocator } from "./domain/locator.js";
6
6
  export { parseMarkdown } from "./domain/markdown.js";
7
7
  export { parseMarkdownSpans } from "./domain/markdown-spans.js";
8
8
  export { CURRENCY_SCALES, currencyOf, minorUnits, moneyLabel, moneyOf, } from "./domain/money.js";
9
+ export { ORDER_PURPOSES, ORDER_STATUSES, } from "./domain/orders.js";
9
10
  export { countedPoll } from "./domain/polls.js";
10
11
  export { FOLDER_PROVIDERS, isFolderProvider, isKind, KINDS, kindIn, kindOf, PROVIDERS, providerNamed, providers, UNKNOWN_PROVIDER, useProviders, } from "./domain/providers.js";
11
12
  export { rankingOptions } from "./domain/rankings-options.js";
@@ -0,0 +1,6 @@
1
+ import type { DeclaredSetting } from "../../cli/setting-defaults.js";
2
+ import { type BulkTriage } from "../newsletter.js";
3
+ /** The bulk-mail settings a host that mounts `review --holder` writes into its config file's top level (WC-CMD-30). */
4
+ export declare const MAIL_BULK_SETTINGS: readonly DeclaredSetting[];
5
+ /** `MAIL_BULK_SETTINGS` as the config file gives them; a missing or unusable value is left to the default. */
6
+ export declare const bulkTriageOf: (config: Record<string, unknown>) => BulkTriage;
@@ -0,0 +1,65 @@
1
+ import { BULK_TRIAGE_DEFAULTS, NEWSLETTER_DEFAULTS, NEWSLETTER_SENDERS, NEWSLETTER_WORDS, ORDER_WORDS, } from "../newsletter.js";
2
+ const listed = (words) => words.join(", ");
3
+ /** The bulk-mail settings a host that mounts `review --holder` writes into its config file's top level (WC-CMD-30). */
4
+ export const MAIL_BULK_SETTINGS = [
5
+ {
6
+ key: "mail_bulk_excerpts",
7
+ default: BULK_TRIAGE_DEFAULTS.excerpts,
8
+ description: "review --holder gives an agent bulk mail that is not surely a newsletter as its subject and first lines; " +
9
+ "false leaves all bulk mail out",
10
+ },
11
+ {
12
+ key: "mail_bulk_excerpts_per_read",
13
+ default: BULK_TRIAGE_DEFAULTS.perRead,
14
+ description: "the most bulk excerpts one review --holder gives; the rest come with the next read",
15
+ },
16
+ {
17
+ key: "mail_bulk_excerpt_chars",
18
+ default: NEWSLETTER_DEFAULTS.excerptChars,
19
+ description: "how many characters of a bulk email's own text an excerpt shows",
20
+ },
21
+ {
22
+ key: "mail_bulk_newsletter_score",
23
+ default: NEWSLETTER_DEFAULTS.threshold,
24
+ description: "bulk mail scoring this or more is left out as a newsletter: a newsletter word counts 2 in the subject, 1 in " +
25
+ "the first lines, a newsletter's address 2, an emoji in the subject 1, an unsubscribe link at the top 1, " +
26
+ "an order word in the first lines -2",
27
+ },
28
+ {
29
+ key: "mail_bulk_newsletter_words",
30
+ default: listed(NEWSLETTER_WORDS),
31
+ description: "comma-separated words that say advertising; each matches at the start of a word, any case",
32
+ },
33
+ {
34
+ key: "mail_bulk_newsletter_senders",
35
+ default: listed(NEWSLETTER_SENDERS),
36
+ description: "comma-separated parts of a sender's address (news, digest) or domains (substack.com) of newsletters",
37
+ },
38
+ {
39
+ key: "mail_bulk_order_words",
40
+ default: listed(ORDER_WORDS),
41
+ description: "comma-separated words of orders, invoices and requests: one in the subject keeps a bulk email for the agent",
42
+ },
43
+ ];
44
+ const wordsOf = (value) => typeof value === "string"
45
+ ? value
46
+ .split(",")
47
+ .map((word) => word.trim())
48
+ .filter(Boolean)
49
+ : undefined;
50
+ const countOf = (value, least) => typeof value === "number" && Number.isSafeInteger(value) && value >= least ? value : undefined;
51
+ /** `MAIL_BULK_SETTINGS` as the config file gives them; a missing or unusable value is left to the default. */
52
+ export const bulkTriageOf = (config) => {
53
+ const { cues } = BULK_TRIAGE_DEFAULTS;
54
+ return {
55
+ excerpts: typeof config.mail_bulk_excerpts === "boolean" ? config.mail_bulk_excerpts : BULK_TRIAGE_DEFAULTS.excerpts,
56
+ perRead: countOf(config.mail_bulk_excerpts_per_read, 1) ?? BULK_TRIAGE_DEFAULTS.perRead,
57
+ cues: {
58
+ threshold: countOf(config.mail_bulk_newsletter_score, 1) ?? cues.threshold,
59
+ excerptChars: countOf(config.mail_bulk_excerpt_chars, 40) ?? cues.excerptChars,
60
+ newsletterWords: wordsOf(config.mail_bulk_newsletter_words) ?? cues.newsletterWords,
61
+ newsletterSenders: wordsOf(config.mail_bulk_newsletter_senders) ?? cues.newsletterSenders,
62
+ orderWords: wordsOf(config.mail_bulk_order_words) ?? cues.orderWords,
63
+ },
64
+ };
65
+ };
@@ -1,7 +1,9 @@
1
1
  import { Command } from "commander";
2
+ import type { BulkTriage } from "../../mail/newsletter.js";
2
3
  import { type MailServerAccess } from "../../mail/server-search.js";
3
4
  import type { AppIdentity } from "../../ports/app.js";
4
5
  import { type AccountKey, type MessageStore } from "../../store/store.js";
6
+ export { bulkTriageOf, MAIL_BULK_SETTINGS } from "./bulk-settings.js";
5
7
  export { mailMark, markItem, markText } from "./marks.js";
6
8
  export { MAIL_REVIEW_QUERY, MAIL_REVIEW_STEPS, mailReviewSteps } from "./review.js";
7
9
  /**
@@ -37,6 +39,8 @@ export interface MailReads {
37
39
  * With it, `messages show` reads an email whose attachment a sync's policy left unread before it answers.
38
40
  */
39
41
  whole?: (command: Command, store: MessageStore, mailbox: AccountKey, emailId: string) => Promise<void>;
42
+ /** How `review --holder` treats bulk mail, from the host's config file (`MAIL_BULK_SETTINGS`); defaults without it. */
43
+ bulkTriage?: (command: Command) => BulkTriage;
40
44
  }
41
45
  export declare const THREAD_QUERY: {
42
46
  readonly entity: "mail_thread";
@@ -21,6 +21,7 @@ import { sourceKey } from "../../services/server-search.js";
21
21
  import { queryFilter, THREAD_COLUMNS } from "../../store/store.js";
22
22
  import { acknowledgeCommand } from "./marks.js";
23
23
  import { mailReviewCommand } from "./review.js";
24
+ export { bulkTriageOf, MAIL_BULK_SETTINGS } from "./bulk-settings.js";
24
25
  export { mailMark, markItem, markText } from "./marks.js";
25
26
  export { MAIL_REVIEW_QUERY, MAIL_REVIEW_STEPS, mailReviewSteps } from "./review.js";
26
27
  /** A locked or broken store must fail the command, not read as "nothing stored yet". */