seshat_cli/lib.rs
1//! # Seshat CLI
2//!
3//! CLI commands and TUI for developer interaction. Provides the human-facing
4//! interface to Seshat's capabilities:
5//!
6//! - `seshat scan <path>` — scan a project and display analysis report
7//! - `seshat serve` — start MCP server for AI agent connections
8//! - `seshat status` — show indexed projects, submodules, and database info
9//! - `seshat review` — interactive TUI for convention review (stub)
10//! - `seshat init` — generate MCP configuration for detected AI clients (stub)
11//!
12//! Uses `clap` for argument parsing, `indicatif` for progress bars, and
13//! `tracing-subscriber` for log output.
14
15/// Command-line argument definitions (clap derive types).
16pub mod args;
17/// `seshat completions` — generate shell completion scripts.
18pub mod completions;
19/// Application configuration loading from `seshat.toml`.
20pub mod config;
21/// Per-OS dangerous-cwd denylist (see [`dangerous_path::is_dangerous_cwd`]).
22pub mod dangerous_path;
23/// Shared database path utilities (XDG resolution, project name extraction).
24pub mod db;
25/// Debug command: dump conventions with evidence snippets from the DB.
26pub mod debug;
27/// Implementation of the `seshat decisions` subcommands.
28pub mod decisions;
29/// CLI error types.
30pub mod error;
31/// Shared output formatting utilities (color, verbosity, bar charts, etc.).
32pub mod format;
33/// Implementation of the `seshat init` command.
34pub mod init;
35/// Agent instruction file management (upsert, skill install, hooks).
36pub mod instructions;
37/// Scan report rendering (overview, conventions, next steps).
38pub mod report;
39/// Implementation of the `seshat review` command.
40pub mod review;
41/// Implementation of the `seshat scan` command.
42pub mod scan;
43/// Implementation of the `seshat serve` command.
44pub mod serve;
45/// Implementation of the `seshat status` command.
46pub mod status;
47/// TUI components for interactive convention review.
48pub mod tui;
49/// Implementation of the `seshat uninstall` command.
50pub mod uninstall;
51/// Implementation of the `seshat update` command.
52pub mod update;
53/// Version check cache utilities for self-update.
54pub mod version_cache;
55
56pub use args::{Cli, Command};
57pub use db::{find_git_root, get_current_branch};
58pub use error::CliError;
59pub use format::Verbosity;
60
61use clap::Parser;
62use tracing_subscriber::EnvFilter;
63
64/// Parse CLI arguments, initialize logging, and dispatch to the appropriate
65/// command handler.
66///
67/// This is the single entry point called by `seshat-bin/main.rs`.
68pub fn run() -> Result<(), CliError> {
69 let cli = Cli::parse();
70
71 // Initialize tracing. SESHAT_LOG env var controls level (e.g. "debug").
72 // Default to "warn" so that library tracing doesn't clutter CLI output.
73 tracing_subscriber::fmt()
74 .with_env_filter(
75 EnvFilter::try_from_env("SESHAT_LOG").unwrap_or_else(|_| EnvFilter::new("warn")),
76 )
77 .with_target(false)
78 .with_writer(std::io::stderr)
79 .init();
80
81 // Print background update notice for all commands except update/update --check
82 // and completions (which is typically captured by shell rc files).
83 // Uses the 24h cache so at most one GitHub API call per day.
84 // Network failures are silently ignored — no delay, no output.
85 // Goes to stderr so MCP protocol consumers are unaffected.
86 if !matches!(
87 cli.command,
88 Command::Update { .. } | Command::Completions { .. }
89 ) {
90 update::cleanup_stale_old_binary();
91 update::check_and_print_update_notice();
92 }
93
94 match cli.command {
95 Command::Scan {
96 path,
97 verbose,
98 quiet,
99 exclude_submodules,
100 } => scan::run_scan(&path, verbose, quiet, exclude_submodules),
101
102 Command::Serve {
103 repo,
104 host,
105 port,
106 call_log,
107 } => serve::run_serve(repo.as_deref(), host, port, call_log),
108
109 Command::Status { verbose } => status::run_status(verbose),
110
111 Command::Review { no_sync } => review::run_review(None, no_sync),
112
113 Command::Decisions { command } => decisions::run_decisions(command),
114
115 Command::DebugSnippets { path } => {
116 let resolved = db::resolve_project(path.as_deref(), "debug")?;
117 let branch = db::detect_branch(&resolved.project_root);
118 debug::run_debug(&resolved.db_path, &branch)
119 }
120
121 Command::Init {
122 client,
123 project,
124 global,
125 dry_run,
126 skip_instructions,
127 } => {
128 let scope = if project {
129 init::ScopeRequest::Project
130 } else if global {
131 init::ScopeRequest::Global
132 } else {
133 init::ScopeRequest::Auto
134 };
135 init::run_init(client.as_deref(), scope, dry_run, skip_instructions)
136 }
137
138 Command::Uninstall {
139 client,
140 project,
141 global,
142 dry_run,
143 } => {
144 let scope = if project {
145 uninstall::ScopeRequest::Project
146 } else if global {
147 uninstall::ScopeRequest::Global
148 } else {
149 uninstall::ScopeRequest::Auto
150 };
151 uninstall::run_uninstall(client.as_deref(), scope, dry_run)
152 }
153
154 Command::Update { check } => update::run_update(check),
155
156 Command::Completions { shell } => completions::run_completions(shell),
157 }
158}