mirror of
https://github.com/lWolvesl/claw-code.git
synced 2026-04-03 05:31:51 +08:00
Make CLI command discovery closer to Claude Code
Improve top-level help and shared slash-command help so the implemented surface is easier to discover, with explicit resume-safe markings and concrete examples for saved-session workflows. This keeps the command registry authoritative while making the CLI feel less skeletal and more like a real operator-facing tool. Constraint: Help text must reflect the actual implemented surface without advertising unsupported offline/runtime behavior Rejected: Separate bespoke help tables for REPL and --resume | would drift from the shared command registry Confidence: high Scope-risk: narrow Reversibility: clean Directive: Add new slash commands to the shared registry first so help and resume capability stay synchronized Tested: cargo fmt --manifest-path ./rust/Cargo.toml --all; cargo clippy --manifest-path ./rust/Cargo.toml --workspace --all-targets -- -D warnings; cargo test --manifest-path ./rust/Cargo.toml --workspace Not-tested: Manual UX comparison against upstream Claude Code help output
This commit is contained in:
@@ -35,6 +35,7 @@ pub struct SlashCommandSpec {
|
|||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
pub summary: &'static str,
|
pub summary: &'static str,
|
||||||
pub argument_hint: Option<&'static str>,
|
pub argument_hint: Option<&'static str>,
|
||||||
|
pub resume_supported: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
const SLASH_COMMAND_SPECS: &[SlashCommandSpec] = &[
|
const SLASH_COMMAND_SPECS: &[SlashCommandSpec] = &[
|
||||||
@@ -42,56 +43,67 @@ const SLASH_COMMAND_SPECS: &[SlashCommandSpec] = &[
|
|||||||
name: "help",
|
name: "help",
|
||||||
summary: "Show available slash commands",
|
summary: "Show available slash commands",
|
||||||
argument_hint: None,
|
argument_hint: None,
|
||||||
|
resume_supported: true,
|
||||||
},
|
},
|
||||||
SlashCommandSpec {
|
SlashCommandSpec {
|
||||||
name: "status",
|
name: "status",
|
||||||
summary: "Show current session status",
|
summary: "Show current session status",
|
||||||
argument_hint: None,
|
argument_hint: None,
|
||||||
|
resume_supported: true,
|
||||||
},
|
},
|
||||||
SlashCommandSpec {
|
SlashCommandSpec {
|
||||||
name: "compact",
|
name: "compact",
|
||||||
summary: "Compact local session history",
|
summary: "Compact local session history",
|
||||||
argument_hint: None,
|
argument_hint: None,
|
||||||
|
resume_supported: true,
|
||||||
},
|
},
|
||||||
SlashCommandSpec {
|
SlashCommandSpec {
|
||||||
name: "model",
|
name: "model",
|
||||||
summary: "Show or switch the active model",
|
summary: "Show or switch the active model",
|
||||||
argument_hint: Some("[model]"),
|
argument_hint: Some("[model]"),
|
||||||
|
resume_supported: false,
|
||||||
},
|
},
|
||||||
SlashCommandSpec {
|
SlashCommandSpec {
|
||||||
name: "permissions",
|
name: "permissions",
|
||||||
summary: "Show or switch the active permission mode",
|
summary: "Show or switch the active permission mode",
|
||||||
argument_hint: Some("[read-only|workspace-write|danger-full-access]"),
|
argument_hint: Some("[read-only|workspace-write|danger-full-access]"),
|
||||||
|
resume_supported: false,
|
||||||
},
|
},
|
||||||
SlashCommandSpec {
|
SlashCommandSpec {
|
||||||
name: "clear",
|
name: "clear",
|
||||||
summary: "Start a fresh local session",
|
summary: "Start a fresh local session",
|
||||||
argument_hint: None,
|
argument_hint: None,
|
||||||
|
resume_supported: true,
|
||||||
},
|
},
|
||||||
SlashCommandSpec {
|
SlashCommandSpec {
|
||||||
name: "cost",
|
name: "cost",
|
||||||
summary: "Show cumulative token usage for this session",
|
summary: "Show cumulative token usage for this session",
|
||||||
argument_hint: None,
|
argument_hint: None,
|
||||||
|
resume_supported: true,
|
||||||
},
|
},
|
||||||
SlashCommandSpec {
|
SlashCommandSpec {
|
||||||
name: "resume",
|
name: "resume",
|
||||||
summary: "Load a saved session into the REPL",
|
summary: "Load a saved session into the REPL",
|
||||||
argument_hint: Some("<session-path>"),
|
argument_hint: Some("<session-path>"),
|
||||||
|
resume_supported: false,
|
||||||
},
|
},
|
||||||
SlashCommandSpec {
|
SlashCommandSpec {
|
||||||
name: "config",
|
name: "config",
|
||||||
summary: "Inspect discovered Claude config files",
|
summary: "Inspect discovered Claude config files",
|
||||||
argument_hint: None,
|
argument_hint: None,
|
||||||
|
resume_supported: true,
|
||||||
},
|
},
|
||||||
SlashCommandSpec {
|
SlashCommandSpec {
|
||||||
name: "memory",
|
name: "memory",
|
||||||
summary: "Inspect loaded Claude instruction memory files",
|
summary: "Inspect loaded Claude instruction memory files",
|
||||||
argument_hint: None,
|
argument_hint: None,
|
||||||
|
resume_supported: true,
|
||||||
},
|
},
|
||||||
SlashCommandSpec {
|
SlashCommandSpec {
|
||||||
name: "init",
|
name: "init",
|
||||||
summary: "Create a starter CLAUDE.md for this repo",
|
summary: "Create a starter CLAUDE.md for this repo",
|
||||||
argument_hint: None,
|
argument_hint: None,
|
||||||
|
resume_supported: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -149,15 +161,31 @@ pub fn slash_command_specs() -> &'static [SlashCommandSpec] {
|
|||||||
SLASH_COMMAND_SPECS
|
SLASH_COMMAND_SPECS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn resume_supported_slash_commands() -> Vec<&'static SlashCommandSpec> {
|
||||||
|
slash_command_specs()
|
||||||
|
.iter()
|
||||||
|
.filter(|spec| spec.resume_supported)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn render_slash_command_help() -> String {
|
pub fn render_slash_command_help() -> String {
|
||||||
let mut lines = vec!["Available commands:".to_string()];
|
let mut lines = vec![
|
||||||
|
"Available commands:".to_string(),
|
||||||
|
" (resume-safe commands are marked with [resume])".to_string(),
|
||||||
|
];
|
||||||
for spec in slash_command_specs() {
|
for spec in slash_command_specs() {
|
||||||
let name = match spec.argument_hint {
|
let name = match spec.argument_hint {
|
||||||
Some(argument_hint) => format!("/{} {}", spec.name, argument_hint),
|
Some(argument_hint) => format!("/{} {}", spec.name, argument_hint),
|
||||||
None => format!("/{}", spec.name),
|
None => format!("/{}", spec.name),
|
||||||
};
|
};
|
||||||
lines.push(format!(" {name:<20} {}", spec.summary));
|
let resume = if spec.resume_supported {
|
||||||
|
" [resume]"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
|
lines.push(format!(" {name:<20} {}{}", spec.summary, resume));
|
||||||
}
|
}
|
||||||
lines.join("\n")
|
lines.join("\n")
|
||||||
}
|
}
|
||||||
@@ -210,7 +238,8 @@ pub fn handle_slash_command(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{
|
use super::{
|
||||||
handle_slash_command, render_slash_command_help, slash_command_specs, SlashCommand,
|
handle_slash_command, render_slash_command_help, resume_supported_slash_commands,
|
||||||
|
slash_command_specs, SlashCommand,
|
||||||
};
|
};
|
||||||
use runtime::{CompactionConfig, ContentBlock, ConversationMessage, MessageRole, Session};
|
use runtime::{CompactionConfig, ContentBlock, ConversationMessage, MessageRole, Session};
|
||||||
|
|
||||||
@@ -250,6 +279,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn renders_help_from_shared_specs() {
|
fn renders_help_from_shared_specs() {
|
||||||
let help = render_slash_command_help();
|
let help = render_slash_command_help();
|
||||||
|
assert!(help.contains("resume-safe commands"));
|
||||||
assert!(help.contains("/help"));
|
assert!(help.contains("/help"));
|
||||||
assert!(help.contains("/status"));
|
assert!(help.contains("/status"));
|
||||||
assert!(help.contains("/compact"));
|
assert!(help.contains("/compact"));
|
||||||
@@ -262,6 +292,7 @@ mod tests {
|
|||||||
assert!(help.contains("/memory"));
|
assert!(help.contains("/memory"));
|
||||||
assert!(help.contains("/init"));
|
assert!(help.contains("/init"));
|
||||||
assert_eq!(slash_command_specs().len(), 11);
|
assert_eq!(slash_command_specs().len(), 11);
|
||||||
|
assert_eq!(resume_supported_slash_commands().len(), 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ use api::{
|
|||||||
ToolResultContentBlock,
|
ToolResultContentBlock,
|
||||||
};
|
};
|
||||||
|
|
||||||
use commands::{handle_slash_command, render_slash_command_help, SlashCommand};
|
use commands::{
|
||||||
|
handle_slash_command, render_slash_command_help, resume_supported_slash_commands, SlashCommand,
|
||||||
|
};
|
||||||
use compat_harness::{extract_manifest, UpstreamPaths};
|
use compat_harness::{extract_manifest, UpstreamPaths};
|
||||||
use render::{Spinner, TerminalRenderer};
|
use render::{Spinner, TerminalRenderer};
|
||||||
use runtime::{
|
use runtime::{
|
||||||
@@ -1065,21 +1067,38 @@ fn print_help() {
|
|||||||
println!("rusty-claude-cli");
|
println!("rusty-claude-cli");
|
||||||
println!();
|
println!();
|
||||||
println!("Usage:");
|
println!("Usage:");
|
||||||
println!(" rusty-claude-cli [--model MODEL] Start interactive REPL");
|
println!(" rusty-claude-cli [--model MODEL]");
|
||||||
println!(
|
println!(" Start interactive REPL");
|
||||||
" rusty-claude-cli [--model MODEL] prompt TEXT Send one prompt and stream the response"
|
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 dump-manifests");
|
||||||
println!(" rusty-claude-cli bootstrap-plan");
|
println!(" rusty-claude-cli bootstrap-plan");
|
||||||
println!(" rusty-claude-cli system-prompt [--cwd PATH] [--date YYYY-MM-DD]");
|
println!(" rusty-claude-cli system-prompt [--cwd PATH] [--date YYYY-MM-DD]");
|
||||||
println!(" rusty-claude-cli --resume SESSION.json [/status] [/compact] [...]");
|
println!();
|
||||||
|
println!("Interactive slash commands:");
|
||||||
|
println!("{}", render_slash_command_help());
|
||||||
|
println!();
|
||||||
|
let resume_commands = resume_supported_slash_commands()
|
||||||
|
.into_iter()
|
||||||
|
.map(|spec| match spec.argument_hint {
|
||||||
|
Some(argument_hint) => format!("/{} {}", spec.name, argument_hint),
|
||||||
|
None => format!("/{}", spec.name),
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.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");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{
|
use super::{
|
||||||
format_status_line, normalize_permission_mode, parse_args, render_init_claude_md,
|
format_status_line, normalize_permission_mode, parse_args, render_init_claude_md,
|
||||||
render_repl_help, CliAction, SlashCommand, DEFAULT_MODEL,
|
render_repl_help, resume_supported_slash_commands, CliAction, SlashCommand, DEFAULT_MODEL,
|
||||||
};
|
};
|
||||||
use runtime::{ContentBlock, ConversationMessage, MessageRole};
|
use runtime::{ContentBlock, ConversationMessage, MessageRole};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
@@ -1182,6 +1201,18 @@ mod tests {
|
|||||||
assert!(help.contains("/exit"));
|
assert!(help.contains("/exit"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resume_supported_command_list_matches_expected_surface() {
|
||||||
|
let names = resume_supported_slash_commands()
|
||||||
|
.into_iter()
|
||||||
|
.map(|spec| spec.name)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
assert_eq!(
|
||||||
|
names,
|
||||||
|
vec!["help", "status", "compact", "clear", "cost", "config", "memory", "init",]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn status_line_reports_model_and_token_totals() {
|
fn status_line_reports_model_and_token_totals() {
|
||||||
let status = format_status_line(
|
let status = format_status_line(
|
||||||
|
|||||||
Reference in New Issue
Block a user