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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tpv1gBaepKjQiAVJqejGbA
This commit is contained in:
swaitsandClaude Opus 5.5 committed 2026-10-12 19:41:48 -06:00
1 parent b38c9e39cc
commit 799aee58ea
2 files changed
+22 -2

No files matched your search

+17 -2
View File
@@ -15,7 +15,8 @@
//! `.gitignore`, and `books-data/`. //! `.gitignore`, and `books-data/`.
//! 3. Registers the MCP server for this directory only: `.mcp.json`, plus //! 3. Registers the MCP server for this directory only: `.mcp.json`, plus
//! `.claude/settings.json` approving it, so Claude Code loads the tools //! `.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. //! 4. `git init`s the directory (its history is the archive) and commits.
//! 5. Starts `claude` in the directory with the onboarding prompt, unless //! 5. Starts `claude` in the directory with the onboarding prompt, unless
//! `--no-start` or there's no terminal. With `--codex`, registers the //! `--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, /// `.claude/settings.json`: approve this directory's server, and its tools,
/// so the first session starts working instead of asking. The server still /// 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#"{ const CLAUDE_SETTINGS: &str = r#"{
"enabledMcpjsonServers": ["wanderbooks"], "enabledMcpjsonServers": ["wanderbooks"],
"permissions": { "permissions": {
"allow": ["mcp__wanderbooks"] "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"
}
]
}
]
} }
} }
"#; "#;
+5
View File
@@ -97,6 +97,11 @@ fn the_server_is_registered_for_this_dir() {
settings["enabledMcpjsonServers"], settings["enabledMcpjsonServers"],
serde_json::json!(["wanderbooks"]) 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. // The justfile opens this dir's books in the GUI.
let just = fs::read_to_string(books.join("justfile")).unwrap(); let just = fs::read_to_string(books.join("justfile")).unwrap();
assert!(just.contains("ledger-gui\" --data-dir \"{{justfile_directory()}}/books-data\"")); assert!(just.contains("ledger-gui\" --data-dir \"{{justfile_directory()}}/books-data\""));