@wirecat/cli-core 0.556.0 → 0.558.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 (62) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/calendar/mcp/index.d.ts +12 -12
  3. package/dist/cli/index.d.ts +0 -1
  4. package/dist/cli/index.js +0 -1
  5. package/dist/cli/messenger/store-maintenance-command.d.ts +1 -1
  6. package/dist/domain/finance.d.ts +40 -0
  7. package/dist/domain/finance.js +39 -0
  8. package/dist/domain/money.d.ts +29 -0
  9. package/dist/domain/money.js +98 -0
  10. package/dist/domain/ref-summaries.js +2 -0
  11. package/dist/domain/references.d.ts +1 -1
  12. package/dist/domain/references.js +2 -0
  13. package/dist/entities/fact.d.ts +1 -12
  14. package/dist/entities/fact.js +0 -13
  15. package/dist/entities/index.d.ts +2 -12
  16. package/dist/entities/meeting-findings.d.ts +1 -1
  17. package/dist/errors/reasons.js +16 -0
  18. package/dist/index.d.ts +2 -0
  19. package/dist/index.js +2 -0
  20. package/dist/mail/cli/review.js +0 -1
  21. package/dist/meetings/findings-rules.d.ts +7 -4
  22. package/dist/meetings/findings-rules.js +11 -4
  23. package/dist/meetings/mcp/index.d.ts +15 -15
  24. package/dist/meetings/payment.d.ts +10 -6
  25. package/dist/meetings/payment.js +21 -6
  26. package/dist/services/project-status/index.d.ts +1 -1
  27. package/dist/services/project-status/index.js +7 -3
  28. package/dist/services/project-status/signals/payments.d.ts +3 -3
  29. package/dist/services/project-status/signals/payments.js +43 -17
  30. package/dist/services/proposals/kinds/finance.d.ts +4 -0
  31. package/dist/services/proposals/kinds/finance.js +40 -0
  32. package/dist/services/proposals/registry.js +3 -0
  33. package/dist/store/evidence-findings.js +16 -8
  34. package/dist/store/index.d.ts +2 -1
  35. package/dist/store/project-transfer/request.d.ts +1 -1
  36. package/dist/store/project-transfer/schema.d.ts +2 -2
  37. package/dist/store/project-transfer/snapshot-model.d.ts +2 -2
  38. package/dist/store/sqlite/entity-types.d.ts +2 -0
  39. package/dist/store/sqlite/entity-types.js +2 -0
  40. package/dist/store/sqlite/facts.d.ts +0 -13
  41. package/dist/store/sqlite/facts.js +5 -58
  42. package/dist/store/sqlite/finance.d.ts +203 -0
  43. package/dist/store/sqlite/finance.js +529 -0
  44. package/dist/store/sqlite/findings-core.js +5 -14
  45. package/dist/store/sqlite/knowledge.js +3 -1
  46. package/dist/store/sqlite/manifest.js +3 -0
  47. package/dist/store/sqlite/migrations.generated.js +43 -0
  48. package/dist/store/sqlite/open-store.js +2 -0
  49. package/dist/store/sqlite/payment-entries.d.ts +26 -0
  50. package/dist/store/sqlite/payment-entries.js +91 -0
  51. package/dist/store/sqlite/payment-facts.d.ts +12 -0
  52. package/dist/store/sqlite/payment-facts.js +92 -0
  53. package/dist/store/sqlite/project-transfer/snapshot.d.ts +1 -1
  54. package/dist/store/sqlite/ref-summaries.js +33 -0
  55. package/dist/store/sqlite/schema.d.ts +1043 -0
  56. package/dist/store/sqlite/schema.js +98 -0
  57. package/dist/store/sqlite/things.js +2 -0
  58. package/dist/store/store.d.ts +3 -1
  59. package/dist/store/store.js +5 -0
  60. package/package.json +1 -1
  61. package/dist/cli/payment-query.d.ts +0 -8
  62. package/dist/cli/payment-query.js +0 -27
package/CHANGELOG.md CHANGED
@@ -7,6 +7,43 @@ shape of this file.
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ ## 0.558.0 — 02.11.2026
11
+
12
+ ### Changed — may break callers
13
+
14
+ - **A payment is no longer a fact (store version 46).** Money lives only in `store.finance`. `facts.add` refuses type
15
+ `payment` with reason `fact.payment-retired`, naming `store.finance.addPayment`, and a trigger refuses one from an
16
+ older build. `FACT_TYPES`, the `fact` entity and `Fact` lose `payment`; `PaymentFilter`, `facts.list({ payment })`,
17
+ `PAYMENT_QUERY_FIELDS` and `paymentFilterOf` are gone. The first writable open moves stored payment facts into rows.
18
+ - **An accepted payment finding writes a finance row, not a fact.** A paid one is a `payment:` row, an expected one
19
+ an `invoice:` of one line, proposed by the agent and confirmed by the owner's acceptance. `supersedes` now names
20
+ the `invoice:` a paid one settles or a cancelled one voids, not a `fact:`. An expected one needs a `person:` or
21
+ `organization:` counterparty, and the amount must fit the currency's decimals. New optional field `paid_at`.
22
+ - **`projects status` reads money from `store.finance`.** The `payments` signal (version 3) lists payments and open
23
+ invoices by their `payment:` and `invoice:` refs, `status` being `proposed` or `confirmed`; the open total counts
24
+ confirmed invoices only. Finance rows gain `evidence`, and `addInvoice` and `addPayment` take it.
25
+
26
+ ## 0.557.0 — 02.11.2026
27
+
28
+ ### Added
29
+
30
+ - **Invoices and payments in the store (store version 45).** `store.finance` adds, lists and shows invoices with
31
+ their lines and payments, allocates a payment to invoices and sums what open invoices still owe per currency
32
+ (`openTotals`). Money is an integer in the currency's minor units (`CURRENCY_SCALES`, ISO 4217), passed and read as
33
+ decimal strings; an unknown currency is refused and nothing is converted. A total must equal its lines, and an
34
+ allocation never exceeds the payment or the invoice or crosses currencies; triggers back the code. The status
35
+ follows confirmed allocations; only the owner issues a draft or voids an invoice.
36
+ - **An agent's or an import's invoice or payment is a proposal.** The owner's entry is confirmed at once; a bot's
37
+ waits as `invoice` or `payment` in `proposalRegistry`, and only the owner decides it. A pending payment's
38
+ allocations never count against an invoice, so they never block the owner's own. Confirming one counts them in the
39
+ same write, or is refused while other confirmed payments already cover the invoice; rejecting drops them.
40
+ - **`invoice:<id>` and `payment:<id>` references,** with `refs.summaries` titles such as
41
+ `42 · 50 000.00 RUB to ООО Ромашка, due 15.11 (partly paid)`.
42
+
43
+ ### Fixed
44
+
45
+ - **Mail `review` gives three examples**, as the command lint asks; `--waiting` stays in its help.
46
+
10
47
  ## 0.556.0 — 02.11.2026
11
48
 
12
49
  ### Added
@@ -10,13 +10,13 @@ export declare const calendarTools: ({
10
10
  inputSchema: {
11
11
  type: string;
12
12
  properties: {
13
- limit?: undefined;
14
13
  account_id: {
15
14
  type: string;
16
15
  minimum: number;
17
16
  };
18
17
  query?: undefined;
19
18
  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;
49
48
  query?: undefined;
50
49
  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;
62
61
  query?: undefined;
63
62
  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;
84
83
  account_id: {
85
84
  type: string;
86
85
  minimum: number;
87
86
  };
88
87
  query?: undefined;
89
88
  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;
119
118
  query?: undefined;
120
119
  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;
132
131
  query?: undefined;
133
132
  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;
154
153
  account_id: {
155
154
  type: string;
156
155
  minimum: number;
157
156
  };
158
157
  query?: undefined;
159
158
  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;
189
188
  query?: undefined;
190
189
  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;
202
201
  query?: undefined;
203
202
  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;
224
223
  account_id: {
225
224
  type: string;
226
225
  minimum: number;
227
226
  };
228
227
  query?: undefined;
229
228
  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;
259
258
  query?: undefined;
260
259
  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;
272
271
  query?: undefined;
273
272
  timezone?: undefined;
273
+ limit?: undefined;
274
274
  page?: undefined;
275
275
  event?: undefined;
276
276
  account_id?: undefined;
@@ -111,7 +111,6 @@ export { type Saving, stored } from "./messenger/stored.js";
111
111
  export { dateOrNone } from "./messenger/tasks-command.js";
112
112
  export { MODEL_CATALOGUE_QUERY, type ModelCatalogueOptions } from "./model-catalogue-command.js";
113
113
  export { positiveCount, window, withPaging } from "./paging.js";
114
- export { PAYMENT_QUERY_FIELDS, paymentFilterOf } from "./payment-query.js";
115
114
  export { type CommandPermissions, commandPathKey, commandPermissions, knownBeside, knownPermissionKeys, type PermissionKeyOf, type PermissionKeySource, refuseUnknownPermissionKeys, unknownPermissionIssues, unknownPermissionKeys, } from "./permission-keys.js";
116
115
  export { asFirstWord, commandWords, liftProfile, profileFree, refuseCommandName, rootOf, takesProfile, usableProfileName, } from "./profile.js";
117
116
  export { createProgram, type ProgramDefinition, type ProgramOptions, type RunOptions, run } from "./program.js";
package/dist/cli/index.js CHANGED
@@ -101,7 +101,6 @@ export { stored } from "./messenger/stored.js";
101
101
  export { dateOrNone } from "./messenger/tasks-command.js";
102
102
  export { MODEL_CATALOGUE_QUERY } from "./model-catalogue-command.js";
103
103
  export { positiveCount, window, withPaging } from "./paging.js";
104
- export { PAYMENT_QUERY_FIELDS, paymentFilterOf } from "./payment-query.js";
105
104
  export { commandPathKey, commandPermissions, knownBeside, knownPermissionKeys, refuseUnknownPermissionKeys, unknownPermissionIssues, unknownPermissionKeys, } from "./permission-keys.js";
106
105
  export { asFirstWord, commandWords, liftProfile, profileFree, refuseCommandName, rootOf, takesProfile, usableProfileName, } from "./profile.js";
107
106
  export { createProgram, run } from "./program.js";
@@ -3,9 +3,9 @@ import type { CacheDatabase } from "../../store/driver.js";
3
3
  import type { Messenger } from "./context.js";
4
4
  /** `doctor`'s short summary of the store. Must answer for a broken file, so an error is a field. */
5
5
  export declare const storeSummary: (env: NodeJS.ProcessEnv) => Promise<{
6
- error?: undefined;
7
6
  path: string;
8
7
  exists: boolean;
8
+ error?: undefined;
9
9
  } | {
10
10
  path: string;
11
11
  exists: boolean;
@@ -0,0 +1,40 @@
1
+ export declare const INVOICE_DIRECTIONS: readonly ["receivable", "payable"];
2
+ export declare const INVOICE_STATUSES: readonly ["draft", "issued", "partly_paid", "paid", "void"];
3
+ /** `in` is money to the owner, `out` money from the owner. */
4
+ export declare const MONEY_DIRECTIONS: readonly ["in", "out"];
5
+ /** The owner's entry is confirmed at once; an agent's or an import's waits as proposed. */
6
+ export declare const FINANCE_REVIEWS: readonly ["proposed", "confirmed", "rejected"];
7
+ export type InvoiceDirection = (typeof INVOICE_DIRECTIONS)[number];
8
+ export type InvoiceStatus = (typeof INVOICE_STATUSES)[number];
9
+ export type MoneyDirection = (typeof MONEY_DIRECTIONS)[number];
10
+ export type FinanceReview = (typeof FINANCE_REVIEWS)[number];
11
+ /** An invoice's status from what confirmed payments settle; `draft` and `void` are the owner's and stay. */
12
+ export declare const settledStatus: (current: InvoiceStatus, total: number, paid: number) => InvoiceStatus;
13
+ export interface InvoiceTitleFields {
14
+ id: number;
15
+ number: string | null;
16
+ direction: InvoiceDirection;
17
+ total: number;
18
+ currency: string;
19
+ counterpartyName: string | null;
20
+ dueDate: string | null;
21
+ status: InvoiceStatus;
22
+ review: FinanceReview;
23
+ }
24
+ /** "42 · 50 000.00 RUB to ООО Ромашка, due 15.11 (partly paid)": a ref summary's title, which `refLabel` prefixes. */
25
+ export declare const invoiceLine: (invoice: InvoiceTitleFields) => string;
26
+ export interface PaymentTitleFields {
27
+ direction: MoneyDirection;
28
+ amount: number;
29
+ currency: string;
30
+ /** Who paid the owner, or whom the owner paid. */
31
+ partyName: string | null;
32
+ date: string;
33
+ review: FinanceReview;
34
+ }
35
+ /** "50 000.00 RUB from ООО Ромашка, 14.11 (proposed)". */
36
+ export declare const paymentLine: (payment: PaymentTitleFields) => string;
37
+ /** "Invoice 42 · 50 000.00 RUB to ООО Ромашка, due 15.11 (partly paid)". */
38
+ export declare const invoiceTitle: (invoice: InvoiceTitleFields) => string;
39
+ /** "Payment · 50 000.00 RUB from ООО Ромашка, 14.11 (proposed)". */
40
+ export declare const paymentEntryTitle: (payment: PaymentTitleFields) => string;
@@ -0,0 +1,39 @@
1
+ import { moneyLabel } from "./money.js";
2
+ export const INVOICE_DIRECTIONS = ["receivable", "payable"];
3
+ export const INVOICE_STATUSES = ["draft", "issued", "partly_paid", "paid", "void"];
4
+ /** `in` is money to the owner, `out` money from the owner. */
5
+ export const MONEY_DIRECTIONS = ["in", "out"];
6
+ /** The owner's entry is confirmed at once; an agent's or an import's waits as proposed. */
7
+ export const FINANCE_REVIEWS = ["proposed", "confirmed", "rejected"];
8
+ /** An invoice's status from what confirmed payments settle; `draft` and `void` are the owner's and stay. */
9
+ export const settledStatus = (current, total, paid) => {
10
+ if (current === "draft" || current === "void")
11
+ return current;
12
+ if (paid <= 0)
13
+ return "issued";
14
+ return paid < total ? "partly_paid" : "paid";
15
+ };
16
+ const dayMonth = (date) => (date === null ? null : `${date.slice(8, 10)}.${date.slice(5, 7)}`);
17
+ const notes = (...words) => {
18
+ const kept = words.filter((word) => word !== null);
19
+ return kept.length === 0 ? "" : ` (${kept.join(", ")})`;
20
+ };
21
+ /** "42 · 50 000.00 RUB to ООО Ромашка, due 15.11 (partly paid)": a ref summary's title, which `refLabel` prefixes. */
22
+ export const invoiceLine = (invoice) => {
23
+ const party = invoice.counterpartyName === null
24
+ ? ""
25
+ : `${invoice.direction === "receivable" ? " to" : " from"} ${invoice.counterpartyName}`;
26
+ const due = invoice.dueDate === null ? "" : `, due ${dayMonth(invoice.dueDate)}`;
27
+ return (`${invoice.number ?? `#${invoice.id}`} · ${moneyLabel(invoice.total, invoice.currency)}${party}${due}` +
28
+ notes(invoice.status.replace("_", " "), invoice.review === "confirmed" ? null : invoice.review));
29
+ };
30
+ /** "50 000.00 RUB from ООО Ромашка, 14.11 (proposed)". */
31
+ export const paymentLine = (payment) => {
32
+ const party = payment.partyName === null ? "" : `${payment.direction === "in" ? " from" : " to"} ${payment.partyName}`;
33
+ return (`${moneyLabel(payment.amount, payment.currency)}${party}, ${dayMonth(payment.date)}` +
34
+ notes(payment.review === "confirmed" ? null : payment.review));
35
+ };
36
+ /** "Invoice 42 · 50 000.00 RUB to ООО Ромашка, due 15.11 (partly paid)". */
37
+ export const invoiceTitle = (invoice) => `Invoice ${invoiceLine(invoice)}`;
38
+ /** "Payment · 50 000.00 RUB from ООО Ромашка, 14.11 (proposed)". */
39
+ export const paymentEntryTitle = (payment) => `Payment · ${paymentLine(payment)}`;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Minor-unit digits per ISO 4217 for the currencies the store keeps money in. A currency outside the list is refused
3
+ * rather than guessed: a wrong scale would store every amount 100 times off.
4
+ */
5
+ export declare const CURRENCY_SCALES: Readonly<Record<string, number>>;
6
+ /** An exact amount as callers read and write it: a decimal string with the currency's scale, never a float. */
7
+ export interface Money {
8
+ amount: string;
9
+ currency: string;
10
+ }
11
+ /** A non-negative decimal as an integer of `scale` decimal places; `undefined` when it is not one. */
12
+ export declare const parseDecimal: (text: string, scale: number) => bigint | undefined;
13
+ export declare const formatDecimal: (units: bigint | number, scale: number) => string;
14
+ /** `50000.00` as `50 000.00`: groups of three in the whole part, as a list prints an amount. */
15
+ export declare const groupDigits: (decimal: string) => string;
16
+ export declare const currencyOf: (text: unknown) => string;
17
+ export declare const scaleOf: (currency: string) => number;
18
+ /** Minor units of `currency` for a decimal string such as "50000.00"; refuses more decimals than the currency has. */
19
+ export declare const minorUnits: (amount: unknown, currency: string, what?: string) => number;
20
+ export declare const moneyOf: (units: number, currency: string) => Money;
21
+ /** "50 000.00 RUB". */
22
+ export declare const moneyLabel: (units: number, currency: string) => string;
23
+ /** A quantity such as "1.5" (hours, days), kept as text: at most 3 decimals, more than 0. */
24
+ export declare const quantityOf: (text: unknown) => {
25
+ text: string;
26
+ thousandths: bigint;
27
+ };
28
+ /** Quantity times unit price in minor units, half rounded up. */
29
+ export declare const lineUnits: (thousandths: bigint, unitPrice: number) => number;
@@ -0,0 +1,98 @@
1
+ import { CliError } from "@wirecat/cli-base";
2
+ /**
3
+ * Minor-unit digits per ISO 4217 for the currencies the store keeps money in. A currency outside the list is refused
4
+ * rather than guessed: a wrong scale would store every amount 100 times off.
5
+ */
6
+ export const CURRENCY_SCALES = {
7
+ AED: 2,
8
+ AMD: 2,
9
+ AUD: 2,
10
+ BHD: 3,
11
+ BYN: 2,
12
+ CAD: 2,
13
+ CHF: 2,
14
+ CLP: 0,
15
+ CNY: 2,
16
+ CZK: 2,
17
+ DKK: 2,
18
+ EUR: 2,
19
+ GBP: 2,
20
+ GEL: 2,
21
+ HUF: 2,
22
+ ILS: 2,
23
+ INR: 2,
24
+ ISK: 0,
25
+ JPY: 0,
26
+ KGS: 2,
27
+ KRW: 0,
28
+ KWD: 3,
29
+ KZT: 2,
30
+ NOK: 2,
31
+ PLN: 2,
32
+ RSD: 2,
33
+ RUB: 2,
34
+ SEK: 2,
35
+ THB: 2,
36
+ TRY: 2,
37
+ UAH: 2,
38
+ USD: 2,
39
+ UZS: 2,
40
+ VND: 0,
41
+ };
42
+ const refuse = (message, reason) => {
43
+ throw new CliError("validation_error", message, { reason });
44
+ };
45
+ const DECIMAL = /^(\d{1,15})(?:\.(\d+))?$/;
46
+ /** A non-negative decimal as an integer of `scale` decimal places; `undefined` when it is not one. */
47
+ export const parseDecimal = (text, scale) => {
48
+ const match = DECIMAL.exec(text.trim());
49
+ if (!match || (match[2] ?? "").length > scale)
50
+ return undefined;
51
+ return BigInt(`${match[1]}${(match[2] ?? "").padEnd(scale, "0")}`);
52
+ };
53
+ export const formatDecimal = (units, scale) => {
54
+ const digits = BigInt(units)
55
+ .toString()
56
+ .padStart(scale + 1, "0");
57
+ return scale === 0 ? digits : `${digits.slice(0, -scale)}.${digits.slice(-scale)}`;
58
+ };
59
+ /** `50000.00` as `50 000.00`: groups of three in the whole part, as a list prints an amount. */
60
+ export const groupDigits = (decimal) => {
61
+ const [whole = "0", fraction] = decimal.split(".");
62
+ const grouped = whole.replace(/\B(?=(\d{3})+(?!\d))/g, " ");
63
+ return fraction === undefined ? grouped : `${grouped}.${fraction}`;
64
+ };
65
+ export const currencyOf = (text) => {
66
+ const code = typeof text === "string" ? text.trim().toUpperCase() : "";
67
+ if (CURRENCY_SCALES[code] === undefined)
68
+ return refuse(`${typeof text === "string" && text.trim() ? text.trim() : "an empty currency"} is not a currency the store keeps; ` +
69
+ `use one of ${Object.keys(CURRENCY_SCALES).join(", ")}`, "money.currency");
70
+ return code;
71
+ };
72
+ export const scaleOf = (currency) => CURRENCY_SCALES[currencyOf(currency)];
73
+ /** Minor units of `currency` for a decimal string such as "50000.00"; refuses more decimals than the currency has. */
74
+ export const minorUnits = (amount, currency, what = "an amount") => {
75
+ const scale = scaleOf(currency);
76
+ const units = typeof amount === "string" ? parseDecimal(amount, scale) : undefined;
77
+ if (units === undefined || units > BigInt(Number.MAX_SAFE_INTEGER))
78
+ return refuse(`${what} is a decimal string with at most ${scale} decimal${scale === 1 ? "" : "s"} for ${currency}, ` +
79
+ `such as "${formatDecimal(5_000 * 10 ** scale, scale)}"`, "money.amount");
80
+ return Number(units);
81
+ };
82
+ export const moneyOf = (units, currency) => ({
83
+ amount: formatDecimal(units, scaleOf(currency)),
84
+ currency,
85
+ });
86
+ /** "50 000.00 RUB". */
87
+ export const moneyLabel = (units, currency) => `${groupDigits(formatDecimal(units, scaleOf(currency)))} ${currency}`;
88
+ const QUANTITY_SCALE = 3;
89
+ /** A quantity such as "1.5" (hours, days), kept as text: at most 3 decimals, more than 0. */
90
+ export const quantityOf = (text) => {
91
+ const thousandths = typeof text === "string" ? parseDecimal(text, QUANTITY_SCALE) : undefined;
92
+ if (thousandths === undefined || thousandths === 0n)
93
+ return refuse('a quantity is a decimal string above 0 with at most 3 decimals, such as "1.5"', "money.quantity");
94
+ const plain = formatDecimal(thousandths, QUANTITY_SCALE).replace(/\.?0+$/, "");
95
+ return { text: plain, thousandths };
96
+ };
97
+ /** Quantity times unit price in minor units, half rounded up. */
98
+ export const lineUnits = (thousandths, unitPrice) => Number((thousandths * BigInt(unitPrice) + 500n) / 1000n);
@@ -49,6 +49,8 @@ const WORDS = {
49
49
  memory: "Memory",
50
50
  fact: "Fact",
51
51
  bot: "Bot",
52
+ invoice: "Invoice",
53
+ payment: "Payment",
52
54
  email: "Email",
53
55
  email_thread: "Email thread",
54
56
  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", "entity"];
36
+ export declare const SIMPLE_REFERENCES: readonly ["note", "document", "person", "organization", "project", "task", "memory", "fact", "bot", "event", "invoice", "payment", "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> {
@@ -16,6 +16,8 @@ export const SIMPLE_REFERENCES = [
16
16
  "fact",
17
17
  "bot",
18
18
  "event",
19
+ "invoice",
20
+ "payment",
19
21
  "entity",
20
22
  ];
21
23
  const malformed = (text, shape) => new CliError("validation_error", `"${singleLine(text)}" is not a reference — expected ${shape}`, {
@@ -2,7 +2,7 @@ import * as v from "valibot";
2
2
  export declare const fact: v.StrictObjectSchema<{
3
3
  readonly ref: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
4
4
  readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
5
- readonly kind: v.PicklistSchema<readonly ["fact", "statement", "question", "decision", "promise", "payment"], undefined>;
5
+ readonly kind: v.PicklistSchema<readonly ["fact", "statement", "question", "decision", "promise"], undefined>;
6
6
  readonly text: v.StringSchema<undefined>;
7
7
  readonly status: v.PicklistSchema<readonly ["proposed", "accepted", "superseded", "reversed", "rejected"], undefined>;
8
8
  readonly project: v.NullableSchema<v.StringSchema<undefined>, undefined>;
@@ -29,17 +29,6 @@ export declare const fact: v.StrictObjectSchema<{
29
29
  /** Who recorded it: the owner, or an agent whose fact waits as proposed. */
30
30
  readonly origin: v.PicklistSchema<["owner", "agent"], undefined>;
31
31
  readonly evidence: v.ArraySchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
32
- /** A `payment` fact's fields. */
33
- readonly payment: v.OptionalSchema<v.NullableSchema<v.StrictObjectSchema<{
34
- readonly amount: v.StringSchema<undefined>;
35
- readonly currency: v.StringSchema<undefined>;
36
- readonly direction: v.PicklistSchema<readonly ["to_us", "from_us"], undefined>;
37
- readonly counterparty: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
38
- readonly counterparty_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
39
- readonly due_at: v.NullableSchema<v.StringSchema<undefined>, undefined>;
40
- readonly state: v.PicklistSchema<readonly ["expected", "paid", "cancelled"], undefined>;
41
- readonly item: v.NullableSchema<v.StringSchema<undefined>, undefined>;
42
- }, undefined>, undefined>, undefined>;
43
32
  /** With `--sources`: each piece of evidence read back. */
44
33
  readonly sources: v.OptionalSchema<v.ArraySchema<v.StrictObjectSchema<{
45
34
  readonly ref: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
@@ -1,17 +1,6 @@
1
1
  import * as v from "valibot";
2
2
  import { FACT_STATUSES, FACT_TYPES, MEMORY_SCOPES } from "../domain/knowledge.js";
3
- import { PAYMENT_DIRECTIONS, PAYMENT_STATES } from "../meetings/index.js";
4
3
  import { author, evidenceSource, id, reference, text, time } from "./fields.js";
5
- const payment = v.strictObject({
6
- amount: text,
7
- currency: text,
8
- direction: v.picklist(PAYMENT_DIRECTIONS),
9
- counterparty: v.nullable(reference),
10
- counterparty_name: v.nullable(text),
11
- due_at: v.nullable(text),
12
- state: v.picklist(PAYMENT_STATES),
13
- item: v.nullable(text),
14
- });
15
4
  export const fact = v.strictObject({
16
5
  ref: reference,
17
6
  id,
@@ -35,8 +24,6 @@ export const fact = v.strictObject({
35
24
  /** Who recorded it: the owner, or an agent whose fact waits as proposed. */
36
25
  origin: v.picklist(["owner", "agent"]),
37
26
  evidence: v.array(reference),
38
- /** A `payment` fact's fields. */
39
- payment: v.optional(v.nullable(payment)),
40
27
  /** With `--sources`: each piece of evidence read back. */
41
28
  sources: v.optional(v.array(evidenceSource)),
42
29
  created_at: time,
@@ -5761,7 +5761,7 @@ export declare const ENTITIES: {
5761
5761
  readonly fact: v.StrictObjectSchema<{
5762
5762
  readonly ref: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
5763
5763
  readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
5764
- readonly kind: v.PicklistSchema<readonly ["fact", "statement", "question", "decision", "promise", "payment"], undefined>;
5764
+ readonly kind: v.PicklistSchema<readonly ["fact", "statement", "question", "decision", "promise"], undefined>;
5765
5765
  readonly text: v.StringSchema<undefined>;
5766
5766
  readonly status: v.PicklistSchema<readonly ["proposed", "accepted", "superseded", "reversed", "rejected"], undefined>;
5767
5767
  readonly project: v.NullableSchema<v.StringSchema<undefined>, undefined>;
@@ -5784,16 +5784,6 @@ export declare const ENTITIES: {
5784
5784
  readonly scope: v.NullableSchema<v.PicklistSchema<readonly ["personal", "work"], undefined>, undefined>;
5785
5785
  readonly origin: v.PicklistSchema<["owner", "agent"], undefined>;
5786
5786
  readonly evidence: v.ArraySchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
5787
- readonly payment: v.OptionalSchema<v.NullableSchema<v.StrictObjectSchema<{
5788
- readonly amount: v.StringSchema<undefined>;
5789
- readonly currency: v.StringSchema<undefined>;
5790
- readonly direction: v.PicklistSchema<readonly ["to_us", "from_us"], undefined>;
5791
- readonly counterparty: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
5792
- readonly counterparty_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5793
- readonly due_at: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5794
- readonly state: v.PicklistSchema<readonly ["expected", "paid", "cancelled"], undefined>;
5795
- readonly item: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5796
- }, undefined>, undefined>, undefined>;
5797
5787
  readonly sources: v.OptionalSchema<v.ArraySchema<v.StrictObjectSchema<{
5798
5788
  readonly ref: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
5799
5789
  readonly kind: v.StringSchema<undefined>;
@@ -6589,7 +6579,7 @@ export declare const ENTITIES: {
6589
6579
  readonly fingerprint: v.StringSchema<undefined>;
6590
6580
  }, undefined>, undefined>;
6591
6581
  readonly kind: v.LiteralSchema<"fact", undefined>;
6592
- readonly type: v.PicklistSchema<readonly ["fact", "statement", "question", "decision", "promise", "payment"], undefined>;
6582
+ readonly type: v.PicklistSchema<readonly ["fact", "statement", "question", "decision", "promise"], undefined>;
6593
6583
  readonly statement: v.StringSchema<undefined>;
6594
6584
  }, undefined>, v.StrictObjectSchema<{
6595
6585
  readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
@@ -17,7 +17,7 @@ export declare const meetingFindings: v.StrictObjectSchema<{
17
17
  readonly fingerprint: v.StringSchema<undefined>;
18
18
  }, undefined>, undefined>;
19
19
  readonly kind: v.LiteralSchema<"fact", undefined>;
20
- readonly type: v.PicklistSchema<readonly ["fact", "statement", "question", "decision", "promise", "payment"], undefined>;
20
+ readonly type: v.PicklistSchema<readonly ["fact", "statement", "question", "decision", "promise"], undefined>;
21
21
  readonly statement: v.StringSchema<undefined>;
22
22
  }, undefined>, v.StrictObjectSchema<{
23
23
  readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
@@ -138,8 +138,24 @@ const OTHERS = [
138
138
  validation("input.required", "the command needs an answer and cannot ask for one"),
139
139
  validation("input.too-large", "the input is over its size limit"),
140
140
  ["meeting-project.stale", "configuration_error", "the meeting's project changed meanwhile"],
141
+ validation("fact.payment-retired", "a payment is a payments row in store.finance, not a fact"),
142
+ validation("finance.allocate", "the invoice or payment cannot take an allocation in its state"),
143
+ validation("finance.currency", "the payment and the invoice are in different currencies; nothing is converted"),
144
+ validation("finance.field", "a field of the invoice or payment is not valid"),
145
+ validation("finance.line-amount", "the line's amount is not its quantity times its unit price"),
146
+ validation("finance.lines", "the invoice's lines are not valid"),
147
+ validation("finance.over-invoice", "the allocation would settle more than the invoice's total"),
148
+ validation("finance.over-payment", "the allocation would use more than the payment's amount"),
149
+ ["finance.owner-only", "permission_error", "only the owner does this with money"],
150
+ validation("finance.party", "a party is a person: or organization: reference"),
151
+ validation("finance.ref", "the reference names no invoice or payment"),
152
+ validation("finance.status", "the invoice's status does not allow this"),
153
+ validation("finance.total", "the invoice's total is not the sum of its lines"),
141
154
  validation("memory.evidence-required", "a memory needs its evidence"),
142
155
  validation("memory.scope-required", "a memory needs its scope"),
156
+ validation("money.amount", "the amount is not a decimal string in the currency's scale"),
157
+ validation("money.currency", "the currency is not one the store keeps money in"),
158
+ validation("money.quantity", "the quantity is not a decimal above 0"),
143
159
  ["model.not-downloaded", "not_found", "the model is not downloaded on this machine"],
144
160
  ["person.no-owner", "not_found", "no person is marked as the owner in the store"],
145
161
  ["transcription.no-key", "authentication_error", "no OpenAI key is set for transcription"],
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";
@@ -188,7 +188,6 @@ export const mailReviewCommand = (mail) => annotate(new Command("review"), {
188
188
  `${mail.app.command} personal review "date>2h"`,
189
189
  `${mail.app.command} personal review --new`,
190
190
  `${mail.app.command} personal review --unanswered 1d`,
191
- `${mail.app.command} personal review --waiting 7d`,
192
191
  ],
193
192
  effects: LOCAL_READ,
194
193
  partial: true,