.claude/skills/slash-command-system/SKILL.md
This skill should be used when the user asks about "slash command", "builtin command", "builtinCommandNames", "builtinCommandDefs", "handleCommand", "command routing", "command registration", "command dispatch", "add a command", "add slash command", "composite command", "command aliases", "/help", "command i18n", "disabled commands", "command prefix matching", "matchPrefix", "matchBuiltinPrefix", "initBuiltinCommands", "commandHandler", "builtinCommandDef", "command registry pattern", or needs to debug, extend, or understand how built-in slash commands are registered, routed, and rendered in the help menu.
npx skillsauth add liuyu520/cc-connect-fork slash-command-systemInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Document the architecture of cc-connect's built-in slash command system: how commands are registered via the registry pattern, how user input is matched and routed to handlers, how command descriptions are internationalized, and common patterns for adding new commands.
| File | Role |
|------|------|
| core/engine_commands.go | builtinCommandNames, builtinCommandDef type, commandHandler type, initBuiltinCommands(), handleCommand() dispatch, matchPrefix(), matchBuiltinPrefix(), all cmd*() handlers, GetAllCommands() |
| core/engine.go | Engine struct (builtinCommandDefs field), NewEngine() calls initBuiltinCommands(), resolveDisabledCmds() |
| core/i18n.go | MsgBuiltinCmd* constants + 5-language translations for command descriptions |
| core/engine_cards.go | renderHelpCard(), renderHelpGroupCard(), helpCardGroups() — help menu card rendering |
Commands are registered in two layers:
builtinCommandNames)A package-level variable used by resolveDisabledCmds and matchPrefix:
// core/engine_commands.go
var builtinCommandNames = []struct {
names []string // aliases, first = canonical name
id string // unique command identifier
}{
{[]string{"new"}, "new"},
{[]string{"list", "sessions"}, "list"},
{[]string{"resume"}, "resume"},
// ...
}
builtinCommandDefs)An Engine instance field initialized in initBuiltinCommands(), binding each command
to its handler function:
// core/engine_commands.go
// commandHandler 是内建命令的统一处理函数签名。
type commandHandler func(p Platform, msg *Message, args []string)
// builtinCommandDef 定义一个内建命令及其处理器。
type builtinCommandDef struct {
names []string // 命令名(第一个为主名称,其余为别名)
id string // 命令唯一 ID
handler commandHandler // 命令处理函数
privileged bool // 需要 admin_from 授权
}
initBuiltinCommands() is called at the end of NewEngine():
func (e *Engine) initBuiltinCommands() {
e.builtinCommandDefs = []builtinCommandDef{
{names: []string{"new"}, id: "new", handler: e.cmdNew},
{names: []string{"list", "sessions"}, id: "list", handler: e.cmdList},
{names: []string{"resume"}, id: "resume", handler: func(p Platform, msg *Message, args []string) {
if len(args) == 0 { e.cmdList(p, msg, args) } else { e.cmdSwitch(p, msg, args) }
}},
// ... all commands ...
{names: []string{"shell", "sh", "exec", "run"}, id: "shell",
handler: func(p Platform, msg *Message, _ []string) { e.cmdShell(p, msg, msg.Content) },
privileged: true},
}
}
names: First entry is the canonical name; additional entries are aliases (e.g., "list" + "sessions")id: Used for: command lookup, i18n lookup (MsgKey(primaryName)), disabled commands checkhandler: Unified commandHandler signature; commands with different signatures use closure adaptersprivileged: Replaces the old privilegedCommands map; checked in handleCommand() before dispatch/help in registration orderCommands that don't take args or need special parameters use closures:
// No-args command (signature: p, msg)
{names: []string{"stop"}, id: "stop", handler: func(p Platform, msg *Message, _ []string) {
e.cmdStop(p, msg)
}},
// Needs raw message content instead of args
{names: []string{"shell", "sh", "exec", "run"}, id: "shell",
handler: func(p Platform, msg *Message, _ []string) {
e.cmdShell(p, msg, msg.Content)
}, privileged: true},
// Conditional dispatch (composite command)
{names: []string{"resume"}, id: "resume", handler: func(p Platform, msg *Message, args []string) {
if len(args) == 0 { e.cmdList(p, msg, args) } else { e.cmdSwitch(p, msg, args) }
}},
// Extra pre-check
{names: []string{"workspace", "ws"}, id: "workspace", handler: func(p Platform, msg *Message, args []string) {
if !e.multiWorkspace { e.reply(p, msg.ReplyCtx, e.i18n.T(MsgWsNotEnabled)); return }
e.handleWorkspaceCommand(p, msg, args)
}},
handleCommand() uses registry lookup instead of a switch statement:
User types: "/resume 1"
→ parts = ["/resume", "1"]
→ cmd = "resume"
→ args = ["1"]
→ cmdID = e.matchBuiltinPrefix("resume") → "resume"
→ find entry in e.builtinCommandDefs where id == "resume"
→ check disabled, check privileged
→ entry.handler(p, msg, args)
matchBuiltinPrefix() is an Engine method that searches e.builtinCommandDefs:
names in all entries for exact string match"" (no match)Privileged commands (requiring admin_from authorization) are marked with
privileged: true in builtinCommandDef:
// Currently privileged: shell, dir, restart, upgrade
{names: []string{"shell", ...}, id: "shell", handler: ..., privileged: true},
This replaces the old privilegedCommands map. The check happens in handleCommand():
if entry != nil && entry.privileged && !e.isAdmin(msg.UserID) {
e.reply(p, msg.ReplyCtx, fmt.Sprintf(e.i18n.T(MsgAdminRequired), "/"+cmdID))
return true
}
Commands can be disabled per-project or per-role. resolveDisabledCmds() in
engine.go uses builtinCommandNames (the name-only registry) to resolve
wildcards:
if c == "*" {
for _, bc := range builtinCommandNames {
m[bc.id] = true
}
}
// In initBuiltinCommands:
{names: []string{"model"}, id: "model", handler: e.cmdModel},
// Handler (signature matches commandHandler):
func (e *Engine) cmdModel(p Platform, msg *Message, args []string) { ... }
{names: []string{"resume"}, id: "resume", handler: func(p Platform, msg *Message, args []string) {
if len(args) == 0 { e.cmdList(p, msg, args) } else { e.cmdSwitch(p, msg, args) }
}},
{names: []string{"provider"}, id: "provider", handler: e.cmdProvider},
func (e *Engine) cmdProvider(p Platform, msg *Message, args []string) {
sub := matchSubCommand(strings.ToLower(args[0]), []string{
"list", "add", "remove", "switch", ...
})
switch sub {
case "list": ...
case "add": ...
}
}
{names: []string{"compress", "compact"}, id: "compress", handler: ...},
{names: []string{"delete", "del", "rm"}, id: "delete", handler: e.cmdDelete},
Each command needs a MsgKey constant and 5-language translations:
// 1. Constant in i18n.go (MsgKey value MUST equal the command id)
MsgBuiltinCmdResume MsgKey = "resume"
// 2. Translations in the messages map
MsgBuiltinCmdResume: {
LangEnglish: "Resume a session: ...",
LangChinese: "恢复会话:...",
LangTraditionalChinese: "恢復會話:...",
LangJapanese: "セッション復元:...",
LangSpanish: "Reanudar sesión: ...",
},
Critical: The MsgKey value must match the command's id, because the help
system uses MsgKey(primaryName) for lookup.
Only 1 place to modify (was 3 before the registry refactoring):
initBuiltinCommands() in core/engine_commands.go:
{names: []string{"mycommand", "alias"}, id: "mycommand", handler: e.cmdMyCommand},
privileged: true if it requires admin authorizationcommandHandlerbuiltinCommandNames (same file, package-level var) for disabled-command resolutionfunc (e *Engine) cmdMyCommand(p Platform, msg *Message, args []string)core/i18n.gogo build ./... and go test ./core/ -v| Command | Behavior | Handler |
|---------|----------|---------|
| /new [name] | Start a new session | cmdNew |
| /list | Show all sessions | cmdList |
| /resume [arg] | No arg = list; with arg = switch | closure → cmdList / cmdSwitch |
| /switch <num\|id\|name> | Switch to a specific session | cmdSwitch |
| /search <keyword> | Search sessions by name/ID | cmdSearch |
| /delete <num> | Delete session(s) | cmdDelete |
| /name [num] <text> | Name a session | cmdName |
| /current | Show current active session | cmdCurrent |
| Symptom | Check |
|---------|-------|
| Command not recognized | Is it in builtinCommandNames? In initBuiltinCommands()? Prefix collision? |
| Command description missing in help | Is MsgKey value == command id? Are translations added? |
| Command disabled unexpectedly | Check disabled_commands in config, check user role permissions |
| Alias not working | Verify alias is in the names slice, not just the id |
| Privileged check not working | Is privileged: true set in the builtinCommandDef entry? |
add-new-feature — General feature addition workflow (Step 3: Add i18n Strings)message-flow-architecture — How messages reach handleCommand() in the first placetools
This skill should be used when the user asks about "webui", "web ui", "vibe coding", "WebUIServer", "web interface", "browser Claude Code", "web frontend", "React admin dashboard", "web/src", "VibeCoding page", "WebSocket /api/vibe/ws", "webui config", "port 9830", "static file serving", "webuiSession", "claude code subprocess from web", "project dropdown", "work dir select", "listProjects", "Management API frontend", "multi tab vibe", "TabBar", "VibeSession component", "copy work dir", "clipboard copy", "disconnect confirm", "断开确认", "复制路径", "copyWorkDir", "AgentSystemPrompt", "project awareness", "/project command in agent", "attachment upload", "sendWithAttachments", "file upload vibe", "image upload vibe", "drag drop vibe", "paste image vibe", or needs to debug, extend, or understand the browser-based Vibe Coding interface and its Go backend.
tools
This skill should be used when the user asks about "permission request", "control_request", "control_response", "permission prompt tool", "permission-prompt-tool stdio", "permission popup not showing", "permission dialog missing", "authorize tool use", "allow deny button", "webuiSession permission", "respondPermission", "updatedInput", "control_cancel_request", "permission cancelled", "permission_cancelled", "pendingInputs", "permission flow webui", "vibe permission", "前端没有弹出授权", "权限请求不显示", "权限弹窗", "webuiSession vs claudeSession", "webui parity", or needs to debug, extend, or understand how tool permission requests flow between Claude Code CLI, the Go WebUI backend, and the React frontend.
tools
This skill should be used when the user asks about "export markdown", "export chat", "download markdown", "export conversation", "导出 Markdown", "导出聊天记录", "ExportRequest", "ExportMessage", "handleVibeExportMarkdown", "buildExportMarkdown", "POST /api/vibe/export", "exportVibeSession", "chat export", "markdown download", "sanitizeFilename", "Content-Disposition attachment", "export button vibe", "history export", "tool_use in export", "message type mapping export", or needs to debug, extend, or understand the chat history Markdown export feature including the full-stack data flow from frontend trigger to file download.
tools
This skill should be used when the user asks about "attachment upload", "file upload", "image upload", "paste image", "drag drop file", "vibe attachment", "pendingAttachments", "sendWithAttachments", "fileToAttachment", "AttachmentItem", "base64 attachment", "WebSocket attachment protocol", "multimodal content", "image content block", "file content block", "clipboard paste image", "drag and drop upload", "attachment preview", "file size limit", "10MB limit", "ExtFromMime", "附件上传", "图片上传", "拖拽上传", "粘贴图片", "附件预览", "文件大小限制", or needs to debug, extend, or understand the WebUI attachment upload feature including the full-stack data flow from browser to Claude Code CLI.