Skip to main content

claude_dialog/
cli.rs

1//! Command-line argument parsing for the Claude Dialog CLI
2//!
3//! This module handles parsing and validation of command-line arguments using the `clap` crate.
4//! It supports specifying custom system prompts, model selection, and prompt modification options.
5//!
6//! # Examples
7//!
8//! ```
9//! use claude_dialog::cli::{Args, parse_args};
10//!
11//! // Parse arguments with a custom system prompt
12//! let args = parse_args(vec!["claude-dialog", "--system-prompt", "custom.md"]).unwrap();
13//! assert_eq!(args.system_prompt_files, vec!["custom.md"]);
14//!
15//! // Parse arguments with a model specification
16//! let args = parse_args(vec!["claude-dialog", "--model", "claude-3-opus"]).unwrap();
17//! assert_eq!(args.model, Some("claude-3-opus".to_string()));
18//! ```
19
20use clap::Parser;
21use anyhow::{Result, anyhow};
22
23/// Command-line arguments for the Claude Dialog application
24///
25/// This structure defines all available command-line options for configuring
26/// the Claude conversation interface.
27///
28/// # Examples
29///
30/// ```
31/// use claude_dialog::cli::Args;
32///
33/// let args = Args {
34///     system_prompt_files: vec!["prompt1.md".to_string(), "prompt2.md".to_string()],
35///     append_prompt_file: None,
36///     model: Some("claude-3-opus".to_string()),
37/// };
38///
39/// assert_eq!(args.system_prompt_files.len(), 2);
40/// assert!(args.append_prompt_file.is_none());
41/// ```
42#[derive(Parser, Debug)]
43#[command(
44    name = "claude-dialog",
45    about = "Interactive CLI for Claude conversations with custom system prompts",
46    long_about = None,
47    version
48)]
49pub struct Args {
50    /// Custom system prompt files (can be specified multiple times)
51    ///
52    /// These files will completely replace the default system prompt.
53    /// Multiple files can be specified and will be concatenated in order.
54    ///
55    /// # Example
56    ///
57    /// ```bash
58    /// claude-dialog --system-prompt prompt1.md --system-prompt prompt2.md
59    /// ```
60    #[arg(long = "system-prompt", value_name = "FILE", action = clap::ArgAction::Append)]
61    pub system_prompt_files: Vec<String>,
62
63    /// Append to default system prompt
64    ///
65    /// This file's contents will be appended to the default system prompt
66    /// rather than replacing it. Cannot be used together with `--system-prompt`.
67    ///
68    /// # Example
69    ///
70    /// ```bash
71    /// claude-dialog --append-system-prompt additional_instructions.md
72    /// ```
73    #[arg(long = "append-system-prompt", value_name = "FILE", conflicts_with = "system_prompt_files")]
74    pub append_prompt_file: Option<String>,
75
76    /// Claude model to use
77    ///
78    /// Specifies which Claude model to use for the conversation.
79    /// Common options include: claude-3-opus, claude-3-sonnet, claude-3-haiku
80    ///
81    /// # Example
82    ///
83    /// ```bash
84    /// claude-dialog --model claude-3-opus
85    /// ```
86    #[arg(long = "model", value_name = "MODEL")]
87    pub model: Option<String>,
88}
89
90/// Parse command-line arguments from a vector of strings
91///
92/// This function wraps the clap parser and converts parsing errors
93/// into anyhow errors for consistent error handling.
94///
95/// # Arguments
96///
97/// * `args` - A vector of string slices representing command-line arguments
98///
99/// # Returns
100///
101/// * `Result<Args>` - Parsed arguments or an error if parsing fails
102///
103/// # Examples
104///
105/// ```
106/// use claude_dialog::cli::parse_args;
107///
108/// // Basic usage
109/// let args = parse_args(vec!["claude-dialog"]).unwrap();
110/// assert!(args.system_prompt_files.is_empty());
111///
112/// // With system prompt
113/// let args = parse_args(vec!["claude-dialog", "--system-prompt", "custom.md"]).unwrap();
114/// assert_eq!(args.system_prompt_files, vec!["custom.md"]);
115///
116/// // With multiple options
117/// let args = parse_args(vec![
118///     "claude-dialog",
119///     "--system-prompt", "p1.md",
120///     "--system-prompt", "p2.md",
121///     "--model", "claude-3-opus"
122/// ]).unwrap();
123/// assert_eq!(args.system_prompt_files.len(), 2);
124/// assert_eq!(args.model, Some("claude-3-opus".to_string()));
125/// ```
126pub fn parse_args(args: Vec<&str>) -> Result<Args> {
127    let args = Args::try_parse_from(args)
128        .map_err(|e| anyhow!(e.to_string()))?;
129    Ok(args)
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    #[test]
137    fn test_args_struct() {
138        let args = Args {
139            system_prompt_files: vec!["test.md".to_string()],
140            append_prompt_file: None,
141            model: Some("claude-3".to_string()),
142        };
143        assert_eq!(args.system_prompt_files.len(), 1);
144        assert!(args.append_prompt_file.is_none());
145        assert_eq!(args.model, Some("claude-3".to_string()));
146    }
147}