diff options
Diffstat (limited to 'firefly_cli/commands')
| -rw-r--r-- | firefly_cli/commands/account.py | 38 | ||||
| -rw-r--r-- | firefly_cli/commands/auth.py | 4 | ||||
| -rw-r--r-- | firefly_cli/commands/category.py | 2 | ||||
| -rw-r--r-- | firefly_cli/commands/tag.py | 2 | ||||
| -rw-r--r-- | firefly_cli/commands/transaction.py | 59 |
5 files changed, 94 insertions, 11 deletions
diff --git a/firefly_cli/commands/account.py b/firefly_cli/commands/account.py index fa2dc65..9dbfab6 100644 --- a/firefly_cli/commands/account.py +++ b/firefly_cli/commands/account.py @@ -1,26 +1,58 @@ # Copyright (C) 2026 Danilo M. <danix@danix.xyz> GPL-2.0-only from firefly_cli import registry, output +from firefly_cli.errors import FireflyError + +# v1 scope: the everyday types. Liabilities need extra required fields +# (liability_type/direction/amount); add when needed. +_CREATE_TYPES = ("asset", "expense", "revenue") def _list_args(p): p.add_argument("--type", help="filter: asset, expense, revenue, liability, ...") -@registry.command("account list", help="list accounts", args=_list_args) +@registry.command("account list", help="list accounts; optionally filter by --type", args=_list_args) def cmd_list(args, ctx): params = {"type": args.type} if getattr(args, "type", None) else None resp = ctx.client.request("GET", "/api/v1/accounts", params=params) output.emit(output.unwrap(resp), human=ctx.human) return 0 +def _create_args(p): + p.add_argument("name", help="account name (must be unique)") + p.add_argument("--type", required=True, + help="asset, expense, or revenue") + p.add_argument("--opening-balance", dest="opening_balance", default=None, + help="initial balance (asset accounts); dated today") + p.add_argument("--currency", default=None, help="currency code, e.g. EUR") + +@registry.command("account create", help="create an asset, expense, or revenue account", args=_create_args) +def cmd_create(args, ctx): + if args.type not in _CREATE_TYPES: + raise FireflyError( + f'Unsupported account type "{args.type}". ' + f'Use one of: {", ".join(_CREATE_TYPES)}.') + body = {"name": args.name, "type": args.type} + if args.type == "asset": + body["account_role"] = "defaultAsset" + if args.opening_balance is not None: + from datetime import date as _date + body["opening_balance"] = str(args.opening_balance) + body["opening_balance_date"] = _date.today().isoformat() + if args.currency: + body["currency_code"] = args.currency + resp = ctx.client.request("POST", "/api/v1/accounts", body=body) + output.emit(output.unwrap(resp), human=ctx.human) + return 0 + def _name_arg(p): p.add_argument("account", help="account name or id") -@registry.command("account get", help="show one account", args=_name_arg) +@registry.command("account get", help="show full details for one account (name or id)", args=_name_arg) def cmd_get(args, ctx): acc = ctx.resolver.account(args.account) output.emit(acc, human=ctx.human) return 0 -@registry.command("account balance", help="show account balance", args=_name_arg) +@registry.command("account balance", help="show current balance for one account (name or id)", args=_name_arg) def cmd_balance(args, ctx): acc = ctx.resolver.account(args.account) output.emit({"id": acc["id"], "name": acc.get("name"), diff --git a/firefly_cli/commands/auth.py b/firefly_cli/commands/auth.py index d2319e9..99e79c9 100644 --- a/firefly_cli/commands/auth.py +++ b/firefly_cli/commands/auth.py @@ -6,7 +6,7 @@ def _set_args(p): p.add_argument("--url", help="Firefly III base URL") p.add_argument("--token", help="Personal Access Token") -@registry.command("auth set", help="write url+token to config", args=_set_args) +@registry.command("auth set", help="save url + token to the config file (no API call)", args=_set_args) def cmd_set(args, ctx): url = args.url or input("Firefly III URL: ").strip() token = args.token or getpass.getpass("Personal Access Token: ").strip() @@ -14,7 +14,7 @@ def cmd_set(args, ctx): output.emit({"written": str(path)}, human=ctx.human) return 0 -@registry.command("auth test", help="verify connectivity and token") +@registry.command("auth test", help="check the configured url + token reach Firefly") def cmd_test(args, ctx): resp = ctx.client.request("GET", "/api/v1/about") output.emit(resp.get("data", resp), human=ctx.human) diff --git a/firefly_cli/commands/category.py b/firefly_cli/commands/category.py index a7ddbe8..d4bad93 100644 --- a/firefly_cli/commands/category.py +++ b/firefly_cli/commands/category.py @@ -1,7 +1,7 @@ # Copyright (C) 2026 Danilo M. <danix@danix.xyz> GPL-2.0-only from firefly_cli import registry, output -@registry.command("category list", help="list categories") +@registry.command("category list", help="list all categories known to Firefly") def cmd_list(args, ctx): resp = ctx.client.request("GET", "/api/v1/categories") output.emit(output.unwrap(resp), human=ctx.human) diff --git a/firefly_cli/commands/tag.py b/firefly_cli/commands/tag.py index 668c4ae..beed56c 100644 --- a/firefly_cli/commands/tag.py +++ b/firefly_cli/commands/tag.py @@ -1,7 +1,7 @@ # Copyright (C) 2026 Danilo M. <danix@danix.xyz> GPL-2.0-only from firefly_cli import registry, output -@registry.command("tag list", help="list tags") +@registry.command("tag list", help="list all tags known to Firefly") def cmd_list(args, ctx): resp = ctx.client.request("GET", "/api/v1/tags") output.emit(output.unwrap(resp), human=ctx.human) diff --git a/firefly_cli/commands/transaction.py b/firefly_cli/commands/transaction.py index be8d281..af8a4fb 100644 --- a/firefly_cli/commands/transaction.py +++ b/firefly_cli/commands/transaction.py @@ -1,5 +1,6 @@ # Copyright (C) 2026 Danilo M. <danix@danix.xyz> GPL-2.0-only from firefly_cli import registry, output +from firefly_cli.errors import FireflyError # Inference table keyed by (source_type, destination_type) -> firefly tx type. def _infer_type(src_type, dst_type): @@ -30,7 +31,7 @@ def _add_args(p): p.add_argument("--type", default=None, help="withdrawal|deposit|transfer (overrides inference)") -@registry.command("tx add", help="record a transaction", args=_add_args) +@registry.command("tx add", help="record a transaction; source/destination resolve to accounts, category/tags auto-create", args=_add_args) def cmd_add(args, ctx): src = ctx.resolver.account(args.source) dst = ctx.resolver.account(args.dest) @@ -54,13 +55,63 @@ def cmd_add(args, ctx): output.emit(output.unwrap(resp), human=ctx.human) return 0 +def _edit_args(p): + p.add_argument("id") + p.add_argument("--amount", default=None) + p.add_argument("--date", default=None, help="YYYY-MM-DD") + p.add_argument("--desc", default=None) + p.add_argument("--from", dest="source", default=None, help="source account") + p.add_argument("--to", dest="dest", default=None, help="destination account") + p.add_argument("--category", default=None) + p.add_argument("--tags", default=None, help="comma-separated") + p.add_argument("--type", default=None, help="withdrawal|deposit|transfer") + +# ponytail: single-split journals only; multi-split edits need transaction_journal_id per row. +@registry.command("tx edit", help="modify one transaction by id; only the fields you pass change", args=_edit_args) +def cmd_edit(args, ctx): + split = {} + if args.amount is not None: + split["amount"] = str(args.amount) + if args.date is not None: + split["date"] = args.date + if args.desc is not None: + split["description"] = args.desc + if args.source is not None: + split["source_id"] = ctx.resolver.account(args.source)["id"] + if args.dest is not None: + split["destination_id"] = ctx.resolver.account(args.dest)["id"] + if args.category is not None: + split["category_name"] = args.category + if args.tags is not None: + split["tags"] = [t.strip() for t in args.tags.split(",") if t.strip()] + if args.type is not None: + split["type"] = args.type + if not split: + raise FireflyError("tx edit: nothing to change; pass at least one field") + resp = ctx.client.request("PUT", f"/api/v1/transactions/{args.id}", + body={"transactions": [split]}) + output.emit(output.unwrap(resp), human=ctx.human) + return 0 + +def _delete_args(p): + p.add_argument("id") + p.add_argument("--yes", action="store_true", help="confirm deletion (required)") + +@registry.command("tx delete", help="delete one transaction by id (requires --yes)", args=_delete_args) +def cmd_delete(args, ctx): + if not args.yes: + raise FireflyError(f"tx delete {args.id}: refusing without --yes") + ctx.client.request("DELETE", f"/api/v1/transactions/{args.id}") + output.emit({"deleted": args.id}, human=ctx.human) + return 0 + def _list_args(p): p.add_argument("--since", default=None, help="start date YYYY-MM-DD") p.add_argument("--until", default=None, help="end date YYYY-MM-DD") p.add_argument("--account", default=None, help="filter by account name") p.add_argument("--limit", type=int, default=20) -@registry.command("tx list", help="list transactions", args=_list_args) +@registry.command("tx list", help="list recent transactions (newest first)", args=_list_args) def cmd_list(args, ctx): if args.account: acc = ctx.resolver.account(args.account) @@ -79,7 +130,7 @@ def cmd_list(args, ctx): def _id_arg(p): p.add_argument("id") -@registry.command("tx get", help="show one transaction", args=_id_arg) +@registry.command("tx get", help="show full details for one transaction by id", args=_id_arg) def cmd_get(args, ctx): resp = ctx.client.request("GET", f"/api/v1/transactions/{args.id}") output.emit(output.unwrap(resp), human=ctx.human) @@ -88,7 +139,7 @@ def cmd_get(args, ctx): def _query_arg(p): p.add_argument("query") -@registry.command("tx search", help="search transactions", args=_query_arg) +@registry.command("tx search", help="search transactions by Firefly query string", args=_query_arg) def cmd_search(args, ctx): resp = ctx.client.request("GET", "/api/v1/search/transactions", params={"query": args.query}) |
