@slothmoney/agent-cli 0.7.0 → 0.9.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 CHANGED
@@ -2,6 +2,23 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.9.0 - 2026-08-12
6
+
7
+ - Manage owned manual balance and manual transaction accounts through partial
8
+ account updates, including metadata, ownership, balance-only settings, and
9
+ goal-savings membership where supported.
10
+ - Preview or apply idempotent manual account archival while retaining the
11
+ underlying account, transaction, import, balance, and categorisation records.
12
+ - Keep account writes local-only by default and require `--apply` before any
13
+ authenticated PATCH or DELETE request is sent.
14
+
15
+ ## 0.8.0 - 2026-08-12
16
+
17
+ - Read each goal's one-based priority and move one goal to a new position with
18
+ automatic shifting of the intervening goals.
19
+
20
+ ## 0.7.0 - 2026-08-09
21
+
5
22
  - Read personal or joint budget periods with categories, line items, funding,
6
23
  and planned amounts.
7
24
  - Preview or apply planned line-item updates that overwrite the selected period
package/README.md CHANGED
@@ -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.7.0 -- sloth-agent --help
18
+ npm exec --yes --package=@slothmoney/agent-cli@0.9.0 -- sloth-agent --help
19
19
  ```
20
20
 
21
21
  ## Authenticate
@@ -27,7 +27,7 @@ where the CLI runs.
27
27
  New tokens are view-only. That is enough for `auth status`, `accounts`, `investments`,
28
28
  `budget`, `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, update planned budgets, change goal-savings account membership, ask a partner for an explanation, or manage goals. Token
30
+ or line items, update planned budgets, manage accounts, 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
@@ -305,21 +305,38 @@ Missing values are JSON
305
305
  are excluded, while enabled shared joint accounts follow Sloth's existing
306
306
  visibility rules.
307
307
 
308
- Goal-savings changes are previews unless `--apply` is present. Only
309
- caller-owned connected accounts can be changed; partner-owned shared accounts
310
- and fixed manual accounts return an explanatory error.
308
+ Account changes are previews unless `--apply` is present. Connected accounts
309
+ support only goal-savings membership. Manual current accounts support their
310
+ institution, name, currency, and ownership. Manual balance accounts also
311
+ support balance, Savings/Investments type, and goal-savings membership.
312
+ Partner-owned shared accounts return an explanatory error.
311
313
 
312
314
  ```bash
313
315
  sloth-agent accounts update \
314
316
  --account-ref sloth_account_v1_... \
315
- --goal-savings-source true
317
+ --institution-name "Hargreaves Lansdown" \
318
+ --account-name "Stocks & Shares ISA" \
319
+ --currency GBP \
320
+ --ownership individual \
321
+ --balance-amount 12500.75 \
322
+ --account-type investments \
323
+ --goal-savings-source false
316
324
 
317
325
  sloth-agent accounts update \
318
326
  --account-ref sloth_account_v1_... \
319
- --goal-savings-source true \
327
+ --goal-savings-source false \
320
328
  --apply
321
329
  ```
322
330
 
331
+ Archive an owned manual account. The account disappears from active Sloth
332
+ surfaces, but its underlying records are retained. Repeating an applied removal
333
+ is safe and returns `changed: false`.
334
+
335
+ ```bash
336
+ sloth-agent accounts remove --account-ref sloth_account_v1_...
337
+ sloth-agent accounts remove --account-ref sloth_account_v1_... --apply
338
+ ```
339
+
323
340
  Read linked investment accounts and their cached holdings:
324
341
 
325
342
  ```bash
@@ -355,7 +372,7 @@ sloth-agent goals create \
355
372
  --apply
356
373
  ```
357
374
 
358
- Use the `id` from list or create output to update or delete a goal:
375
+ Use the `id` from list or create output to update or delete goals:
359
376
 
360
377
  ```bash
361
378
  sloth-agent goals update \
@@ -365,6 +382,11 @@ sloth-agent goals update \
365
382
  --achieved=false \
366
383
  --apply
367
384
 
385
+ sloth-agent goals update \
386
+ --goal-id house-goal-id \
387
+ --priority 2 \
388
+ --apply
389
+
368
390
  sloth-agent goals delete --goal-id goal-id --apply
369
391
  ```
370
392
 
@@ -373,7 +395,13 @@ remove an optional value. Marking a goal achieved removes its forecast
373
395
  assignment. Deleting a goal also removes its forecast assignments and drift
374
396
  history. Goal sharing remains app-managed. Change an active shared goal's
375
397
  pot-tracked target amount in the Sloth Budget app, where account balances can
376
- be reallocated across goals in priority order.
398
+ be reallocated across goals in priority order. Goal list output includes a
399
+ one-based `priority`; `1` is highest. Moving one goal automatically shifts the
400
+ goals between its old and new positions. The
401
+ priority option must be used on its own, and the write persists immediately.
402
+ Forecast assignments and shared pot
403
+ progress are browser-owned derived state and refresh when the owner next opens
404
+ the Forecast screen.
377
405
 
378
406
  Read uncategorised contributions to the joint budget:
379
407
 
package/dist/args.js CHANGED
@@ -64,6 +64,16 @@ function parseGoalMonthKey(value, name) {
64
64
  }
65
65
  return value;
66
66
  }
67
+ function parseGoalPriority(value) {
68
+ if (!/^[1-9]\d*$/.test(value)) {
69
+ throw new UsageError('--priority must be a positive whole-number position');
70
+ }
71
+ const priority = Number(value);
72
+ if (!Number.isSafeInteger(priority)) {
73
+ throw new UsageError('--priority must be a positive whole-number position');
74
+ }
75
+ return priority;
76
+ }
67
77
  function parseExplicitBoolean(value, name) {
68
78
  if (value !== 'true' && value !== 'false') {
69
79
  throw new UsageError(`${name} must be true or false`);
@@ -232,6 +242,19 @@ function parseAccountRef(value) {
232
242
  }
233
243
  return value;
234
244
  }
245
+ function parseAccountName(value, option) {
246
+ const name = value.trim();
247
+ if (name.length > 300)
248
+ throw new UsageError(`${option} must be at most 300 characters`);
249
+ return name;
250
+ }
251
+ function parseAccountBalance(value) {
252
+ const amount = Number(value);
253
+ if (!/^\d+(?:\.\d+)?$/.test(value) || !Number.isFinite(amount) || amount < 0) {
254
+ throw new UsageError('--balance-amount must be a nonnegative amount');
255
+ }
256
+ return amount;
257
+ }
235
258
  function parseAccounts(args, baseUrl) {
236
259
  const subcommand = args.shift();
237
260
  if (subcommand === undefined || subcommand === 'list') {
@@ -240,15 +263,71 @@ function parseAccounts(args, baseUrl) {
240
263
  return withBaseUrl({ command: 'accounts' }, baseUrl);
241
264
  }
242
265
  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') {
266
+ const { values, apply } = parseNamedOptions(args, 'accounts update', new Set([
267
+ '--account-ref',
268
+ '--institution-name',
269
+ '--account-name',
270
+ '--currency',
271
+ '--ownership',
272
+ '--balance-amount',
273
+ '--account-type',
274
+ '--goal-savings-source',
275
+ ]));
276
+ const institutionName = values.get('--institution-name');
277
+ const accountName = values.get('--account-name');
278
+ const currencyValue = values.get('--currency');
279
+ const ownershipValue = values.get('--ownership');
280
+ const balanceValue = values.get('--balance-amount');
281
+ const accountTypeValue = values.get('--account-type');
282
+ const sourceValue = values.get('--goal-savings-source');
283
+ if (currencyValue !== undefined && !/^[A-Za-z]{3}$/.test(currencyValue)) {
284
+ throw new UsageError('--currency must be a three-letter currency code');
285
+ }
286
+ if (ownershipValue !== undefined
287
+ && ownershipValue !== 'individual'
288
+ && ownershipValue !== 'joint') {
289
+ throw new UsageError('--ownership must be individual or joint');
290
+ }
291
+ if (accountTypeValue !== undefined
292
+ && accountTypeValue !== 'savings'
293
+ && accountTypeValue !== 'investments') {
294
+ throw new UsageError('--account-type must be savings or investments');
295
+ }
296
+ if (sourceValue !== undefined && sourceValue !== 'true' && sourceValue !== 'false') {
246
297
  throw new UsageError('--goal-savings-source must be true or false');
247
298
  }
299
+ const update = {
300
+ ...(institutionName === undefined
301
+ ? {}
302
+ : { institutionName: parseAccountName(institutionName, '--institution-name') }),
303
+ ...(accountName === undefined
304
+ ? {}
305
+ : { accountName: parseAccountName(accountName, '--account-name') }),
306
+ ...(currencyValue === undefined ? {} : { currency: currencyValue.toUpperCase() }),
307
+ ...(ownershipValue === undefined
308
+ ? {}
309
+ : { ownership: ownershipValue === 'individual' ? 'personal' : 'joint' }),
310
+ ...(balanceValue === undefined ? {} : { balanceAmount: parseAccountBalance(balanceValue) }),
311
+ ...(accountTypeValue === undefined
312
+ ? {}
313
+ : { accountType: accountTypeValue }),
314
+ ...(sourceValue === undefined ? {} : { isGoalSavingsSource: sourceValue === 'true' }),
315
+ };
316
+ if (Object.keys(update).length === 0) {
317
+ throw new UsageError('accounts update requires at least one field to update');
318
+ }
248
319
  return withBaseUrl({
249
320
  command: 'accounts-update',
250
321
  accountRef: parseAccountRef(requiredOption(values, '--account-ref', 'accounts update')),
251
- isGoalSavingsSource: source === 'true',
322
+ update,
323
+ apply,
324
+ }, baseUrl);
325
+ }
326
+ if (subcommand === 'remove') {
327
+ const { values, apply } = parseNamedOptions(args, 'accounts remove', new Set(['--account-ref']));
328
+ return withBaseUrl({
329
+ command: 'accounts-remove',
330
+ accountRef: parseAccountRef(requiredOption(values, '--account-ref', 'accounts remove')),
252
331
  apply,
253
332
  }, baseUrl);
254
333
  }
@@ -467,6 +546,7 @@ function parseGoals(args, baseUrl) {
467
546
  let targetAmount;
468
547
  let targetMonthKey;
469
548
  let isAchieved;
549
+ let priority;
470
550
  let apply = false;
471
551
  for (let index = 0; index < args.length; index += 1) {
472
552
  const argument = args[index];
@@ -497,7 +577,8 @@ function parseGoals(args, baseUrl) {
497
577
  && option !== '--name'
498
578
  && option !== '--target-amount'
499
579
  && option !== '--target-month'
500
- && option !== '--achieved') {
580
+ && option !== '--achieved'
581
+ && option !== '--priority') {
501
582
  throw new UsageError(`Unknown goals update option: ${argument}`);
502
583
  }
503
584
  const value = requireNonEmpty(inlineValue ?? readOptionValue(args, index, option), option);
@@ -521,6 +602,9 @@ function parseGoals(args, baseUrl) {
521
602
  }
522
603
  targetMonthKey = parseGoalMonthKey(value, option);
523
604
  }
605
+ else if (option === '--priority') {
606
+ priority = setOnce(priority, parseGoalPriority(value), option);
607
+ }
524
608
  else {
525
609
  isAchieved = setOnce(isAchieved, parseExplicitBoolean(value, option), option);
526
610
  }
@@ -530,9 +614,17 @@ function parseGoals(args, baseUrl) {
530
614
  if (name === undefined
531
615
  && targetAmount === undefined
532
616
  && targetMonthKey === undefined
533
- && isAchieved === undefined) {
617
+ && isAchieved === undefined
618
+ && priority === undefined) {
534
619
  throw new UsageError('goals update requires at least one field to update');
535
620
  }
621
+ if (priority !== undefined
622
+ && (name !== undefined
623
+ || targetAmount !== undefined
624
+ || targetMonthKey !== undefined
625
+ || isAchieved !== undefined)) {
626
+ throw new UsageError('--priority must be used on its own');
627
+ }
536
628
  return withBaseUrl({
537
629
  command: 'goals-update',
538
630
  goalId,
@@ -540,6 +632,7 @@ function parseGoals(args, baseUrl) {
540
632
  ...(targetAmount === undefined ? {} : { targetAmount }),
541
633
  ...(targetMonthKey === undefined ? {} : { targetMonthKey }),
542
634
  ...(isAchieved === undefined ? {} : { isAchieved }),
635
+ ...(priority === undefined ? {} : { priority }),
543
636
  apply,
544
637
  }, baseUrl);
545
638
  }
@@ -633,6 +726,8 @@ function helpTopic(argv) {
633
726
  || command === 'ask-partner') {
634
727
  if (command === 'accounts' && subcommand === 'update')
635
728
  return 'accounts-update';
729
+ if (command === 'accounts' && subcommand === 'remove')
730
+ return 'accounts-remove';
636
731
  return command;
637
732
  }
638
733
  if (command === 'investments')
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@ import { ICON_KEYS } from './category-metadata.js';
4
4
  import { parseApiResponse, validateAssignmentPayload, validateBudgetUpdatePayload, } 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.0';
7
+ export const CLI_VERSION = '0.9.0';
8
8
  const REQUEST_TIMEOUT_MS = 60_000;
9
9
  const API_ORIGIN_HELP_LINES = [
10
10
  '',
@@ -26,7 +26,8 @@ export function usageText() {
26
26
  ' sloth-agent auth status [--base-url URL]',
27
27
  ' sloth-agent auth logout [--base-url URL]',
28
28
  ' sloth-agent accounts [list] [--base-url URL]',
29
- ' sloth-agent accounts update --account-ref REF --goal-savings-source true|false [--apply]',
29
+ ' sloth-agent accounts update --account-ref REF [fields] [--apply]',
30
+ ' sloth-agent accounts remove --account-ref REF [--apply]',
30
31
  ' sloth-agent investments [--account-ref REF] [--base-url URL]',
31
32
  ' sloth-agent budget --scope personal|joint [--period YYYY-MM] [--base-url URL]',
32
33
  ' sloth-agent budget update --scope personal|joint [--period YYYY-MM]',
@@ -308,19 +309,29 @@ export function accountsUpdateHelpText() {
308
309
  return [
309
310
  'Sloth Agent CLI — accounts update',
310
311
  '',
311
- 'Preview or update whether an owned connected account is used for goal savings.',
312
+ 'Preview or update an owned account. Manual accounts support their editable fields.',
312
313
  '',
313
314
  'Usage:',
314
- ' sloth-agent accounts update --account-ref REF --goal-savings-source true|false [--apply] [--base-url URL]',
315
+ ' sloth-agent accounts update --account-ref REF [fields] [--apply] [--base-url URL]',
315
316
  '',
316
- 'Required inputs:',
317
+ 'Required input:',
317
318
  ' --account-ref REF Opaque accountRef from sloth-agent accounts.',
318
- ' --goal-savings-source true|false Enable or disable goal-savings membership.',
319
+ '',
320
+ 'Update fields (at least one):',
321
+ ' --institution-name NAME Manual account institution.',
322
+ ' --account-name NAME Manual account name.',
323
+ ' --currency CODE Three-letter currency code.',
324
+ ' --ownership individual|joint Manual account ownership.',
325
+ ' --balance-amount AMOUNT Balance-only account balance.',
326
+ ' --account-type savings|investments Balance-only account type.',
327
+ ' --goal-savings-source true|false Goal-savings membership.',
319
328
  '',
320
329
  'Write behavior:',
321
330
  ' Without --apply, returns a JSON preview without credentials or a network request.',
322
331
  ' With --apply, requires agent:write on a write-enabled token and updates saved Sloth metadata.',
323
- ' Partner-owned shared accounts and manual accounts cannot be changed.',
332
+ ' Connected accounts support only --goal-savings-source.',
333
+ ' Manual current accounts cannot change type, balance, or goal-savings membership.',
334
+ ' Partner-owned shared accounts cannot be changed.',
324
335
  ' Unknown, disconnected, or inaccessible references return Account not found.',
325
336
  ...API_ORIGIN_HELP_LINES,
326
337
  '',
@@ -329,6 +340,30 @@ export function accountsUpdateHelpText() {
329
340
  ' Apply mode returns changed and the complete persisted account.',
330
341
  ].join('\n');
331
342
  }
343
+ export function accountsRemoveHelpText() {
344
+ return [
345
+ 'Sloth Agent CLI — accounts remove',
346
+ '',
347
+ 'Preview or archive an owned manual account while retaining its underlying records.',
348
+ '',
349
+ 'Usage:',
350
+ ' sloth-agent accounts remove --account-ref REF [--apply] [--base-url URL]',
351
+ '',
352
+ 'Required input:',
353
+ ' --account-ref REF Opaque accountRef from sloth-agent accounts.',
354
+ '',
355
+ 'Write behavior:',
356
+ ' Without --apply, returns a JSON preview without credentials or a network request.',
357
+ ' With --apply, requires agent:write and archives the manual account.',
358
+ ' Connected and partner-owned accounts cannot be removed.',
359
+ ' Repeating an applied removal succeeds with changed false.',
360
+ ...API_ORIGIN_HELP_LINES,
361
+ '',
362
+ 'Output:',
363
+ ' Preview mode returns dryRun, method, and endpoint.',
364
+ ' Apply mode returns removed, changed, and accountRef.',
365
+ ].join('\n');
366
+ }
332
367
  export function investmentsHelpText() {
333
368
  return [
334
369
  'Sloth Agent CLI — investments',
@@ -562,7 +597,7 @@ export function goalsListHelpText() {
562
597
  ' This command is read-only.',
563
598
  '',
564
599
  'Output:',
565
- ' JSON containing currency and goals. Each goal contains id, name,',
600
+ ' JSON containing currency and goals. Each goal contains id, name, priority,',
566
601
  ' targetAmount, targetMonthKey, isAchieved, and sharedWithPartner.',
567
602
  ].join('\n');
568
603
  }
@@ -615,6 +650,7 @@ export function goalsUpdateHelpText() {
615
650
  ' --target-month YYYY-MM Optional. Replace the target month.',
616
651
  ' --clear-target-month Optional. Remove the target month.',
617
652
  ' --achieved=true|false Optional. Mark the goal achieved or active.',
653
+ ' --priority POSITION Optional. Positive whole-number position; 1 is highest.',
618
654
  ' --apply Optional. Write the partial update.',
619
655
  ' --base-url URL Optional. Override the API origin.',
620
656
  ' -h, --help Show this help.',
@@ -622,6 +658,11 @@ export function goalsUpdateHelpText() {
622
658
  '',
623
659
  'Constraints:',
624
660
  ' Provide at least one field to update.',
661
+ ' Priority must be updated on its own.',
662
+ ' Priority 1 is highest. The position cannot exceed the current goal count.',
663
+ ' Moving a goal shifts the intervening goals automatically.',
664
+ ' Forecast assignments and shared progress refresh when the owner next opens',
665
+ ' the Forecast screen.',
625
666
  ' Set and clear options for the same field are mutually exclusive.',
626
667
  ' Marking a goal achieved removes its forecast assignment.',
627
668
  ' Marking it active again does not restore the previous assignment.',
@@ -634,6 +675,9 @@ export function goalsUpdateHelpText() {
634
675
  ' Without --apply, the command returns a dry-run preview and does not write.',
635
676
  ' Applying requires a write-enabled token created with Allow changes.',
636
677
  '',
678
+ 'Example:',
679
+ ' sloth-agent goals update --goal-id goal-3 --priority 2 --apply',
680
+ '',
637
681
  'Output:',
638
682
  ' Preview mode returns dryRun, method, endpoint, and payload.',
639
683
  ' Apply mode returns the complete persisted goal and currency.',
@@ -702,6 +746,7 @@ export function commandHelpText(topic) {
702
746
  'auth-logout': authLogoutHelpText,
703
747
  accounts: accountsHelpText,
704
748
  'accounts-update': accountsUpdateHelpText,
749
+ 'accounts-remove': accountsRemoveHelpText,
705
750
  investments: investmentsHelpText,
706
751
  budget: budgetHelpText,
707
752
  'budget-update': budgetUpdateHelpText,
@@ -724,6 +769,23 @@ export function commandHelpText(topic) {
724
769
  function writeJson(write, data) {
725
770
  write(`${JSON.stringify(data, null, 2)}\n`);
726
771
  }
772
+ function withListedGoalPriorities(value) {
773
+ const response = value;
774
+ return {
775
+ ...response,
776
+ goals: response.goals.map((goal, index) => ({
777
+ ...goal,
778
+ priority: index + 1,
779
+ })),
780
+ };
781
+ }
782
+ function withUpdatedGoalPriority(value, priority) {
783
+ const response = value;
784
+ return {
785
+ ...response,
786
+ goal: { ...response.goal, priority },
787
+ };
788
+ }
727
789
  function redact(value, token) {
728
790
  return token ? value.split(token).join('[REDACTED]') : value;
729
791
  }
@@ -972,7 +1034,16 @@ export async function runCli(argv = process.argv.slice(2), options = {}) {
972
1034
  dryRun: true,
973
1035
  endpoint,
974
1036
  method: 'PATCH',
975
- payload: { isGoalSavingsSource: parsed.isGoalSavingsSource },
1037
+ payload: parsed.update,
1038
+ });
1039
+ return 0;
1040
+ }
1041
+ if (parsed.command === 'accounts-remove' && !parsed.apply) {
1042
+ const endpoint = `${baseUrl}/api/agent/v1/accounts/${encodeURIComponent(parsed.accountRef)}`;
1043
+ writeJson(writeStdout, {
1044
+ dryRun: true,
1045
+ endpoint,
1046
+ method: 'DELETE',
976
1047
  });
977
1048
  return 0;
978
1049
  }
@@ -997,11 +1068,21 @@ export async function runCli(argv = process.argv.slice(2), options = {}) {
997
1068
  const headers = requestHeaders(token);
998
1069
  if (parsed.command === 'accounts-update') {
999
1070
  const endpoint = `${baseUrl}/api/agent/v1/accounts/${encodeURIComponent(parsed.accountRef)}`;
1000
- const payload = { isGoalSavingsSource: parsed.isGoalSavingsSource };
1001
1071
  const response = await fetchImplementation(endpoint, {
1002
1072
  method: 'PATCH',
1003
1073
  headers: { ...headers, 'Content-Type': 'application/json' },
1004
- body: JSON.stringify(payload),
1074
+ body: JSON.stringify(parsed.update),
1075
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
1076
+ });
1077
+ const data = parseApiResponse(parsed.command, await parseHttpResponse(response, token));
1078
+ writeJson(writeStdout, data);
1079
+ return 0;
1080
+ }
1081
+ if (parsed.command === 'accounts-remove') {
1082
+ const endpoint = `${baseUrl}/api/agent/v1/accounts/${encodeURIComponent(parsed.accountRef)}`;
1083
+ const response = await fetchImplementation(endpoint, {
1084
+ method: 'DELETE',
1085
+ headers,
1005
1086
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
1006
1087
  });
1007
1088
  const data = parseApiResponse(parsed.command, await parseHttpResponse(response, token));
@@ -1104,6 +1185,9 @@ export async function runCli(argv = process.argv.slice(2), options = {}) {
1104
1185
  ...(parsed.isAchieved === undefined
1105
1186
  ? {}
1106
1187
  : { isAchieved: parsed.isAchieved }),
1188
+ ...(parsed.priority === undefined
1189
+ ? {}
1190
+ : { priority: parsed.priority }),
1107
1191
  };
1108
1192
  if (!parsed.apply) {
1109
1193
  writeJson(writeStdout, {
@@ -1124,7 +1208,9 @@ export async function runCli(argv = process.argv.slice(2), options = {}) {
1124
1208
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
1125
1209
  });
1126
1210
  const data = parseApiResponse('goals-update', await parseHttpResponse(response, token));
1127
- writeJson(writeStdout, data);
1211
+ writeJson(writeStdout, parsed.priority === undefined
1212
+ ? data
1213
+ : withUpdatedGoalPriority(data, parsed.priority));
1128
1214
  return 0;
1129
1215
  }
1130
1216
  if (parsed.command === 'goals-delete') {
@@ -1187,7 +1273,7 @@ export async function runCli(argv = process.argv.slice(2), options = {}) {
1187
1273
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
1188
1274
  });
1189
1275
  const data = parseApiResponse('goals-list', await parseHttpResponse(response, token));
1190
- writeJson(writeStdout, data);
1276
+ writeJson(writeStdout, withListedGoalPriorities(data));
1191
1277
  return 0;
1192
1278
  }
1193
1279
  if (parsed.command === 'budget') {
package/dist/contracts.js CHANGED
@@ -423,6 +423,14 @@ function isAccountMutationResponse(value) {
423
423
  && typeof value.changed === 'boolean'
424
424
  && isAccount(value.account));
425
425
  }
426
+ function isAccountRemovalResponse(value) {
427
+ return (isObject(value)
428
+ && hasOnlyFields(value, ['removed', 'changed', 'accountRef'])
429
+ && value.removed === true
430
+ && typeof value.changed === 'boolean'
431
+ && typeof value.accountRef === 'string'
432
+ && /^sloth_account_v1_[A-Za-z0-9_-]{43}$/.test(value.accountRef));
433
+ }
426
434
  function isInvestmentHolding(value) {
427
435
  return (isObject(value)
428
436
  && hasOnlyFields(value, [
@@ -492,27 +500,29 @@ export function parseApiResponse(command, value) {
492
500
  ? isAccountsResponse(value)
493
501
  : command === 'accounts-update'
494
502
  ? isAccountMutationResponse(value)
495
- : command === 'investments'
496
- ? isInvestmentsResponse(value)
497
- : command === 'budget' || command === 'budget-update'
498
- ? isBudgetResponse(value)
499
- : command === 'categories'
500
- ? isCategoryResponse(value)
501
- : command === 'categories-create' || command === 'categories-rename'
502
- ? isCategoryMutationResponse(value)
503
- : command === 'line-items-create' || command === 'line-items-rename'
504
- ? isLineItemMutationResponse(value)
505
- : command === 'transactions'
506
- ? isTransactionsResponse(value)
507
- : command === 'assign'
508
- ? isAssignmentResponse(value)
509
- : command === 'ask-partner'
510
- ? isPartnerResponse(value)
511
- : command === 'goals-list'
512
- ? isGoalsResponse(value)
513
- : command === 'goals-delete'
514
- ? isGoalDeleteResponse(value)
515
- : isGoalMutationResponse(value);
503
+ : command === 'accounts-remove'
504
+ ? isAccountRemovalResponse(value)
505
+ : command === 'investments'
506
+ ? isInvestmentsResponse(value)
507
+ : command === 'budget' || command === 'budget-update'
508
+ ? isBudgetResponse(value)
509
+ : command === 'categories'
510
+ ? isCategoryResponse(value)
511
+ : command === 'categories-create' || command === 'categories-rename'
512
+ ? isCategoryMutationResponse(value)
513
+ : command === 'line-items-create' || command === 'line-items-rename'
514
+ ? isLineItemMutationResponse(value)
515
+ : command === 'transactions'
516
+ ? isTransactionsResponse(value)
517
+ : command === 'assign'
518
+ ? isAssignmentResponse(value)
519
+ : command === 'ask-partner'
520
+ ? isPartnerResponse(value)
521
+ : command === 'goals-list'
522
+ ? isGoalsResponse(value)
523
+ : command === 'goals-delete'
524
+ ? isGoalDeleteResponse(value)
525
+ : isGoalMutationResponse(value);
516
526
  if (!valid) {
517
527
  const label = command === 'assign' ? 'assignment' : command;
518
528
  throw new ApiError(`Invalid ${label} response from the Agent API`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slothmoney/agent-cli",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "Command-line access to the Sloth Money Agent API.",
5
5
  "type": "module",
6
6
  "bin": {