summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--SKILL.md19
-rw-r--r--firefly_cli/commands/transaction.py3
-rw-r--r--tests/unit/test_commands_transaction.py12
3 files changed, 26 insertions, 8 deletions
diff --git a/SKILL.md b/SKILL.md
index 6051321..373e5f4 100644
--- a/SKILL.md
+++ b/SKILL.md
@@ -32,11 +32,15 @@ If `firefly` is not on PATH, run from the repo with `python -m firefly_cli ...`
object. Do not pass `--human` when you intend to parse, it prints tables.
- **Exit code 0 = success, 1 = error.** On error, `{"error": "..."}` goes to
stderr. Always check the exit code before trusting output.
-- **You pass names, not IDs.** `--from test01`, `--category Groceries`. The CLI
- resolves them. An unknown or ambiguous name is a HARD error that lists the
- candidates and exits 1. When that happens, read the candidates, pick the
- right one, and retry. NEVER guess an account, a wrong account moves real
- money.
+- **You pass names, not IDs.** `--from test01`, `--to Groceries`. For
+ **accounts** the CLI resolves the name to an ID; an unknown or ambiguous
+ account is a HARD error that lists the candidates and exits 1. When that
+ happens, read the candidates, pick the right one, and retry. NEVER guess an
+ account, a wrong account moves real money.
+- **Categories and tags auto-create.** `--category NAME` and `--tags a,b` are
+ passed straight to Firefly, which creates the category/tag if it does not
+ exist. No resolution, no error on a new name. Reuse an existing name (see
+ `firefly category list` / `firefly tag list`) to avoid duplicates.
- **`tx add` infers the transaction type** from the account types: asset to
expense = withdrawal, revenue to asset = deposit, asset to asset = transfer.
Override with `--type withdrawal|deposit|transfer` only when inference is
@@ -68,8 +72,9 @@ Global: `--human` (tables, do not use when parsing), `--url`/`--token`
firefly tx add 42.50 --from test01 --to Groceries --desc "weekly shop" --tags food
```
`test01` is an asset account, `Groceries` an expense account, so this is a
-withdrawal. If `Groceries` does not exist, the CLI errors with the available
-expense accounts; pick one or ask the user before creating a new category.
+withdrawal. `Groceries` here is the destination **expense account** and must
+exist (resolved by name). The `--tags food` and any `--category NAME` are
+auto-created by Firefly if new.
**Record income:**
```bash
diff --git a/firefly_cli/commands/transaction.py b/firefly_cli/commands/transaction.py
index 7d48f78..be8d281 100644
--- a/firefly_cli/commands/transaction.py
+++ b/firefly_cli/commands/transaction.py
@@ -45,7 +45,8 @@ def cmd_add(args, ctx):
"destination_id": dst["id"],
}
if args.category:
- split["category_name"] = ctx.resolver.category(args.category).get("name", args.category)
+ # Pass name raw; Firefly auto-creates the category if it doesn't exist.
+ split["category_name"] = args.category
if args.tags:
split["tags"] = [t.strip() for t in args.tags.split(",") if t.strip()]
resp = ctx.client.request("POST", "/api/v1/transactions",
diff --git a/tests/unit/test_commands_transaction.py b/tests/unit/test_commands_transaction.py
index db187b7..7301002 100644
--- a/tests/unit/test_commands_transaction.py
+++ b/tests/unit/test_commands_transaction.py
@@ -55,6 +55,18 @@ class TestTxAdd(unittest.TestCase):
self.assertEqual(split["type"], "transfer")
self.assertEqual(split["tags"], ["food", "fun"])
+ def test_category_passed_raw_not_resolved(self):
+ # Category name goes straight to Firefly (auto-creates); resolver untouched.
+ ctx, client, resolver = make_ctx()
+ resolver.account.side_effect = lambda n: {"id": "1", "type": "asset", "name": n}
+ client.request.return_value = {"data": {"id": "1", "attributes": {}}}
+ args = MagicMock(amount="5", source="A", dest="B", desc=None, date=None,
+ category="Brand New Cat", tags=None, type="withdrawal")
+ tx.cmd_add(args, ctx)
+ split = client.request.call_args[1]["body"]["transactions"][0]
+ self.assertEqual(split["category_name"], "Brand New Cat")
+ resolver.category.assert_not_called()
+
class TestTxList(unittest.TestCase):
def test_list_passes_date_params(self):
ctx, client, _ = make_ctx()