aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--SKILL.md6
-rw-r--r--completions/firefly.bash2
-rw-r--r--firefly_cli/__init__.py2
-rw-r--r--firefly_cli/commands/transaction.py20
-rw-r--r--pyproject.toml2
-rw-r--r--tests/unit/test_commands_transaction.py48
6 files changed, 75 insertions, 5 deletions
diff --git a/SKILL.md b/SKILL.md
index b90ad2e..98b9472 100644
--- a/SKILL.md
+++ b/SKILL.md
@@ -61,7 +61,7 @@ firefly tx edit <id>
[--amount N] [--date YYYY-MM-DD] [--desc TEXT] [--from <acct>] [--to <acct>]
[--category NAME] [--tags a,b] [--type T] # only fields passed are changed
firefly tx delete <id> --yes # --yes required, no prompt
-firefly tx list [--since YYYY-MM-DD] [--until YYYY-MM-DD] [--account NAME] [--limit N]
+firefly tx list [--since YYYY-MM-DD] [--until YYYY-MM-DD] [--account NAME] [--limit N] [--all]
firefly tx get <id>
firefly tx search <query>
firefly category list
@@ -132,6 +132,10 @@ firefly tx delete 76 --yes # remove a duplicate
- `tx list` with no transactions in range returns `[]`. Empty is not an error;
it means no matches, not a failure.
+- `tx list` returns at most `--limit` rows (default 20). When more match, it
+ prints `showing N of M (use --all for all)` to **stderr** (stdout JSON stays
+ clean). Pass `--all` to auto-paginate every page. Critical for reconciliation:
+ a truncated list makes a correct ledger look wrong.
- Amounts are strings in responses, often with trailing zeros
(`"0.010000000000"`). Compare numerically, do not string-match.
- Dates in `tx list` filter by transaction date; omit them to use Firefly's
diff --git a/completions/firefly.bash b/completions/firefly.bash
index b66b400..697cd1a 100644
--- a/completions/firefly.bash
+++ b/completions/firefly.bash
@@ -42,7 +42,7 @@ _firefly() {
"tx add") leaf_opts="--category --date --desc --from --tags --to --type";;
"tx delete") leaf_opts="--yes";;
"tx edit") leaf_opts="--amount --category --date --desc --from --tags --to --type";;
- "tx list") leaf_opts="--account --limit --since --until";;
+ "tx list") leaf_opts="--account --all --limit --since --until";;
esac
# Leaves per group.
diff --git a/firefly_cli/__init__.py b/firefly_cli/__init__.py
index f41267d..79ea6e3 100644
--- a/firefly_cli/__init__.py
+++ b/firefly_cli/__init__.py
@@ -2,4 +2,4 @@
# Copyright (C) 2026 Danilo M. <danix@danix.xyz>
# Licensed under the GNU General Public License v2.0 only.
-__version__ = "0.3.0"
+__version__ = "0.3.1"
diff --git a/firefly_cli/commands/transaction.py b/firefly_cli/commands/transaction.py
index af8a4fb..1d6f219 100644
--- a/firefly_cli/commands/transaction.py
+++ b/firefly_cli/commands/transaction.py
@@ -110,6 +110,8 @@ def _list_args(p):
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)
+ p.add_argument("--all", action="store_true",
+ help="fetch every page (ignores --limit truncation)")
@registry.command("tx list", help="list recent transactions (newest first)", args=_list_args)
def cmd_list(args, ctx):
@@ -123,7 +125,25 @@ def cmd_list(args, ctx):
params["start"] = args.since
if args.until:
params["end"] = args.until
+
+ if args.all:
+ rows, page = [], 1
+ while True:
+ resp = ctx.client.request("GET", path, params={**params, "page": page})
+ rows.extend(output.unwrap(resp))
+ pg = (resp.get("meta") or {}).get("pagination") or {}
+ if page >= (pg.get("total_pages") or 1):
+ break
+ page += 1
+ output.emit(rows, human=ctx.human)
+ return 0
+
resp = ctx.client.request("GET", path, params=params)
+ pg = (resp.get("meta") or {}).get("pagination") or {}
+ total, count = pg.get("total"), pg.get("count")
+ if total is not None and count is not None and count < total:
+ import sys
+ print(f"showing {count} of {total} (use --all for all)", file=sys.stderr)
output.emit(output.unwrap(resp), human=ctx.human)
return 0
diff --git a/pyproject.toml b/pyproject.toml
index 4637c1e..cb0fdaa 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "firefly-iii-agent"
-version = "0.3.0"
+version = "0.3.1"
description = "CLI tool for agent interaction with Firefly III"
readme = "README.md"
requires-python = ">=3.11"
diff --git a/tests/unit/test_commands_transaction.py b/tests/unit/test_commands_transaction.py
index 325dcb7..d8b81da 100644
--- a/tests/unit/test_commands_transaction.py
+++ b/tests/unit/test_commands_transaction.py
@@ -139,9 +139,55 @@ class TestTxList(unittest.TestCase):
ctx, client, _ = make_ctx()
client.request.return_value = {"data": []}
args = MagicMock(since="2026-06-01", until="2026-06-30",
- account=None, limit=10)
+ account=None, limit=10, all=False)
tx.cmd_list(args, ctx)
params = client.request.call_args[1]["params"]
self.assertEqual(params["start"], "2026-06-01")
self.assertEqual(params["end"], "2026-06-30")
self.assertEqual(params["limit"], 10)
+
+ def test_list_warns_when_truncated(self):
+ import io
+ from contextlib import redirect_stderr
+ ctx, client, _ = make_ctx()
+ client.request.return_value = {
+ "data": [{"id": str(i)} for i in range(20)],
+ "meta": {"pagination": {"total": 90, "count": 20,
+ "current_page": 1, "total_pages": 5}},
+ }
+ args = MagicMock(since=None, until=None, account=None, limit=20, all=False)
+ buf = io.StringIO()
+ with redirect_stderr(buf):
+ tx.cmd_list(args, ctx)
+ self.assertIn("showing 20 of 90", buf.getvalue())
+ self.assertEqual(client.request.call_count, 1)
+
+ def test_list_no_warn_when_complete(self):
+ import io
+ from contextlib import redirect_stderr
+ ctx, client, _ = make_ctx()
+ client.request.return_value = {
+ "data": [{"id": "1"}],
+ "meta": {"pagination": {"total": 1, "count": 1,
+ "current_page": 1, "total_pages": 1}},
+ }
+ args = MagicMock(since=None, until=None, account=None, limit=20, all=False)
+ buf = io.StringIO()
+ with redirect_stderr(buf):
+ tx.cmd_list(args, ctx)
+ self.assertEqual(buf.getvalue(), "")
+
+ def test_list_all_paginates(self):
+ ctx, client, _ = make_ctx()
+ def page(method, path, params=None, body=None):
+ p = params["page"]
+ return {
+ "data": [{"id": f"{p}-{i}"} for i in range(2)],
+ "meta": {"pagination": {"total": 4, "count": 2,
+ "current_page": p, "total_pages": 2}},
+ }
+ client.request.side_effect = page
+ args = MagicMock(since=None, until=None, account=None, limit=2, all=True)
+ rc = tx.cmd_list(args, ctx)
+ self.assertEqual(rc, 0)
+ self.assertEqual(client.request.call_count, 2)