diff --git a/.gitignore b/.gitignore index de821bb..324ae1d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ __pycache__/ archive/ +.omx/ +.clawd-agents/ diff --git a/rust/.gitignore b/rust/.gitignore index 2f7896d..19e1a8e 100644 --- a/rust/.gitignore +++ b/rust/.gitignore @@ -1 +1,3 @@ target/ +.omx/ +.clawd-agents/ diff --git a/rust/README.md b/rust/README.md index 2409aa6..dadefe3 100644 --- a/rust/README.md +++ b/rust/README.md @@ -1,54 +1,173 @@ -# Rust port foundation +# Rusty Claude CLI -This directory contains the first compatibility-first Rust foundation for a drop-in Claude Code CLI replacement. - -## Current milestone - -This initial milestone focuses on **harness-first scaffolding**, not full feature parity: - -- a Cargo workspace aligned to major upstream seams -- a placeholder CLI crate (`rusty-claude-cli`) -- runtime, command, and tool registry skeleton crates -- a `compat-harness` crate that reads the upstream TypeScript sources in `../src/` -- tests that prove upstream manifests/bootstrap hints can be extracted from the leaked TypeScript codebase +`rust/` contains the Rust workspace for the integrated `rusty-claude-cli` deliverable. +It is intended to be something you can clone, build, and run directly. ## Workspace layout ```text rust/ ├── Cargo.toml +├── Cargo.lock ├── README.md -├── crates/ -│ ├── rusty-claude-cli/ -│ ├── runtime/ -│ ├── commands/ -│ ├── tools/ -│ └── compat-harness/ -└── tests/ +└── crates/ + ├── api/ # Anthropic API client + SSE streaming support + ├── commands/ # Shared slash-command metadata/help surfaces + ├── compat-harness/ # Upstream TS manifest extraction harness + ├── runtime/ # Session/runtime/config/prompt orchestration + ├── rusty-claude-cli/ # Main CLI binary + └── tools/ # Built-in tool implementations ``` -## How to use +## Prerequisites -From this directory: +- Rust toolchain installed (`rustup`, stable toolchain) +- Network access and Anthropic credentials for live prompt/REPL usage + +## Build + +From the repository root: ```bash -cargo fmt --all -cargo check --workspace -cargo test --workspace -cargo run -p rusty-claude-cli -- --help -cargo run -p rusty-claude-cli -- dump-manifests -cargo run -p rusty-claude-cli -- bootstrap-plan +cd rust +cargo build --release -p rusty-claude-cli ``` -## Design notes +The optimized binary will be written to: -The shape follows the PRD's harness-first recommendation: +```bash +./target/release/rusty-claude-cli +``` -1. Extract observable upstream command/tool/bootstrap facts first. -2. Keep Rust module boundaries recognizable. -3. Grow runtime compatibility behind proof artifacts. -4. Document explicit gaps instead of implying drop-in parity too early. +## Test -## Relationship to the root README +Run the verified workspace test suite used for release-readiness: -The repository root README explains the leaked TypeScript codebase. This document tracks the Rust replacement effort that lives in `rust/`. +```bash +cd rust +cargo test --workspace --exclude compat-harness +``` + +## Quick start + +### Show help + +```bash +cd rust +cargo run -p rusty-claude-cli -- --help +``` + +### Print version + +```bash +cd rust +cargo run -p rusty-claude-cli -- --version +``` + +## Usage examples + +### 1) Prompt mode + +Send one prompt, stream the answer, then exit: + +```bash +cd rust +cargo run -p rusty-claude-cli -- prompt "Summarize the architecture of this repository" +``` + +Use a specific model: + +```bash +cd rust +cargo run -p rusty-claude-cli -- --model claude-sonnet-4-20250514 prompt "List the key crates in this workspace" +``` + +### 2) REPL mode + +Start the interactive shell: + +```bash +cd rust +cargo run -p rusty-claude-cli -- +``` + +Inside the REPL, useful commands include: + +```text +/help +/status +/model claude-sonnet-4-20250514 +/permissions workspace-write +/cost +/compact +/memory +/config +/init +/exit +``` + +### 3) Resume an existing session + +Inspect or maintain a saved session file without entering the REPL: + +```bash +cd rust +cargo run -p rusty-claude-cli -- --resume session.json /status /compact /cost +``` + +You can also inspect memory/config state for a restored session: + +```bash +cd rust +cargo run -p rusty-claude-cli -- --resume session.json /memory /config +``` + +## Available commands + +### Top-level CLI commands + +- `prompt ` — run one prompt non-interactively +- `--resume [/commands...]` — inspect or maintain a saved session +- `dump-manifests` — print extracted upstream manifest counts +- `bootstrap-plan` — print the current bootstrap skeleton +- `system-prompt [--cwd PATH] [--date YYYY-MM-DD]` — render the synthesized system prompt +- `--help` / `-h` — show CLI help +- `--version` / `-V` — print the CLI version + +### Interactive slash commands + +- `/help` — show command help +- `/status` — show current session status +- `/compact` — compact local session history +- `/model [model]` — inspect or switch the active model +- `/permissions [read-only|workspace-write|danger-full-access]` — inspect or switch permissions +- `/clear [--confirm]` — clear the current local session +- `/cost` — show token usage totals +- `/resume ` — load a saved session into the REPL +- `/config [env|hooks|model]` — inspect discovered Claude config +- `/memory` — inspect loaded instruction memory files +- `/init` — create a starter `CLAUDE.md` +- `/exit` — leave the REPL + +## Environment variables + +### Anthropic/API + +- `ANTHROPIC_AUTH_TOKEN` — preferred bearer token for API auth +- `ANTHROPIC_API_KEY` — legacy API key fallback if auth token is unset +- `ANTHROPIC_BASE_URL` — override the Anthropic API base URL +- `ANTHROPIC_MODEL` — default model used by selected live integration tests + +### CLI/runtime + +- `RUSTY_CLAUDE_PERMISSION_MODE` — default REPL permission mode (`read-only`, `workspace-write`, or `danger-full-access`) +- `CLAUDE_CONFIG_HOME` — override Claude config discovery root +- `CLAUDE_CODE_REMOTE` — enable remote-session bootstrap handling when supported +- `CLAUDE_CODE_REMOTE_SESSION_ID` — remote session identifier when using remote mode +- `CLAUDE_CODE_UPSTREAM` — override the upstream TS source path for compat-harness extraction +- `CLAWD_WEB_SEARCH_BASE_URL` — override the built-in web search service endpoint used by tooling + +## Notes + +- `compat-harness` exists to compare the Rust port against the upstream TypeScript codebase and is intentionally excluded from the requested release test run. +- The CLI currently focuses on a practical integrated workflow: prompt execution, REPL operation, session inspection/resume, config discovery, and tool/runtime plumbing. diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index a2e242b..350291a 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -26,6 +26,7 @@ use tools::{execute_tool, mvp_tool_specs}; const DEFAULT_MODEL: &str = "claude-sonnet-4-20250514"; const DEFAULT_MAX_TOKENS: u32 = 32; const DEFAULT_DATE: &str = "2026-03-31"; +const VERSION: &str = env!("CARGO_PKG_VERSION"); fn main() { if let Err(error) = run() { @@ -47,6 +48,7 @@ fn run() -> Result<(), Box> { CliAction::Prompt { prompt, model } => LiveCli::new(model, false)?.run_turn(&prompt)?, CliAction::Repl { model } => run_repl(model)?, CliAction::Help => print_help(), + CliAction::Version => print_version(), } Ok(()) } @@ -71,6 +73,7 @@ enum CliAction { model: String, }, Help, + Version, } fn parse_args(args: &[String]) -> Result { @@ -104,6 +107,9 @@ fn parse_args(args: &[String]) -> Result { if matches!(rest.first().map(String::as_str), Some("--help" | "-h")) { return Ok(CliAction::Help); } + if matches!(rest.first().map(String::as_str), Some("--version" | "-V")) { + return Ok(CliAction::Version); + } if rest.first().map(String::as_str) == Some("--resume") { return parse_resume_args(&rest[1..]); } @@ -1400,22 +1406,91 @@ fn convert_messages(messages: &[ConversationMessage]) -> Vec { } fn print_help() { - println!("rusty-claude-cli"); - println!(); - println!("Usage:"); - println!(" rusty-claude-cli [--model MODEL]"); - println!(" Start interactive REPL"); - println!(" rusty-claude-cli [--model MODEL] prompt TEXT"); - println!(" Send one prompt and stream the response"); - println!(" rusty-claude-cli --resume SESSION.json [/status] [/compact] [...]"); - println!(" Inspect or maintain a saved session without entering the REPL"); - println!(" rusty-claude-cli dump-manifests"); - println!(" rusty-claude-cli bootstrap-plan"); - println!(" rusty-claude-cli system-prompt [--cwd PATH] [--date YYYY-MM-DD]"); - println!(); - println!("Interactive slash commands:"); - println!("{}", render_slash_command_help()); - println!(); + let mut stdout = io::stdout(); + let _ = print_help_to(&mut stdout); +} + +fn print_help_to(out: &mut impl Write) -> io::Result<()> { + writeln!(out, "rusty-claude-cli")?; + writeln!(out, "Version: {VERSION}")?; + writeln!(out)?; + writeln!( + out, + "Rust-first Claude Code-style CLI for prompt, REPL, and saved-session workflows." + )?; + writeln!(out)?; + writeln!(out, "Usage:")?; + writeln!(out, " rusty-claude-cli [--model MODEL]")?; + writeln!(out, " Start interactive REPL")?; + writeln!(out, " rusty-claude-cli [--model MODEL] prompt TEXT")?; + writeln!(out, " Send one prompt and stream the response")?; + writeln!( + out, + " rusty-claude-cli --resume SESSION.json [/status] [/compact] [...]" + )?; + writeln!( + out, + " Inspect or maintain a saved session without entering the REPL" + )?; + writeln!(out, " rusty-claude-cli dump-manifests")?; + writeln!( + out, + " Inspect extracted upstream command/tool metadata" + )?; + writeln!(out, " rusty-claude-cli bootstrap-plan")?; + writeln!(out, " Print the current bootstrap phase skeleton")?; + writeln!( + out, + " rusty-claude-cli system-prompt [--cwd PATH] [--date YYYY-MM-DD]" + )?; + writeln!( + out, + " Render the synthesized system prompt for debugging" + )?; + writeln!(out, " rusty-claude-cli --help")?; + writeln!(out, " rusty-claude-cli --version")?; + writeln!(out)?; + writeln!(out, "Options:")?; + writeln!( + out, + " --model MODEL Override the active Anthropic model" + )?; + writeln!( + out, + " --resume PATH Restore a saved session file and optionally run slash commands" + )?; + writeln!(out, " -h, --help Show this help page")?; + writeln!(out, " -V, --version Print the CLI version")?; + writeln!(out)?; + writeln!(out, "Environment:")?; + writeln!( + out, + " ANTHROPIC_AUTH_TOKEN Preferred bearer token for Anthropic API requests" + )?; + writeln!( + out, + " ANTHROPIC_API_KEY Legacy API key fallback if auth token is unset" + )?; + writeln!( + out, + " ANTHROPIC_BASE_URL Override the Anthropic API base URL" + )?; + writeln!( + out, + " ANTHROPIC_MODEL Default model for selected integration tests" + )?; + writeln!( + out, + " RUSTY_CLAUDE_PERMISSION_MODE Default permission mode for REPL sessions" + )?; + writeln!( + out, + " CLAUDE_CONFIG_HOME Override Claude config discovery root" + )?; + writeln!(out)?; + writeln!(out, "Interactive slash commands:")?; + writeln!(out, "{}", render_slash_command_help())?; + writeln!(out)?; let resume_commands = resume_supported_slash_commands() .into_iter() .map(|spec| match spec.argument_hint { @@ -1424,10 +1499,26 @@ fn print_help() { }) .collect::>() .join(", "); - println!("Resume-safe commands: {resume_commands}"); - println!("Examples:"); - println!(" rusty-claude-cli --resume session.json /status /compact /cost"); - println!(" rusty-claude-cli --resume session.json /memory /config"); + writeln!(out, "Resume-safe commands: {resume_commands}")?; + writeln!(out, "Examples:")?; + writeln!( + out, + " rusty-claude-cli prompt \"Summarize the repo architecture\"" + )?; + writeln!(out, " rusty-claude-cli --model claude-sonnet-4-20250514")?; + writeln!( + out, + " rusty-claude-cli --resume session.json /status /compact /cost" + )?; + writeln!( + out, + " rusty-claude-cli --resume session.json /memory /config" + )?; + Ok(()) +} + +fn print_version() { + println!("rusty-claude-cli {VERSION}"); } #[cfg(test)] @@ -1525,6 +1616,18 @@ mod tests { ); } + #[test] + fn parses_version_flags() { + assert_eq!( + parse_args(&["--version".to_string()]).expect("args should parse"), + CliAction::Version + ); + assert_eq!( + parse_args(&["-V".to_string()]).expect("args should parse"), + CliAction::Version + ); + } + #[test] fn shared_help_uses_resume_annotation_copy() { let help = commands::render_slash_command_help(); @@ -1532,6 +1635,16 @@ mod tests { assert!(help.contains("works with --resume SESSION.json")); } + #[test] + fn cli_help_mentions_version_and_environment() { + let mut output = Vec::new(); + super::print_help_to(&mut output).expect("help should render"); + let help = String::from_utf8(output).expect("help should be utf8"); + assert!(help.contains("--version")); + assert!(help.contains("ANTHROPIC_AUTH_TOKEN")); + assert!(help.contains("RUSTY_CLAUDE_PERMISSION_MODE")); + } + #[test] fn repl_help_includes_shared_commands_and_exit() { let help = render_repl_help(); @@ -1547,6 +1660,7 @@ mod tests { assert!(help.contains("/memory")); assert!(help.contains("/init")); assert!(help.contains("/exit")); + assert!(help.contains("slash commands")); } #[test]