.claude/skills/project-router/SKILL.md
This skill should be used when the user asks about "shared platform", "multi-project", "project router", "ProjectRouter", "platform deduplication", "/project command", "project selection", "project binding", "multiple projects sharing one bot", or needs to debug, extend, or understand the multi-project shared platform routing system.
npx skillsauth add liuyu520/cc-connect-fork project-routerInstall 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.
ProjectRouter enables multiple [[projects]] in config.toml to share the same
IM platform credentials (e.g. one Feishu bot serving three projects). It
maintains a single platform connection and routes incoming messages to the
correct Engine based on per-session bindings.
Platform (1 connection) → ProjectRouter → Engine A (project-a)
→ Engine B (project-b)
→ Engine C (project-c)
| File | Role |
|------|------|
| core/project_router.go | Core routing logic, binding persistence, /project command, baseSessionKey(), setBinding() |
| core/project_router_test.go | Unit tests (26 test cases, including 4 BaseSessionKeyer tests) |
| core/interfaces.go | BaseSessionKeyer optional interface for thread isolation fallback |
| core/engine.go | externalPlatforms field, SetExternalPlatform(), HandleIncomingMessage() |
| core/i18n.go | MsgProjectSelect, MsgProjectCurrent, MsgProjectSwitched, MsgProjectInvalid, MsgProjectList, MsgProjectHelp |
| cmd/cc-connect/main.go | Platform cache deduplication (platformConfigKey), router creation, lifecycle |
ProjectRouter.handleMessage()/project command → intercepts if matchedbindings[sessionKey] → routes to bound engine if foundbindings[baseSessionKey] for fallback (thread isolation support)pending[sessionKey] → handles selection response if pendingplatformConfigKey(type, options) generates a deterministic key from platform
type + JSON-marshaled options. Platforms with identical keys share one instance.
The sharedGroups map tracks which engines share each platform. Only groups
with len > 1 create a ProjectRouter.
Engine.SetExternalPlatform(p) marks a platform as router-managedEngine.Start() / Engine.Stop() skip external platformsEngine.HandleIncomingMessage(p, msg) allows the router to inject messagesEngine.OnPlatformReady(p) / Engine.OnPlatformUnavailable(p, err) are called
by the router's lifecycle handler to propagate async platform stateBindings are stored as JSON in {dataDir}/project_bindings_{hash}.json:
{"bindings": {"feishu:chat1:user1": "project-a"}}
On startup, loadBindings() restores only bindings whose project still exists.
/project Command| Command | Behavior |
|---------|----------|
| /project | Show current project + numbered list |
| /project list | Same as above |
| /project <name> | Switch to named project (case-insensitive) |
| /project <number> | Switch by index (1-based) |
When platforms use thread isolation (e.g., Feishu thread_isolation), each
top-level message gets a unique session key. Without special handling, /project
switches only apply to one thread. The BaseSessionKeyer optional interface
solves this:
baseSessionKey(msg) calls platform.BaseSessionKey(msg) if implementedsetBinding(msg, project) stores both exact key and base key bindingsfeishu:chatId:root:msgId → feishu:chatId:userIdInlineButtonSender get button-based selection
(button data: __project__:<name>)To support card-based selection, check for CardSender in
showProjectSelection() before the InlineButtonSender check.
Extend handleProjectCommand() with additional args matching. Follow the
pattern: parse args → validate → update state → reply.
Define MsgKey constant in core/i18n.go, add translations for all 5
languages (EN, ZH, ZH-TW, JA, ES), then use r.i18n.T(key) or
r.i18n.Tf(key, args...).
Run ProjectRouter tests:
go test ./core/ -v -run "TestProjectRouter"
Test categories cover:
/project command (list, switch, invalid)For detailed architecture patterns and conventions:
references/architecture.md — Dependency rules, interface patterns, registration flowsession-key-architecture — How session keys are constructed and affect routingmessage-flow-architecture — What happens after ProjectRouter routes a message to an Enginetools
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.