1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
use clap::Parser;
const EXAMPLES: &str = "\
EXAMPLES:
# Check whether a command would auto-approve (exit 0 = allowed):
safe-chains \"git status\"
# See a per-segment breakdown of why a command does or doesn't approve:
safe-chains --explain \"grep foo . && ./deploy.sh\"
# Offer to support a command safe-chains doesn't recognize. This writes or
# upgrades the local .safe-chains.toml, then prints the pin to hand-add to
# ~/.config/safe-chains.toml so the definition takes effect:
safe-chains --suggest \"mytool sync --dry-run\"
";
#[derive(Parser)]
#[command(name = "safe-chains")]
#[command(about = "Auto-allow safe bash commands in agentic coding tools")]
#[command(version, disable_version_flag = true)]
#[command(after_help = EXAMPLES)]
#[allow(clippy::struct_excessive_bools)]
pub struct Cli {
/// Command string to check (omit for Claude hook mode via stdin)
pub command: Option<String>,
/// Print version information.
#[arg(short = 'v', short_alias = 'V', long, action = clap::ArgAction::Version)]
pub version: Option<bool>,
/// Safety level threshold; only commands at or below it auto-approve. Levels, locked → open:
/// paranoid, reader, editor, developer, local-admin, network-admin, yolo. The legacy names
/// inert / safe-read / safe-write still work (mapped to paranoid / reader / developer, with a
/// notice). Default: developer.
#[arg(long)]
pub level: Option<String>,
/// Working directory to resolve relative paths against (as a harness hook would pass).
/// Pair with --root so e.g. `cd`-relative writes classify against the real directory.
#[arg(long)]
pub cwd: Option<String>,
/// Project root, so a relative path under it is worktree-local and one outside it (the
/// cwd having escaped the project) is scored as its real absolute target.
#[arg(long)]
pub root: Option<String>,
/// Print a per-segment breakdown of why a command would or would not auto-approve.
#[arg(long)]
pub explain: bool,
/// Offer to support a command safe-chains doesn't recognize yet. Pass the command as the
/// argument: `safe-chains --suggest "<command>"`. It writes (or upgrades) the project's local
/// `.safe-chains.toml` with a definition for the unrecognized command, then prints the
/// `[[trusted]]` pin for you to add to ~/.config/safe-chains.toml so that file takes effect.
#[arg(long)]
pub suggest: bool,
/// List all supported commands in Markdown format
#[arg(long)]
pub list_commands: bool,
/// Generate mdBook command reference pages in docs/src/commands/
#[arg(long)]
pub generate_book: bool,
/// Configure the hook for the named tool (default: claude). Use --auto-detect for every installed tool.
#[arg(long)]
pub setup: bool,
/// Pair with --setup to select the target tool by name. See --list-tools.
#[arg(long, value_name = "NAME")]
pub tool: Option<String>,
/// Pair with --setup to install for every installed tool detected on this machine.
#[arg(long)]
pub auto_detect: bool,
/// Print the names of every supported integration target.
#[arg(long)]
pub list_tools: bool,
/// Hook subcommand: read this tool's stdin envelope, validate the command, write the response.
#[command(subcommand)]
pub subcommand: Option<Subcommand>,
}
#[derive(clap::Subcommand)]
pub enum Subcommand {
/// Run as a runtime hook for the named tool.
Hook {
/// Tool to read/write the hook envelope for. See --list-tools.
#[arg(value_name = "TOOL")]
tool: String,
},
}