1use std::time::Duration;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4pub enum ClaudeOutputFormat {
5 Text,
6 Json,
7 StreamJson,
8}
9
10impl ClaudeOutputFormat {
11 pub(crate) fn as_arg_value(&self) -> &'static str {
12 match self {
13 ClaudeOutputFormat::Text => "text",
14 ClaudeOutputFormat::Json => "json",
15 ClaudeOutputFormat::StreamJson => "stream-json",
16 }
17 }
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum ClaudeInputFormat {
22 Text,
23 StreamJson,
24}
25
26impl ClaudeInputFormat {
27 pub(crate) fn as_arg_value(&self) -> &'static str {
28 match self {
29 ClaudeInputFormat::Text => "text",
30 ClaudeInputFormat::StreamJson => "stream-json",
31 }
32 }
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum ClaudeChromeMode {
37 Chrome,
38 NoChrome,
39}
40
41#[derive(Debug, Clone)]
42pub struct ClaudePrintRequest {
43 pub(crate) prompt: Option<String>,
44 pub(crate) stdin: Option<Vec<u8>>,
45 pub(crate) output_format: ClaudeOutputFormat,
46 pub(crate) input_format: Option<ClaudeInputFormat>,
47 pub(crate) json_schema: Option<String>,
48 pub(crate) model: Option<String>,
49 pub(crate) allowed_tools: Vec<String>,
50 pub(crate) disallowed_tools: Vec<String>,
51 pub(crate) permission_mode: Option<String>,
52 pub(crate) dangerously_skip_permissions: bool,
53 pub(crate) add_dirs: Vec<String>,
54 pub(crate) mcp_config: Option<String>,
55 pub(crate) strict_mcp_config: bool,
56 pub(crate) agent: Option<String>,
57 pub(crate) agents: Option<String>,
58 pub(crate) allow_dangerously_skip_permissions: bool,
59 pub(crate) append_system_prompt: Option<String>,
60 pub(crate) betas: Vec<String>,
61 pub(crate) chrome_mode: Option<ClaudeChromeMode>,
62 pub(crate) continue_session: bool,
63 pub(crate) debug: bool,
64 pub(crate) debug_file: Option<String>,
65 pub(crate) disable_slash_commands: bool,
66 pub(crate) fallback_model: Option<String>,
67 pub(crate) files: Vec<String>,
68 pub(crate) fork_session: bool,
69 pub(crate) from_pr: bool,
70 pub(crate) from_pr_value: Option<String>,
71 pub(crate) ide: bool,
72 pub(crate) include_partial_messages: bool,
73 pub(crate) max_budget_usd: Option<f64>,
74 pub(crate) mcp_debug: bool,
75 pub(crate) no_session_persistence: bool,
76 pub(crate) plugin_dirs: Vec<String>,
77 pub(crate) replay_user_messages: bool,
78 pub(crate) resume: bool,
79 pub(crate) resume_value: Option<String>,
80 pub(crate) session_id: Option<String>,
81 pub(crate) setting_sources: Option<String>,
82 pub(crate) settings: Option<String>,
83 pub(crate) system_prompt: Option<String>,
84 pub(crate) tools: Vec<String>,
85 pub(crate) verbose: bool,
86 pub(crate) timeout: Option<Duration>,
87 pub(crate) extra_args: Vec<String>,
88}
89
90impl ClaudePrintRequest {
91 pub fn new(prompt: impl Into<String>) -> Self {
92 Self {
93 prompt: Some(prompt.into()),
94 stdin: None,
95 output_format: ClaudeOutputFormat::Text,
96 input_format: None,
97 json_schema: None,
98 model: None,
99 allowed_tools: Vec::new(),
100 disallowed_tools: Vec::new(),
101 permission_mode: None,
102 dangerously_skip_permissions: false,
103 add_dirs: Vec::new(),
104 mcp_config: None,
105 strict_mcp_config: false,
106 agent: None,
107 agents: None,
108 allow_dangerously_skip_permissions: false,
109 append_system_prompt: None,
110 betas: Vec::new(),
111 chrome_mode: None,
112 continue_session: false,
113 debug: false,
114 debug_file: None,
115 disable_slash_commands: false,
116 fallback_model: None,
117 files: Vec::new(),
118 fork_session: false,
119 from_pr: false,
120 from_pr_value: None,
121 ide: false,
122 include_partial_messages: false,
123 max_budget_usd: None,
124 mcp_debug: false,
125 no_session_persistence: false,
126 plugin_dirs: Vec::new(),
127 replay_user_messages: false,
128 resume: false,
129 resume_value: None,
130 session_id: None,
131 setting_sources: None,
132 settings: None,
133 system_prompt: None,
134 tools: Vec::new(),
135 verbose: false,
136 timeout: None,
137 extra_args: Vec::new(),
138 }
139 }
140
141 pub fn no_prompt(mut self) -> Self {
142 self.prompt = None;
143 self
144 }
145
146 pub fn output_format(mut self, format: ClaudeOutputFormat) -> Self {
147 self.output_format = format;
148 self
149 }
150
151 pub fn input_format(mut self, format: ClaudeInputFormat) -> Self {
152 self.input_format = Some(format);
153 self
154 }
155
156 pub fn json_schema(mut self, schema: impl Into<String>) -> Self {
157 self.json_schema = Some(schema.into());
158 self
159 }
160
161 pub fn model(mut self, model: impl Into<String>) -> Self {
162 self.model = Some(model.into());
163 self
164 }
165
166 pub fn allowed_tools(mut self, tools: impl IntoIterator<Item = impl Into<String>>) -> Self {
167 self.allowed_tools = tools.into_iter().map(Into::into).collect();
168 self
169 }
170
171 pub fn disallowed_tools(mut self, tools: impl IntoIterator<Item = impl Into<String>>) -> Self {
172 self.disallowed_tools = tools.into_iter().map(Into::into).collect();
173 self
174 }
175
176 pub fn permission_mode(mut self, mode: impl Into<String>) -> Self {
177 self.permission_mode = Some(mode.into());
178 self
179 }
180
181 pub fn dangerously_skip_permissions(mut self, enabled: bool) -> Self {
182 self.dangerously_skip_permissions = enabled;
183 self
184 }
185
186 pub fn add_dirs(mut self, dirs: impl IntoIterator<Item = impl Into<String>>) -> Self {
187 self.add_dirs = dirs.into_iter().map(Into::into).collect();
188 self
189 }
190
191 pub fn mcp_config(mut self, config: impl Into<String>) -> Self {
192 self.mcp_config = Some(config.into());
193 self
194 }
195
196 pub fn strict_mcp_config(mut self, enabled: bool) -> Self {
197 self.strict_mcp_config = enabled;
198 self
199 }
200
201 pub fn agent(mut self, agent: impl Into<String>) -> Self {
202 self.agent = Some(agent.into());
203 self
204 }
205
206 pub fn agents(mut self, json: impl Into<String>) -> Self {
207 self.agents = Some(json.into());
208 self
209 }
210
211 pub fn allow_dangerously_skip_permissions(mut self, enabled: bool) -> Self {
212 self.allow_dangerously_skip_permissions = enabled;
213 self
214 }
215
216 pub fn append_system_prompt(mut self, prompt: impl Into<String>) -> Self {
217 self.append_system_prompt = Some(prompt.into());
218 self
219 }
220
221 pub fn betas(mut self, betas: impl IntoIterator<Item = impl Into<String>>) -> Self {
222 self.betas = betas.into_iter().map(Into::into).collect();
223 self
224 }
225
226 pub fn chrome(mut self) -> Self {
227 self.chrome_mode = Some(ClaudeChromeMode::Chrome);
228 self
229 }
230
231 pub fn no_chrome(mut self) -> Self {
232 self.chrome_mode = Some(ClaudeChromeMode::NoChrome);
233 self
234 }
235
236 pub fn continue_session(mut self, enabled: bool) -> Self {
237 self.continue_session = enabled;
238 self
239 }
240
241 pub fn debug(mut self, enabled: bool) -> Self {
242 self.debug = enabled;
243 self
244 }
245
246 pub fn debug_file(mut self, path: impl Into<String>) -> Self {
247 self.debug_file = Some(path.into());
248 self
249 }
250
251 pub fn disable_slash_commands(mut self, enabled: bool) -> Self {
252 self.disable_slash_commands = enabled;
253 self
254 }
255
256 pub fn fallback_model(mut self, model: impl Into<String>) -> Self {
257 self.fallback_model = Some(model.into());
258 self
259 }
260
261 pub fn files(mut self, specs: impl IntoIterator<Item = impl Into<String>>) -> Self {
262 self.files = specs.into_iter().map(Into::into).collect();
263 self
264 }
265
266 pub fn fork_session(mut self, enabled: bool) -> Self {
267 self.fork_session = enabled;
268 self
269 }
270
271 pub fn from_pr(mut self, enabled: bool) -> Self {
272 self.from_pr = enabled;
273 self
274 }
275
276 pub fn from_pr_value(mut self, value: impl Into<String>) -> Self {
277 self.from_pr_value = Some(value.into());
278 self
279 }
280
281 pub fn ide(mut self, enabled: bool) -> Self {
282 self.ide = enabled;
283 self
284 }
285
286 pub fn include_partial_messages(mut self, enabled: bool) -> Self {
287 self.include_partial_messages = enabled;
288 self
289 }
290
291 pub fn max_budget_usd(mut self, amount: f64) -> Self {
292 self.max_budget_usd = Some(amount);
293 self
294 }
295
296 pub fn mcp_debug(mut self, enabled: bool) -> Self {
297 self.mcp_debug = enabled;
298 self
299 }
300
301 pub fn no_session_persistence(mut self, enabled: bool) -> Self {
302 self.no_session_persistence = enabled;
303 self
304 }
305
306 pub fn plugin_dirs(mut self, paths: impl IntoIterator<Item = impl Into<String>>) -> Self {
307 self.plugin_dirs = paths.into_iter().map(Into::into).collect();
308 self
309 }
310
311 pub fn replay_user_messages(mut self, enabled: bool) -> Self {
312 self.replay_user_messages = enabled;
313 self
314 }
315
316 pub fn resume(mut self, enabled: bool) -> Self {
317 self.resume = enabled;
318 self
319 }
320
321 pub fn resume_value(mut self, value: impl Into<String>) -> Self {
322 self.resume_value = Some(value.into());
323 self
324 }
325
326 pub fn session_id(mut self, id: impl Into<String>) -> Self {
327 self.session_id = Some(id.into());
328 self
329 }
330
331 pub fn setting_sources(mut self, sources: impl Into<String>) -> Self {
332 self.setting_sources = Some(sources.into());
333 self
334 }
335
336 pub fn settings(mut self, file_or_json: impl Into<String>) -> Self {
337 self.settings = Some(file_or_json.into());
338 self
339 }
340
341 pub fn system_prompt(mut self, prompt: impl Into<String>) -> Self {
342 self.system_prompt = Some(prompt.into());
343 self
344 }
345
346 pub fn tools(mut self, tools: impl IntoIterator<Item = impl Into<String>>) -> Self {
347 self.tools = tools.into_iter().map(Into::into).collect();
348 self
349 }
350
351 pub fn verbose(mut self, enabled: bool) -> Self {
352 self.verbose = enabled;
353 self
354 }
355
356 pub fn stdin_bytes(mut self, bytes: Vec<u8>) -> Self {
357 self.stdin = Some(bytes);
358 self
359 }
360
361 pub fn timeout(mut self, timeout: Duration) -> Self {
362 self.timeout = Some(timeout);
363 self
364 }
365
366 pub fn extra_args(mut self, args: impl IntoIterator<Item = impl Into<String>>) -> Self {
367 self.extra_args = args.into_iter().map(Into::into).collect();
368 self
369 }
370
371 pub fn argv(&self) -> Vec<String> {
372 let mut out: Vec<String> = Vec::new();
373
374 out.push("--print".to_string());
375 out.push("--output-format".to_string());
376 out.push(self.output_format.as_arg_value().to_string());
377
378 if let Some(input_format) = self.input_format {
379 out.push("--input-format".to_string());
380 out.push(input_format.as_arg_value().to_string());
381 }
382
383 if let Some(schema) = self.json_schema.as_ref() {
384 out.push("--json-schema".to_string());
385 out.push(schema.clone());
386 }
387
388 if let Some(model) = self.model.as_ref() {
389 out.push("--model".to_string());
390 out.push(model.clone());
391 }
392
393 if !self.allowed_tools.is_empty() {
394 out.push("--allowedTools".to_string());
395 out.extend(self.allowed_tools.iter().cloned());
396 }
397
398 if !self.disallowed_tools.is_empty() {
399 out.push("--disallowedTools".to_string());
400 out.extend(self.disallowed_tools.iter().cloned());
401 }
402
403 if let Some(mode) = self.permission_mode.as_ref() {
404 out.push("--permission-mode".to_string());
405 out.push(mode.clone());
406 }
407
408 if self.dangerously_skip_permissions {
409 out.push("--dangerously-skip-permissions".to_string());
410 }
411
412 if self.allow_dangerously_skip_permissions {
413 out.push("--allow-dangerously-skip-permissions".to_string());
414 }
415
416 if !self.add_dirs.is_empty() {
417 out.push("--add-dir".to_string());
418 out.extend(self.add_dirs.iter().cloned());
419 }
420
421 if let Some(config) = self.mcp_config.as_ref() {
422 out.push("--mcp-config".to_string());
423 out.push(config.clone());
424 }
425
426 if self.strict_mcp_config {
427 out.push("--strict-mcp-config".to_string());
428 }
429
430 if let Some(agent) = self.agent.as_ref() {
431 out.push("--agent".to_string());
432 out.push(agent.clone());
433 }
434
435 if let Some(agents) = self.agents.as_ref() {
436 out.push("--agents".to_string());
437 out.push(agents.clone());
438 }
439
440 if let Some(prompt) = self.append_system_prompt.as_ref() {
441 out.push("--append-system-prompt".to_string());
442 out.push(prompt.clone());
443 }
444
445 if !self.betas.is_empty() {
446 out.push("--betas".to_string());
447 out.extend(self.betas.iter().cloned());
448 }
449
450 if let Some(mode) = self.chrome_mode {
451 match mode {
452 ClaudeChromeMode::Chrome => out.push("--chrome".to_string()),
453 ClaudeChromeMode::NoChrome => out.push("--no-chrome".to_string()),
454 }
455 }
456
457 if self.continue_session {
458 out.push("--continue".to_string());
459 }
460
461 if self.debug {
462 out.push("--debug".to_string());
463 }
464
465 if let Some(path) = self.debug_file.as_ref() {
466 out.push("--debug-file".to_string());
467 out.push(path.clone());
468 }
469
470 if self.disable_slash_commands {
471 out.push("--disable-slash-commands".to_string());
472 }
473
474 if let Some(model) = self.fallback_model.as_ref() {
475 out.push("--fallback-model".to_string());
476 out.push(model.clone());
477 }
478
479 if !self.files.is_empty() {
480 out.push("--file".to_string());
481 out.extend(self.files.iter().cloned());
482 }
483
484 if self.fork_session {
485 out.push("--fork-session".to_string());
486 }
487
488 if let Some(value) = self.from_pr_value.as_ref() {
489 out.push("--from-pr".to_string());
490 out.push(value.clone());
491 } else if self.from_pr {
492 out.push("--from-pr".to_string());
493 }
494
495 if self.ide {
496 out.push("--ide".to_string());
497 }
498
499 if self.include_partial_messages {
500 out.push("--include-partial-messages".to_string());
501 }
502
503 if let Some(amount) = self.max_budget_usd {
504 out.push("--max-budget-usd".to_string());
505 out.push(amount.to_string());
506 }
507
508 if self.mcp_debug {
509 out.push("--mcp-debug".to_string());
510 }
511
512 if self.no_session_persistence {
513 out.push("--no-session-persistence".to_string());
514 }
515
516 if !self.plugin_dirs.is_empty() {
517 out.push("--plugin-dir".to_string());
518 out.extend(self.plugin_dirs.iter().cloned());
519 }
520
521 if self.replay_user_messages {
522 out.push("--replay-user-messages".to_string());
523 }
524
525 if let Some(value) = self.resume_value.as_ref() {
526 out.push("--resume".to_string());
527 out.push(value.clone());
528 } else if self.resume {
529 out.push("--resume".to_string());
530 }
531
532 if let Some(id) = self.session_id.as_ref() {
533 out.push("--session-id".to_string());
534 out.push(id.clone());
535 }
536
537 if let Some(sources) = self.setting_sources.as_ref() {
538 out.push("--setting-sources".to_string());
539 out.push(sources.clone());
540 }
541
542 if let Some(settings) = self.settings.as_ref() {
543 out.push("--settings".to_string());
544 out.push(settings.clone());
545 }
546
547 if let Some(prompt) = self.system_prompt.as_ref() {
548 out.push("--system-prompt".to_string());
549 out.push(prompt.clone());
550 }
551
552 if !self.tools.is_empty() {
553 out.push("--tools".to_string());
554 out.extend(self.tools.iter().cloned());
555 }
556
557 let stream_json_requires_verbose =
562 matches!(self.output_format, ClaudeOutputFormat::StreamJson);
563 if self.verbose || stream_json_requires_verbose {
564 out.push("--verbose".to_string());
565 }
566
567 out.extend(self.extra_args.iter().cloned());
568
569 if let Some(prompt) = self.prompt.as_ref() {
570 out.push(prompt.clone());
571 }
572
573 out
574 }
575}