1pub const HARNESSES: &str = "<harnesses>";
10
11pub struct Command {
14 pub name: &'static str,
15 pub help: &'static [(&'static str, &'static str)],
16 pub subs: &'static [&'static str],
17 pub flags: &'static [&'static str],
18 pub path: bool,
19}
20
21const fn cmd(name: &'static str, help: &'static [(&'static str, &'static str)], subs: &'static [&'static str], flags: &'static [&'static str]) -> Command {
22 Command { name, help, subs, flags, path: false }
23}
24
25pub const GROUPS: &[(&str, &[Command])] = &[
27 (
28 "Setup",
29 &[
30 cmd("setup", &[("[--yes] [--no-vectors]", "guided setup: collectives, team, vectors, wiring")], &[], crate::install::setup::FLAGS),
31 cmd("install", &[("<HARNESS|all> [--apply]", "wire the hooks (a dry run without --apply)")], &[HARNESSES, "all"], &["--apply", "--home"]),
32 cmd("uninstall", &[("<HARNESS|all> [--apply]", "unwire them")], &[HARNESSES, "all"], &["--apply", "--home"]),
33 cmd("status", &[("", "each collective's lessons, outcomes and wiring")], &[], &["--home"]),
34 cmd("doctor", &[("", "what is broken: config, hooks, adapters, the model CLI")], &[], &["--home"]),
35 Command { path: true, ..cmd("collective", &[("[DIR]", "which collective a directory belongs to")], &[], &[]) },
36 cmd("vectors", &[("[--fetch|--rebuild] [--store LESSONS]", "the vector model and index")], &[], &["--fetch", "--rebuild", "--store"]),
37 cmd("completions", &[("<bash|zsh|fish>", "a shell completion script")], &["bash", "zsh", "fish"], &[]),
38 cmd("version", &[("", "borg's version (also --version, -V)")], &[], &[]),
39 ],
40 ),
41 (
42 "Learning",
43 &[
44 cmd("learn", &[("", "turn waiting corrections into lessons now")], &[], &[]),
45 cmd(
46 "no",
47 &[("[--session ID] <reason>", "say no to a drone's work: shown lessons lose credit, the reason becomes a lesson")],
48 &[],
49 crate::veto::FLAGS,
50 ),
51 cmd(
52 "assimilate",
53 &[
54 ("[--apply] [--since DATE] [--limit N]", "import corrections from past sessions"),
55 ("--from-dir DIR [--plan FILE] [--apply]", "import lessons from team documents"),
56 ],
57 &[],
58 crate::assimilate::FLAGS,
59 ),
60 cmd("reach", &[("[--plan FILE] [--apply] [--jobs N]", "sort lessons into team, repo-only and personal")], &[], &["--plan", "--apply", "--jobs"]),
61 cmd("facets", &[("[--apply] [--store FILE] [--jobs N]", "search facets for lessons")], &[], &["--apply", "--store", "--jobs"]),
62 ],
63 ),
64 (
65 "Decisions",
66 &[
67 cmd(
68 "queen",
69 &[("[show|ask|answer|skip|dismiss|dedupe]", "open decisions waiting for you")],
70 &["show", "ask", "answer", "skip", "dismiss", "dedupe"],
71 &["--json", "--repo", "--dir", "--collective", "--apply"],
72 ),
73 Command {
74 path: true,
75 ..cmd("why", &[("<path>[:<line>[-<line>]] | <commit>", "the reasoning behind code [--full] [--json]")], &[], &["--full", "--json", "--dir"])
76 },
77 cmd("notes", &[("fetch [<commit>...]", "fetch the team's reasoning notes behind commits")], &["fetch"], &["--dir"]),
78 cmd(
79 "regress",
80 &[("[--replay A..B] [--explain] [--json]", "which earlier decisions later commits fixed (dry run)")],
81 &[],
82 crate::notes::regress_cli::FLAGS,
83 ),
84 cmd(
85 "review-fix",
86 &[("[--since DATE] [--json]", "bugs workflow reviews found and fix steps fixed, before any commit")],
87 &[],
88 crate::review_fix::FLAGS,
89 ),
90 cmd("trust", &[("[--dir DIR]", "trust per engineer and area")], &[], &["--dir"]),
91 ],
92 ),
93 (
94 "Watching",
95 &[
96 cmd("top", &[("[--once] [--json] [--dir DIR]", "the drones running across the team, live (q quits)")], &[], &["--once", "--json", "--dir"]),
97 cmd("web", &[("[--port PORT] [--dir DIR]", "a live page of knowledge and stats on 127.0.0.1")], &[], &["--port", "--dir"]),
98 ],
99 ),
100 (
101 "Team",
102 &[
103 cmd(
104 "mesh",
105 &[("<command>", "sync with team members (borg mesh --help)")],
106 &["id", "init", "invite", "invites", "join", "revoke", "members", "peer", "serve", "sync", "fetch"],
107 &["--collective", "--apply", "--paths", "--role", "--expires", "--uses", "--png", "--revoke"],
108 ),
109 cmd(
110 "tell",
111 &[(
112 "<session|drone|local|collective|engineer> <text> [--re ID]",
113 "a message to other sessions: local is this machine's, collective (or all) every member's",
114 )],
115 &["local", "collective", "all"],
116 crate::msg::FLAGS,
117 ),
118 cmd(
119 "ask",
120 &[("<session|drone|local|collective|engineer> <question> [--re ID]", "a message that asks for a reply")],
121 &["local", "collective", "all"],
122 crate::msg::FLAGS,
123 ),
124 cmd(
125 "inbox",
126 &[("[--all] [--json] [--show ID]", "this session's messages; from a terminal, every recent one; --show ID: one whole")],
127 &[],
128 crate::msg::INBOX_FLAGS,
129 ),
130 cmd(
131 "listen",
132 &[("[--off]", "wake this session when a message arrives while it is idle"), ("--wait|--stream|--queue", "the waiter (started by borg)")],
133 &[],
134 crate::listen::FLAGS,
135 ),
136 cmd(
137 "pause",
138 &[("<session|drone|local|collective> [note]", "refuse a session's tool calls until it is resumed")],
139 &["local", "collective", "all"],
140 crate::msg::STEER_FLAGS,
141 ),
142 cmd(
143 "resume",
144 &[("<session|drone|local|collective> [note]", "let a paused session's tool calls run again")],
145 &["local", "collective", "all"],
146 crate::msg::STEER_FLAGS,
147 ),
148 ],
149 ),
150];
151
152pub const DEBUG: &[Command] = &[
154 Command { path: true, ..cmd("recall", &[("<lessons.jsonl> [before_ms]", "rank lessons for text on stdin")], &[], &[]) },
155 Command { path: true, ..cmd("resolve", &[("<lessons.jsonl>", "the word rule's changed/ignored for {asked, before, edits, final} on stdin")], &[], &[]) },
156 Command { path: true, ..cmd("prefilter", &[("<lessons.jsonl> [before_ms]", "the free gate's score for a turn on stdin")], &[], &[]) },
157 Command { path: true, ..cmd("memory", &[("<lessons.jsonl> [--session ID]", "each lesson's activation and usefulness")], &[], &["--session"]) },
158 Command {
159 path: true,
160 ..cmd("expert", &[("<repo> --at <commit> --q <text>", "at most 3 cited claims from an expert repo")], &[], &["--at", "--version", "--q"])
161 },
162 cmd("vectors", &[("--query [--store LESSONS]", "the vector shortlist for a context on stdin")], &[], &["--query", "--store"]),
163 cmd("ope", &[("[--dir DIR | --file FILE] [--json]", "off-policy estimates of the belief's hand-set weights")], &[], &["--dir", "--file", "--json"]),
164 cmd(
165 "select",
166 &[(
167 "[--replay] [--rebuild] [--dir DIR | --file FILE] [--json] [--eval RUNS_DIR --qrels FILE]",
168 "the lesson selection policy and its pre-registered checks",
169 )],
170 &[],
171 &["--replay", "--rebuild", "--dir", "--file", "--json", "--eval", "--qrels"],
172 ),
173];
174
175pub const INTERNAL: &[&str] = &["hook", "check"];
177
178pub fn commands() -> impl Iterator<Item = &'static Command> {
180 GROUPS.iter().flat_map(|(_, cmds)| cmds.iter())
181}
182
183pub fn known(name: &str) -> bool {
186 commands().any(|c| c.name == name) || INTERNAL.contains(&name) || matches!(name, "debug" | "help" | "-h" | "--help" | "-V" | "--version")
187}
188
189fn completed() -> Vec<(&'static str, Vec<&'static str>, Vec<&'static str>, bool)> {
192 let mut out: Vec<_> = commands().map(|c| (c.name, subs_of(c.subs), c.flags.to_vec(), c.path)).collect();
193 let mut flags: Vec<&str> = DEBUG.iter().flat_map(|c| c.flags.iter().copied()).collect();
194 flags.sort_unstable();
195 flags.dedup();
196 out.push(("debug", DEBUG.iter().map(|c| c.name).chain(["help"]).collect(), flags, true));
197 out
198}
199
200fn nested() -> Vec<(&'static str, String)> {
203 DEBUG.iter().filter(|c| !c.subs.is_empty()).map(|c| (c.name, [subs_of(c.subs).as_slice(), c.flags].concat().join(" "))).collect()
204}
205
206const USAGE: &str = "usage: borg completions <bash|zsh|fish>
207 bash: borg completions bash > ~/.local/share/bash-completion/completions/borg
208 zsh: borg completions zsh > \"${fpath[1]}/_borg\" (then restart the shell)
209 fish: borg completions fish > ~/.config/fish/completions/borg.fish";
210
211pub fn main(args: &[&str]) -> i32 {
212 match args.first().copied() {
213 Some("bash") => print!("{}", bash()),
214 Some("zsh") => print!("{}", zsh()),
215 Some("fish") => print!("{}", fish()),
216 _ => {
217 eprintln!("{USAGE}");
218 return 2;
219 }
220 }
221 0
222}
223
224fn subs_of(subs: &[&'static str]) -> Vec<&'static str> {
226 subs.iter().flat_map(|s| if *s == HARNESSES { crate::harness::visible().map(|h| h.name()).collect() } else { vec![*s] }).collect()
227}
228
229fn cases(line: impl Fn(&str, &str, bool) -> String) -> String {
231 completed().iter().map(|(cmd, subs, flags, path)| line(cmd, &[subs.as_slice(), flags].concat().join(" "), *path)).collect()
232}
233
234fn names() -> String {
235 completed().iter().map(|c| c.0).collect::<Vec<_>>().join(" ")
236}
237
238pub fn bash() -> String {
239 let cases = cases(|cmd, words, path| format!(" {cmd}) words=\"{words}\"; files=\"{}\" ;;\n", if path { " -f" } else { "" }));
240 let nested: String =
241 nested().iter().map(|(cmd, words)| format!(" {cmd}) COMPREPLY=($(compgen -W \"{words}\" -- \"$cur\")); return ;;\n")).collect();
242 format!(
243 r#"# bash completion for borg (borg completions bash)
244_borg() {{
245 local cur="${{COMP_WORDS[COMP_CWORD]}}" words="" files=""
246 if [ "$COMP_CWORD" -eq 1 ]; then
247 COMPREPLY=($(compgen -W "{names}" -- "$cur"))
248 return
249 fi
250 if [ "$COMP_CWORD" -gt 2 ] && [ "${{COMP_WORDS[1]}}" = debug ]; then
251 case "${{COMP_WORDS[2]}}" in
252{nested} esac
253 fi
254 case "${{COMP_WORDS[1]}}" in
255{cases} esac
256 COMPREPLY=($(compgen -W "$words" -- "$cur"))
257 if [ -n "$files" ] && [ "${{cur:0:1}}" != "-" ]; then
258 COMPREPLY+=($(compgen -f -- "$cur"))
259 fi
260}}
261complete -o default -F _borg borg
262"#,
263 names = names()
264 )
265}
266
267pub fn zsh() -> String {
268 let cases = cases(|cmd, words, path| format!(" {cmd}) compadd -- {words}{} ;;\n", if path { "; _files" } else { "" }));
269 let nested: String = nested().iter().map(|(cmd, words)| format!(" {cmd}) compadd -- {words}; return ;;\n")).collect();
270 format!(
271 r#"#compdef borg
272# zsh completion for borg (borg completions zsh)
273_borg() {{
274 if (( CURRENT == 2 )); then
275 compadd -- {names}
276 return
277 fi
278 if (( CURRENT > 3 )) && [[ ${{words[2]}} == debug ]]; then
279 case "${{words[3]}}" in
280{nested} esac
281 fi
282 case "${{words[2]}}" in
283{cases} esac
284}}
285_borg "$@"
286"#,
287 names = names()
288 )
289}
290
291pub fn fish() -> String {
292 let mut out =
293 format!("# fish completion for borg (borg completions fish)\ncomplete -c borg -f\ncomplete -c borg -n __fish_use_subcommand -a '{}'\n", names());
294 for (cmd, subs, flags, path) in completed() {
297 let when = if DEBUG.iter().any(|d| d.name == cmd) {
298 format!("__fish_seen_subcommand_from {cmd}; and not __fish_seen_subcommand_from debug")
299 } else {
300 format!("__fish_seen_subcommand_from {cmd}")
301 };
302 if !subs.is_empty() {
303 out.push_str(&format!("complete -c borg -n '{when}' -a '{}'\n", subs.join(" ")));
304 }
305 for f in flags {
306 out.push_str(&format!("complete -c borg -n '{when}' -l {}\n", f.trim_start_matches("--")));
307 }
308 if path {
309 out.push_str(&format!("complete -c borg -n '{when}' -F\n"));
310 }
311 }
312 for c in DEBUG.iter().filter(|c| !c.subs.is_empty()) {
313 let subs = subs_of(c.subs).join(" ");
314 out.push_str(&format!("complete -c borg -n '__fish_seen_subcommand_from debug; and __fish_seen_subcommand_from {}' -a '{subs}'\n", c.name));
315 }
316 out
317}
318
319#[cfg(test)]
320mod tests {
321 use super::*;
322
323 #[test]
324 fn every_shell_names_every_command() {
325 for script in [bash(), zsh(), fish()] {
326 for (cmd, ..) in completed() {
327 assert!(script.contains(cmd), "{cmd} missing");
328 }
329 }
330 assert!(bash().contains("queen) words=\"show ask answer"));
331 assert!(zsh().starts_with("#compdef borg"));
332 assert!(fish().contains("-n '__fish_seen_subcommand_from install' -l apply"));
333 assert!(bash().contains("install) words=\"claude omp opencode codex all --apply"), "{}", bash());
334 assert!(!bash().contains(" sim ") && !bash().contains(HARNESSES), "the simulated harness stays hidden");
335 }
336
337 #[test]
338 fn a_command_shared_with_debug_completes_outside_debug_only() {
339 let fish = fish();
340 assert!(fish.contains("-n '__fish_seen_subcommand_from vectors; and not __fish_seen_subcommand_from debug' -l fetch"), "{fish}");
341 assert!(fish.contains("-n '__fish_seen_subcommand_from debug' -a 'recall"), "{fish}");
342 }
343}