cai-cli 0.1.0

Command-line interface for Coding Agent Insights
# cai-cli - Claude Code Instructions

## Crate Purpose

`cai-cli` is the main command-line interface for CAI. It orchestrates all other crates to provide a unified user experience.

## Architecture

The CLI is built as a thin orchestrator layer:

```
cai-cli (main.rs)
    ├── Parses commands with clap
    ├── Initializes storage
    ├── Delegates to specialized crates
    └── Formats output for user
```

## Command Structure

### Main Commands

```rust
#[derive(Subcommand, Clone)]
enum Commands {
    Query { query: String, output: String },
    Ingest { source: String, path: Option<String> },
    Tui,
    Web { port: u16 },
}
```

### Command Flow

1. **Parse** - clap parses arguments into structured types
2. **Initialize** - Set up storage and dependencies
3. **Execute** - Delegate to appropriate crate
4. **Output** - Format and display results

## Working with Commands

### Adding a New Command

```rust
#[derive(Subcommand, Clone)]
enum Commands {
    // Existing commands...
    /// My new command
    NewCommand {
        /// Description of argument
        #[arg(short, long)]
        arg: String,
    },
}

// In main()
Commands::NewCommand { arg } => {
    // Handle the command
    println!("Executing with: {}", arg);
    Ok(())
}
```

### Adding Arguments to Existing Commands

```rust
Query {
    query: String,
    output: String,
    // Add new argument
    #[arg(short, long, default_value = "100")]
    limit: usize,
}
```

## Storage Initialization

Commands need storage. Current pattern:

```rust
#[tokio::main]
async fn main() -> cai_core::Result<()> {
    let cli = Cli::parse();

    match cli.command {
        Commands::Query { query, output } => {
            // Initialize in-memory for now
            let storage = std::sync::Arc::new(cai_storage::MemoryStorage::new());

            // TODO: Use persistent storage based on config
            // let storage = std::sync::Arc::new(
            //     cai_storage::SqliteStorage::new("cai.db").await?
            // );

            // Execute query with storage
            // ...
        }
        // ...
    }
}
```

## Error Handling

User-friendly error messages:

```rust
use colored::Colorize;

// In command handlers
Commands::Query { query, output } => {
    match execute_query(&storage, &query).await {
        Ok(results) => {
            println!("{}", "Success".green());
            // Display results
        }
        Err(e) => {
            eprintln!("{} {}", "Error:".red(), e);
            std::process::exit(1);
        }
    }
}
```

## Output Formatting

### Table Output (Default)

```rust
use cai_output::TableFormatter;
use colored::*;

println!("{}", "Query Results:".green().bold());
let formatter = TableFormatter::new();
let output = formatter.format(&results)?;
println!("{}", output);
```

### JSON Output

```rust
use cai_output::JsonFormatter;

let formatter = JsonFormatter::new();
let output = formatter.format(&results)?;
println!("{}", output);
```

### Conditional Formatting

```rust
let output: String = match output.as_str() {
    "json" => JsonFormatter::new().format(&results)?,
    "csv" => CsvFormatter::new().format(&results)?,
    "table" => TableFormatter::new().format(&results)?,
    _ => return Err(Error::Message(format!("Unknown output format: {}", output))),
};
```

## Configuration

### Future: Config File

Plan to support `~/.cai/config.toml`:

```toml
[storage]
type = "sqlite"
path = "~/.cai/data.db"

[output]
default_format = "table"
max_rows = 1000

[ingest]
claude_path = "~/.claude/conversations"
codex_path = "~/.codex/history.jsonl"
```

### Environment Variables

```rust
use clap::Parser;

#[derive(Parser, Clone)]
struct Cli {
    #[arg(short, long, env = "CAI_OUTPUT")]
    output: Option<String>,
}
```

## Testing CLI

### Unit Tests

Test command parsing:

```rust
#[cfg(test)]
mod tests {
    use super::*;
    use clap::Parser;

    #[test]
    fn test_parse_query_command() {
        let cli = Cli::try_parse_from(["cai", "query", "SELECT *"]).unwrap();
        matches!(cli.command, Commands::Query { .. });
    }

    #[test]
    fn test_parse_with_output_flag() {
        let cli = Cli::try_parse_from(["cai", "query", "SELECT *", "--output", "json"]).unwrap();
        if let Commands::Query { output, .. } = cli.command {
            assert_eq!(output, "json");
        }
    }
}
```

### Integration Tests

Test end-to-end flows in `tests/cli_tests.rs`:

```rust
use std::process::Command;

#[test]
fn test_query_command() {
    let output = Command::new("./target/debug/cai")
        .args(["query", "SELECT * FROM history LIMIT 1"])
        .output()
        .expect("Failed to execute");

    assert!(output.status.success());
}
```

## Common Patterns

### Async Command Execution

```rust
Commands::Query { query, output } => {
    tokio::spawn(async move {
        // Long-running operation
        execute_query(&storage, &query).await
    });
    Ok(())
}
```

### Progress Indicators

```rust
use indicatif::ProgressBar;

Commands::Ingest { source, path } => {
    let progress = ProgressBar::new_spinner();
    progress.set_message(format!("Ingesting from {}...", source));

    // Do work...

    progress.finish_with_message("Done!");
    Ok(())
}
```

### Multi-Source Ingestion

```rust
Commands::Ingest { source, path } => {
    let parser = match source.as_str() {
        "claude" => cai_ingest::ClaudeParser::new(),
        "codex" => cai_ingest::CodexParser::new(),
        "git" => cai_ingest::GitParser::new(),
        _ => return Err(Error::Message(format!("Unknown source: {}", source))),
    };

    let path = path.unwrap_or_else(|| default_path(&source));
    let entries = parser.parse(&path)?;
    // ...
}
```

## Debugging

### Enable Logging

```bash
RUST_LOG=cai_cli=debug cai query "SELECT *"
```

### Trace All Crates

```bash
RUST_LOG=cai_core=trace,cai_storage=trace,cai_query=trace cai query "SELECT *"
```

### Verbose Mode

Add verbose flag:

```rust
#[derive(Parser, Clone)]
struct Cli {
    #[arg(short, long, action = clap::ArgAction::Count)]
    verbose: u8,
}

// In main()
let log_level = match cli.verbose {
    0 => tracing::Level::WARN,
    1 => tracing::Level::INFO,
    2 => tracing::Level::DEBUG,
    _ => tracing::Level::TRACE,
};
```

## Release Checklist

Before releasing cai-cli:

1. **Test all commands** - Manual testing of each command
2. **Help text** - Verify `cai --help` and `cai command --help`
3. **Error messages** - Ensure user-friendly errors
4. **Dependencies** - Check for unused dependencies
5. **Binary size** - Consider `cargo bloat` for size analysis

## Dependencies

`cai-cli` depends on:
- `cai-core` - Shared types
- `cai-ingest` - Data ingestion
- `cai-query` - Query engine
- `cai-storage` - Storage backends
- `cai-output` - Output formatters
- `cai-tui` - Terminal UI
- `cai-web` - Web dashboard

Avoid adding heavy CLI libraries. Use `clap` for parsing, `colored` for colors.

## Getting Help

- See crate `README.md` for usage examples
- Check `src/main.rs` for command implementations
- Review clap documentation for advanced parsing