aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-07-03 17:18:39 +0200
committerDanilo M. <danix@danix.xyz>2026-07-03 17:18:39 +0200
commit3dac2b81101c7d195f1366afeaff82f3a97c96af (patch)
tree6093fb69a6c654a4a6ab1b002afa9aa8e188abea
parent71658d023fdbf60be6b4501372519bf8171d6e13 (diff)
downloadfirefly-cli-3dac2b81101c7d195f1366afeaff82f3a97c96af.tar.gz
firefly-cli-3dac2b81101c7d195f1366afeaff82f3a97c96af.zip
docs: spec for budget management group (list/create/delete/enable/disable/limit + tx add --budget)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
-rw-r--r--docs/superpowers/specs/2026-07-03-budget-management-design.md156
1 files changed, 156 insertions, 0 deletions
diff --git a/docs/superpowers/specs/2026-07-03-budget-management-design.md b/docs/superpowers/specs/2026-07-03-budget-management-design.md
new file mode 100644
index 0000000..854425f
--- /dev/null
+++ b/docs/superpowers/specs/2026-07-03-budget-management-design.md
@@ -0,0 +1,156 @@
+# Budget management design
+
+Date: 2026-07-03
+Status: approved (design), pending spec review
+Firefly reference: v6.6.6 (matches running instance)
+
+## Goal
+
+Let the agent (and user) manage Firefly III budgets from the `firefly` CLI:
+see spending vs budget, set/change monthly limits, create/delete budgets,
+enable/disable them, and assign a budget to a transaction when adding it.
+
+Ships as ONE MINOR release (new `budget` group + one new optional flag on
+`tx add`, no breaking change to the existing contract).
+
+## Firefly API facts (verified against v6.6.6)
+
+- `GET /api/v1/budgets?start=&end=` — list budgets. With a start/end range the
+ budget objects carry `spent` (array per currency) for that range. Without it,
+ `spent` is null. Budget transformer fields include: `id`, `name`, `active`,
+ `auto_budget_type`, `auto_budget_period`, `auto_budget_amount`,
+ `currency_code`, `spent`.
+- `POST /api/v1/budgets` — create. Accepts `name` (required, unique per user),
+ `active` (bool), `auto_budget_type` (in: reset,rollover,adjusted,none),
+ `auto_budget_amount`, `auto_budget_period`, `currency_code`.
+- `PUT /api/v1/budgets/{id}` — update. Accepts `name`, `active`, and the same
+ auto-budget fields. There is NO dedicated enable/disable endpoint; toggling
+ `active` is done via this update.
+- `DELETE /api/v1/budgets/{id}` — delete.
+- `GET /api/v1/budgets/{id}/limits` — list budget limits (the period caps).
+- `POST /api/v1/budgets/{id}/limits` — create a limit. Accepts `start`
+ (required, date, before end), `end` (required, after start), `amount`
+ (required, positive), `currency_id`/`currency_code`, `notes`. BudgetLimit
+ transformer fields include: `id`, `budget_id`, `start`, `end`, `amount`,
+ `period`, `spent`, `currency_code`.
+- Transaction split accepts `budget_id` (and `budget_name`) — see
+ `StoreRequest.php` lines 262-263. Firefly does NOT auto-create budgets from a
+ transaction: the budget must already exist. So we resolve name -> id and pass
+ `budget_id`.
+
+Budget names are unique per user (`uniqueObjectForUser:budgets,name`), so
+name->id resolution is unambiguous in practice; the resolver still errors hard
+on a miss, matching account handling.
+
+## Commands
+
+New module `firefly_cli/commands/budget.py`. All handlers register via
+`@registry.command(...)` with the decorator immediately above the handler def
+(the v0.3.7 misbinding bug — keep decorator adjacent to `cmd_*`).
+
+| Command | HTTP | Args |
+|---|---|---|
+| `budget list` | `GET /budgets?start&end` | `--start`, `--end` (default: current month) |
+| `budget create <name>` | `POST /budgets` | `--active`/`--inactive` (default active), `--auto-budget-amount`, `--auto-budget-period`, `--auto-budget-type`, `--currency` |
+| `budget delete <ref>` | `DELETE /budgets/{id}` | `--yes` (required, matches `tx delete`) |
+| `budget enable <ref>` | `PUT /budgets/{id}` `{active:true}` | — |
+| `budget disable <ref>` | `PUT /budgets/{id}` `{active:false}` | — |
+| `budget limit list <ref>` | `GET /budgets/{id}/limits` | — |
+| `budget limit set <ref>` | `POST /budgets/{id}/limits` | `--amount` (required), `--start`, `--end` (default current month), `--currency` |
+
+`<ref>` is a budget name OR id, resolved by the new `resolver.budget()`.
+
+`budget limit` is a two-level group (`budget limit list`, `budget limit set`),
+consistent with how the registry keys multi-word command names.
+
+### tx add --budget
+
+Add one optional flag to `tx add`:
+`--budget <ref>` — resolve name/id via `resolver.budget()`, set `budget_id` on
+the transaction split. Omitted = no budget (unchanged behavior).
+
+Not added to `tx edit` this release (YAGNI; add when a re-budgeting task needs
+it).
+
+## Resolver
+
+Add `Resolver.budget(name_or_id)` mirroring `Resolver.account`:
+- numeric-looking arg -> fetch `GET /budgets/{id}` (via a `budget_by_id` path
+ or inline), return the object; miss -> `ResolutionError`.
+- otherwise list budgets and match by name; ambiguous or missing ->
+ `ResolutionError` listing candidates.
+
+Reuse the existing `_match` helper and `_list` pattern. `GET /budgets/{id}`
+returns a single budget; if Firefly returns a non-404 (e.g. the 401 quirk seen
+for unknown account ids) the resolver still fails closed. Add a `ponytail:`
+note only if the id path shows the same 401-for-unknown-id quirk in smoke.
+
+## Period helper
+
+`_current_month()` -> `(first_iso, last_iso)` using `datetime.date`:
+first = `date.today().replace(day=1)`, last = first day of next month minus one
+day (via `calendar.monthrange`). Shared by `budget list` and `budget limit set`
+default range. Stdlib only.
+
+## Output views (--human)
+
+JSON stays the default. Add to `output.py` `_VIEWS`:
+- budget view: columns `id`, `name`, `active`, `spent` (summed/trimmed like tx
+ amounts), plus limit info if a limit is present in the range.
+- budget-limit view: `id`, `budget_id`, `start`, `end`, `amount`, `spent`,
+ `period`.
+
+Match the existing `_VIEWS` signature-matching mechanism (a set of keys ->
+column list). If a resource shape doesn't match cleanly, fall back to raw JSON
+(existing behavior), no crash.
+
+## Completion
+
+Regenerate `completions/firefly.bash` via
+`python scripts/gen_completion.py > completions/firefly.bash`. Add fixed-enum
+values to `FLAG_VALUES` in `gen_completion.py` for `--auto-budget-type`
+(reset|rollover|adjusted|none), `--auto-budget-period`, and the
+`--active/--inactive` pair.
+
+## Errors
+
+All failures are `FireflyError` subclasses (existing contract): unresolved
+budget ref -> `ResolutionError` with candidates; a missing required `--amount`
+on `limit set` -> argparse error; Firefly 4xx/5xx -> surfaced by `client.py`.
+`budget delete` without `--yes` -> hard error, no call made.
+
+## Testing
+
+- Unit tests under `tests/unit/` per command, mocking `ctx.client` /
+ `ctx.resolver` (the established pattern). Cover: list with/without period,
+ create (active + inactive + auto-budget), delete (with and without --yes),
+ enable/disable body shape, limit list, limit set (default + explicit period),
+ tx add --budget sets `budget_id`, resolver.budget name + id + miss +
+ ambiguous.
+- One handler-registration regression test asserting each registered
+ `budget*` command binds to its `cmd_*` (registry misbinding is invisible to
+ direct-call unit tests — the v0.3.7 lesson).
+- Live smoke against the test instance (source
+ `~/.config/firefly-cli/test-creds.env`) after implementation: create a
+ budget, set a limit, list, assign via tx add (create-then-delete own data),
+ because the mocked suite bypasses the registry.
+
+## SKILL.md
+
+Document the new `budget` group, name->id resolution for budgets (must
+pre-exist, not auto-created), the current-month default period, and the
+`tx add --budget` flag.
+
+## Versioning
+
+New command group + new optional flag, contract-additive -> MINOR bump. Bump
+`pyproject.toml` and `firefly_cli/__init__.py` together, tag `vX.Y.Z` signed,
+push `--follow-tags`.
+
+## Out of scope (YAGNI)
+
+- `budget update` for rename / auto-budget edits (delete+recreate, or web UI).
+- `budget limit update` / `budget limit delete`.
+- `tx edit --budget`.
+- available-budgets endpoints, object-groups.
+Add any of these when a concrete task needs it.