@slothmoney/agent-cli 0.21.0 → 0.22.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.
- package/CHANGELOG.md +19 -0
- package/README.md +45 -3
- package/dist/args.js +47 -0
- package/dist/cli.js +88 -2
- package/dist/contracts.js +122 -39
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.22.0 - 2026-08-29
|
|
4
|
+
|
|
5
|
+
- Add read-only `partner status` output for the current settlement balance and
|
|
6
|
+
paginated recorded partner payments.
|
|
7
|
+
- Add opt-in `transactions --include-pending` output from the latest complete
|
|
8
|
+
observation in the command's normal refresh flow, with explicit availability
|
|
9
|
+
and non-writable pending rows.
|
|
10
|
+
- Strictly validate both new response contracts and cover their nested help in
|
|
11
|
+
packed-binary and release-preflight checks.
|
|
12
|
+
- Accept the existing completed-refresh `checkpointId` while continuing to
|
|
13
|
+
reject it from non-completed refresh states.
|
|
14
|
+
|
|
15
|
+
## 0.21.1 - 2026-08-29
|
|
16
|
+
|
|
17
|
+
- Clarify in portfolio, current budget status, and transaction help that a
|
|
18
|
+
completed eligible refresh updates the configured Budget balance audit.
|
|
19
|
+
- Explain that a same-day cached read reuses fresh bank data without adding
|
|
20
|
+
another audit checkpoint.
|
|
21
|
+
|
|
3
22
|
## 0.21.0 - 2026-08-28
|
|
4
23
|
|
|
5
24
|
- Add `scenarios` commands to list, create, update, activate, and delete the
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Sloth Agent CLI
|
|
2
2
|
|
|
3
|
-
Use your own agent to inspect personal and household accounts, investments,
|
|
3
|
+
Use your own agent to inspect personal and household accounts, investments, budgets, pending card activity, and partner settlement context; manage goals and forecast scenarios; move assigned budget money; update planned amounts; categorise booked transactions; and configure payment notifications through the
|
|
4
4
|
[Sloth Money Agent API](https://slothmoney.app/developers/).
|
|
5
5
|
|
|
6
6
|
## Install
|
|
@@ -15,7 +15,7 @@ sloth-agent --version
|
|
|
15
15
|
For a one-off pinned run:
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
npm exec --yes --package=@slothmoney/agent-cli@0.
|
|
18
|
+
npm exec --yes --package=@slothmoney/agent-cli@0.22.0 -- sloth-agent --help
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
## Authenticate
|
|
@@ -25,7 +25,7 @@ Create a personal access token in Sloth Money under
|
|
|
25
25
|
where the CLI runs.
|
|
26
26
|
|
|
27
27
|
New tokens are view-only. That is enough for `auth status`, `accounts`, `investments`, `portfolio`,
|
|
28
|
-
`budget`, `categories`, `transactions`, `goals`, and `scenarios` list. Enable **Allow changes** when
|
|
28
|
+
`budget`, `categories`, `transactions`, `partner status`, `goals`, and `scenarios` list. Enable **Allow changes** when
|
|
29
29
|
creating the token only if the CLI must apply assignments, manage categories
|
|
30
30
|
or line items, move assigned budget money, update planned budgets, manage accounts, ask a partner for an explanation, or manage goals and scenarios. Token
|
|
31
31
|
permissions cannot be changed later - revoke and reissue the token instead.
|
|
@@ -113,6 +113,8 @@ sloth-agent categories create --help
|
|
|
113
113
|
sloth-agent line-items --help
|
|
114
114
|
sloth-agent line-items create --help
|
|
115
115
|
sloth-agent transactions --help
|
|
116
|
+
sloth-agent partner --help
|
|
117
|
+
sloth-agent partner status --help
|
|
116
118
|
sloth-agent rules --help
|
|
117
119
|
sloth-agent assign --help
|
|
118
120
|
sloth-agent receipts --help
|
|
@@ -449,6 +451,10 @@ nonnegative `moneyInPence` and `moneyOutPence` plus their difference as
|
|
|
449
451
|
custom categories are normal rows. A transaction with no category at all is
|
|
450
452
|
reported separately under `activity.uncategorized`.
|
|
451
453
|
|
|
454
|
+
A completed current-period refresh updates the Budget balance audit for any
|
|
455
|
+
configured backing accounts. A same-day cached read does not add another audit
|
|
456
|
+
checkpoint.
|
|
457
|
+
|
|
452
458
|
`budget` contains assigned, spent, and available category amounts when a
|
|
453
459
|
trustworthy period plan exists. It is `null` when it does not; activity still
|
|
454
460
|
returns. The response includes only the period budget currency and silently
|
|
@@ -648,6 +654,9 @@ planning only. It does not change account ownership, transaction access, Goal
|
|
|
648
654
|
funding, or who can move money. Totals use the viewer's budget currency and
|
|
649
655
|
exclude other native currencies without converting them.
|
|
650
656
|
|
|
657
|
+
A completed balance refresh updates the Budget balance audit for any configured
|
|
658
|
+
backing accounts. A same-day cached read does not add another audit checkpoint.
|
|
659
|
+
|
|
651
660
|
Archive an owned manual account. The account disappears from active Sloth
|
|
652
661
|
surfaces, but its underlying records are retained. Repeating an applied removal
|
|
653
662
|
is safe and returns `changed: false`.
|
|
@@ -803,6 +812,23 @@ Read uncategorised contributions to the joint budget:
|
|
|
803
812
|
sloth-agent transactions --assignment-scope joint --uncategorized
|
|
804
813
|
```
|
|
805
814
|
|
|
815
|
+
Include the current pending snapshot while reviewing transactions:
|
|
816
|
+
|
|
817
|
+
```bash
|
|
818
|
+
sloth-agent transactions --include-pending
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
This option reuses the transaction command's normal linked-bank refresh. It
|
|
822
|
+
does not force a second refresh. Sloth Money keeps the latest complete pending
|
|
823
|
+
observation until the next fully successful refresh, including an empty result.
|
|
824
|
+
Booked rows remain in `transactions`; pending
|
|
825
|
+
rows appear in `pending.transactions` with `writable: false` and
|
|
826
|
+
`writeBlockReason: "pending"`, so they cannot be passed to `assign`. A current
|
|
827
|
+
empty list means the latest complete observation had no matching pending rows.
|
|
828
|
+
`unavailable` means no valid complete snapshot is available and must not be interpreted as
|
|
829
|
+
proof that there are no pending payments. Date, text, and account filters apply
|
|
830
|
+
to pending rows; categorisation and pagination filters remain booked-only.
|
|
831
|
+
|
|
806
832
|
The first transaction read after the UTC day changes may refresh linked bank
|
|
807
833
|
data. The CLI waits up to 45 seconds for that refresh to persist, then returns
|
|
808
834
|
the requested booked transactions. If the refresh is still running, partially
|
|
@@ -819,9 +845,25 @@ a structured `refresh` object:
|
|
|
819
845
|
}
|
|
820
846
|
```
|
|
821
847
|
|
|
848
|
+
A completed refresh also updates the Budget balance audit for any configured
|
|
849
|
+
backing accounts. A same-day cached read reuses the existing data and does not
|
|
850
|
+
add another audit checkpoint.
|
|
851
|
+
|
|
822
852
|
Re-run the transaction query later to observe the completed refresh. A partial
|
|
823
853
|
account failure remains eligible for an automatic retry.
|
|
824
854
|
|
|
855
|
+
Read partner settlement context when an incoming payment may be a recorded
|
|
856
|
+
partner payment:
|
|
857
|
+
|
|
858
|
+
```bash
|
|
859
|
+
sloth-agent partner status
|
|
860
|
+
```
|
|
861
|
+
|
|
862
|
+
The read-only response reports whether a mutual partner is connected, the
|
|
863
|
+
current settlement direction and amount in pence, and recent sent or received
|
|
864
|
+
payments. It uses opaque payment references and paginates with `nextCursor`.
|
|
865
|
+
The command does not refresh bank accounts or change partner records.
|
|
866
|
+
|
|
825
867
|
Set `"assignmentScope": "joint"` on an assignment to categorise the eligible
|
|
826
868
|
shared portion for the joint budget.
|
|
827
869
|
|
package/dist/args.js
CHANGED
|
@@ -241,6 +241,10 @@ function parseTransactions(args) {
|
|
|
241
241
|
const filters = {};
|
|
242
242
|
for (let index = 0; index < args.length; index += 1) {
|
|
243
243
|
const argument = args[index];
|
|
244
|
+
if (argument === '--include-pending') {
|
|
245
|
+
filters.includePending = setOnce(filters.includePending, true, '--include-pending');
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
244
248
|
if (argument === '--uncategorized') {
|
|
245
249
|
filters.uncategorized = setOnce(filters.uncategorized, true, '--uncategorized');
|
|
246
250
|
continue;
|
|
@@ -332,6 +336,41 @@ function parseTransactions(args) {
|
|
|
332
336
|
}
|
|
333
337
|
return filters;
|
|
334
338
|
}
|
|
339
|
+
function parsePartner(args, baseUrl) {
|
|
340
|
+
const subcommand = args.shift();
|
|
341
|
+
if (subcommand !== 'status') {
|
|
342
|
+
throw new UsageError('partner requires the status subcommand');
|
|
343
|
+
}
|
|
344
|
+
let limit;
|
|
345
|
+
let cursor;
|
|
346
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
347
|
+
const argument = args[index];
|
|
348
|
+
const [name, inlineValue] = argument.includes('=')
|
|
349
|
+
? argument.split(/=(.*)/s, 2)
|
|
350
|
+
: [argument, undefined];
|
|
351
|
+
if (name !== '--limit' && name !== '--cursor') {
|
|
352
|
+
throw new UsageError(`Unknown partner status option: ${argument}`);
|
|
353
|
+
}
|
|
354
|
+
const value = requireNonEmpty(inlineValue ?? readOptionValue(args, index, name), name);
|
|
355
|
+
if (inlineValue === undefined)
|
|
356
|
+
index += 1;
|
|
357
|
+
if (name === '--limit') {
|
|
358
|
+
const parsedLimit = Number(value);
|
|
359
|
+
if (!Number.isInteger(parsedLimit) || parsedLimit < 1 || parsedLimit > 200) {
|
|
360
|
+
throw new UsageError('--limit must be an integer between 1 and 200');
|
|
361
|
+
}
|
|
362
|
+
limit = setOnce(limit, parsedLimit, '--limit');
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
cursor = setOnce(cursor, value, '--cursor');
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return withBaseUrl({
|
|
369
|
+
command: 'partner-status',
|
|
370
|
+
...(limit === undefined ? {} : { limit }),
|
|
371
|
+
...(cursor === undefined ? {} : { cursor }),
|
|
372
|
+
}, baseUrl);
|
|
373
|
+
}
|
|
335
374
|
function parseNamedOptions(args, commandLabel, allowed) {
|
|
336
375
|
const values = new Map();
|
|
337
376
|
let apply = false;
|
|
@@ -1233,6 +1272,11 @@ function helpTopic(argv) {
|
|
|
1233
1272
|
return 'receipts-remove';
|
|
1234
1273
|
return 'receipts';
|
|
1235
1274
|
}
|
|
1275
|
+
if (command === 'partner') {
|
|
1276
|
+
if (subcommand === 'status')
|
|
1277
|
+
return 'partner-status';
|
|
1278
|
+
return 'partner';
|
|
1279
|
+
}
|
|
1236
1280
|
if (command === 'accounts'
|
|
1237
1281
|
|| command === 'transactions'
|
|
1238
1282
|
|| command === 'assign'
|
|
@@ -1294,6 +1338,9 @@ export function parseArgs(argv) {
|
|
|
1294
1338
|
if (command === 'transactions') {
|
|
1295
1339
|
return withBaseUrl({ command, filters: parseTransactions(args) }, baseUrl);
|
|
1296
1340
|
}
|
|
1341
|
+
if (command === 'partner') {
|
|
1342
|
+
return parsePartner(args, baseUrl);
|
|
1343
|
+
}
|
|
1297
1344
|
if (command === 'assign') {
|
|
1298
1345
|
let input;
|
|
1299
1346
|
let apply = false;
|
package/dist/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import { ICON_KEYS } from './category-metadata.js';
|
|
|
6
6
|
import { parseApiResponse, parseAssignmentOperationResponse, toLegacyAssignmentResponse, validateAssignmentPayload, validateBudgetMovementResponse, validateBudgetUpdatePayload, validateNotificationRulePayload, validateReceiptConfirmation, } from './contracts.js';
|
|
7
7
|
import { createSystemCredentialStore, secureStorageUnavailableError, } from './credential-store.js';
|
|
8
8
|
import { ApiError, CliError, ConfigError, UsageError, } from './errors.js';
|
|
9
|
-
export const CLI_VERSION = '0.
|
|
9
|
+
export const CLI_VERSION = '0.22.0';
|
|
10
10
|
const REQUEST_TIMEOUT_MS = 60_000;
|
|
11
11
|
const MAX_CONTRACT_PDF_BYTES = 6_000_000;
|
|
12
12
|
const API_ORIGIN_HELP_LINES = [
|
|
@@ -49,7 +49,8 @@ export function usageText() {
|
|
|
49
49
|
' sloth-agent transactions [--uncategorized[=true|false]] [--shared[=true|false]] [--limit N]',
|
|
50
50
|
' [--start-date YYYY-MM-DD] [--end-date YYYY-MM-DD] [--q TEXT]',
|
|
51
51
|
' [--account-ref REF] [--category-id ID] [--line-item-id ID]',
|
|
52
|
-
' [--cursor CURSOR] [--base-url URL]',
|
|
52
|
+
' [--cursor CURSOR] [--include-pending] [--base-url URL]',
|
|
53
|
+
' sloth-agent partner status [--limit N] [--cursor CURSOR] [--base-url URL]',
|
|
53
54
|
' sloth-agent assign --input assignments.json [--apply] [--base-url URL]',
|
|
54
55
|
' sloth-agent rules [list] [--base-url URL]',
|
|
55
56
|
' sloth-agent rules get --transaction-ref REF [--base-url URL]',
|
|
@@ -469,6 +470,8 @@ export function portfolioHelpText() {
|
|
|
469
470
|
'',
|
|
470
471
|
'Access:',
|
|
471
472
|
' This read-only command waits up to 45 seconds for eligible linked balances to refresh.',
|
|
473
|
+
' A completed refresh updates the Budget balance audit for configured backing accounts.',
|
|
474
|
+
' A same-day cached read does not add another audit checkpoint.',
|
|
472
475
|
' Partner shows only balances or holdings your partner explicitly shared.',
|
|
473
476
|
' Household combines your accounts with those shared balances and deduplicates joint accounts.',
|
|
474
477
|
' Shared data supports planning only. It does not assign partner accounts to Goals or change ownership.',
|
|
@@ -533,6 +536,8 @@ export function budgetStatusHelpText() {
|
|
|
533
536
|
'Access and freshness:',
|
|
534
537
|
' This command is read-only, requires agent:read, and never changes the budget.',
|
|
535
538
|
' Current periods use the normal transaction refresh policy; historical periods are cache-only.',
|
|
539
|
+
' A completed refresh updates the Budget balance audit for configured backing accounts.',
|
|
540
|
+
' A same-day cached read does not add another audit checkpoint.',
|
|
536
541
|
' A historical response returns refresh as null.',
|
|
537
542
|
'',
|
|
538
543
|
'Output:',
|
|
@@ -645,6 +650,8 @@ export function transactionsHelpText() {
|
|
|
645
650
|
' --assignment-scope SCOPE Optional. Filter assignments by personal or joint.',
|
|
646
651
|
' The transaction\'s native scope is used when omitted.',
|
|
647
652
|
' --cursor CURSOR Optional. Continue from a previous nextCursor.',
|
|
653
|
+
' --include-pending Optional. Include the latest complete pending observation',
|
|
654
|
+
' from the normal linked-bank refresh flow.',
|
|
648
655
|
' --base-url URL Optional. Override the API origin.',
|
|
649
656
|
' -h, --help Show this help.',
|
|
650
657
|
...API_ORIGIN_HELP_LINES,
|
|
@@ -654,10 +661,20 @@ export function transactionsHelpText() {
|
|
|
654
661
|
' --end-date must not be before --start-date.',
|
|
655
662
|
' The first transaction read each UTC day may refresh linked bank data.',
|
|
656
663
|
' Refresh remotely persists booked transactions and account balances.',
|
|
664
|
+
' A completed refresh updates the Budget balance audit for configured backing accounts.',
|
|
665
|
+
' A same-day cached read does not add another audit checkpoint.',
|
|
657
666
|
' The command waits up to 45 seconds, then returns cached data if refresh continues.',
|
|
667
|
+
' --include-pending does not force an extra refresh or make pending rows writable.',
|
|
668
|
+
' Date, text, and account filters apply to pending rows. Assignment, sharing, category,',
|
|
669
|
+
' line-item, limit, and cursor filters apply only to booked transactions.',
|
|
658
670
|
'',
|
|
659
671
|
'Output:',
|
|
660
672
|
' JSON containing transactions, nextCursor, and structured refresh status.',
|
|
673
|
+
' With --include-pending, pending reports availability current or unavailable.',
|
|
674
|
+
' A current empty list means the latest complete observation had no matching pending rows.',
|
|
675
|
+
' Unavailable means no pending snapshot was returned; it does not mean there are none.',
|
|
676
|
+
' Pending rows have opaque pendingRef and accountRef values plus writable: false and',
|
|
677
|
+
' writeBlockReason: "pending". They cannot be passed to assign or other write commands.',
|
|
661
678
|
' Every transaction includes accountRef for its originating account.',
|
|
662
679
|
' Refresh failures do not hide readable cached transactions.',
|
|
663
680
|
' Personal assignments use the top-level categoryId, lineItemId, and categorySplits.',
|
|
@@ -670,9 +687,57 @@ export function transactionsHelpText() {
|
|
|
670
687
|
'Examples:',
|
|
671
688
|
' sloth-agent transactions --uncategorized --limit 50',
|
|
672
689
|
' sloth-agent transactions --assignment-scope joint --uncategorized',
|
|
690
|
+
' sloth-agent transactions --include-pending --start-date 2026-08-27',
|
|
673
691
|
' sloth-agent transactions --q "tesco" --start-date 2026-05-01 --end-date 2026-05-31',
|
|
674
692
|
].join('\n');
|
|
675
693
|
}
|
|
694
|
+
export function partnerHelpText() {
|
|
695
|
+
return [
|
|
696
|
+
'Sloth Agent CLI — partner',
|
|
697
|
+
'',
|
|
698
|
+
'Read partner settlement context and recorded partner payments.',
|
|
699
|
+
'',
|
|
700
|
+
'Commands:',
|
|
701
|
+
' sloth-agent partner status Read the current settlement balance and payment activity',
|
|
702
|
+
'',
|
|
703
|
+
'Help:',
|
|
704
|
+
' Run sloth-agent partner status --help for inputs, output, and examples.',
|
|
705
|
+
...API_ORIGIN_HELP_LINES,
|
|
706
|
+
].join('\n');
|
|
707
|
+
}
|
|
708
|
+
export function partnerStatusHelpText() {
|
|
709
|
+
return [
|
|
710
|
+
'Sloth Agent CLI — partner status',
|
|
711
|
+
'',
|
|
712
|
+
'Read the current partner settlement balance and recorded partner payments.',
|
|
713
|
+
'',
|
|
714
|
+
'Usage:',
|
|
715
|
+
' sloth-agent partner status [--limit N] [--cursor CURSOR] [--base-url URL]',
|
|
716
|
+
'',
|
|
717
|
+
'Options:',
|
|
718
|
+
' --limit N Optional. Return 1 to 200 payment records; defaults to 50.',
|
|
719
|
+
' --cursor CURSOR Optional. Continue payment activity from a previous nextCursor.',
|
|
720
|
+
' --base-url URL Optional. Override the API origin.',
|
|
721
|
+
' -h, --help Show this help.',
|
|
722
|
+
...API_ORIGIN_HELP_LINES,
|
|
723
|
+
'',
|
|
724
|
+
'Read behavior:',
|
|
725
|
+
' Requires agent:read. This command does not refresh bank accounts or change Sloth Money.',
|
|
726
|
+
' It calculates settlement from shared booked transactions and recorded partner payments.',
|
|
727
|
+
'',
|
|
728
|
+
'Output:',
|
|
729
|
+
' JSON containing asOf, partnerStatus, settlement, payments, and nextCursor.',
|
|
730
|
+
' settlement is null when no partner is connected. Otherwise balance.direction is',
|
|
731
|
+
' settled when amountPence is 0. The you_owe and partner_owes_you directions have',
|
|
732
|
+
' a positive amountPence.',
|
|
733
|
+
' Each payment has an opaque paymentRef, sent or received direction, amountPence,',
|
|
734
|
+
' currency, and occurredAt. A null nextCursor means there are no more payments.',
|
|
735
|
+
'',
|
|
736
|
+
'Examples:',
|
|
737
|
+
' sloth-agent partner status',
|
|
738
|
+
' sloth-agent partner status --limit 100',
|
|
739
|
+
].join('\n');
|
|
740
|
+
}
|
|
676
741
|
export function assignHelpText() {
|
|
677
742
|
return [
|
|
678
743
|
'Sloth Agent CLI — assign',
|
|
@@ -1408,6 +1473,8 @@ export function commandHelpText(topic) {
|
|
|
1408
1473
|
'line-items-create': lineItemsCreateHelpText,
|
|
1409
1474
|
'line-items-rename': lineItemsRenameHelpText,
|
|
1410
1475
|
transactions: transactionsHelpText,
|
|
1476
|
+
partner: partnerHelpText,
|
|
1477
|
+
'partner-status': partnerStatusHelpText,
|
|
1411
1478
|
assign: assignHelpText,
|
|
1412
1479
|
rules: rulesHelpText,
|
|
1413
1480
|
'rules-get': rulesGetHelpText,
|
|
@@ -1577,6 +1644,9 @@ function buildTransactionsQuery(filters) {
|
|
|
1577
1644
|
if (filters.uncategorized !== undefined) {
|
|
1578
1645
|
params.set('uncategorized', String(filters.uncategorized));
|
|
1579
1646
|
}
|
|
1647
|
+
if (filters.includePending !== undefined) {
|
|
1648
|
+
params.set('includePending', String(filters.includePending));
|
|
1649
|
+
}
|
|
1580
1650
|
if (filters.shared !== undefined)
|
|
1581
1651
|
params.set('shared', String(filters.shared));
|
|
1582
1652
|
if (filters.limit !== undefined)
|
|
@@ -2387,6 +2457,22 @@ export async function runCli(argv = process.argv.slice(2), options = {}) {
|
|
|
2387
2457
|
writeJson(writeStdout, data);
|
|
2388
2458
|
return 0;
|
|
2389
2459
|
}
|
|
2460
|
+
if (parsed.command === 'partner-status') {
|
|
2461
|
+
const query = new URLSearchParams();
|
|
2462
|
+
if (parsed.limit !== undefined)
|
|
2463
|
+
query.set('limit', String(parsed.limit));
|
|
2464
|
+
if (parsed.cursor !== undefined)
|
|
2465
|
+
query.set('cursor', parsed.cursor);
|
|
2466
|
+
const suffix = query.size > 0 ? `?${query.toString()}` : '';
|
|
2467
|
+
const response = await fetchImplementation(`${baseUrl}/api/agent/v1/partner-status${suffix}`, {
|
|
2468
|
+
method: 'GET',
|
|
2469
|
+
headers,
|
|
2470
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
2471
|
+
});
|
|
2472
|
+
const data = parseApiResponse('partner-status', await parseHttpResponse(response, token));
|
|
2473
|
+
writeJson(writeStdout, data);
|
|
2474
|
+
return 0;
|
|
2475
|
+
}
|
|
2390
2476
|
const path = parsed.command === 'accounts'
|
|
2391
2477
|
? '/api/agent/v1/accounts'
|
|
2392
2478
|
: parsed.command === 'portfolio'
|
package/dist/contracts.js
CHANGED
|
@@ -436,20 +436,101 @@ const REFRESH_REASONS = new Set([
|
|
|
436
436
|
'refresh_error',
|
|
437
437
|
]);
|
|
438
438
|
function isRefreshStatus(value) {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
439
|
+
if (!isObject(value)
|
|
440
|
+
|| typeof value.status !== 'string'
|
|
441
|
+
|| !REFRESH_STATUSES.has(value.status)
|
|
442
|
+
|| typeof value.reason !== 'string'
|
|
443
|
+
|| !REFRESH_REASONS.has(value.reason)
|
|
444
|
+
|| !isIsoDate(value.utcDate))
|
|
445
|
+
return false;
|
|
446
|
+
return value.status === 'completed'
|
|
447
|
+
? hasOnlyFields(value, ['status', 'reason', 'utcDate', 'checkpointId'])
|
|
448
|
+
&& typeof value.checkpointId === 'string'
|
|
449
|
+
&& value.checkpointId.length > 0
|
|
450
|
+
: hasOnlyFields(value, ['status', 'reason', 'utcDate']);
|
|
446
451
|
}
|
|
447
452
|
function isTransactionsResponse(value) {
|
|
453
|
+
const isPendingTransaction = (transaction) => (isObject(transaction)
|
|
454
|
+
&& hasOnlyFields(transaction, [
|
|
455
|
+
'pendingRef', 'name', 'amount', 'currency', 'date', 'status',
|
|
456
|
+
'accountRef', 'scope', 'writable', 'writeBlockReason',
|
|
457
|
+
])
|
|
458
|
+
&& typeof transaction.pendingRef === 'string'
|
|
459
|
+
&& /^sloth_pending_v1_[A-Za-z0-9_-]{43}$/.test(transaction.pendingRef)
|
|
460
|
+
&& typeof transaction.name === 'string'
|
|
461
|
+
&& typeof transaction.amount === 'number'
|
|
462
|
+
&& Number.isFinite(transaction.amount)
|
|
463
|
+
&& isCurrency(transaction.currency)
|
|
464
|
+
&& isIsoDate(transaction.date)
|
|
465
|
+
&& transaction.status === 'pending'
|
|
466
|
+
&& isAccountRef(transaction.accountRef)
|
|
467
|
+
&& (transaction.scope === 'personal' || transaction.scope === 'joint')
|
|
468
|
+
&& transaction.writable === false
|
|
469
|
+
&& transaction.writeBlockReason === 'pending');
|
|
470
|
+
const isPendingSnapshot = (snapshot) => {
|
|
471
|
+
if (!isObject(snapshot))
|
|
472
|
+
return false;
|
|
473
|
+
if (snapshot.availability === 'current') {
|
|
474
|
+
return hasOnlyFields(snapshot, ['availability', 'observedAt', 'transactions', 'truncated'])
|
|
475
|
+
&& isIsoDateTime(snapshot.observedAt)
|
|
476
|
+
&& Array.isArray(snapshot.transactions)
|
|
477
|
+
&& snapshot.transactions.length <= 200
|
|
478
|
+
&& snapshot.transactions.every(isPendingTransaction)
|
|
479
|
+
&& typeof snapshot.truncated === 'boolean';
|
|
480
|
+
}
|
|
481
|
+
return snapshot.availability === 'unavailable'
|
|
482
|
+
&& hasOnlyFields(snapshot, ['availability', 'observedAt', 'transactions', 'truncated'])
|
|
483
|
+
&& snapshot.observedAt === null
|
|
484
|
+
&& Array.isArray(snapshot.transactions)
|
|
485
|
+
&& snapshot.transactions.length === 0
|
|
486
|
+
&& snapshot.truncated === false;
|
|
487
|
+
};
|
|
448
488
|
return (isObject(value)
|
|
489
|
+
&& hasOnlyFields(value, ['transactions', 'nextCursor', 'refresh', 'pending'])
|
|
449
490
|
&& Array.isArray(value.transactions)
|
|
450
491
|
&& value.transactions.every(isTransaction)
|
|
451
492
|
&& (value.nextCursor === null || typeof value.nextCursor === 'string')
|
|
452
|
-
&& isRefreshStatus(value.refresh)
|
|
493
|
+
&& isRefreshStatus(value.refresh)
|
|
494
|
+
&& (value.pending === undefined || isPendingSnapshot(value.pending)));
|
|
495
|
+
}
|
|
496
|
+
function isPartnerStatusResponse(value) {
|
|
497
|
+
if (!isObject(value)
|
|
498
|
+
|| !hasOnlyFields(value, ['asOf', 'partnerStatus', 'settlement', 'payments', 'nextCursor'])
|
|
499
|
+
|| !isIsoDateTime(value.asOf)
|
|
500
|
+
|| (value.partnerStatus !== 'connected' && value.partnerStatus !== 'not_connected')
|
|
501
|
+
|| (value.nextCursor !== null && typeof value.nextCursor !== 'string')
|
|
502
|
+
|| !Array.isArray(value.payments)
|
|
503
|
+
|| value.payments.length > 200)
|
|
504
|
+
return false;
|
|
505
|
+
const settlementIsValid = isObject(value.settlement)
|
|
506
|
+
&& hasOnlyFields(value.settlement, ['currency', 'balance'])
|
|
507
|
+
&& isCurrency(value.settlement.currency)
|
|
508
|
+
&& isObject(value.settlement.balance)
|
|
509
|
+
&& hasOnlyFields(value.settlement.balance, ['direction', 'amountPence'])
|
|
510
|
+
&& (value.settlement.balance.direction === 'you_owe'
|
|
511
|
+
|| value.settlement.balance.direction === 'partner_owes_you'
|
|
512
|
+
|| value.settlement.balance.direction === 'settled')
|
|
513
|
+
&& isNonnegativeSafeInteger(value.settlement.balance.amountPence)
|
|
514
|
+
&& ((value.settlement.balance.direction === 'settled'
|
|
515
|
+
&& value.settlement.balance.amountPence === 0)
|
|
516
|
+
|| (value.settlement.balance.direction !== 'settled'
|
|
517
|
+
&& value.settlement.balance.amountPence > 0));
|
|
518
|
+
if ((value.partnerStatus === 'connected' && !settlementIsValid)
|
|
519
|
+
|| (value.partnerStatus === 'not_connected' && value.settlement !== null)
|
|
520
|
+
|| (value.partnerStatus === 'not_connected' && value.payments.length > 0)
|
|
521
|
+
|| (value.partnerStatus === 'not_connected' && value.nextCursor !== null))
|
|
522
|
+
return false;
|
|
523
|
+
return value.payments.every((payment) => (isObject(payment)
|
|
524
|
+
&& hasOnlyFields(payment, [
|
|
525
|
+
'paymentRef', 'direction', 'amountPence', 'currency', 'occurredAt',
|
|
526
|
+
])
|
|
527
|
+
&& typeof payment.paymentRef === 'string'
|
|
528
|
+
&& /^sloth_partner_payment_v1_[A-Za-z0-9_-]{43}$/.test(payment.paymentRef)
|
|
529
|
+
&& (payment.direction === 'sent' || payment.direction === 'received')
|
|
530
|
+
&& isNonnegativeSafeInteger(payment.amountPence)
|
|
531
|
+
&& payment.amountPence > 0
|
|
532
|
+
&& isCurrency(payment.currency)
|
|
533
|
+
&& (payment.occurredAt === null || isIsoDateTime(payment.occurredAt))));
|
|
453
534
|
}
|
|
454
535
|
function isAssignmentResponse(value) {
|
|
455
536
|
const isResponseSplit = (split) => (isObject(split)
|
|
@@ -1223,37 +1304,39 @@ export function parseApiResponse(command, value) {
|
|
|
1223
1304
|
? isLineItemMutationResponse(value)
|
|
1224
1305
|
: command === 'transactions'
|
|
1225
1306
|
? isTransactionsResponse(value)
|
|
1226
|
-
: command === '
|
|
1227
|
-
?
|
|
1228
|
-
: command === 'rules-
|
|
1229
|
-
?
|
|
1230
|
-
: command === 'rules-
|
|
1231
|
-
?
|
|
1232
|
-
: command === 'rules-
|
|
1233
|
-
?
|
|
1234
|
-
: command === '
|
|
1235
|
-
?
|
|
1236
|
-
: command === 'receipts-
|
|
1237
|
-
?
|
|
1238
|
-
: command === 'receipts-
|
|
1239
|
-
?
|
|
1240
|
-
: command === 'receipts-
|
|
1241
|
-
?
|
|
1242
|
-
: command === '
|
|
1243
|
-
?
|
|
1244
|
-
: command === '
|
|
1245
|
-
?
|
|
1246
|
-
: command === '
|
|
1247
|
-
?
|
|
1248
|
-
: command === '
|
|
1249
|
-
?
|
|
1250
|
-
: command === 'scenarios-
|
|
1251
|
-
?
|
|
1252
|
-
: command === '
|
|
1253
|
-
?
|
|
1254
|
-
: command === 'goals-
|
|
1255
|
-
?
|
|
1256
|
-
:
|
|
1307
|
+
: command === 'partner-status'
|
|
1308
|
+
? isPartnerStatusResponse(value)
|
|
1309
|
+
: command === 'rules-list'
|
|
1310
|
+
? isNotificationRuleListResponse(value)
|
|
1311
|
+
: command === 'rules-get' || command === 'rules-set'
|
|
1312
|
+
? isNotificationRuleResponse(value)
|
|
1313
|
+
: command === 'rules-delete'
|
|
1314
|
+
? isNotificationRuleDeleteResponse(value)
|
|
1315
|
+
: command === 'rules-scan-contract'
|
|
1316
|
+
? isRenewalExtractionResponse(value)
|
|
1317
|
+
: command === 'receipts-extract'
|
|
1318
|
+
? isReceiptExtractResponse(value)
|
|
1319
|
+
: command === 'receipts-get'
|
|
1320
|
+
? isReceiptLookupResponse(value)
|
|
1321
|
+
: command === 'receipts-attach'
|
|
1322
|
+
? isReceiptMutationResponse(value)
|
|
1323
|
+
: command === 'receipts-remove'
|
|
1324
|
+
? isReceiptDeleteResponse(value)
|
|
1325
|
+
: command === 'assign'
|
|
1326
|
+
? isAssignmentResponse(value)
|
|
1327
|
+
: command === 'ask-partner'
|
|
1328
|
+
? isPartnerResponse(value)
|
|
1329
|
+
: command === 'goals-list'
|
|
1330
|
+
? isGoalsResponse(value)
|
|
1331
|
+
: command === 'scenarios-list'
|
|
1332
|
+
? isScenariosResponse(value)
|
|
1333
|
+
: command === 'scenarios-mutation'
|
|
1334
|
+
? isScenarioMutationResponse(value)
|
|
1335
|
+
: command === 'goals-preview'
|
|
1336
|
+
? isGoalPreviewResponse(value)
|
|
1337
|
+
: command === 'goals-delete'
|
|
1338
|
+
? isGoalDeleteResponse(value)
|
|
1339
|
+
: isGoalMutationResponse(value);
|
|
1257
1340
|
if (!valid) {
|
|
1258
1341
|
const label = command === 'assign' ? 'assignment' : command;
|
|
1259
1342
|
throw new ApiError(`Invalid ${label} response from the Agent API`);
|