Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions crates/tui/src/commands/groups/memory/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
use std::fs;
use std::path::Path;

use super::CommandResult;
use crate::commands::CommandResult;
use crate::tui::app::App;

const MEMORY_USAGE: &str = "/memory [show|path|clear|edit|help]";
Expand All @@ -43,7 +43,7 @@ fn memory_help(path: &Path) -> String {
)
}

pub fn memory(app: &mut App, arg: Option<&str>) -> CommandResult {
fn memory(app: &mut App, arg: Option<&str>) -> CommandResult {
if !app.use_memory {
return CommandResult::error(
"user memory is disabled. Enable with `[memory] enabled = true` in `~/.codewhale/config.toml` or `DEEPSEEK_MEMORY=on` in your environment, then restart the TUI.",
Expand Down Expand Up @@ -85,6 +85,29 @@ pub fn memory(app: &mut App, arg: Option<&str>) -> CommandResult {
}
}

pub(in crate::commands) const COMMAND_INFO: crate::commands::traits::CommandInfo =
crate::commands::traits::CommandInfo {
name: "memory",
aliases: &[],
usage: "/memory [show|path|clear|edit|help]",
description_id: crate::localization::MessageId::CmdMemoryDescription,
};

pub(in crate::commands) struct MemoryCmd;

impl crate::commands::traits::RegisterCommand for MemoryCmd {
fn info() -> &'static crate::commands::traits::CommandInfo {
&COMMAND_INFO
}

fn execute(
app: &mut crate::tui::app::App,
arg: Option<&str>,
) -> crate::commands::CommandResult {
memory(app, arg)
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
52 changes: 9 additions & 43 deletions crates/tui/src/commands/groups/memory/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,55 +7,21 @@
mod memory;
mod note;

use crate::commands::CommandResult;
use crate::commands::traits::{Command, CommandGroup, CommandInfo, FunctionCommand};
use crate::localization::MessageId;
use crate::tui::app::App;
use crate::commands::traits::{Command, CommandGroup, FunctionCommand, RegisterCommand};

pub struct MemoryCommands;

impl CommandGroup for MemoryCommands {
fn commands(&self) -> Vec<Box<dyn Command>> {
vec![
Box::new(FunctionCommand::new(&NOTE_INFO, run_note)),
Box::new(FunctionCommand::new(&MEMORY_INFO, run_memory)),
Box::new(FunctionCommand::new(
note::NoteCmd::info(),
note::NoteCmd::execute,
)),
Box::new(FunctionCommand::new(
memory::MemoryCmd::info(),
memory::MemoryCmd::execute,
)),
]
}
}

static NOTE_INFO: CommandInfo = CommandInfo {
name: "note",
aliases: &[],
usage: "/note [add|list|show|edit|remove|clear|path]",
description_id: MessageId::CmdNoteDescription,
};
static MEMORY_INFO: CommandInfo = CommandInfo {
name: "memory",
aliases: &[],
usage: "/memory [show|path|clear|edit|help]",
description_id: MessageId::CmdMemoryDescription,
};

fn run_registered(app: &mut App, name: &str, arg: Option<&str>) -> CommandResult {
dispatch(app, name, arg).expect("registered memory command should dispatch")
}

fn run_note(app: &mut App, arg: Option<&str>) -> CommandResult {
run_registered(app, "note", arg)
}
fn run_memory(app: &mut App, arg: Option<&str>) -> CommandResult {
run_registered(app, "memory", arg)
}

pub(in crate::commands) fn dispatch(
app: &mut App,
command: &str,
arg: Option<&str>,
) -> Option<CommandResult> {
let result = match command {
"memory" => memory::memory(app, arg),
"note" => note::note(app, arg),
_ => return None,
};
Some(result)
}
27 changes: 25 additions & 2 deletions crates/tui/src/commands/groups/memory/note.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};

use super::CommandResult;
use crate::commands::CommandResult;

const USAGE: &str = "/note <text> | /note add <text> | /note list | /note show <n> | /note edit <n> <text> | /note remove <n> | /note clear | /note path";

/// Manage the persistent workspace notes file.
pub fn note(app: &mut App, content: Option<&str>) -> CommandResult {
fn note(app: &mut App, content: Option<&str>) -> CommandResult {
let input = match content {
Some(c) => c.trim(),
None => {
Expand Down Expand Up @@ -266,6 +266,29 @@ fn parse_note_index(rest: Option<&str>, note_count: usize, usage: &str) -> Resul
Ok(index - 1)
}

pub(in crate::commands) const COMMAND_INFO: crate::commands::traits::CommandInfo =
crate::commands::traits::CommandInfo {
name: "note",
aliases: &[],
usage: "/note [add|list|show|edit|remove|clear|path]",
description_id: crate::localization::MessageId::CmdNoteDescription,
};

pub(in crate::commands) struct NoteCmd;

impl crate::commands::traits::RegisterCommand for NoteCmd {
fn info() -> &'static crate::commands::traits::CommandInfo {
&COMMAND_INFO
}

fn execute(
app: &mut crate::tui::app::App,
arg: Option<&str>,
) -> crate::commands::CommandResult {
note(app, arg)
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
2 changes: 2 additions & 0 deletions crates/tui/src/commands/groups/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub mod config;
pub mod core;
pub mod debug;
pub mod memory;
pub mod plugins;
pub mod project;
pub mod session;
pub mod skills;
Expand All @@ -25,6 +26,7 @@ pub fn all_command_groups() -> Vec<&'static dyn CommandGroup> {
&project::ProjectCommands,
&skills::SkillsCommands,
&memory::MemoryCommands,
&plugins::PluginsCommands,
&utility::UtilityCommands,
]
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,60 @@
//! `/plugins` slash command — list and inspect script plugin tools.
//! Plugin command area: list installed plugins and (future) execute plugins.
//!
//! Plugins are script-based tools discovered in a configured plugin directory
//! (default: `~/.codewhale/tools`). The `/plugins` command lists them and
//! shows per-plugin metadata. A future `/plugin` command will handle execution.

use std::path::PathBuf;

use crate::commands::CommandResult;
use crate::commands::traits::{
Command, CommandGroup, CommandInfo, FunctionCommand, RegisterCommand,
};
use crate::config::Config;
use crate::localization::{MessageId, tr};
use crate::tools::plugin::scan_plugin_dir;
use crate::tools::spec::ApprovalRequirement;
use crate::tui::app::App;

pub struct PluginsCommands;

impl CommandGroup for PluginsCommands {
fn commands(&self) -> Vec<Box<dyn Command>> {
vec![Box::new(FunctionCommand::new(
PluginsCmd::info(),
PluginsCmd::execute,
))]
}
}

// ---------------------------------------------------------------------------
// `/plugins` — list or show detail
// ---------------------------------------------------------------------------

pub(in crate::commands) const PLUGINS_INFO: CommandInfo = CommandInfo {
name: "plugins",
aliases: &["plugin"],
usage: "/plugins [name]",
description_id: MessageId::CmdPluginDescription,
};

pub(in crate::commands) struct PluginsCmd;

impl RegisterCommand for PluginsCmd {
fn info() -> &'static CommandInfo {
&PLUGINS_INFO
}

fn execute(app: &mut App, arg: Option<&str>) -> CommandResult {
plugins(app, arg)
}
}

/// List discovered plugins, or show details for a named plugin.
pub fn plugins(app: &mut App, arg: Option<&str>) -> CommandResult {
fn plugins(app: &mut App, arg: Option<&str>) -> CommandResult {
let Some(plugin_dir) = plugin_dir_for(app) else {
return CommandResult::error(
"Could not resolve plugin directory. Set [tools].plugin_dir in config.toml or ensure ~/.codewhale/tools exists.".to_string(),
"Could not resolve plugin directory. Set [tools].plugin_dir in config.toml or ensure ~/.codewhale/tools exists.",
);
};

Expand Down Expand Up @@ -103,11 +145,11 @@ fn show_plugin_detail(
CommandResult::message(out)
}

fn approval_label(approval: crate::tools::spec::ApprovalRequirement) -> &'static str {
fn approval_label(approval: ApprovalRequirement) -> &'static str {
match approval {
crate::tools::spec::ApprovalRequirement::Auto => "auto",
crate::tools::spec::ApprovalRequirement::Suggest => "suggest",
crate::tools::spec::ApprovalRequirement::Required => "required",
ApprovalRequirement::Auto => "auto",
ApprovalRequirement::Suggest => "suggest",
ApprovalRequirement::Required => "required",
}
}

Expand Down
25 changes: 23 additions & 2 deletions crates/tui/src/commands/groups/project/goal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

use std::io::Write;

use crate::commands::traits::{CommandInfo, RegisterCommand};
use crate::localization::MessageId;
use crate::tools::goal::GoalStatus;
use crate::tui::app::{App, AppAction, HuntVerdict};

use super::CommandResult;
use crate::commands::CommandResult;

/// Declare, show, pause, resume, or close a goal.
pub fn hunt(app: &mut App, arg: Option<&str>) -> CommandResult {
fn hunt(app: &mut App, arg: Option<&str>) -> CommandResult {
match arg {
Some("clear") | Some("reset") => {
app.hunt.quarry = None;
Expand Down Expand Up @@ -301,6 +303,25 @@ fn write_trophy_card_contents(mut f: impl Write, card: TrophyCard<'_>) -> std::i
Ok(())
}

pub(in crate::commands) const COMMAND_INFO: CommandInfo = CommandInfo {
name: "goal",
aliases: &["hunt", "mubiao", "狩猎"],
usage: "/goal [objective|clear|pause|resume|complete|blocked] [budget: N]",
description_id: MessageId::CmdGoalDescription,
};

pub(in crate::commands) struct GoalCmd;

impl RegisterCommand for GoalCmd {
fn info() -> &'static CommandInfo {
&COMMAND_INFO
}

fn execute(app: &mut App, arg: Option<&str>) -> CommandResult {
hunt(app, arg)
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
27 changes: 25 additions & 2 deletions crates/tui/src/commands/groups/project/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ use std::process::Command;
use crate::project_context;
use crate::tui::app::{App, AppAction};

use super::CommandResult;
use crate::commands::CommandResult;

/// Generate an AGENTS.md file for the current project by gathering context and
/// delegating content generation to the LLM agent.
pub fn init(app: &mut App) -> CommandResult {
fn init(app: &mut App) -> CommandResult {
let workspace = &app.workspace;

// Ensure .deepseek/ is gitignored if we're inside a git repo.
Expand Down Expand Up @@ -794,6 +794,29 @@ fn build_init_prompt(
prompt
}

pub(in crate::commands) const COMMAND_INFO: crate::commands::traits::CommandInfo =
crate::commands::traits::CommandInfo {
name: "init",
aliases: &[],
usage: "/init",
description_id: crate::localization::MessageId::CmdInitDescription,
};

pub(in crate::commands) struct InitCmd;

impl crate::commands::traits::RegisterCommand for InitCmd {
fn info() -> &'static crate::commands::traits::CommandInfo {
&COMMAND_INFO
}

fn execute(
app: &mut crate::tui::app::App,
_arg: Option<&str>,
) -> crate::commands::CommandResult {
init(app)
}
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
Expand Down
28 changes: 28 additions & 0 deletions crates/tui/src/commands/groups/project/lsp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//! `/lsp` command — enable/disable LSP integration.
//!
//! Bridges to config::config::lsp_command for actual execution.

use crate::commands::traits::{CommandInfo, RegisterCommand};
use crate::localization::MessageId;
use crate::tui::app::App;

use crate::commands::CommandResult;

pub(in crate::commands) const COMMAND_INFO: CommandInfo = CommandInfo {
name: "lsp",
aliases: &[],
usage: "/lsp [on|off|status]",
description_id: MessageId::CmdLspDescription,
};

pub(in crate::commands) struct LspCmd;

impl RegisterCommand for LspCmd {
fn info() -> &'static CommandInfo {
&COMMAND_INFO
}

fn execute(app: &mut App, arg: Option<&str>) -> CommandResult {
super::super::config::config::lsp_command(app, arg)
}
}
Loading
Loading