1use crate::error::Error;
6use crate::init::InitFormat;
7use crate::{init, init_pi};
8
9#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum InitMode {
13 Format(InitFormat),
15 Pi {
17 global: bool,
19 },
20 ClaudeCode {
22 global: bool,
24 },
25}
26
27pub const SUPPORTED_AGENTS: &[&str] = &["pi", "claude-code"];
30
31#[derive(Debug, PartialEq, Eq)]
33enum FormatArg {
34 Bare,
35 Value(String),
36}
37
38pub(crate) fn parse_init_mode(args: &[String]) -> Result<InitMode, String> {
49 let supported = SUPPORTED_AGENTS.join(", ");
50 let mut agent: Option<String> = None;
51 let mut global = false;
52 let mut format: Option<FormatArg> = None;
53 let mut iter = args.iter().peekable();
54 while let Some(arg) = iter.next() {
55 match arg.as_str() {
56 "--agent" => {
57 if agent.is_some() {
58 return Err("--agent given more than once".to_string());
59 }
60 let Some(value) = iter.next().filter(|v| !v.starts_with('-')) else {
61 return Err(format!(
64 "--agent requires a value; supported agents: {supported}"
65 ));
66 };
67 agent = Some(value.clone());
68 }
69 "--global" => {
70 if global {
71 return Err("--global given more than once".to_string());
72 }
73 global = true;
74 }
75 "--format" => {
76 if format.is_some() {
77 return Err("--format given more than once".to_string());
78 }
79 format = Some(match iter.peek() {
80 Some(v) if !v.starts_with('-') => {
81 let value = (*v).clone();
82 iter.next();
83 FormatArg::Value(value)
84 }
85 _ => FormatArg::Bare,
86 });
87 }
88 other if other.starts_with('-') => {
89 return Err(format!(
90 "init: unknown option '{other}' (try: oo init --help)"
91 ));
92 }
93 other => {
94 return Err(format!(
95 "init: unexpected argument '{other}' (try: oo init --help)"
96 ));
97 }
98 }
99 }
100 let Some(agent) = agent else {
101 if global {
104 return Err("--global requires --agent".to_string());
105 }
106 return match format {
107 None => Ok(InitMode::Format(InitFormat::Claude)),
108 Some(FormatArg::Bare) => {
109 Err("--format requires a value; supported formats: claude, generic".to_string())
110 }
111 Some(FormatArg::Value(value)) => parse_init_format(&value).map(InitMode::Format),
112 };
113 };
114 if format.is_some() {
115 return Err("--agent and --format cannot be used together".to_string());
117 }
118 match agent.as_str() {
119 "pi" => Ok(InitMode::Pi { global }),
120 "claude-code" => Ok(InitMode::ClaudeCode { global }),
121 _ => Err(format!(
122 "unknown --agent value '{agent}'; supported agents: {supported}"
123 )),
124 }
125}
126
127fn parse_init_format(value: &str) -> Result<InitFormat, String> {
132 match value {
133 "generic" => Ok(InitFormat::Generic),
134 "claude" => Ok(InitFormat::Claude),
135 other => Err(format!(
136 "oo init: unsupported format '{other}' (supported: claude, generic)"
137 )),
138 }
139}
140
141pub fn cmd_init(mode: InitMode) -> i32 {
143 match &mode {
144 InitMode::Format(format) => match init::run(*format) {
145 Ok(()) => 0,
146 Err(e) => {
147 eprintln!("oo: {e}");
148 1
149 }
150 },
151 InitMode::Pi { global } => {
152 let Ok(cwd) = std::env::current_dir() else {
153 eprintln!("oo: cannot determine working directory");
154 return 1;
155 };
156 match init_pi::run(&cwd, *global) {
157 Ok(()) => 0,
158 Err(e) => {
159 eprintln!("oo: {e}");
160 1
161 }
162 }
163 }
164 InitMode::ClaudeCode { global } => match std::env::current_dir().map_err(Error::from) {
165 Err(e) => {
166 eprintln!("oo: {e}");
167 1
168 }
169 Ok(cwd) => match crate::hook::claude_settings_path(&cwd, *global) {
170 Err(e) => {
171 eprintln!("oo: {e}");
172 1
173 }
174 Ok(path) => match crate::hook::merge_oo_hook(&path) {
175 Ok(_) => {
176 println!(
177 "Installed `oo hook claude` PreToolUse hook in {}",
178 path.display()
179 );
180 println!("Uninstall: remove the `oo hook claude` entry from that file.");
181 0
182 }
183 Err(e) => {
184 eprintln!("oo: {e}");
185 1
186 }
187 },
188 },
189 },
190 }
191}