Skip to main content

agent_docs/
lib.rs

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