summaryrefslogtreecommitdiffstats
path: root/docs/superpowers
diff options
context:
space:
mode:
Diffstat (limited to 'docs/superpowers')
-rw-r--r--docs/superpowers/specs/2026-06-30-firefly-cli-design.md164
1 files changed, 164 insertions, 0 deletions
diff --git a/docs/superpowers/specs/2026-06-30-firefly-cli-design.md b/docs/superpowers/specs/2026-06-30-firefly-cli-design.md
new file mode 100644
index 0000000..13d9f4f
--- /dev/null
+++ b/docs/superpowers/specs/2026-06-30-firefly-cli-design.md
@@ -0,0 +1,164 @@
+# firefly-cli — Design
+
+Date: 2026-06-30
+
+## Goal
+
+A command-line tool that lets an LLM agent (and the user) interact with a
+Firefly III instance over its REST API. Agent-first output, a lean set of
+curated high-value commands, minimal dependencies, and an internal structure
+that makes adding new commands mechanical.
+
+The tool documents and reads from the cloned Firefly III codebase at
+`../GITHUB/firefly-iii/` for reference only. It never writes to that codebase.
+
+## Decisions (locked during brainstorming)
+
+- **Scope:** curated agent verbs, not a full mirror of all ~254 endpoints.
+ Grow the verb set over time.
+- **Language:** Python 3, standard library only (no third-party runtime deps).
+- **Output:** JSON by default (agent-first); `--human` flag for aligned tables.
+- **Config/auth:** Personal Access Token (Bearer). Config at
+ `~/.config/firefly-cli/config.toml`, read with stdlib `tomllib`. Env vars
+ `FIREFLY_URL` / `FIREFLY_TOKEN` override the file. `auth set` writes the
+ 2-key file via a string template (tomllib cannot write; templating keeps
+ deps at zero).
+- **Name resolution:** name args (`--from`, `--to`, `--category`, account/tag
+ names) resolve to numeric IDs internally. Ambiguous (>1 match) or no match
+ is a hard error listing candidates — never a silent guess. Real money: an
+ agent picking the wrong account must fail loudly.
+- **Project size:** treated as a large project — a proper Python package from
+ day one (not single-file). Package name `firefly_cli` (CLI command
+ `firefly`) to avoid clashing with the main `firefly-iii` project.
+- **Testing:** TDD. Unit tests with mocked HTTP run always. Integration tests
+ hit a live test account, gated behind `FIREFLY_TEST_URL` /
+ `FIREFLY_TEST_TOKEN`, skipped otherwise. They verify real response shapes
+ and self-clean (create-then-delete their own records). Never run against
+ real data.
+- **License:** GPLv2-only. Full LICENSE, per-file header, README section,
+ added early.
+
+## Architecture
+
+A Python package, command `firefly` (also runnable as `python -m firefly_cli`).
+
+```
+firefly-cli/ # repo root
+├── firefly_cli/
+│ ├── __init__.py
+│ ├── __main__.py # python -m firefly_cli -> cli.main()
+│ ├── cli.py # argparse top-level; builds subparsers from the registry
+│ ├── config.py # tomllib read + template write + env override
+│ ├── client.py # HTTP layer, auth headers, error surfacing
+│ ├── resolver.py # name -> id lookup, ambiguity/no-match errors
+│ ├── output.py # json (default) / --human table rendering
+│ └── commands/
+│ ├── __init__.py # command registry; discovers/loads command modules
+│ ├── auth.py
+│ ├── account.py
+│ ├── transaction.py
+│ ├── category.py
+│ └── tag.py
+├── tests/
+│ ├── unit/ # mocked HTTP, always run
+│ └── integration/ # live test account, gated by FIREFLY_TEST_*
+├── pyproject.toml # console_script: firefly = firefly_cli.cli:main
+├── LICENSE # GPLv2-only
+├── README.md
+└── CLAUDE.md
+```
+
+### Layers and responsibilities
+
+- **config.py** — `load()` returns resolved `url` + `token` (env over file over
+ error). `write(url, token)` templates the TOML file and creates the config
+ dir. Clear error if config missing, pointing at `firefly auth set`.
+- **client.py** — single `request(method, path, params=None, body=None)` using
+ `urllib.request`. Sets `Authorization: Bearer <token>`,
+ `Accept: application/vnd.api+json`, `Content-Type: application/json`. On
+ non-2xx raises an error carrying HTTP status + Firefly's structured error
+ body (Firefly returns useful validation errors). Returns parsed JSON.
+- **resolver.py** — `resolve(kind, name) -> id`. Lists the relevant collection
+ via client, case-insensitive exact name match. 0 matches or >1 matches raise
+ with the candidate list. Numeric-as-ID is a later enhancement, not v1.
+- **output.py** — `emit(data, human=False)`. Default prints JSON; for list
+ results, unwraps Firefly's JSON:API envelope so the agent gets a clean array
+ of resource objects. `--human` renders aligned tables. Errors go to stderr as
+ JSON `{"error": ...}` with non-zero exit.
+- **commands/** — each module defines one or more commands and **self-registers**
+ with the registry in `commands/__init__.py`. A command declares: name, the
+ argparse arguments it needs, and a handler `fn(args, ctx)` where `ctx` bundles
+ config + client + resolver + output. `cli.py` iterates the registry to build
+ subparsers. **Adding a command group = drop a module in `commands/`** — no
+ changes elsewhere. This is the expandability mechanism.
+
+## Commands (v1)
+
+```
+firefly auth set # prompt/flags -> write config.toml
+firefly auth test # GET /about, confirm connectivity + token
+
+firefly account list [--type asset|expense|revenue|liability|...]
+firefly account get <name|id>
+firefly account balance <name|id>
+
+firefly tx add --from <acct> --to <acct> <amount> \
+ [--desc TEXT] [--date YYYY-MM-DD] [--category NAME] [--tags a,b]
+firefly tx list [--since DATE] [--until DATE] [--account NAME] [--limit N]
+firefly tx get <id>
+firefly tx search <query>
+
+firefly category list
+firefly tag list
+```
+
+Global flags: `--human`, `--url`, `--token` (overrides for one invocation).
+
+Budgets are intentionally deferred from v1 (easy to add later via a new
+command module).
+
+### tx add semantics
+
+Firefly transactions are split-based; `tx add` builds a single-split
+transaction. Transaction **type is inferred from the from/to account types**:
+asset→expense = withdrawal, revenue→asset = deposit, asset→asset = transfer.
+Overridable with `--type`. `--date` defaults to today.
+
+## Data flow (tx add example)
+
+1. Parse args. 2. Resolve `--from`/`--to` names to account IDs (and types).
+3. Infer transaction type from account types (or use `--type`).
+4. Resolve `--category` name if given. 5. Build the JSON:API transaction body.
+6. `POST /api/v1/transactions`. 7. Emit created resource (JSON or `--human`).
+
+## Error handling
+
+- Missing/invalid config → message pointing to `firefly auth set`, non-zero exit.
+- API 4xx/5xx → surface Firefly's error message + HTTP status, non-zero exit.
+- Name resolution failure (0 or >1 match) → list candidates, non-zero exit.
+- All errors as `{"error": ...}` on stderr in JSON mode.
+
+## Testing strategy
+
+- **Unit (always):** mock `client.request`. Cover request building
+ (URL/params/headers/body), name resolution including ambiguity and no-match,
+ type inference, envelope unwrapping, config precedence (env over file),
+ config templating round-trip. stdlib `unittest`.
+- **Integration (gated):** run only when `FIREFLY_TEST_URL` /
+ `FIREFLY_TEST_TOKEN` are set, else skipped. Verify real response shapes
+ (envelope, account-type values, error format) against the user's test
+ account. Write tests create-then-delete their own records. Capture confirmed
+ shapes as fixtures so unit tests stay grounded in reality.
+
+## Licensing
+
+GPLv2-only. Fetch official LICENSE text from gnu.org. Per-source-file header:
+`Copyright (C) 2026 Danilo M. <danix@danix.xyz>`. README License section.
+Added at project start.
+
+## Out of scope (v1)
+
+Budgets, bills, piggy banks, rules, recurring transactions, attachments,
+reports, currencies management, OAuth flow, multi-split transactions, the
+generic raw-API escape hatch. All are natural later additions via new command
+modules.