From b50dac67b7f7d48c0578b3f2c6fb05cdf3a85cdd Mon Sep 17 00:00:00 2001 From: Steven Coaila Date: Sat, 15 Aug 2026 11:26:50 -0500 Subject: [PATCH] feat(accounts): expose the vault flag on create and update accounts create --vault and accounts update --vault/--no-vault forward the backend savings-vault flag. The backend owns the rule: a vault account counts toward totals but is rejected as the source of an expense, card payment, loan payment or subscription. --- CHANGELOG.md | 9 ++++++++ README.md | 4 ++++ src/commands/accounts/create.ts | 6 ++++++ src/commands/accounts/update.ts | 3 +++ src/lib/types.ts | 1 + tests/commands/accounts-vault.test.ts | 31 +++++++++++++++++++++++++++ 6 files changed, 54 insertions(+) create mode 100644 tests/commands/accounts-vault.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 30b304a..32fbeca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Added + +- `accounts create --vault` and `accounts update --vault/--no-vault` set the + savings-vault flag. A vault account keeps counting toward totals but the + backend rejects it as the source of an expense, a card payment, a loan + payment or a subscription with `422 VAULT_CANNOT_SEND`; transferring out of + it stays allowed. CREDIT and INVESTMENT accounts answer + `422 VAULT_TYPE_UNSUPPORTED`. + ## [0.9.0] - 2026-08-12 ### Added diff --git a/README.md b/README.md index 5ca58f6..5ccbe9a 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,10 @@ List commands are intentionally agent-friendly: - `accounts list --include-archived` includes archived accounts in the account array and adds `archivedAccounts` metadata. Balance/debt totals remain the active-account totals returned by LucasApp. +- `accounts` exposes the `vault` flag (`create --vault`, + `update --vault/--no-vault`). A vault account counts toward totals and can + receive money, but the backend rejects it as the source of an expense, card + payment, loan payment or subscription; only transfers out of it are allowed. - `accounts list` adds `availableCredit` to CREDIT accounts: `max(0, creditLimit - currentDebt)`. A negative `currentDebt` (overpaid card) intentionally raises `availableCredit` above `creditLimit`. diff --git a/src/commands/accounts/create.ts b/src/commands/accounts/create.ts index e2bfad5..9e233e1 100644 --- a/src/commands/accounts/create.ts +++ b/src/commands/accounts/create.ts @@ -15,6 +15,7 @@ export interface CreateAccountOptions { cashbackRate?: string; color?: string; icon?: string; + vault?: boolean; } export function buildCreateAccountBody( @@ -53,6 +54,7 @@ export function buildCreateAccountBody( body.cashbackEnabled = opts.cashbackEnabled; if (opts.cashbackRate !== undefined) body.cashbackRate = parseFiniteNumber(opts.cashbackRate, "--cashback-rate"); + if (opts.vault !== undefined) body.vault = opts.vault; return body; } @@ -89,4 +91,8 @@ export const createAccountCommand = new Command("create") ) .option("--color ", "Account color") .option("--icon ", "Account icon") + .option( + "--vault", + "Savings vault: counts toward totals but cannot fund payments (not CREDIT/INVESTMENT)", + ) .action(runCreateAccount); diff --git a/src/commands/accounts/update.ts b/src/commands/accounts/update.ts index 7de12e1..1bb3eb2 100644 --- a/src/commands/accounts/update.ts +++ b/src/commands/accounts/update.ts @@ -31,6 +31,8 @@ export const updateAccountCommand = new Command("update") .option("--display-order ", "Display order") .option("--excluded", "Exclude from totals") .option("--no-excluded", "Include in totals") + .option("--vault", "Mark as savings vault (cannot fund payments)") + .option("--no-vault", "Clear the savings vault flag") .option("--is-archived", "Archive account") .option("--no-is-archived", "Unarchive account") .action(async (id: string, opts) => { @@ -68,6 +70,7 @@ export const updateAccountCommand = new Command("update") }, { opt: "displayOrder", body: "displayOrder", type: "number" }, { opt: "excluded", body: "excluded", type: "boolean" }, + { opt: "vault", body: "vault", type: "boolean" }, { opt: "isArchived", body: "isArchived", type: "boolean" }, ]); const data = await apiRequest( diff --git a/src/lib/types.ts b/src/lib/types.ts index a802b4c..a4a32a2 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -7,6 +7,7 @@ export interface Account { type?: string; creditLimit?: number | null; currentDebt?: number | null; + vault?: boolean; [key: string]: unknown; } diff --git a/tests/commands/accounts-vault.test.ts b/tests/commands/accounts-vault.test.ts new file mode 100644 index 0000000..38503c3 --- /dev/null +++ b/tests/commands/accounts-vault.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, it, vi } from "vitest"; + +const apiRequest = vi.fn(); + +vi.mock("../../src/lib/api-client.js", () => ({ + apiRequest, +})); + +const { buildCreateAccountBody } = + await import("../../src/commands/accounts/create.js"); + +describe("accounts vault flag", () => { + it("omits vault when the flag is not passed", () => { + const body = buildCreateAccountBody({ + name: "ITK Soles", + type: "DEBIT", + bank: "Interbank", + }); + expect(body).not.toHaveProperty("vault"); + }); + + it("sends vault true when --vault is passed", () => { + const body = buildCreateAccountBody({ + name: "Ahorro carro", + type: "SAVINGS", + bank: "Interbank", + vault: true, + }); + expect(body).toMatchObject({ type: "SAVINGS", vault: true }); + }); +});