use std::collections::HashMap;
use crate::app::TuiApp;
use crate::types::{Activity, Message, Role};
pub struct CommandContext<'a> {
pub app: &'a mut TuiApp,
}
impl CommandContext<'_> {
pub fn push_system(&mut self, content: String) {
self.app.push_message(Message {
role: Role::System,
content,
is_pending: false,
});
}
pub fn push_user(&mut self, content: String) {
self.app.push_message(Message {
role: Role::User,
content,
is_pending: false,
});
}
pub fn send_to_backend(&self, msg: String) {
self.app.tx.try_send(msg).ok();
}
pub fn is_idle(&self) -> bool {
self.app.activity == Activity::Idle
}
pub fn sync_approve_mode(&mut self) {
if let Some(ref shared) = self.app.shared_approve_mode {
shared.store(
self.app.approve_mode.to_u8(),
std::sync::atomic::Ordering::Relaxed,
);
}
if self.app.approve_mode == crate::types::ApproveMode::Auto
&& self.app.waiting_for_ask
&& let Some(ref ask_tx) = self.app.ask_tx
{
ask_tx.try_send("y".to_string()).ok();
self.app.waiting_for_ask = false;
}
self.app
.tx
.try_send(format!("/mode:{}", self.app.approve_mode))
.ok();
}
pub fn auto_scroll(&mut self) {
self.app.auto_scroll = true;
}
}
pub trait Command: Send + Sync {
fn name(&self) -> &'static str;
fn aliases(&self) -> &[&'static str] {
&[]
}
#[allow(dead_code)]
fn help(&self) -> Option<&'static str> {
None
}
fn execute(&self, ctx: &mut CommandContext, args: &[&str]);
}
pub struct CommandRegistry {
commands: HashMap<&'static str, Box<dyn Command>>,
}
impl CommandRegistry {
pub fn new() -> Self {
Self {
commands: HashMap::new(),
}
}
pub fn register(&mut self, command: Box<dyn Command>) {
let name = command.name();
self.commands.insert(name, command);
}
pub fn dispatch(&self, cmd: &str, ctx: &mut CommandContext) -> bool {
let parts: Vec<&str> = cmd.split_whitespace().collect();
let command_name = parts.first().copied().unwrap_or("");
let command_name = command_name.strip_prefix('/').unwrap_or(command_name);
let args = &parts[1..];
if let Some(command) = self.commands.get(command_name) {
command.execute(ctx, args);
true
} else {
for cmd in self.commands.values() {
if cmd.aliases().contains(&command_name) {
cmd.execute(ctx, args);
return true;
}
}
false
}
}
}
impl Default for CommandRegistry {
fn default() -> Self {
Self::new()
}
}
mod backend;
mod clear;
mod compact;
mod cron;
mod debug;
mod exit;
mod help;
mod history;
mod init;
mod r#loop;
mod mcp;
mod mode;
mod model;
mod new_cmd;
mod retry;
mod workflow;
pub use clear::ClearCommand;
pub use compact::CompactCommand;
pub use cron::CronCommand;
pub use debug::DebugCommand;
pub use exit::ExitCommand;
pub use help::HelpCommand;
pub use history::HistoryCommand;
pub use init::InitCommand;
pub use r#loop::LoopCommand;
pub use mcp::McpCommand;
pub use mode::ModeCommand;
pub use model::ModelCommand;
pub use new_cmd::NewCommand;
pub use retry::RetryCommand;
pub use workflow::WorkflowCommand;
use backend::BackendCommand;
pub fn create_registry() -> CommandRegistry {
let mut registry = CommandRegistry::new();
registry.register(Box::new(ExitCommand));
registry.register(Box::new(ClearCommand));
registry.register(Box::new(HelpCommand));
registry.register(Box::new(DebugCommand));
registry.register(Box::new(ModeCommand));
registry.register(Box::new(ModelCommand));
registry.register(Box::new(HistoryCommand));
registry.register(Box::new(RetryCommand));
registry.register(Box::new(NewCommand));
registry.register(Box::new(CompactCommand));
registry.register(Box::new(InitCommand));
registry.register(Box::new(LoopCommand));
registry.register(Box::new(CronCommand));
registry.register(Box::new(WorkflowCommand));
registry.register(Box::new(McpCommand));
registry.register(Box::new(BackendCommand::new("tools")));
registry.register(Box::new(BackendCommand::new("system")));
registry.register(Box::new(BackendCommand::new("skills")));
registry.register(Box::new(BackendCommand::new("memory")));
registry.register(Box::new(BackendCommand::new("overview")));
registry.register(Box::new(BackendCommand::new("config")));
registry.register(Box::new(BackendCommand::new("save")));
registry.register(Box::new(BackendCommand::new_with_aliases(
"sessions",
&["resume"],
)));
registry
}
use std::sync::OnceLock;
static REGISTRY: OnceLock<CommandRegistry> = OnceLock::new();
fn get_registry() -> &'static CommandRegistry {
REGISTRY.get_or_init(create_registry)
}
pub fn handle_command(app: &mut TuiApp, cmd: &str) {
let registry = get_registry();
let mut ctx = CommandContext { app };
if !registry.dispatch(cmd, &mut ctx) {
if ctx.is_idle() {
ctx.push_user(cmd.to_string());
ctx.send_to_backend(cmd.to_string());
ctx.app.activity = Activity::Thinking;
ctx.app.request_start = Some(std::time::Instant::now());
} else {
ctx.app.pending_messages.push(cmd.to_string());
}
ctx.auto_scroll();
}
}