feat: implement custom slash commands system

Adds a comprehensive slash command system that allows users to create and manage custom commands:

- Backend implementation in Rust for discovering, loading, and managing slash commands
- Support for both user-level (~/.claude/commands/) and project-level (.claude/commands/) commands
- YAML frontmatter support for command metadata (description, allowed-tools)
- Command namespacing with directory structure (e.g., /namespace:command)
- Detection of special features: bash commands (\!), file references (@), and arguments ($ARGUMENTS)

Frontend enhancements:
- SlashCommandPicker component with autocomplete UI and keyboard navigation
- SlashCommandsManager component for CRUD operations on commands
- Integration with FloatingPromptInput to trigger picker on "/" input
- Visual indicators for command features (bash, files, arguments)
- Grouped display by namespace with search functionality

API additions:
- slash_commands_list: Discover all available commands
- slash_command_get: Retrieve specific command by ID
- slash_command_save: Create or update commands
- slash_command_delete: Remove commands

This implementation provides a foundation for users to create reusable command templates and workflows. Commands are stored as markdown files with optional YAML frontmatter for metadata.

Addresses #127 and #134
This commit is contained in:
Mufeed VH
2025-07-06 22:51:08 +05:30
parent 985de02404
commit 8af922944b
12 changed files with 1753 additions and 4 deletions

View File

@@ -383,6 +383,36 @@ export interface MCPServerConfig {
env: Record<string, string>;
}
/**
* Represents a custom slash command
*/
export interface SlashCommand {
/** Unique identifier for the command */
id: string;
/** Command name (without prefix) */
name: string;
/** Full command with prefix (e.g., "/project:optimize") */
full_command: string;
/** Command scope: "project" or "user" */
scope: string;
/** Optional namespace (e.g., "frontend" in "/project:frontend:component") */
namespace?: string;
/** Path to the markdown file */
file_path: string;
/** Command content (markdown body) */
content: string;
/** Optional description from frontmatter */
description?: string;
/** Allowed tools from frontmatter */
allowed_tools: string[];
/** Whether the command has bash commands (!) */
has_bash_commands: boolean;
/** Whether the command has file references (@) */
has_file_references: boolean;
/** Whether the command uses $ARGUMENTS placeholder */
accepts_arguments: boolean;
}
/**
* Result of adding a server
*/
@@ -1724,5 +1754,85 @@ export const api = {
console.error("Failed to get merged hooks config:", error);
throw error;
}
},
// Slash Commands API methods
/**
* Lists all available slash commands
* @param projectPath - Optional project path to include project-specific commands
* @returns Promise resolving to array of slash commands
*/
async slashCommandsList(projectPath?: string): Promise<SlashCommand[]> {
try {
return await invoke<SlashCommand[]>("slash_commands_list", { projectPath });
} catch (error) {
console.error("Failed to list slash commands:", error);
throw error;
}
},
/**
* Gets a single slash command by ID
* @param commandId - Unique identifier of the command
* @returns Promise resolving to the slash command
*/
async slashCommandGet(commandId: string): Promise<SlashCommand> {
try {
return await invoke<SlashCommand>("slash_command_get", { commandId });
} catch (error) {
console.error("Failed to get slash command:", error);
throw error;
}
},
/**
* Creates or updates a slash command
* @param scope - Command scope: "project" or "user"
* @param name - Command name (without prefix)
* @param namespace - Optional namespace for organization
* @param content - Markdown content of the command
* @param description - Optional description
* @param allowedTools - List of allowed tools for this command
* @param projectPath - Required for project scope commands
* @returns Promise resolving to the saved command
*/
async slashCommandSave(
scope: string,
name: string,
namespace: string | undefined,
content: string,
description: string | undefined,
allowedTools: string[],
projectPath?: string
): Promise<SlashCommand> {
try {
return await invoke<SlashCommand>("slash_command_save", {
scope,
name,
namespace,
content,
description,
allowedTools,
projectPath
});
} catch (error) {
console.error("Failed to save slash command:", error);
throw error;
}
},
/**
* Deletes a slash command
* @param commandId - Unique identifier of the command to delete
* @returns Promise resolving to deletion message
*/
async slashCommandDelete(commandId: string): Promise<string> {
try {
return await invoke<string>("slash_command_delete", { commandId });
} catch (error) {
console.error("Failed to delete slash command:", error);
throw error;
}
}
};