Skip to main content

agent_docs/
lib.rs

1mod cli;
2pub mod commands;
3mod completion;
4pub mod config;
5pub mod env;
6pub mod model;
7pub mod output;
8pub mod paths;
9pub mod resolver;
10
11use clap::Parser;
12
13use cli::{Cli, Command};
14use env::{PathOverrides, resolve_roots};
15use model::{ConfigErrorKind, OutputFormat};
16use output::{
17    render_baseline, render_contexts, render_resolve, render_scaffold_baseline, render_stub,
18};
19
20const EXIT_OK: i32 = 0;
21const EXIT_STRICT_MISSING_REQUIRED: i32 = 1;
22const EXIT_USAGE: i32 = 2;
23const EXIT_CONFIG: i32 = 3;
24const EXIT_RUNTIME: i32 = 4;
25
26pub fn run() -> i32 {
27    run_with_args(std::env::args_os())
28}
29
30pub fn run_with_args<I, T>(args: I) -> i32
31where
32    I: IntoIterator<Item = T>,
33    T: Into<std::ffi::OsString> + Clone,
34{
35    let cli = match Cli::try_parse_from(args) {
36        Ok(parsed) => parsed,
37        Err(err) => {
38            let code = err.exit_code();
39            let _ = err.print();
40            return code;
41        }
42    };
43
44    dispatch(cli)
45}
46
47fn dispatch(cli: Cli) -> i32 {
48    let fallback_mode = cli.worktree_fallback;
49    let overrides = PathOverrides {
50        agent_home: cli.agent_home,
51        project_path: cli.project_path,
52    };
53
54    match cli.command {
55        Command::Contexts(args) => print_rendered(
56            render_contexts(args.format, resolver::supported_contexts()),
57            EXIT_OK,
58        ),
59        Command::Resolve(args) => {
60            let roots = match resolve_roots_or_exit(&overrides) {
61                Ok(roots) => roots,
62                Err(code) => return code,
63            };
64
65            let report =
66                match resolver::resolve_with_mode(args.context, &roots, args.strict, fallback_mode)
67                {
68                    Ok(report) => report,
69                    Err(err) => {
70                        eprintln!("error: {err}");
71                        return config_error_exit_code(&err);
72                    }
73                };
74            let exit_code = if args.strict && report.has_missing_required() {
75                EXIT_STRICT_MISSING_REQUIRED
76            } else {
77                EXIT_OK
78            };
79            print_rendered(render_resolve(args.format, &report), exit_code)
80        }
81        Command::Add(args) => {
82            let roots = match resolve_roots_or_exit(&overrides) {
83                Ok(roots) => roots,
84                Err(code) => return code,
85            };
86            let request = commands::add::AddDocumentRequest {
87                target: args.target,
88                context: args.context,
89                scope: args.scope,
90                path: args.path,
91                required: args.required,
92                when: args.when,
93                notes: args.notes,
94            };
95
96            match commands::add::upsert_document(&roots, request) {
97                Ok(report) => {
98                    let message = format!(
99                        "target={} action={} config={} entries={}",
100                        report.target,
101                        report.action,
102                        report.config_path.display(),
103                        report.document_count
104                    );
105                    print_rendered(render_stub(OutputFormat::Text, "add", message), EXIT_OK)
106                }
107                Err(err) => {
108                    eprintln!("error: {err}");
109                    config_error_exit_code(&err)
110                }
111            }
112        }
113        Command::ScaffoldAgents(args) => {
114            let roots = match resolve_roots_or_exit(&overrides) {
115                Ok(roots) => roots,
116                Err(code) => return code,
117            };
118            let request = commands::scaffold_agents::ScaffoldAgentsRequest {
119                target: args.target,
120                output: args.output,
121                force: args.force,
122            };
123
124            match commands::scaffold_agents::scaffold_agents(&request, &roots) {
125                Ok(report) => {
126                    let message = format!(
127                        "target={} mode={} output={}",
128                        report.target,
129                        report.write_mode,
130                        report.output_path.display()
131                    );
132                    print_rendered(
133                        render_stub(OutputFormat::Text, "scaffold-agents", message),
134                        EXIT_OK,
135                    )
136                }
137                Err(err) => {
138                    eprintln!("error: {err}");
139                    match err.kind {
140                        commands::scaffold_agents::ScaffoldAgentsErrorKind::AlreadyExists => {
141                            EXIT_STRICT_MISSING_REQUIRED
142                        }
143                        commands::scaffold_agents::ScaffoldAgentsErrorKind::Io => EXIT_RUNTIME,
144                    }
145                }
146            }
147        }
148        Command::Baseline(args) => {
149            if !args.check {
150                eprintln!("error: baseline currently supports only --check");
151                return EXIT_USAGE;
152            }
153
154            let roots = match resolve_roots_or_exit(&overrides) {
155                Ok(roots) => roots,
156                Err(code) => return code,
157            };
158            let report = match commands::baseline::check_builtin_baseline_with_mode(
159                args.target,
160                &roots,
161                args.strict,
162                fallback_mode,
163            ) {
164                Ok(report) => report,
165                Err(err) => {
166                    eprintln!("error: {err}");
167                    return config_error_exit_code(&err);
168                }
169            };
170            let exit_code = if args.strict && report.has_missing_required() {
171                EXIT_STRICT_MISSING_REQUIRED
172            } else {
173                EXIT_OK
174            };
175            print_rendered(render_baseline(args.format, &report), exit_code)
176        }
177        Command::ScaffoldBaseline(args) => {
178            let roots = match resolve_roots_or_exit(&overrides) {
179                Ok(roots) => roots,
180                Err(code) => return code,
181            };
182            let request = commands::scaffold_baseline::ScaffoldBaselineRequest {
183                target: args.target,
184                missing_only: args.missing_only,
185                force: args.force,
186                dry_run: args.dry_run,
187            };
188
189            match commands::scaffold_baseline::scaffold_baseline(&request, &roots) {
190                Ok(report) => {
191                    print_rendered(render_scaffold_baseline(args.format, &report), EXIT_OK)
192                }
193                Err(err) => {
194                    eprintln!("error: {err}");
195                    EXIT_RUNTIME
196                }
197            }
198        }
199        Command::Completion(args) => completion::run(args.shell),
200    }
201}
202
203fn resolve_roots_or_exit(overrides: &PathOverrides) -> Result<env::ResolvedRoots, i32> {
204    resolve_roots(overrides).map_err(|err| {
205        eprintln!("error: {err:#}");
206        EXIT_RUNTIME
207    })
208}
209
210fn config_error_exit_code(err: &model::ConfigLoadError) -> i32 {
211    match err.kind {
212        ConfigErrorKind::Validation | ConfigErrorKind::Parse => EXIT_CONFIG,
213        ConfigErrorKind::Io => EXIT_RUNTIME,
214    }
215}
216
217fn print_rendered(rendered: anyhow::Result<String>, success_exit_code: i32) -> i32 {
218    match rendered {
219        Ok(body) => {
220            println!("{body}");
221            success_exit_code
222        }
223        Err(err) => {
224            eprintln!("error: {err:#}");
225            EXIT_RUNTIME
226        }
227    }
228}