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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
use std::ffi::OsString;
use std::path::PathBuf;
use clap::{Parser, Subcommand};
use crate::enforce::Backend;
/// Filesystem policy for AI coding agents.
///
/// `agent.lock` declares what AI-controlled processes may not modify, the same
/// way `.gitignore` declares what Git may not track.
#[derive(Debug, Parser)]
#[command(name = "ralon", version, about, long_about = None)]
pub struct Cli {
/// Directory to look for agent.lock in (default: the current directory)
#[arg(short = 'C', long = "dir", global = true, value_name = "DIR")]
pub directory: Option<PathBuf>,
#[command(subcommand)]
pub command: Command,
}
#[derive(Debug, Subcommand)]
pub enum Command {
/// Write a starter agent.lock and wire up the agents
Init {
/// Overwrite an existing agent.lock
#[arg(long)]
force: bool,
/// Write the policy only, and configure nothing
#[arg(long)]
no_hooks: bool,
},
/// Report what the policy protects, or whether given paths are protected
///
/// Exits 1 if any given path is protected, which makes it usable as an
/// agent pre-write hook.
Check {
/// Paths to test. With none, lists everything the policy protects.
#[arg(value_name = "PATH")]
paths: Vec<PathBuf>,
},
/// Show the policy and which enforcement backends this kernel offers
Status,
/// Install or run the agent hook
///
/// The hook refuses an agent's own edit tools before they touch a protected
/// path. It is a courtesy layer, not enforcement — an agent that shells out
/// bypasses it — but on platforms `run` cannot restrict, it is the only
/// thing standing between an agent and your policy.
Hook {
#[command(subcommand)]
action: HookAction,
},
/// Protect the project against every process, with no command to wrap
///
/// `run` protects the agent it starts. A guard protects the ones it does
/// not: it holds the locks itself, and Windows refuses those to every
/// process on the machine, so an agent started from anywhere — a terminal,
/// an IDE, an extension, one installed next month — is refused without
/// knowing Ralon exists. Start it once and stop it with `--stop`.
Guard {
/// Keep running after this terminal closes
#[arg(long, conflicts_with = "stop")]
detach: bool,
/// Release a running guard and clear anything it left behind
#[arg(long)]
stop: bool,
/// This *is* the background guard `--detach` started. Not for people:
/// it means "you have no console, do not try to write to one".
#[arg(long, hide = true, conflicts_with_all = ["detach", "stop"])]
detached: bool,
},
/// Run a command that cannot modify the protected paths
Run {
/// Enforcement backend
#[arg(long, value_enum, default_value_t = Backend::Auto)]
backend: Backend,
/// Print what would be enforced and exit
#[arg(long)]
dry_run: bool,
/// Do not print the lock summary before running
#[arg(short, long)]
quiet: bool,
/// Command to run, e.g. `ralon run -- claude`
#[arg(
value_name = "COMMAND",
required = true,
trailing_var_arg = true,
allow_hyphen_values = true
)]
command: Vec<OsString>,
},
}
#[derive(Debug, Subcommand)]
pub enum HookAction {
/// Wire the hook into an agent's configuration
Install {
/// Which agent to configure
#[arg(long, value_enum, default_value_t = Agent::All)]
agent: Agent,
/// Print the configuration instead of writing it
#[arg(long)]
dry_run: bool,
},
/// Decide one edit, reading the agent's request on stdin
///
/// This is what the installed hook calls. Parsing the request here rather
/// than in a shell snippet keeps the configuration free of quoting, and
/// means the hook behaves identically on every platform.
Check,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
pub enum Agent {
/// Every agent below. A policy should hold whichever tool the project is
/// opened with, and you cannot know that in advance.
All,
/// Claude Code — .claude/settings.json
Claude,
/// Cursor — .cursor/hooks.json
Cursor,
/// OpenCode — .opencode/plugins/ralon.js
Opencode,
/// GitHub Copilot in VS Code — .github/hooks/ralon.json
Copilot,
/// OpenAI Codex — .codex/hooks.json
Codex,
/// Gemini CLI — .gemini/settings.json
Gemini,
/// Google Antigravity — .agents/hooks.json
Antigravity,
/// Windsurf / Cascade — .windsurf/hooks.json
Windsurf,
/// Cline — .clinerules/hooks/PreToolUse
Cline,
}