@slothmoney/agent-cli 0.5.0 → 0.6.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 +9 -0
- package/README.md +35 -5
- package/dist/args.js +56 -4
- package/dist/cli.js +91 -9
- package/dist/contracts.js +74 -18
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.6.0 - 2026-08-08
|
|
6
|
+
|
|
7
|
+
- Expose goal-savings membership on account inventory rows and preview or apply
|
|
8
|
+
owner-authorized changes through opaque account references.
|
|
9
|
+
- Read cache-only linked investment portfolios with provider-native holdings,
|
|
10
|
+
quantities, valuations, currencies, and freshness metadata.
|
|
11
|
+
- Keep strict response validation, JSON-only stdout, command-specific help,
|
|
12
|
+
and clean-install package coverage synchronized with Agent API v1.
|
|
13
|
+
|
|
5
14
|
## 0.5.0 - 2026-08-07
|
|
6
15
|
|
|
7
16
|
- Create and rename custom categories, with existing icon and category type
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Sloth Agent CLI
|
|
2
2
|
|
|
3
|
-
Use your own agent to inspect
|
|
3
|
+
Use your own agent to inspect accounts and investment holdings, manage goals, and categorise transactions 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.6.0 -- sloth-agent --help
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
## Authenticate
|
|
@@ -24,10 +24,10 @@ Create a personal access token in Sloth Money under
|
|
|
24
24
|
**Settings > Developer access**, then choose the authentication method for
|
|
25
25
|
where the CLI runs.
|
|
26
26
|
|
|
27
|
-
New tokens are view-only. That is enough for `auth status`, `accounts`,
|
|
27
|
+
New tokens are view-only. That is enough for `auth status`, `accounts`, `investments`,
|
|
28
28
|
`categories`, `transactions`, and `goals` list. Enable **Allow changes** when
|
|
29
29
|
creating the token only if the CLI must apply assignments, manage categories
|
|
30
|
-
or line items, ask a partner for an explanation, or manage goals. Token
|
|
30
|
+
or line items, change goal-savings account membership, ask a partner for an explanation, or manage goals. Token
|
|
31
31
|
permissions cannot be changed later - revoke and reissue the token instead.
|
|
32
32
|
|
|
33
33
|
### Local computer
|
|
@@ -250,11 +250,41 @@ sloth-agent accounts
|
|
|
250
250
|
The command is read-only and cache-only: it does not refresh linked banks or
|
|
251
251
|
change account data. Each result contains an opaque `accountRef`, personal or
|
|
252
252
|
joint ownership, connected or manual source, native balance/currency when
|
|
253
|
-
known, `lastBalanceUpdatedAt`, and `
|
|
253
|
+
known, `lastBalanceUpdatedAt`, `connectionState`, and `isGoalSavingsSource`.
|
|
254
|
+
Missing values are JSON
|
|
254
255
|
`null`; currencies are never converted or combined. Partner personal accounts
|
|
255
256
|
are excluded, while enabled shared joint accounts follow Sloth's existing
|
|
256
257
|
visibility rules.
|
|
257
258
|
|
|
259
|
+
Goal-savings changes are previews unless `--apply` is present. Only
|
|
260
|
+
caller-owned connected accounts can be changed; partner-owned shared accounts
|
|
261
|
+
and fixed manual accounts return an explanatory error.
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
sloth-agent accounts update \
|
|
265
|
+
--account-ref sloth_account_v1_... \
|
|
266
|
+
--goal-savings-source true
|
|
267
|
+
|
|
268
|
+
sloth-agent accounts update \
|
|
269
|
+
--account-ref sloth_account_v1_... \
|
|
270
|
+
--goal-savings-source true \
|
|
271
|
+
--apply
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Read linked investment accounts and their cached holdings:
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
sloth-agent investments
|
|
278
|
+
sloth-agent investments --account-ref sloth_account_v1_...
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Investment reads are cache-only and do not refresh a brokerage. Holding
|
|
282
|
+
quantities, unit prices, market values, currencies, and freshness are returned
|
|
283
|
+
in provider-native terms. They are not converted or guaranteed to reconcile
|
|
284
|
+
to an account total reported in another currency. Caller-owned personal and
|
|
285
|
+
joint linked investment accounts are included; partner-owned accounts, manual
|
|
286
|
+
holdings, and investment activities are not.
|
|
287
|
+
|
|
258
288
|
List your goals:
|
|
259
289
|
|
|
260
290
|
```bash
|
package/dist/args.js
CHANGED
|
@@ -226,6 +226,54 @@ function requiredOption(values, option, commandLabel) {
|
|
|
226
226
|
throw new UsageError(`${commandLabel} requires ${option} <value>`);
|
|
227
227
|
return value;
|
|
228
228
|
}
|
|
229
|
+
function parseAccountRef(value) {
|
|
230
|
+
if (!/^sloth_account_v1_[A-Za-z0-9_-]{43}$/.test(value)) {
|
|
231
|
+
throw new UsageError('--account-ref must be a valid accountRef from sloth-agent accounts');
|
|
232
|
+
}
|
|
233
|
+
return value;
|
|
234
|
+
}
|
|
235
|
+
function parseAccounts(args, baseUrl) {
|
|
236
|
+
const subcommand = args.shift();
|
|
237
|
+
if (subcommand === undefined || subcommand === 'list') {
|
|
238
|
+
if (args.length > 0)
|
|
239
|
+
throw new UsageError(`Unknown accounts option: ${args[0]}`);
|
|
240
|
+
return withBaseUrl({ command: 'accounts' }, baseUrl);
|
|
241
|
+
}
|
|
242
|
+
if (subcommand === 'update') {
|
|
243
|
+
const { values, apply } = parseNamedOptions(args, 'accounts update', new Set(['--account-ref', '--goal-savings-source']));
|
|
244
|
+
const source = requiredOption(values, '--goal-savings-source', 'accounts update');
|
|
245
|
+
if (source !== 'true' && source !== 'false') {
|
|
246
|
+
throw new UsageError('--goal-savings-source must be true or false');
|
|
247
|
+
}
|
|
248
|
+
return withBaseUrl({
|
|
249
|
+
command: 'accounts-update',
|
|
250
|
+
accountRef: parseAccountRef(requiredOption(values, '--account-ref', 'accounts update')),
|
|
251
|
+
isGoalSavingsSource: source === 'true',
|
|
252
|
+
apply,
|
|
253
|
+
}, baseUrl);
|
|
254
|
+
}
|
|
255
|
+
if (subcommand.startsWith('-')) {
|
|
256
|
+
throw new UsageError(`Unknown accounts option: ${subcommand}`);
|
|
257
|
+
}
|
|
258
|
+
throw new UsageError(`Unknown accounts command: ${subcommand}`);
|
|
259
|
+
}
|
|
260
|
+
function parseInvestments(args, baseUrl) {
|
|
261
|
+
let accountRef;
|
|
262
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
263
|
+
const argument = args[index];
|
|
264
|
+
if (argument === '--account-ref') {
|
|
265
|
+
accountRef = setOnce(accountRef, parseAccountRef(readOptionValue(args, index, '--account-ref')), '--account-ref');
|
|
266
|
+
index += 1;
|
|
267
|
+
}
|
|
268
|
+
else if (argument.startsWith('--account-ref=')) {
|
|
269
|
+
accountRef = setOnce(accountRef, parseAccountRef(requireNonEmpty(argument.slice('--account-ref='.length), '--account-ref')), '--account-ref');
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
throw new UsageError(`Unknown investments option: ${argument}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return withBaseUrl({ command: 'investments', ...(accountRef ? { accountRef } : {}) }, baseUrl);
|
|
276
|
+
}
|
|
229
277
|
function parseCategories(args, baseUrl) {
|
|
230
278
|
const subcommand = args.shift();
|
|
231
279
|
if (subcommand === undefined || subcommand === 'list') {
|
|
@@ -555,8 +603,12 @@ function helpTopic(argv) {
|
|
|
555
603
|
|| command === 'transactions'
|
|
556
604
|
|| command === 'assign'
|
|
557
605
|
|| command === 'ask-partner') {
|
|
606
|
+
if (command === 'accounts' && subcommand === 'update')
|
|
607
|
+
return 'accounts-update';
|
|
558
608
|
return command;
|
|
559
609
|
}
|
|
610
|
+
if (command === 'investments')
|
|
611
|
+
return 'investments';
|
|
560
612
|
return undefined;
|
|
561
613
|
}
|
|
562
614
|
export function parseArgs(argv) {
|
|
@@ -583,10 +635,10 @@ export function parseArgs(argv) {
|
|
|
583
635
|
return parseLineItems(args, baseUrl);
|
|
584
636
|
}
|
|
585
637
|
if (command === 'accounts') {
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
return
|
|
638
|
+
return parseAccounts(args, baseUrl);
|
|
639
|
+
}
|
|
640
|
+
if (command === 'investments') {
|
|
641
|
+
return parseInvestments(args, baseUrl);
|
|
590
642
|
}
|
|
591
643
|
if (command === 'transactions') {
|
|
592
644
|
return withBaseUrl({ command, filters: parseTransactions(args) }, baseUrl);
|
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import { ICON_KEYS } from './category-metadata.js';
|
|
|
4
4
|
import { parseApiResponse, validateAssignmentPayload, } from './contracts.js';
|
|
5
5
|
import { createSystemCredentialStore, secureStorageUnavailableError, } from './credential-store.js';
|
|
6
6
|
import { ApiError, CliError, ConfigError, UsageError, } from './errors.js';
|
|
7
|
-
export const CLI_VERSION = '0.
|
|
7
|
+
export const CLI_VERSION = '0.6.0';
|
|
8
8
|
const REQUEST_TIMEOUT_MS = 60_000;
|
|
9
9
|
const API_ORIGIN_HELP_LINES = [
|
|
10
10
|
'',
|
|
@@ -25,7 +25,9 @@ export function usageText() {
|
|
|
25
25
|
' sloth-agent auth login [--token-stdin | --from-env] [--base-url URL]',
|
|
26
26
|
' sloth-agent auth status [--base-url URL]',
|
|
27
27
|
' sloth-agent auth logout [--base-url URL]',
|
|
28
|
-
' sloth-agent accounts [--base-url URL]',
|
|
28
|
+
' sloth-agent accounts [list] [--base-url URL]',
|
|
29
|
+
' sloth-agent accounts update --account-ref REF --goal-savings-source true|false [--apply]',
|
|
30
|
+
' sloth-agent investments [--account-ref REF] [--base-url URL]',
|
|
29
31
|
' sloth-agent categories [list] [--base-url URL]',
|
|
30
32
|
' sloth-agent categories create --name NAME --icon-key KEY --type TYPE [--apply]',
|
|
31
33
|
' sloth-agent categories rename --category-id ID --name NAME [--apply]',
|
|
@@ -280,7 +282,7 @@ export function accountsHelpText() {
|
|
|
280
282
|
'Read the existing Sloth account inventory known to the authenticated user.',
|
|
281
283
|
'',
|
|
282
284
|
'Usage:',
|
|
283
|
-
' sloth-agent accounts [--base-url URL]',
|
|
285
|
+
' sloth-agent accounts [list] [--base-url URL]',
|
|
284
286
|
'',
|
|
285
287
|
'Options:',
|
|
286
288
|
' --base-url URL Optional. Override the API origin.',
|
|
@@ -296,6 +298,57 @@ export function accountsHelpText() {
|
|
|
296
298
|
' accounts[].ownership personal or joint',
|
|
297
299
|
' accounts[].balanceAmount and currency in the native currency when known',
|
|
298
300
|
' accounts[].connectionState and lastBalanceUpdatedAt for freshness',
|
|
301
|
+
' accounts[].isGoalSavingsSource whether the owner uses it for goal savings',
|
|
302
|
+
].join('\n');
|
|
303
|
+
}
|
|
304
|
+
export function accountsUpdateHelpText() {
|
|
305
|
+
return [
|
|
306
|
+
'Sloth Agent CLI — accounts update',
|
|
307
|
+
'',
|
|
308
|
+
'Preview or update whether an owned connected account is used for goal savings.',
|
|
309
|
+
'',
|
|
310
|
+
'Usage:',
|
|
311
|
+
' sloth-agent accounts update --account-ref REF --goal-savings-source true|false [--apply] [--base-url URL]',
|
|
312
|
+
'',
|
|
313
|
+
'Required inputs:',
|
|
314
|
+
' --account-ref REF Opaque accountRef from sloth-agent accounts.',
|
|
315
|
+
' --goal-savings-source true|false Enable or disable goal-savings membership.',
|
|
316
|
+
'',
|
|
317
|
+
'Write behavior:',
|
|
318
|
+
' Without --apply, returns a JSON preview without credentials or a network request.',
|
|
319
|
+
' With --apply, requires agent:write on a write-enabled token and updates saved Sloth metadata.',
|
|
320
|
+
' Partner-owned shared accounts and manual accounts cannot be changed.',
|
|
321
|
+
' Unknown, disconnected, or inaccessible references return Account not found.',
|
|
322
|
+
...API_ORIGIN_HELP_LINES,
|
|
323
|
+
'',
|
|
324
|
+
'Output:',
|
|
325
|
+
' Preview mode returns dryRun, method, endpoint, and payload.',
|
|
326
|
+
' Apply mode returns changed and the complete persisted account.',
|
|
327
|
+
].join('\n');
|
|
328
|
+
}
|
|
329
|
+
export function investmentsHelpText() {
|
|
330
|
+
return [
|
|
331
|
+
'Sloth Agent CLI — investments',
|
|
332
|
+
'',
|
|
333
|
+
'Read linked investment accounts and their cached provider-native holdings.',
|
|
334
|
+
'',
|
|
335
|
+
'Usage:',
|
|
336
|
+
' sloth-agent investments [--account-ref REF] [--base-url URL]',
|
|
337
|
+
'',
|
|
338
|
+
'Options:',
|
|
339
|
+
' --account-ref REF Optional. Return one linked investment account.',
|
|
340
|
+
' --base-url URL Optional. Override the API origin.',
|
|
341
|
+
' -h, --help Show this help.',
|
|
342
|
+
...API_ORIGIN_HELP_LINES,
|
|
343
|
+
'',
|
|
344
|
+
'Access:',
|
|
345
|
+
' This command requires agent:read and is read-only and cache-only; it never refreshes a brokerage.',
|
|
346
|
+
' An unknown or non-investment filter returns Investment account not found.',
|
|
347
|
+
'',
|
|
348
|
+
'Output:',
|
|
349
|
+
' investmentAccounts contains account totals and nested holdings.',
|
|
350
|
+
' Holding quantities, prices, market values, currencies, and freshness are',
|
|
351
|
+
' provider-native and are not converted or guaranteed to reconcile to totals.',
|
|
299
352
|
].join('\n');
|
|
300
353
|
}
|
|
301
354
|
export function transactionsHelpText() {
|
|
@@ -581,6 +634,8 @@ export function commandHelpText(topic) {
|
|
|
581
634
|
'auth-status': authStatusHelpText,
|
|
582
635
|
'auth-logout': authLogoutHelpText,
|
|
583
636
|
accounts: accountsHelpText,
|
|
637
|
+
'accounts-update': accountsUpdateHelpText,
|
|
638
|
+
investments: investmentsHelpText,
|
|
584
639
|
categories: categoriesHelpText,
|
|
585
640
|
'categories-create': categoriesCreateHelpText,
|
|
586
641
|
'categories-rename': categoriesRenameHelpText,
|
|
@@ -833,9 +888,32 @@ export async function runCli(argv = process.argv.slice(2), options = {}) {
|
|
|
833
888
|
});
|
|
834
889
|
return 0;
|
|
835
890
|
}
|
|
891
|
+
if (parsed.command === 'accounts-update' && !parsed.apply) {
|
|
892
|
+
const endpoint = `${baseUrl}/api/agent/v1/accounts/${encodeURIComponent(parsed.accountRef)}`;
|
|
893
|
+
writeJson(writeStdout, {
|
|
894
|
+
dryRun: true,
|
|
895
|
+
endpoint,
|
|
896
|
+
method: 'PATCH',
|
|
897
|
+
payload: { isGoalSavingsSource: parsed.isGoalSavingsSource },
|
|
898
|
+
});
|
|
899
|
+
return 0;
|
|
900
|
+
}
|
|
836
901
|
const credential = await resolveCredential(environment, baseUrl, getCredentialStore);
|
|
837
902
|
token = credential.token;
|
|
838
903
|
const headers = requestHeaders(token);
|
|
904
|
+
if (parsed.command === 'accounts-update') {
|
|
905
|
+
const endpoint = `${baseUrl}/api/agent/v1/accounts/${encodeURIComponent(parsed.accountRef)}`;
|
|
906
|
+
const payload = { isGoalSavingsSource: parsed.isGoalSavingsSource };
|
|
907
|
+
const response = await fetchImplementation(endpoint, {
|
|
908
|
+
method: 'PATCH',
|
|
909
|
+
headers: { ...headers, 'Content-Type': 'application/json' },
|
|
910
|
+
body: JSON.stringify(payload),
|
|
911
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
912
|
+
});
|
|
913
|
+
const data = parseApiResponse(parsed.command, await parseHttpResponse(response, token));
|
|
914
|
+
writeJson(writeStdout, data);
|
|
915
|
+
return 0;
|
|
916
|
+
}
|
|
839
917
|
if (parsed.command === 'categories-create'
|
|
840
918
|
|| parsed.command === 'categories-rename'
|
|
841
919
|
|| parsed.command === 'line-items-create'
|
|
@@ -1004,12 +1082,16 @@ export async function runCli(argv = process.argv.slice(2), options = {}) {
|
|
|
1004
1082
|
}
|
|
1005
1083
|
const path = parsed.command === 'accounts'
|
|
1006
1084
|
? '/api/agent/v1/accounts'
|
|
1007
|
-
: parsed.command === '
|
|
1008
|
-
?
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1085
|
+
: parsed.command === 'investments'
|
|
1086
|
+
? `/api/agent/v1/investments${parsed.accountRef
|
|
1087
|
+
? `?${new URLSearchParams({ accountRef: parsed.accountRef }).toString()}`
|
|
1088
|
+
: ''}`
|
|
1089
|
+
: parsed.command === 'categories'
|
|
1090
|
+
? '/api/agent/v1/categories'
|
|
1091
|
+
: `/api/agent/v1/transactions${(() => {
|
|
1092
|
+
const query = buildTransactionsQuery(parsed.filters);
|
|
1093
|
+
return query ? `?${query}` : '';
|
|
1094
|
+
})()}`;
|
|
1013
1095
|
const response = await fetchImplementation(`${baseUrl}${path}`, {
|
|
1014
1096
|
method: 'GET',
|
|
1015
1097
|
headers: parsed.command === 'transactions'
|
package/dist/contracts.js
CHANGED
|
@@ -303,6 +303,7 @@ function isAccount(value) {
|
|
|
303
303
|
'source',
|
|
304
304
|
'lastBalanceUpdatedAt',
|
|
305
305
|
'connectionState',
|
|
306
|
+
'isGoalSavingsSource',
|
|
306
307
|
])
|
|
307
308
|
&& typeof value.accountRef === 'string'
|
|
308
309
|
&& /^sloth_account_v1_[A-Za-z0-9_-]{43}$/.test(value.accountRef)
|
|
@@ -321,7 +322,8 @@ function isAccount(value) {
|
|
|
321
322
|
&& (value.connectionState === 'active'
|
|
322
323
|
|| value.connectionState === 'expired'
|
|
323
324
|
|| value.connectionState === 'manual'
|
|
324
|
-
|| value.connectionState === 'unknown')
|
|
325
|
+
|| value.connectionState === 'unknown')
|
|
326
|
+
&& typeof value.isGoalSavingsSource === 'boolean');
|
|
325
327
|
}
|
|
326
328
|
function isAccountsResponse(value) {
|
|
327
329
|
return (isObject(value)
|
|
@@ -330,6 +332,56 @@ function isAccountsResponse(value) {
|
|
|
330
332
|
&& Array.isArray(value.accounts)
|
|
331
333
|
&& value.accounts.every(isAccount));
|
|
332
334
|
}
|
|
335
|
+
function isAccountMutationResponse(value) {
|
|
336
|
+
return (isObject(value)
|
|
337
|
+
&& hasOnlyFields(value, ['changed', 'account'])
|
|
338
|
+
&& typeof value.changed === 'boolean'
|
|
339
|
+
&& isAccount(value.account));
|
|
340
|
+
}
|
|
341
|
+
function isInvestmentHolding(value) {
|
|
342
|
+
return (isObject(value)
|
|
343
|
+
&& hasOnlyFields(value, [
|
|
344
|
+
'instrumentType',
|
|
345
|
+
'symbol',
|
|
346
|
+
'name',
|
|
347
|
+
'units',
|
|
348
|
+
'unitPriceAmount',
|
|
349
|
+
'marketValueAmount',
|
|
350
|
+
'currency',
|
|
351
|
+
'providerFreshnessAsOf',
|
|
352
|
+
'syncedAt',
|
|
353
|
+
])
|
|
354
|
+
&& typeof value.instrumentType === 'string'
|
|
355
|
+
&& value.instrumentType.trim().length > 0
|
|
356
|
+
&& (value.symbol === null || (typeof value.symbol === 'string' && value.symbol.trim().length > 0))
|
|
357
|
+
&& typeof value.name === 'string'
|
|
358
|
+
&& value.name.trim().length > 0
|
|
359
|
+
&& typeof value.units === 'number'
|
|
360
|
+
&& Number.isFinite(value.units)
|
|
361
|
+
&& typeof value.unitPriceAmount === 'number'
|
|
362
|
+
&& Number.isFinite(value.unitPriceAmount)
|
|
363
|
+
&& typeof value.marketValueAmount === 'number'
|
|
364
|
+
&& Number.isFinite(value.marketValueAmount)
|
|
365
|
+
&& isCurrency(value.currency)
|
|
366
|
+
&& (value.providerFreshnessAsOf === null || isIsoDateTime(value.providerFreshnessAsOf))
|
|
367
|
+
&& isIsoDateTime(value.syncedAt));
|
|
368
|
+
}
|
|
369
|
+
function isInvestmentsResponse(value) {
|
|
370
|
+
return (isObject(value)
|
|
371
|
+
&& hasOnlyFields(value, ['asOf', 'investmentAccounts'])
|
|
372
|
+
&& isIsoDateTime(value.asOf)
|
|
373
|
+
&& Array.isArray(value.investmentAccounts)
|
|
374
|
+
&& value.investmentAccounts.every((account) => {
|
|
375
|
+
if (!isObject(account))
|
|
376
|
+
return false;
|
|
377
|
+
const { holdings, ...baseAccount } = account;
|
|
378
|
+
return (isAccount(baseAccount)
|
|
379
|
+
&& account.accountType === 'investments'
|
|
380
|
+
&& account.source === 'connected'
|
|
381
|
+
&& Array.isArray(holdings)
|
|
382
|
+
&& holdings.every(isInvestmentHolding));
|
|
383
|
+
}));
|
|
384
|
+
}
|
|
333
385
|
function isGoalsResponse(value) {
|
|
334
386
|
return (isObject(value)
|
|
335
387
|
&& hasOnlyFields(value, ['currency', 'goals'])
|
|
@@ -353,23 +405,27 @@ function isGoalDeleteResponse(value) {
|
|
|
353
405
|
export function parseApiResponse(command, value) {
|
|
354
406
|
const valid = command === 'accounts'
|
|
355
407
|
? isAccountsResponse(value)
|
|
356
|
-
: command === '
|
|
357
|
-
?
|
|
358
|
-
: command === '
|
|
359
|
-
?
|
|
360
|
-
: command === '
|
|
361
|
-
?
|
|
362
|
-
: command === '
|
|
363
|
-
?
|
|
364
|
-
: command === '
|
|
365
|
-
?
|
|
366
|
-
: command === '
|
|
367
|
-
?
|
|
368
|
-
: command === '
|
|
369
|
-
?
|
|
370
|
-
: command === '
|
|
371
|
-
?
|
|
372
|
-
:
|
|
408
|
+
: command === 'accounts-update'
|
|
409
|
+
? isAccountMutationResponse(value)
|
|
410
|
+
: command === 'investments'
|
|
411
|
+
? isInvestmentsResponse(value)
|
|
412
|
+
: command === 'categories'
|
|
413
|
+
? isCategoryResponse(value)
|
|
414
|
+
: command === 'categories-create' || command === 'categories-rename'
|
|
415
|
+
? isCategoryMutationResponse(value)
|
|
416
|
+
: command === 'line-items-create' || command === 'line-items-rename'
|
|
417
|
+
? isLineItemMutationResponse(value)
|
|
418
|
+
: command === 'transactions'
|
|
419
|
+
? isTransactionsResponse(value)
|
|
420
|
+
: command === 'assign'
|
|
421
|
+
? isAssignmentResponse(value)
|
|
422
|
+
: command === 'ask-partner'
|
|
423
|
+
? isPartnerResponse(value)
|
|
424
|
+
: command === 'goals-list'
|
|
425
|
+
? isGoalsResponse(value)
|
|
426
|
+
: command === 'goals-delete'
|
|
427
|
+
? isGoalDeleteResponse(value)
|
|
428
|
+
: isGoalMutationResponse(value);
|
|
373
429
|
if (!valid) {
|
|
374
430
|
const label = command === 'assign' ? 'assignment' : command;
|
|
375
431
|
throw new ApiError(`Invalid ${label} response from the Agent API`);
|