From b38c9e39cc3c6b298662a324eb52319d18755cea Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Mon, 12 Oct 2026 19:38:33 -0600 Subject: [PATCH] cli: just gui runs the viewer in the background; gui-fg, gui-status, [viewer] open_at_start The scaffolded justfile now detaches the viewer (log in books-data/gui.log) and won't open a second one for the same books. AGENTS.md tells the agent to check gui-status at session start and follow open_at_start (ask/always/never) in wanderbooks.toml. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01DeBdcyb3YyRkcpicBZH5wh --- crates/ledger-cli/src/main.rs | 35 +++++++++++++++++++++++++++++++-- crates/ledger-cli/tests/init.rs | 6 ++++-- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/crates/ledger-cli/src/main.rs b/crates/ledger-cli/src/main.rs index d6f1b5d..eef25d3 100644 --- a/crates/ledger-cli/src/main.rs +++ b/crates/ledger-cli/src/main.rs @@ -60,9 +60,24 @@ codex: codex-setup: codex mcp add "@@CODEX_NAME@@" -- "@@LEDGER_MCP@@" --data-dir "{{justfile_directory()}}/books-data" -# Open the live, read-only view of these books +# Open the live, read-only view of these books (in the background; once per books dir) gui: - "@@LEDGER_GUI@@" --data-dir books-data --books-toml wanderbooks.toml + #!/usr/bin/env bash + set -u + if just gui-status &>/dev/null; then echo "The viewer is already open."; exit 0; fi + log="{{justfile_directory()}}/books-data/gui.log" + nohup "@@LEDGER_GUI@@" --data-dir "{{justfile_directory()}}/books-data" \ + --books-toml "{{justfile_directory()}}/wanderbooks.toml" >"$log" 2>&1 /dev/null; then echo "Viewer open (pid $!)."; else echo "The viewer didn't start:"; cat "$log"; exit 1; fi + +# Open the viewer in the foreground (logs in the terminal; Ctrl-C closes it) +gui-fg: + "@@LEDGER_GUI@@" --data-dir "{{justfile_directory()}}/books-data" --books-toml "{{justfile_directory()}}/wanderbooks.toml" + +# Is the viewer open for these books? Prints open/closed; exits 0 only if open +gui-status: + @pgrep -f -- "ledger-gui --data-dir {{justfile_directory()}}/books-data( |$)" >/dev/null && echo open || { echo closed; exit 1; } # Commit the journal: the git history of this dir is the archive backup: @@ -169,6 +184,14 @@ Bank feeds aren't automatic here: import the bank's CSV export them read access) and open it with `wanderbooks gui`, or `ledger-gui --data-dir books-data`. Nothing they do there can change the books. +## Session start + +Run `just gui-status`. If the viewer isn't open, follow `[viewer] +open_at_start` in `wanderbooks.toml`: `"ask"` (the default) offers to open +it, `"always"` opens it with `just gui`, `"never"` leaves it. `just gui` +starts it in the background and does nothing if it's already open. If the +human tells you their preference, write it there. + ## Ongoing - Every transaction: draft, preview, confirm with the human, post, attach @@ -203,6 +226,12 @@ fiscal_year_start = "01-01" data_dir = "books-data" accounting_basis = "accrual" receipt_threshold = "25.00" + +[viewer] +# When a session starts and the viewer isn't open (`just gui-status`), the +# agent: "ask" offers to open it, "always" opens it (`just gui`), "never" +# leaves it closed. +open_at_start = "ask" "#; const GITIGNORE: &str = r#"# The SQLite projection is disposable — it rebuilds from journal.jsonl. @@ -210,6 +239,8 @@ books-data/wanderbooks.db books-data/wanderbooks.db-* # Generated exports can always be regenerated from the journal. exports/ +# The background viewer's log (`just gui`). +books-data/gui.log "#; const START_PROMPT: &str = "Hi! These are brand-new books. Please set them up with me."; diff --git a/crates/ledger-cli/tests/init.rs b/crates/ledger-cli/tests/init.rs index 65618f9..fad8174 100644 --- a/crates/ledger-cli/tests/init.rs +++ b/crates/ledger-cli/tests/init.rs @@ -99,8 +99,8 @@ fn the_server_is_registered_for_this_dir() { ); // The justfile opens this dir's books in the GUI. let just = fs::read_to_string(books.join("justfile")).unwrap(); - assert!(just.contains("ledger-gui\" --data-dir books-data")); - for recipe in ["agent:", "gui:", "backup:", "update:"] { + assert!(just.contains("ledger-gui\" --data-dir \"{{justfile_directory()}}/books-data\"")); + for recipe in ["agent:", "gui:", "gui-fg:", "gui-status:", "backup:", "update:"] { assert!(just.contains(recipe), "justfile has {recipe}"); } assert_eq!( @@ -128,6 +128,8 @@ fn agents_md_carries_the_contract() { "chart_template", "to the cent", "just gui", + "just gui-status", + "open_at_start", ] { assert!(md.contains(phrase), "AGENTS.md mentions {phrase:?}"); }