Finish the Rust CLI command surface for everyday session workflows

This adds the remaining user-facing slash commands, enables non-interactive model and JSON prompt output, and tightens the help and startup copy so the Rust CLI feels coherent as a standalone interface.

The implementation keeps the scope narrow by reusing the existing session JSON format and local runtime machinery instead of introducing new storage layers or dependencies.

Constraint: No new dependencies allowed for this polish pass
Constraint: Do not commit OMX runtime state
Rejected: Add a separate session database | unnecessary complexity for local CLI persistence
Rejected: Rework argument parsing with clap | too broad for the current delivery window
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Managed sessions currently live under .claude/sessions; keep compatibility in mind before changing that path or file shape
Tested: cargo fmt; cargo clippy --workspace --all-targets -- -D warnings; cargo test
Not-tested: Live Anthropic prompt execution and interactive manual UX smoke test
This commit is contained in:
Yeachan-Heo
2026-03-31 22:49:50 +00:00
parent 4d65f5c1a2
commit 92f33c75c0
2 changed files with 611 additions and 32 deletions

View File

@@ -105,6 +105,30 @@ const SLASH_COMMAND_SPECS: &[SlashCommandSpec] = &[
argument_hint: None,
resume_supported: true,
},
SlashCommandSpec {
name: "diff",
summary: "Show git diff for current workspace changes",
argument_hint: None,
resume_supported: true,
},
SlashCommandSpec {
name: "version",
summary: "Show CLI version and build information",
argument_hint: None,
resume_supported: true,
},
SlashCommandSpec {
name: "export",
summary: "Export the current conversation to a file",
argument_hint: Some("[file]"),
resume_supported: true,
},
SlashCommandSpec {
name: "session",
summary: "List or switch managed local sessions",
argument_hint: Some("[list|switch <session-id>]"),
resume_supported: false,
},
];
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -112,14 +136,33 @@ pub enum SlashCommand {
Help,
Status,
Compact,
Model { model: Option<String> },
Permissions { mode: Option<String> },
Clear { confirm: bool },
Model {
model: Option<String>,
},
Permissions {
mode: Option<String>,
},
Clear {
confirm: bool,
},
Cost,
Resume { session_path: Option<String> },
Config { section: Option<String> },
Resume {
session_path: Option<String>,
},
Config {
section: Option<String>,
},
Memory,
Init,
Diff,
Version,
Export {
path: Option<String>,
},
Session {
action: Option<String>,
target: Option<String>,
},
Unknown(String),
}
@@ -155,6 +198,15 @@ impl SlashCommand {
},
"memory" => Self::Memory,
"init" => Self::Init,
"diff" => Self::Diff,
"version" => Self::Version,
"export" => Self::Export {
path: parts.next().map(ToOwned::to_owned),
},
"session" => Self::Session {
action: parts.next().map(ToOwned::to_owned),
target: parts.next().map(ToOwned::to_owned),
},
other => Self::Unknown(other.to_string()),
})
}
@@ -235,6 +287,10 @@ pub fn handle_slash_command(
| SlashCommand::Config { .. }
| SlashCommand::Memory
| SlashCommand::Init
| SlashCommand::Diff
| SlashCommand::Version
| SlashCommand::Export { .. }
| SlashCommand::Session { .. }
| SlashCommand::Unknown(_) => None,
}
}
@@ -294,6 +350,21 @@ mod tests {
);
assert_eq!(SlashCommand::parse("/memory"), Some(SlashCommand::Memory));
assert_eq!(SlashCommand::parse("/init"), Some(SlashCommand::Init));
assert_eq!(SlashCommand::parse("/diff"), Some(SlashCommand::Diff));
assert_eq!(SlashCommand::parse("/version"), Some(SlashCommand::Version));
assert_eq!(
SlashCommand::parse("/export notes.txt"),
Some(SlashCommand::Export {
path: Some("notes.txt".to_string())
})
);
assert_eq!(
SlashCommand::parse("/session switch abc123"),
Some(SlashCommand::Session {
action: Some("switch".to_string()),
target: Some("abc123".to_string())
})
);
}
#[test]
@@ -311,8 +382,12 @@ mod tests {
assert!(help.contains("/config [env|hooks|model]"));
assert!(help.contains("/memory"));
assert!(help.contains("/init"));
assert_eq!(slash_command_specs().len(), 11);
assert_eq!(resume_supported_slash_commands().len(), 8);
assert!(help.contains("/diff"));
assert!(help.contains("/version"));
assert!(help.contains("/export [file]"));
assert!(help.contains("/session [list|switch <session-id>]"));
assert_eq!(slash_command_specs().len(), 15);
assert_eq!(resume_supported_slash_commands().len(), 11);
}
#[test]
@@ -384,5 +459,14 @@ mod tests {
assert!(
handle_slash_command("/config env", &session, CompactionConfig::default()).is_none()
);
assert!(handle_slash_command("/diff", &session, CompactionConfig::default()).is_none());
assert!(handle_slash_command("/version", &session, CompactionConfig::default()).is_none());
assert!(
handle_slash_command("/export note.txt", &session, CompactionConfig::default())
.is_none()
);
assert!(
handle_slash_command("/session list", &session, CompactionConfig::default()).is_none()
);
}
}