Skip to main content

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}