feat(cli): align slash help/status/model handling

Centralize slash command parsing in the commands crate so the REPL can share help metadata and grow toward Claude Code parity without duplicating handlers. This adds shared /help and /model parsing, routes REPL dispatch through the shared parser, and upgrades /status to report model and token totals.

To satisfy the required verification gate, this also fixes existing workspace clippy and test blockers in runtime, tools, api, and compat-harness that were unrelated to the new command behavior but prevented fmt/clippy/test from passing cleanly.

Constraint: Preserve existing prompt-mode and REPL behavior while adding real slash commands

Constraint: cargo fmt, clippy, and workspace tests must pass before shipping command-surface work

Rejected: Keep command handling only in main.rs | would deepen duplication with commands crate and resume path

Confidence: high

Scope-risk: moderate

Reversibility: clean

Directive: Extend new slash commands through the shared commands crate first so REPL and resume entrypoints stay consistent

Tested: cargo fmt; cargo clippy --workspace --all-targets -- -D warnings; cargo test --workspace

Not-tested: live Anthropic network execution beyond existing mocked/integration coverage
This commit is contained in:
Yeachan-Heo
2026-03-31 19:23:05 +00:00
parent 4586764a0e
commit a96bb6c60f
6 changed files with 350 additions and 66 deletions

View File

@@ -146,11 +146,17 @@ pub fn mvp_tool_specs() -> Vec<ToolSpec> {
pub fn execute_tool(name: &str, input: &Value) -> Result<String, String> {
match name {
"bash" => from_value::<BashCommandInput>(input).and_then(run_bash),
"read_file" => from_value::<ReadFileInput>(input).and_then(run_read_file),
"write_file" => from_value::<WriteFileInput>(input).and_then(run_write_file),
"edit_file" => from_value::<EditFileInput>(input).and_then(run_edit_file),
"glob_search" => from_value::<GlobSearchInputValue>(input).and_then(run_glob_search),
"grep_search" => from_value::<GrepSearchInput>(input).and_then(run_grep_search),
"read_file" => from_value::<ReadFileInput>(input).and_then(|input| run_read_file(&input)),
"write_file" => {
from_value::<WriteFileInput>(input).and_then(|input| run_write_file(&input))
}
"edit_file" => from_value::<EditFileInput>(input).and_then(|input| run_edit_file(&input)),
"glob_search" => {
from_value::<GlobSearchInputValue>(input).and_then(|input| run_glob_search(&input))
}
"grep_search" => {
from_value::<GrepSearchInput>(input).and_then(|input| run_grep_search(&input))
}
_ => Err(format!("unsupported tool: {name}")),
}
}
@@ -164,15 +170,17 @@ fn run_bash(input: BashCommandInput) -> Result<String, String> {
.map_err(|error| error.to_string())
}
fn run_read_file(input: ReadFileInput) -> Result<String, String> {
to_pretty_json(read_file(&input.path, input.offset, input.limit).map_err(io_to_string)?)
fn run_read_file(input: &ReadFileInput) -> Result<String, String> {
to_pretty_json(
read_file(&input.path, input.offset, input.limit).map_err(|error| io_to_string(&error))?,
)
}
fn run_write_file(input: WriteFileInput) -> Result<String, String> {
to_pretty_json(write_file(&input.path, &input.content).map_err(io_to_string)?)
fn run_write_file(input: &WriteFileInput) -> Result<String, String> {
to_pretty_json(write_file(&input.path, &input.content).map_err(|error| io_to_string(&error))?)
}
fn run_edit_file(input: EditFileInput) -> Result<String, String> {
fn run_edit_file(input: &EditFileInput) -> Result<String, String> {
to_pretty_json(
edit_file(
&input.path,
@@ -180,23 +188,25 @@ fn run_edit_file(input: EditFileInput) -> Result<String, String> {
&input.new_string,
input.replace_all.unwrap_or(false),
)
.map_err(io_to_string)?,
.map_err(|error| io_to_string(&error))?,
)
}
fn run_glob_search(input: GlobSearchInputValue) -> Result<String, String> {
to_pretty_json(glob_search(&input.pattern, input.path.as_deref()).map_err(io_to_string)?)
fn run_glob_search(input: &GlobSearchInputValue) -> Result<String, String> {
to_pretty_json(
glob_search(&input.pattern, input.path.as_deref()).map_err(|error| io_to_string(&error))?,
)
}
fn run_grep_search(input: GrepSearchInput) -> Result<String, String> {
to_pretty_json(grep_search(&input).map_err(io_to_string)?)
fn run_grep_search(input: &GrepSearchInput) -> Result<String, String> {
to_pretty_json(grep_search(input).map_err(|error| io_to_string(&error))?)
}
fn to_pretty_json<T: serde::Serialize>(value: T) -> Result<String, String> {
serde_json::to_string_pretty(&value).map_err(|error| error.to_string())
}
fn io_to_string(error: std::io::Error) -> String {
fn io_to_string(error: &std::io::Error) -> String {
error.to_string()
}