@hileeon/mcc 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/.claude/CLAUDE.md +204 -0
  2. package/.claude/agents/.gitkeep +0 -0
  3. package/.claude/settings.json +9 -0
  4. package/.claude/skills/.gitkeep +0 -0
  5. package/README.md +127 -0
  6. package/dist/accounts/instance-manager.d.ts +11 -0
  7. package/dist/accounts/instance-manager.d.ts.map +1 -0
  8. package/dist/accounts/instance-manager.js +89 -0
  9. package/dist/accounts/instance-manager.js.map +1 -0
  10. package/dist/accounts/shared-manager.d.ts +25 -0
  11. package/dist/accounts/shared-manager.d.ts.map +1 -0
  12. package/dist/accounts/shared-manager.js +186 -0
  13. package/dist/accounts/shared-manager.js.map +1 -0
  14. package/dist/accounts/store.d.ts +30 -0
  15. package/dist/accounts/store.d.ts.map +1 -0
  16. package/dist/accounts/store.js +128 -0
  17. package/dist/accounts/store.js.map +1 -0
  18. package/dist/core/model-router.d.ts +30 -0
  19. package/dist/core/model-router.d.ts.map +1 -0
  20. package/dist/core/model-router.js +64 -0
  21. package/dist/core/model-router.js.map +1 -0
  22. package/dist/dashboard-server.d.ts +5 -0
  23. package/dist/dashboard-server.d.ts.map +1 -0
  24. package/dist/dashboard-server.js +387 -0
  25. package/dist/dashboard-server.js.map +1 -0
  26. package/dist/mcc.d.ts +8 -0
  27. package/dist/mcc.d.ts.map +1 -0
  28. package/dist/mcc.js +474 -0
  29. package/dist/mcc.js.map +1 -0
  30. package/dist/mcp/external-registry.d.ts +24 -0
  31. package/dist/mcp/external-registry.d.ts.map +1 -0
  32. package/dist/mcp/external-registry.js +99 -0
  33. package/dist/mcp/external-registry.js.map +1 -0
  34. package/dist/mcp/installer.d.ts +31 -0
  35. package/dist/mcp/installer.d.ts.map +1 -0
  36. package/dist/mcp/installer.js +273 -0
  37. package/dist/mcp/installer.js.map +1 -0
  38. package/dist/mcp/mcp-config.d.ts +86 -0
  39. package/dist/mcp/mcp-config.d.ts.map +1 -0
  40. package/dist/mcp/mcp-config.js +178 -0
  41. package/dist/mcp/mcp-config.js.map +1 -0
  42. package/dist/mcp/registry.d.ts +23 -0
  43. package/dist/mcp/registry.d.ts.map +1 -0
  44. package/dist/mcp/registry.js +100 -0
  45. package/dist/mcp/registry.js.map +1 -0
  46. package/dist/proxy/proxy-daemon.d.ts +27 -0
  47. package/dist/proxy/proxy-daemon.d.ts.map +1 -0
  48. package/dist/proxy/proxy-daemon.js +192 -0
  49. package/dist/proxy/proxy-daemon.js.map +1 -0
  50. package/dist/proxy/proxy-entry.d.ts +11 -0
  51. package/dist/proxy/proxy-entry.d.ts.map +1 -0
  52. package/dist/proxy/proxy-entry.js +74 -0
  53. package/dist/proxy/proxy-entry.js.map +1 -0
  54. package/dist/proxy/proxy-paths.d.ts +27 -0
  55. package/dist/proxy/proxy-paths.d.ts.map +1 -0
  56. package/dist/proxy/proxy-paths.js +125 -0
  57. package/dist/proxy/proxy-paths.js.map +1 -0
  58. package/dist/proxy/proxy-server.d.ts +20 -0
  59. package/dist/proxy/proxy-server.d.ts.map +1 -0
  60. package/dist/proxy/proxy-server.js +280 -0
  61. package/dist/proxy/proxy-server.js.map +1 -0
  62. package/dist/proxy/upstream-url.d.ts +7 -0
  63. package/dist/proxy/upstream-url.d.ts.map +1 -0
  64. package/dist/proxy/upstream-url.js +38 -0
  65. package/dist/proxy/upstream-url.js.map +1 -0
  66. package/dist/shared/logger.d.ts +23 -0
  67. package/dist/shared/logger.d.ts.map +1 -0
  68. package/dist/shared/logger.js +184 -0
  69. package/dist/shared/logger.js.map +1 -0
  70. package/dist/shared/provider-preset-catalog.d.ts +41 -0
  71. package/dist/shared/provider-preset-catalog.d.ts.map +1 -0
  72. package/dist/shared/provider-preset-catalog.js +299 -0
  73. package/dist/shared/provider-preset-catalog.js.map +1 -0
  74. package/docs/decisions.md +33 -0
  75. package/docs/lessons.md +8 -0
  76. package/docs/product.md +37 -0
  77. package/lib/mcp/mcc-image-analysis-server.cjs +454 -0
  78. package/lib/mcp/mcc-websearch-server.cjs +339 -0
  79. package/lib/mcp-hooks/image-analysis-runtime.cjs +510 -0
  80. package/lib/mcp-hooks/image-analyzer-transformer.cjs +526 -0
  81. package/lib/mcp-hooks/websearch-transformer.cjs +1421 -0
  82. package/lib/proxy/config/config-loader-facade.js +24 -0
  83. package/lib/proxy/glmt/delta-accumulator.js +363 -0
  84. package/lib/proxy/glmt/glmt-transformer.js +204 -0
  85. package/lib/proxy/glmt/index.js +41 -0
  86. package/lib/proxy/glmt/locale-enforcer.js +69 -0
  87. package/lib/proxy/glmt/pipeline/content-transformer.js +162 -0
  88. package/lib/proxy/glmt/pipeline/index.js +20 -0
  89. package/lib/proxy/glmt/pipeline/request-transformer.js +116 -0
  90. package/lib/proxy/glmt/pipeline/response-builder.js +205 -0
  91. package/lib/proxy/glmt/pipeline/stream-parser.js +234 -0
  92. package/lib/proxy/glmt/pipeline/tool-call-handler.js +78 -0
  93. package/lib/proxy/glmt/pipeline/types.js +6 -0
  94. package/lib/proxy/glmt/reasoning-enforcer.js +151 -0
  95. package/lib/proxy/glmt/sse-parser.js +102 -0
  96. package/lib/proxy/services/logging.js +13 -0
  97. package/lib/proxy/transformers/request-transformer.js +452 -0
  98. package/lib/proxy/transformers/sse-stream-transformer.js +199 -0
  99. package/lib/shared/logger.cjs +138 -0
  100. package/package.json +35 -0
  101. package/src/accounts/instance-manager.ts +58 -0
  102. package/src/accounts/shared-manager.ts +154 -0
  103. package/src/accounts/store.ts +111 -0
  104. package/src/core/model-router.ts +82 -0
  105. package/src/dashboard-server.ts +407 -0
  106. package/src/mcc.ts +474 -0
  107. package/src/mcp/external-registry.ts +73 -0
  108. package/src/mcp/installer.ts +258 -0
  109. package/src/mcp/mcp-config.ts +168 -0
  110. package/src/mcp/registry.ts +89 -0
  111. package/src/proxy/proxy-daemon.ts +184 -0
  112. package/src/proxy/proxy-entry.ts +63 -0
  113. package/src/proxy/proxy-paths.ts +97 -0
  114. package/src/proxy/proxy-server.ts +278 -0
  115. package/src/proxy/upstream-url.ts +38 -0
  116. package/src/shared/logger.ts +140 -0
  117. package/src/shared/provider-preset-catalog.ts +340 -0
  118. package/tsconfig.json +33 -0
  119. package/ui/.prettierrc +9 -0
  120. package/ui/index.html +12 -0
  121. package/ui/package.json +33 -0
  122. package/ui/postcss.config.js +6 -0
  123. package/ui/src/App.tsx +753 -0
  124. package/ui/src/components/ui/button.tsx +48 -0
  125. package/ui/src/components/ui/card.tsx +50 -0
  126. package/ui/src/components/ui/input.tsx +21 -0
  127. package/ui/src/components/ui/label.tsx +20 -0
  128. package/ui/src/components/ui/select.tsx +80 -0
  129. package/ui/src/components/ui/switch.tsx +26 -0
  130. package/ui/src/components/ui/tabs.tsx +52 -0
  131. package/ui/src/index.css +33 -0
  132. package/ui/src/lib/api.ts +185 -0
  133. package/ui/src/lib/utils.ts +6 -0
  134. package/ui/src/main.tsx +10 -0
  135. package/ui/src/vite-env.d.ts +1 -0
  136. package/ui/tailwind.config.js +49 -0
  137. package/ui/tsconfig.json +25 -0
  138. package/ui/vite.config.ts +20 -0
@@ -0,0 +1,204 @@
1
+ # MCC - My Cloud Code
2
+
3
+ ## 项目概述
4
+
5
+ MCC 是一个轻量级多账号切换工具,用于在多个 Claude Code 账号(不同 API Provider)之间快速切换。
6
+
7
+ 核心功能:
8
+ - 多 profile 管理(deepseek、qwen、glm、km、mm、anthropic)
9
+ - 直接 API 模式和 OpenAI 兼容模式(自动翻译 proxy)
10
+ - MCP 工具支持(WebSearch、ImageAnalysis)
11
+ - 每个 profile 独立的 `CLAUDE_CONFIG_DIR` 隔离
12
+ - 跨 profile 共享 skills/commands/agents/plugins/settings
13
+
14
+ ## 设计原则
15
+
16
+ - **KISS**:简单直接,不用 OAuth
17
+ - **File-based**:API Key 存 `~/.mcc/profiles/<name>/.key`
18
+ - **YAGNI**:只做需要的功能
19
+
20
+ ## 目录结构
21
+
22
+ ```
23
+ src/
24
+ ├── mcc.ts # CLI 入口
25
+ ├── accounts/
26
+ │ ├── store.ts # Profile 元数据存储
27
+ │ ├── instance-manager.ts # CLAUDE_CONFIG_DIR 隔离
28
+ │ └── shared-manager.ts # 跨 instance 共享目录(symlink)
29
+ ├── core/
30
+ │ └── model-router.ts # Profile env 构建 + tiered model
31
+ ├── mcp/
32
+ │ ├── registry.ts # MCP 服务器注册表(内置)
33
+ │ ├── installer.ts # MCP 安装到 instance
34
+ │ ├── external-registry.ts # 外部 MCP 注册表
35
+ │ └── mcp-config.ts # WebSearch/ImageAnalysis provider 配置
36
+ ├── proxy/
37
+ │ ├── proxy-server.ts # OpenAI→Anthropic 翻译服务器
38
+ │ ├── proxy-daemon.ts # Proxy 生命周期管理
39
+ │ ├── proxy-entry.ts # Proxy 进程入口
40
+ │ ├── proxy-paths.ts # PID/session 文件路径
41
+ │ └── upstream-url.ts # upstream URL 解析
42
+ ├── dashboard-server.ts # Express Dashboard API
43
+ └── shared/
44
+ ├── logger.ts # 日志系统(session-based,logrotate)
45
+ └── provider-preset-catalog.ts
46
+
47
+ lib/
48
+ ├── mcp/ # MCP server JS 文件
49
+ │ ├── mcc-websearch-server.cjs
50
+ │ └── mcc-image-analysis-server.cjs
51
+ ├── mcp-hooks/ # MCP 运行时 hook
52
+ │ ├── logger.cjs
53
+ │ ├── image-analysis-runtime.cjs
54
+ │ ├── image-analyzer-transformer.cjs
55
+ │ └── websearch-transformer.cjs
56
+ └── shared/
57
+ └── logger.cjs
58
+ ```
59
+
60
+ ## 存储结构
61
+
62
+ ```
63
+ ~/.mcc/
64
+ ├── profiles.json # Profile 元数据
65
+ ├── profiles/ # per-profile
66
+ │ └── <name>/.key # API key
67
+ ├── instances/ # per-profile isolated dirs
68
+ │ └── <name>/
69
+ │ ├── .claude.json # Claude Code 实际读取的 MCP 配置
70
+ │ ├── .mcp/mcpServers.json # 参考副本
71
+ │ └── (CLAUDE_CONFIG_DIR subdirs)
72
+ ├── mcp/ # 内置 MCP server 文件
73
+ ├── mcp-hooks/ # MCP runtime hook 文件
74
+ ├── external-mcp-servers.json # 用户添加的外部 MCP 定义
75
+ ├── mcp-config.json # WebSearch/ImageAnalysis provider 配置
76
+ └── proxy/ # Proxy PID/session 文件
77
+ ```
78
+
79
+ ## 开发
80
+
81
+ ```bash
82
+ npm install
83
+ npm run build # tsc
84
+ npm run dev -- help # 直接跑 dist/
85
+ npm run build:ui # 构建 React Dashboard
86
+ npm run dashboard # 编译 + 启动 Dashboard(端口 3000)
87
+ ```
88
+
89
+ ## CLI 命令
90
+
91
+ ```bash
92
+ mcc <profile> [args...] # 用指定 profile 启动 Claude Code
93
+ mcc profile add <name> # 添加 profile
94
+ mcc profile list # 列出所有 profile
95
+ mcc profile remove <name> # 删除 profile
96
+ mcc profile default [name] # 查询或设默认 profile
97
+ mcc mcp list # 列出所有 MCP server
98
+ mcc mcp add --name <id> --command <cmd> [--display-name...] [--args...] [--provider-ref...] # 添加外部 MCP
99
+ mcc mcp remove <name> # 删除外部 MCP
100
+ mcc mcp enable <name> <profile> # 在指定 profile 启用外部 MCP
101
+ mcc mcp disable <name> <profile> # 在指定 profile 禁用外部 MCP
102
+ mcc dashboard # 启动 Web Dashboard(端口 3000)
103
+ mcc help # 显示帮助
104
+ ```
105
+
106
+ ## Profile 与 Protocol
107
+
108
+ 每个 profile 支持 `protocol: 'anthropic'`(默认,直接 API)或 `protocol: 'openai'`(通过翻译 proxy)。
109
+
110
+ OpenAI 兼容 profile 启动时自动在 `127.0.0.1:43456-43555` 范围内启动一个 OpenAI→Anthropic 翻译 proxy,Claude Code 的请求经过本地 proxy 转发给 upstream OpenAI-compatible API。
111
+
112
+ ## MCP 工具
113
+
114
+ 内置两个 MCP server:
115
+ - `mcc-websearch` - Web 搜索(Exa/Tavily/Brave/DuckDuckGo)
116
+ - `mcc-image-analysis` - 图片分析(支持 OpenAI vision 格式)
117
+
118
+ 外部 MCP 通过 `mcc mcp add` 注册,支持 `${MCC_PROVIDER_KEY:<providerId>}` 引用 `mcp-config.json` 里配置的 API key。
119
+
120
+ MCP provider 配置在 `~/.mcc/mcp-config.json`。
121
+
122
+ ## 记忆索引
123
+
124
+ - `docs/product.md` — 理解产品意图和范围时读
125
+ - `docs/decisions.md` — 做架构 / 选型决策前先看有无先例
126
+ - `docs/lessons.md` — 遇到诡异现象时优先 grep,可能已有人踩过
127
+
128
+ <!-- BEGIN: PROJECT MEMORY PROTOCOL (injected by project-memory-init skill) -->
129
+
130
+ ## 项目记忆维护协议
131
+
132
+ 本项目维护三份长期记忆文件:
133
+
134
+ - `docs/product.md` — 当前产品是什么、为谁做、不做什么(滚动更新)
135
+ - `docs/decisions.md` — 重大技术决策的追加式记录(只增不改)
136
+ - `docs/lessons.md` — 踩过的坑与教训(追加为主,修复后剪枝)
137
+
138
+ ### 时间戳规范
139
+
140
+ 追加到 `decisions.md` / `lessons.md` 以及任何归档注释块的条目标题,**必须**使用 `YYYY-MM-DD HH:MM:SS +TZ` 格式(24 小时制 + 时区),例:`2026-04-25 14:30:45 +08:00`。不要只写日期。
141
+
142
+ **为什么**:本项目可能同时开多个 git worktree 改同一份记忆文件,仅凭日期无法稳定排序——合并时容易冲突,错误记录也无法对回到当时的代码状态。
143
+
144
+ **读取时以时间戳为准,不要以文件中出现的顺序为准**:worktree 合并 / 并行追加 / Claude 误插位置等场景下,新条目可能落在文件中较旧条目之后但实际时间更早。判断"哪条最新 / 谁 supersede 谁 / 同一天的先后"一律看 header 里的时间戳,**不要**靠文件位置推断。位置错乱按 `decisions.md` 的「只增不改」原则不主动重排——动既往位置反而违反追加式协议。
145
+
146
+ **获取当前时刻**:
147
+
148
+ - bash:`date "+%Y-%m-%d %H:%M:%S %:z"`
149
+ - PowerShell:`Get-Date -Format "yyyy-MM-dd HH:mm:ss zzz"`
150
+
151
+ `product.md` 是滚动更新文件,不受此规范约束。
152
+
153
+ ### 在以下情况主动提议沉淀
154
+
155
+ 1. **做出非平凡的技术决策**(选型、架构变更、推翻旧方案、引入新依赖)→ 提议追加到 `decisions.md`
156
+ 2. **修复一个花了 30 分钟以上才定位的 bug** → 提议追加到 `lessons.md`
157
+ 3. **用户说"砍掉 X 特性" / "改成 Y 方案" / "这个方向不对"** → 提议更新 `product.md`
158
+ 4. **发现代码里有"看起来奇怪但其实有原因"的地方** → 提议追加到 `lessons.md`
159
+
160
+ **提议方式**:给出**具体的文字草稿**,不要只问"要不要记"。让用户直接回复"记"或"不用"。
161
+
162
+ **决定权在用户**:说"记"则写入,说"不用"则跳过。**绝不自作主张更新文件**。
163
+
164
+ ### `.claude/CLAUDE.md` 与 `README.md` 同步检查
165
+
166
+ `decisions.md` / `lessons.md` 沉淀**历史**;`.claude/CLAUDE.md` 和 `README.md` 描述**当前状态**——前者给未来的 Claude 读,后者给人类读。两份"门面文档"最容易悄悄落后于代码。
167
+
168
+ **触发时机**:上面四类触发里的 #1(非平凡技术决策)和 #3(方向性变化)发生时——简单说,**任何值得写进 `decisions.md` 或改动 `product.md` 的变化**,都要顺带跑一次本节的检查。
169
+
170
+ **检查做什么**:
171
+
172
+ 1. 把这次的变化摘出来
173
+ 2. 对照读 `.claude/CLAUDE.md` 和 `README.md`(两份都看,不是二选一)
174
+ 3. 找三类不一致:
175
+ - **事实相矛盾**(例:"技术栈 = X" 但实际已切成 Y)
176
+ - **功能说明指向已删功能**(例:README 还在教装某个已撤掉的模块)
177
+ - **漏记新能力**(例:新增了 `install.sh`、新目录、新脚本,文件结构树没补)
178
+ 4. 有不一致时,**分别**给出 `.claude/CLAUDE.md` 和 `README.md` 的修改草稿让用户确认——两份受众和粒度不同,不要套用同一份草稿
179
+ 5. 两份都没问题也要**明确说一句**"CLAUDE.md / README.md 已核对,无需改动",避免"默默跳过"让用户不确定你查没查
180
+
181
+ **边界**:
182
+
183
+ - `decisions.md` 记"为什么做这个决策";`.claude/CLAUDE.md` / `README.md` 记"当前是什么状态"。同一变化在多处出现是正常的,角度不同
184
+ - 不要把决策的背景 / 选项 / 代价抄到 CLAUDE.md 或 README.md 里,**只抄结论**(当前事实)
185
+ - 没有实际变化就不改——不要为了"看起来在维护"做无意义修订
186
+
187
+ ### 以下情况不要记录
188
+
189
+ - 小 bug、typo、无需思考就能修的问题
190
+ - 事后看来显然的决策("obvious in retrospect")
191
+ - 可以直接从代码看出的事实
192
+ - 临时讨论、没落地的想法
193
+
194
+ ### 会话状态不落盘
195
+
196
+ "当前在做什么 / 卡在哪 / 下一步做什么"是会话状态,不是项目记忆——留在对话里,不写进任何文件。
197
+
198
+ ### 各文件的变更模式
199
+
200
+ - `product.md`:滚动更新。方向有较大调整时重写对应章节。
201
+ - `decisions.md`:**追加式,只增不改**。历史决策永远不修改;如被推翻,新建条目并在其中引用旧条目(如 "Supersedes 2026-01-15 14:30:45 +08:00 的条目")。
202
+ - `lessons.md`:追加为主。坑被彻底修复后可剪枝对应条目;有长期警示价值的保留。
203
+
204
+ <!-- END: PROJECT MEMORY PROTOCOL -->
File without changes
@@ -0,0 +1,9 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [],
4
+ "deny": [],
5
+ "ask": [],
6
+ "additionalDirectories": [],
7
+ "_comment": "项目级会覆盖/叠加全局:通常在这里补充项目特有的 deny/ask 规则。"
8
+ }
9
+ }
File without changes
package/README.md ADDED
@@ -0,0 +1,127 @@
1
+ # MCC — My Cloud Code
2
+
3
+ 多账号切换工具,在多个 Claude Code 账号(不同 API Provider)之间快速切换。支持直接 API 和 OpenAI 兼容模式(自动翻译 proxy)。
4
+
5
+ ## 快速上手
6
+
7
+ ```bash
8
+ npm install
9
+ npm run build # tsc 编译
10
+ npm run dev -- help # 不编译直接跑
11
+ npm run dashboard # 启动 Web 管理界面(http://localhost:3000)
12
+ ```
13
+
14
+ ## 核心命令
15
+
16
+ ```bash
17
+ mcc <profile> [args...] # 用指定 profile 启动 Claude Code
18
+ mcc profile add <name> # 添加 profile
19
+ mcc profile list # 查看所有 profile
20
+ mcc profile remove <name> # 删除 profile
21
+ mcc profile default [name] # 设默认 profile
22
+ mcc mcp list # 查看可用 MCP 工具
23
+ mcc mcp add --name <id> --command <cmd> # 添加外部 MCP 工具
24
+ mcc mcp remove <name> # 删除外部 MCP 工具
25
+ mcc mcp enable <name> <profile> # 启用外部 MCP(指定 profile)
26
+ mcc mcp disable <name> <profile> # 禁用外部 MCP
27
+ mcc dashboard # 打开 Web Dashboard
28
+ ```
29
+
30
+ ## 核心功能
31
+
32
+ - **多 profile 管理**:API key 存 `~/.mcc/profiles/<name>/.key`,元数据存 `~/.mcc/profiles.json`
33
+ - **实例隔离**:每个 profile 独立 `CLAUDE_CONFIG_DIR`,位于 `~/.mcc/instances/<name>/`
34
+ - **OpenAI 兼容模式**:`--protocol openai` 时自动启动本地翻译 proxy(端口 43456-43555),转发 OpenAI 格式请求到 upstream Anthropic 兼容端点
35
+ - **Tiered model**:profile 支持 `opusModel`/`sonnetModel`/`haikuModel` 三级模型切换
36
+ - **内置 MCP**:`mcc-websearch`(多源搜索)和 `mcc-image-analysis`(图片/PDF 分析)
37
+ - **外部 MCP**:通过 `mcc mcp add` 注册第三方 MCP server,支持 `${MCC_PROVIDER_KEY:<providerId>}` 引用 provider API key
38
+ - **Dashboard**:`npm run dashboard` 启动 Express 管理界面(http://localhost:3000)
39
+
40
+ ## 添加一个 profile
41
+
42
+ ```bash
43
+ # Anthropic 直接 API 模式
44
+ mcc profile add prod --base-url https://api.deepseek.com/anthropic --api-key sk-xxxx --model deepseek-chat
45
+
46
+ # OpenAI 兼容模式(需要翻译 proxy)
47
+ mcc profile add minimax --base-url https://api.minimax.com --api-key sk-xxxx --model MiniMax-Text-01 --protocol openai
48
+
49
+ # 带 tiered model
50
+ mcc profile add big --base-url https://api.anthropic.com --api-key sk-xxxx --model claude-sonnet-4-6 --opus-model claude-opus-4-7 --sonnet-model claude-sonnet-4-6 --haiku-model claude-haiku-4-5
51
+ ```
52
+
53
+ ## 添加外部 MCP
54
+
55
+ ```bash
56
+ mcc mcp add minimax-plan \
57
+ --display-name "MiniMax Token Plan" \
58
+ --command uvx \
59
+ --args "minimax-coding-plan-mcp,-y" \
60
+ --provider-ref minimax
61
+
62
+ # 在指定 profile 启用
63
+ mcc mcp enable minimax-plan minimax
64
+ ```
65
+
66
+ ## 目录结构
67
+
68
+ ```
69
+ src/
70
+ ├── mcc.ts # CLI 入口
71
+ ├── accounts/
72
+ │ ├── store.ts # Profile 元数据
73
+ │ ├── instance-manager.ts # CLAUDE_CONFIG_DIR 隔离
74
+ │ └── shared-manager.ts # 跨 instance 共享目录
75
+ ├── core/model-router.ts # Profile env 构建
76
+ ├── mcp/
77
+ │ ├── registry.ts # 内置 MCP 注册表
78
+ │ ├── installer.ts # MCP 安装到 instance
79
+ │ ├── external-registry.ts # 外部 MCP 注册表
80
+ │ └── mcp-config.ts # provider 配置
81
+ ├── proxy/ # OpenAI→Anthropic 翻译 proxy
82
+ ├── dashboard-server.ts # Express Dashboard
83
+ └── shared/logger.ts # 日志系统
84
+
85
+ lib/
86
+ ├── mcp/ # 内置 MCP server JS
87
+ ├── mcp-hooks/ # MCP runtime hooks
88
+ │ ├── logger.cjs
89
+ │ ├── image-analysis-runtime.cjs
90
+ │ ├── image-analyzer-transformer.cjs
91
+ │ └── websearch-transformer.cjs
92
+ └── shared/logger.cjs
93
+ ```
94
+
95
+ ## 存储结构
96
+
97
+ ```
98
+ ~/.mcc/
99
+ ├── profiles.json
100
+ ├── profiles/<name>/.key
101
+ ├── instances/<name>/
102
+ │ ├── .claude.json # Claude Code 读取的 MCP 配置
103
+ │ └── .mcp/mcpServers.json
104
+ ├── mcp/
105
+ ├── mcp-hooks/
106
+ ├── external-mcp-servers.json # 外部 MCP 定义
107
+ ├── mcp-config.json # WebSearch/ImageAnalysis 配置
108
+ └── proxy/ # proxy PID/session
109
+ ```
110
+
111
+ ## MCP 工具配置
112
+
113
+ 通过 `~/.mcc/mcp-config.json` 配置,或通过 Dashboard UI。
114
+
115
+ WebSearch provider:duckduckgo(默认,免 API key)、exa、tavily、brave
116
+
117
+ ImageAnalysis provider:ali(qwen-vl)、kimi(moonshot-vl)、minimax、deepseek
118
+
119
+ ## 开发
120
+
121
+ ```bash
122
+ npm install
123
+ npm run build # tsc
124
+ npm run dev -- help # 直接跑 dist/
125
+ npm run build:ui # 构建 React Dashboard
126
+ npm run dashboard # 编译 + 启动 Dashboard
127
+ ```
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Instance Manager - Account isolation via CLAUDE_CONFIG_DIR
3
+ */
4
+ export declare class MCCInstanceManager {
5
+ ensureInstance(accountName: string): Promise<string>;
6
+ getInstancePath(accountName: string): string;
7
+ deleteInstance(accountName: string): Promise<void>;
8
+ listInstances(): string[];
9
+ hasInstance(accountName: string): boolean;
10
+ }
11
+ //# sourceMappingURL=instance-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instance-manager.d.ts","sourceRoot":"","sources":["../../src/accounts/instance-manager.ts"],"names":[],"mappings":"AAAA;;GAEG;AAgBH,qBAAa,kBAAkB;IACvB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAc1D,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;IAKtC,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOxD,aAAa,IAAI,MAAM,EAAE;IASzB,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;CAG1C"}
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ /**
3
+ * Instance Manager - Account isolation via CLAUDE_CONFIG_DIR
4
+ */
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18
+ }) : function(o, v) {
19
+ o["default"] = v;
20
+ });
21
+ var __importStar = (this && this.__importStar) || (function () {
22
+ var ownKeys = function(o) {
23
+ ownKeys = Object.getOwnPropertyNames || function (o) {
24
+ var ar = [];
25
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
+ return ar;
27
+ };
28
+ return ownKeys(o);
29
+ };
30
+ return function (mod) {
31
+ if (mod && mod.__esModule) return mod;
32
+ var result = {};
33
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
+ __setModuleDefault(result, mod);
35
+ return result;
36
+ };
37
+ })();
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.MCCInstanceManager = void 0;
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ const shared_manager_1 = require("./shared-manager");
43
+ function getMccHome() {
44
+ return process.env.MCC_HOME ?? path.join(process.env.HOME ?? process.env.USERPROFILE ?? '~', '.mcc');
45
+ }
46
+ function getInstancesDir() {
47
+ return path.join(getMccHome(), 'instances');
48
+ }
49
+ const sharedManager = new shared_manager_1.SharedManager();
50
+ class MCCInstanceManager {
51
+ async ensureInstance(accountName) {
52
+ const instancePath = this.getInstancePath(accountName);
53
+ if (!fs.existsSync(instancePath)) {
54
+ fs.mkdirSync(instancePath, { recursive: true, mode: 0o700 });
55
+ const subdirs = ['session-env', 'todos', 'logs', 'file-history', 'shell-snapshots', 'debug', '.anthropic'];
56
+ for (const dir of subdirs) {
57
+ fs.mkdirSync(path.join(instancePath, dir), { recursive: true, mode: 0o700 });
58
+ }
59
+ }
60
+ // Always re-link shared directories (skills, commands, agents, plugins, settings)
61
+ sharedManager.linkSharedDirectories(instancePath);
62
+ return instancePath;
63
+ }
64
+ getInstancePath(accountName) {
65
+ const safeName = accountName.replace(/[^a-zA-Z0-9_-]/g, '-').toLowerCase();
66
+ return path.join(getInstancesDir(), safeName);
67
+ }
68
+ async deleteInstance(accountName) {
69
+ const instancePath = this.getInstancePath(accountName);
70
+ if (fs.existsSync(instancePath)) {
71
+ fs.rmSync(instancePath, { recursive: true, force: true });
72
+ }
73
+ }
74
+ listInstances() {
75
+ const instancesDir = getInstancesDir();
76
+ if (!fs.existsSync(instancesDir))
77
+ return [];
78
+ return fs.readdirSync(instancesDir).filter((name) => {
79
+ if (name.startsWith('.'))
80
+ return false;
81
+ return fs.statSync(path.join(instancesDir, name)).isDirectory();
82
+ });
83
+ }
84
+ hasInstance(accountName) {
85
+ return fs.existsSync(this.getInstancePath(accountName));
86
+ }
87
+ }
88
+ exports.MCCInstanceManager = MCCInstanceManager;
89
+ //# sourceMappingURL=instance-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instance-manager.js","sourceRoot":"","sources":["../../src/accounts/instance-manager.ts"],"names":[],"mappings":";AAAA;;GAEG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEH,uCAAyB;AACzB,2CAA6B;AAC7B,qDAAiD;AAEjD,SAAS,UAAU;IACjB,OAAO,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,GAAG,EAAE,MAAM,CAAC,CAAC;AACvG,CAAC;AAED,SAAS,eAAe;IACtB,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,WAAW,CAAC,CAAC;AAC9C,CAAC;AAED,MAAM,aAAa,GAAG,IAAI,8BAAa,EAAE,CAAC;AAE1C,MAAa,kBAAkB;IAC7B,KAAK,CAAC,cAAc,CAAC,WAAmB;QACtC,MAAM,YAAY,GAAG,IAAI,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC;QACvD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;YACjC,EAAE,CAAC,SAAS,CAAC,YAAY,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YAC7D,MAAM,OAAO,GAAG,CAAC,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,iBAAiB,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC;YAC3G,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;gBAC1B,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YAC/E,CAAC;QACH,CAAC;QACD,kFAAkF;QAClF,aAAa,CAAC,qBAAqB,CAAC,YAAY,CAAC,CAAC;QAClD,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,eAAe,CAAC,WAAmB;QACjC,MAAM,QAAQ,GAAG,WAAW,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;QAC3E,OAAO,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,QAAQ,CAAC,CAAC;IAChD,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,WAAmB;QACtC,MAAM,YAAY,GAAG,IAAI,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC;QACvD,IAAI,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;YAChC,EAAE,CAAC,MAAM,CAAC,YAAY,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;IAED,aAAa;QACX,MAAM,YAAY,GAAG,eAAe,EAAE,CAAC;QACvC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC;YAAE,OAAO,EAAE,CAAC;QAC5C,OAAO,EAAE,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE;YAClD,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;gBAAE,OAAO,KAAK,CAAC;YACvC,OAAO,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;QAClE,CAAC,CAAC,CAAC;IACL,CAAC;IAED,WAAW,CAAC,WAAmB;QAC7B,OAAO,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC,CAAC;IAC1D,CAAC;CACF;AAvCD,gDAuCC"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * SharedManager - Symlink management for shared directories
3
+ * Ported from CCS management/shared-manager.js (simplified)
4
+ *
5
+ * Creates symlinks: ~/.mcc/instances/<name>/<item> -> ~/.claude/<item>
6
+ * so that skills, commands, agents, plugins, and settings are shared across instances.
7
+ */
8
+ export declare class SharedManager {
9
+ private claudeDir;
10
+ constructor();
11
+ /**
12
+ * Link shared directories to an instance.
13
+ * Creates symlinks from instance/<item> -> ~/.claude/<item>
14
+ */
15
+ linkSharedDirectories(instancePath: string): void;
16
+ /**
17
+ * Remove symlinks from an instance (cleanup).
18
+ */
19
+ unlinkSharedDirectories(instancePath: string): void;
20
+ private pathExists;
21
+ private isCorrectSymlink;
22
+ private removeExisting;
23
+ private copyDirectoryFallback;
24
+ }
25
+ //# sourceMappingURL=shared-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared-manager.d.ts","sourceRoot":"","sources":["../../src/accounts/shared-manager.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAuBH,qBAAa,aAAa;IACxB,OAAO,CAAC,SAAS,CAAS;;IAM1B;;;OAGG;IACH,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;IAgDjD;;OAEG;IACH,uBAAuB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;IAgBnD,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,gBAAgB;IAYxB,OAAO,CAAC,cAAc;IAatB,OAAO,CAAC,qBAAqB;CAY9B"}
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+ /**
3
+ * SharedManager - Symlink management for shared directories
4
+ * Ported from CCS management/shared-manager.js (simplified)
5
+ *
6
+ * Creates symlinks: ~/.mcc/instances/<name>/<item> -> ~/.claude/<item>
7
+ * so that skills, commands, agents, plugins, and settings are shared across instances.
8
+ */
9
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ var desc = Object.getOwnPropertyDescriptor(m, k);
12
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
+ desc = { enumerable: true, get: function() { return m[k]; } };
14
+ }
15
+ Object.defineProperty(o, k2, desc);
16
+ }) : (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ o[k2] = m[k];
19
+ }));
20
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
22
+ }) : function(o, v) {
23
+ o["default"] = v;
24
+ });
25
+ var __importStar = (this && this.__importStar) || (function () {
26
+ var ownKeys = function(o) {
27
+ ownKeys = Object.getOwnPropertyNames || function (o) {
28
+ var ar = [];
29
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
30
+ return ar;
31
+ };
32
+ return ownKeys(o);
33
+ };
34
+ return function (mod) {
35
+ if (mod && mod.__esModule) return mod;
36
+ var result = {};
37
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
38
+ __setModuleDefault(result, mod);
39
+ return result;
40
+ };
41
+ })();
42
+ var __importDefault = (this && this.__importDefault) || function (mod) {
43
+ return (mod && mod.__esModule) ? mod : { "default": mod };
44
+ };
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ exports.SharedManager = void 0;
47
+ const fs = __importStar(require("fs"));
48
+ const path = __importStar(require("path"));
49
+ const picocolors_1 = __importDefault(require("picocolors"));
50
+ function getHome() {
51
+ return process.env.HOME ?? process.env.USERPROFILE ?? '~';
52
+ }
53
+ const SHARED_ITEMS = [
54
+ { name: 'commands', type: 'directory' },
55
+ { name: 'skills', type: 'directory' },
56
+ { name: 'agents', type: 'directory' },
57
+ { name: 'plugins', type: 'directory' },
58
+ { name: 'settings.json', type: 'file' },
59
+ ];
60
+ class SharedManager {
61
+ constructor() {
62
+ this.claudeDir = path.join(getHome(), '.claude');
63
+ }
64
+ /**
65
+ * Link shared directories to an instance.
66
+ * Creates symlinks from instance/<item> -> ~/.claude/<item>
67
+ */
68
+ linkSharedDirectories(instancePath) {
69
+ const linked = [];
70
+ for (const item of SHARED_ITEMS) {
71
+ const claudePath = path.join(this.claudeDir, item.name);
72
+ const linkPath = path.join(instancePath, item.name);
73
+ // Ensure source exists in ~/.claude/
74
+ if (!this.pathExists(claudePath)) {
75
+ if (item.type === 'directory') {
76
+ fs.mkdirSync(claudePath, { recursive: true, mode: 0o700 });
77
+ }
78
+ else {
79
+ fs.writeFileSync(claudePath, '{}', 'utf8');
80
+ }
81
+ }
82
+ // Skip if already a symlink pointing to the correct target
83
+ if (this.isCorrectSymlink(linkPath, claudePath)) {
84
+ continue;
85
+ }
86
+ // Remove existing path (file, dir, or stale symlink)
87
+ this.removeExisting(linkPath, item.type);
88
+ // Create symlink
89
+ try {
90
+ const symlinkType = item.type === 'directory' ? 'dir' : 'file';
91
+ fs.symlinkSync(claudePath, linkPath, symlinkType);
92
+ linked.push(item.name);
93
+ }
94
+ catch (_err) {
95
+ // Windows fallback: copy if symlink fails (Developer Mode not enabled)
96
+ if (process.platform === 'win32') {
97
+ if (item.type === 'directory') {
98
+ this.copyDirectoryFallback(claudePath, linkPath);
99
+ }
100
+ else {
101
+ fs.copyFileSync(claudePath, linkPath);
102
+ }
103
+ console.warn(`[!] Symlink failed for ${item.name}, copied instead (enable Developer Mode for symlinks)`);
104
+ linked.push(`${item.name} (copied)`);
105
+ }
106
+ else {
107
+ throw _err;
108
+ }
109
+ }
110
+ }
111
+ if (linked.length > 0) {
112
+ console.log(` ${picocolors_1.default.dim('shared')} ${picocolors_1.default.dim(linked.join(', '))}`);
113
+ }
114
+ }
115
+ /**
116
+ * Remove symlinks from an instance (cleanup).
117
+ */
118
+ unlinkSharedDirectories(instancePath) {
119
+ for (const item of SHARED_ITEMS) {
120
+ const linkPath = path.join(instancePath, item.name);
121
+ if (!this.pathExists(linkPath))
122
+ continue;
123
+ try {
124
+ const stats = fs.lstatSync(linkPath);
125
+ if (stats.isSymbolicLink()) {
126
+ fs.unlinkSync(linkPath);
127
+ }
128
+ }
129
+ catch {
130
+ // Best-effort
131
+ }
132
+ }
133
+ }
134
+ pathExists(p) {
135
+ try {
136
+ fs.lstatSync(p);
137
+ return true;
138
+ }
139
+ catch {
140
+ return false;
141
+ }
142
+ }
143
+ isCorrectSymlink(linkPath, expectedTarget) {
144
+ try {
145
+ const stats = fs.lstatSync(linkPath);
146
+ if (!stats.isSymbolicLink())
147
+ return false;
148
+ const currentTarget = fs.readlinkSync(linkPath);
149
+ const resolved = path.resolve(path.dirname(linkPath), currentTarget);
150
+ return resolved === expectedTarget;
151
+ }
152
+ catch {
153
+ return false;
154
+ }
155
+ }
156
+ removeExisting(p, type) {
157
+ if (!this.pathExists(p))
158
+ return;
159
+ if (type === 'directory') {
160
+ fs.rmSync(p, { recursive: true, force: true });
161
+ }
162
+ else {
163
+ try {
164
+ fs.unlinkSync(p);
165
+ }
166
+ catch {
167
+ fs.rmSync(p, { force: true });
168
+ }
169
+ }
170
+ }
171
+ copyDirectoryFallback(src, dest) {
172
+ fs.mkdirSync(dest, { recursive: true, mode: 0o700 });
173
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
174
+ const srcPath = path.join(src, entry.name);
175
+ const destPath = path.join(dest, entry.name);
176
+ if (entry.isDirectory()) {
177
+ this.copyDirectoryFallback(srcPath, destPath);
178
+ }
179
+ else {
180
+ fs.copyFileSync(srcPath, destPath);
181
+ }
182
+ }
183
+ }
184
+ }
185
+ exports.SharedManager = SharedManager;
186
+ //# sourceMappingURL=shared-manager.js.map