mirror of
https://github.com/instructkr/claude-code.git
synced 2026-04-06 11:18:51 +03:00
merge: gaebal/cli-dispatch-top5 into main
This commit is contained in:
38
.github/workflows/rust-ci.yml
vendored
Normal file
38
.github/workflows/rust-ci.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
name: rust-ci
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- 'gaebal/**'
|
||||||
|
- 'omx-issue-*'
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
rust-ci:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: rust
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Rust toolchain
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
components: rustfmt, clippy
|
||||||
|
|
||||||
|
- name: Cache cargo
|
||||||
|
uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
workspaces: rust
|
||||||
|
|
||||||
|
- name: cargo fmt
|
||||||
|
run: cargo fmt --all --check
|
||||||
|
|
||||||
|
- name: cargo clippy
|
||||||
|
run: cargo clippy -p rusty-claude-cli --bin claw --no-deps -- -D warnings
|
||||||
|
|
||||||
|
- name: cargo test
|
||||||
|
run: cargo test -p rusty-claude-cli
|
||||||
@@ -44,6 +44,11 @@ pub enum SlashCommand {
|
|||||||
Help,
|
Help,
|
||||||
Status,
|
Status,
|
||||||
Compact,
|
Compact,
|
||||||
|
Model { model: Option<String> },
|
||||||
|
Permissions { mode: Option<String> },
|
||||||
|
Config { section: Option<String> },
|
||||||
|
Memory,
|
||||||
|
Clear { confirm: bool },
|
||||||
Unknown(String),
|
Unknown(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,15 +60,25 @@ impl SlashCommand {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let command = trimmed
|
let mut parts = trimmed.trim_start_matches('/').split_whitespace();
|
||||||
.trim_start_matches('/')
|
let command = parts.next().unwrap_or_default();
|
||||||
.split_whitespace()
|
|
||||||
.next()
|
|
||||||
.unwrap_or_default();
|
|
||||||
Some(match command {
|
Some(match command {
|
||||||
"help" => Self::Help,
|
"help" => Self::Help,
|
||||||
"status" => Self::Status,
|
"status" => Self::Status,
|
||||||
"compact" => Self::Compact,
|
"compact" => Self::Compact,
|
||||||
|
"model" => Self::Model {
|
||||||
|
model: parts.next().map(ToOwned::to_owned),
|
||||||
|
},
|
||||||
|
"permissions" => Self::Permissions {
|
||||||
|
mode: parts.next().map(ToOwned::to_owned),
|
||||||
|
},
|
||||||
|
"config" => Self::Config {
|
||||||
|
section: parts.next().map(ToOwned::to_owned),
|
||||||
|
},
|
||||||
|
"memory" => Self::Memory,
|
||||||
|
"clear" => Self::Clear {
|
||||||
|
confirm: parts.next() == Some("--confirm"),
|
||||||
|
},
|
||||||
other => Self::Unknown(other.to_string()),
|
other => Self::Unknown(other.to_string()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -87,6 +102,26 @@ const SLASH_COMMAND_HANDLERS: &[SlashCommandHandler] = &[
|
|||||||
command: SlashCommand::Compact,
|
command: SlashCommand::Compact,
|
||||||
summary: "Compact local session history",
|
summary: "Compact local session history",
|
||||||
},
|
},
|
||||||
|
SlashCommandHandler {
|
||||||
|
command: SlashCommand::Model { model: None },
|
||||||
|
summary: "Show or switch the active model",
|
||||||
|
},
|
||||||
|
SlashCommandHandler {
|
||||||
|
command: SlashCommand::Permissions { mode: None },
|
||||||
|
summary: "Show or switch the active permission mode",
|
||||||
|
},
|
||||||
|
SlashCommandHandler {
|
||||||
|
command: SlashCommand::Config { section: None },
|
||||||
|
summary: "Inspect current config path or section",
|
||||||
|
},
|
||||||
|
SlashCommandHandler {
|
||||||
|
command: SlashCommand::Memory,
|
||||||
|
summary: "Inspect loaded memory/instruction files",
|
||||||
|
},
|
||||||
|
SlashCommandHandler {
|
||||||
|
command: SlashCommand::Clear { confirm: false },
|
||||||
|
summary: "Start a fresh local session",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
pub struct CliApp {
|
pub struct CliApp {
|
||||||
@@ -158,6 +193,11 @@ impl CliApp {
|
|||||||
SlashCommand::Help => Self::handle_help(out),
|
SlashCommand::Help => Self::handle_help(out),
|
||||||
SlashCommand::Status => self.handle_status(out),
|
SlashCommand::Status => self.handle_status(out),
|
||||||
SlashCommand::Compact => self.handle_compact(out),
|
SlashCommand::Compact => self.handle_compact(out),
|
||||||
|
SlashCommand::Model { model } => self.handle_model(model.as_deref(), out),
|
||||||
|
SlashCommand::Permissions { mode } => self.handle_permissions(mode.as_deref(), out),
|
||||||
|
SlashCommand::Config { section } => self.handle_config(section.as_deref(), out),
|
||||||
|
SlashCommand::Memory => self.handle_memory(out),
|
||||||
|
SlashCommand::Clear { confirm } => self.handle_clear(confirm, out),
|
||||||
SlashCommand::Unknown(name) => {
|
SlashCommand::Unknown(name) => {
|
||||||
writeln!(out, "Unknown slash command: /{name}")?;
|
writeln!(out, "Unknown slash command: /{name}")?;
|
||||||
Ok(CommandResult::Continue)
|
Ok(CommandResult::Continue)
|
||||||
@@ -172,6 +212,11 @@ impl CliApp {
|
|||||||
SlashCommand::Help => "/help",
|
SlashCommand::Help => "/help",
|
||||||
SlashCommand::Status => "/status",
|
SlashCommand::Status => "/status",
|
||||||
SlashCommand::Compact => "/compact",
|
SlashCommand::Compact => "/compact",
|
||||||
|
SlashCommand::Model { .. } => "/model [model]",
|
||||||
|
SlashCommand::Permissions { .. } => "/permissions [mode]",
|
||||||
|
SlashCommand::Config { .. } => "/config [section]",
|
||||||
|
SlashCommand::Memory => "/memory",
|
||||||
|
SlashCommand::Clear { .. } => "/clear [--confirm]",
|
||||||
SlashCommand::Unknown(_) => continue,
|
SlashCommand::Unknown(_) => continue,
|
||||||
};
|
};
|
||||||
writeln!(out, " {name:<9} {}", handler.summary)?;
|
writeln!(out, " {name:<9} {}", handler.summary)?;
|
||||||
@@ -209,6 +254,102 @@ impl CliApp {
|
|||||||
Ok(CommandResult::Continue)
|
Ok(CommandResult::Continue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_model(
|
||||||
|
&mut self,
|
||||||
|
model: Option<&str>,
|
||||||
|
out: &mut impl Write,
|
||||||
|
) -> io::Result<CommandResult> {
|
||||||
|
match model {
|
||||||
|
Some(model) => {
|
||||||
|
self.config.model = model.to_string();
|
||||||
|
self.state.last_model = model.to_string();
|
||||||
|
writeln!(out, "Active model set to {model}")?;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
writeln!(out, "Active model: {}", self.config.model)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(CommandResult::Continue)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_permissions(
|
||||||
|
&mut self,
|
||||||
|
mode: Option<&str>,
|
||||||
|
out: &mut impl Write,
|
||||||
|
) -> io::Result<CommandResult> {
|
||||||
|
match mode {
|
||||||
|
None => writeln!(out, "Permission mode: {:?}", self.config.permission_mode)?,
|
||||||
|
Some("read-only") => {
|
||||||
|
self.config.permission_mode = PermissionMode::ReadOnly;
|
||||||
|
writeln!(out, "Permission mode set to read-only")?;
|
||||||
|
}
|
||||||
|
Some("workspace-write") => {
|
||||||
|
self.config.permission_mode = PermissionMode::WorkspaceWrite;
|
||||||
|
writeln!(out, "Permission mode set to workspace-write")?;
|
||||||
|
}
|
||||||
|
Some("danger-full-access") => {
|
||||||
|
self.config.permission_mode = PermissionMode::DangerFullAccess;
|
||||||
|
writeln!(out, "Permission mode set to danger-full-access")?;
|
||||||
|
}
|
||||||
|
Some(other) => {
|
||||||
|
writeln!(out, "Unknown permission mode: {other}")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(CommandResult::Continue)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_config(
|
||||||
|
&mut self,
|
||||||
|
section: Option<&str>,
|
||||||
|
out: &mut impl Write,
|
||||||
|
) -> io::Result<CommandResult> {
|
||||||
|
match section {
|
||||||
|
None => writeln!(
|
||||||
|
out,
|
||||||
|
"Config path: {}",
|
||||||
|
self.config
|
||||||
|
.config
|
||||||
|
.as_ref()
|
||||||
|
.map_or_else(|| String::from("<none>"), |path| path.display().to_string())
|
||||||
|
)?,
|
||||||
|
Some(section) => writeln!(
|
||||||
|
out,
|
||||||
|
"Config section `{section}` is not fully implemented yet; current config path is {}",
|
||||||
|
self.config
|
||||||
|
.config
|
||||||
|
.as_ref()
|
||||||
|
.map_or_else(|| String::from("<none>"), |path| path.display().to_string())
|
||||||
|
)?,
|
||||||
|
}
|
||||||
|
Ok(CommandResult::Continue)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_memory(&mut self, out: &mut impl Write) -> io::Result<CommandResult> {
|
||||||
|
writeln!(
|
||||||
|
out,
|
||||||
|
"Loaded memory/config file: {}",
|
||||||
|
self.config
|
||||||
|
.config
|
||||||
|
.as_ref()
|
||||||
|
.map_or_else(|| String::from("<none>"), |path| path.display().to_string())
|
||||||
|
)?;
|
||||||
|
Ok(CommandResult::Continue)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_clear(&mut self, confirm: bool, out: &mut impl Write) -> io::Result<CommandResult> {
|
||||||
|
if !confirm {
|
||||||
|
writeln!(out, "Refusing to clear without confirmation. Re-run as /clear --confirm")?;
|
||||||
|
return Ok(CommandResult::Continue);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.state.turns = 0;
|
||||||
|
self.state.compacted_messages = 0;
|
||||||
|
self.state.last_usage = UsageSummary::default();
|
||||||
|
self.conversation_history.clear();
|
||||||
|
writeln!(out, "Started a fresh local session.")?;
|
||||||
|
Ok(CommandResult::Continue)
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_stream_event(
|
fn handle_stream_event(
|
||||||
renderer: &TerminalRenderer,
|
renderer: &TerminalRenderer,
|
||||||
event: StreamEvent,
|
event: StreamEvent,
|
||||||
@@ -369,6 +510,29 @@ mod tests {
|
|||||||
SlashCommand::parse("/compact now"),
|
SlashCommand::parse("/compact now"),
|
||||||
Some(SlashCommand::Compact)
|
Some(SlashCommand::Compact)
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
SlashCommand::parse("/model claude-sonnet"),
|
||||||
|
Some(SlashCommand::Model {
|
||||||
|
model: Some("claude-sonnet".into()),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
SlashCommand::parse("/permissions workspace-write"),
|
||||||
|
Some(SlashCommand::Permissions {
|
||||||
|
mode: Some("workspace-write".into()),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
SlashCommand::parse("/config hooks"),
|
||||||
|
Some(SlashCommand::Config {
|
||||||
|
section: Some("hooks".into()),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
assert_eq!(SlashCommand::parse("/memory"), Some(SlashCommand::Memory));
|
||||||
|
assert_eq!(
|
||||||
|
SlashCommand::parse("/clear --confirm"),
|
||||||
|
Some(SlashCommand::Clear { confirm: true })
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -380,6 +544,11 @@ mod tests {
|
|||||||
assert!(output.contains("/help"));
|
assert!(output.contains("/help"));
|
||||||
assert!(output.contains("/status"));
|
assert!(output.contains("/status"));
|
||||||
assert!(output.contains("/compact"));
|
assert!(output.contains("/compact"));
|
||||||
|
assert!(output.contains("/model [model]"));
|
||||||
|
assert!(output.contains("/permissions [mode]"));
|
||||||
|
assert!(output.contains("/config [section]"));
|
||||||
|
assert!(output.contains("/memory"));
|
||||||
|
assert!(output.contains("/clear [--confirm]"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user