From 799aee58ea0fe1d76c674d67310fa2931dabd8de Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Mon, 12 Oct 2026 19:41:48 -0600 Subject: [PATCH] cli: new books dirs get a SessionStart hook that checks the viewer The viewer offer (open_at_start) lived only in AGENTS.md, so it depended on the agent remembering it on its first turn. The scaffold's .claude/settings.json now runs just gui-status at session start and hands the agent the result plus open_at_start. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Tpv1gBaepKjQiAVJqejGbA --- crates/ledger-cli/src/main.rs | 19 +++++++++++++++++-- crates/ledger-cli/tests/init.rs | 5 +++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/crates/ledger-cli/src/main.rs b/crates/ledger-cli/src/main.rs index eef25d3..06e3c12 100644 --- a/crates/ledger-cli/src/main.rs +++ b/crates/ledger-cli/src/main.rs @@ -15,7 +15,8 @@ //! `.gitignore`, and `books-data/`. //! 3. Registers the MCP server for this directory only: `.mcp.json`, plus //! `.claude/settings.json` approving it, so Claude Code loads the tools -//! without `claude mcp add` or a prompt. +//! without `claude mcp add` or a prompt. The same settings carry a +//! SessionStart hook that checks whether the viewer is open. //! 4. `git init`s the directory (its history is the archive) and commits. //! 5. Starts `claude` in the directory with the onboarding prompt, unless //! `--no-start` or there's no terminal. With `--codex`, registers the @@ -363,11 +364,25 @@ fn mcp_json(ledger_mcp: &Path, books: &Path) -> String { /// `.claude/settings.json`: approve this directory's server, and its tools, /// so the first session starts working instead of asking. The server still -/// previews every posting for the human to confirm. +/// previews every posting for the human to confirm. A SessionStart hook +/// checks the viewer and hands the agent `[viewer] open_at_start`, so the +/// offer to open it doesn't hang on the agent remembering AGENTS.md. const CLAUDE_SETTINGS: &str = r#"{ "enabledMcpjsonServers": ["wanderbooks"], "permissions": { "allow": ["mcp__wanderbooks"] + }, + "hooks": { + "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "cd \"$CLAUDE_PROJECT_DIR\" && if just gui-status >/dev/null 2>&1; then echo 'wanderbooks viewer: open.'; else echo \"wanderbooks viewer: CLOSED. [viewer] open_at_start = $(sed -n 's/^open_at_start *= *//p' wanderbooks.toml). Act on this at the top of your first reply, before anything else: \\\"ask\\\" -> offer to open it (just gui); \\\"always\\\" -> run just gui; \\\"never\\\" -> say nothing.\"; fi" + } + ] + } + ] } } "#; diff --git a/crates/ledger-cli/tests/init.rs b/crates/ledger-cli/tests/init.rs index fad8174..d118861 100644 --- a/crates/ledger-cli/tests/init.rs +++ b/crates/ledger-cli/tests/init.rs @@ -97,6 +97,11 @@ fn the_server_is_registered_for_this_dir() { settings["enabledMcpjsonServers"], serde_json::json!(["wanderbooks"]) ); + // Every session starts by checking the viewer against open_at_start. + let hook = settings["hooks"]["SessionStart"][0]["hooks"][0]["command"] + .as_str() + .expect("SessionStart hook command"); + assert!(hook.contains("just gui-status") && hook.contains("open_at_start")); // 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 \"{{justfile_directory()}}/books-data\""));