@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.
- package/.claude/CLAUDE.md +204 -0
- package/.claude/agents/.gitkeep +0 -0
- package/.claude/settings.json +9 -0
- package/.claude/skills/.gitkeep +0 -0
- package/README.md +127 -0
- package/dist/accounts/instance-manager.d.ts +11 -0
- package/dist/accounts/instance-manager.d.ts.map +1 -0
- package/dist/accounts/instance-manager.js +89 -0
- package/dist/accounts/instance-manager.js.map +1 -0
- package/dist/accounts/shared-manager.d.ts +25 -0
- package/dist/accounts/shared-manager.d.ts.map +1 -0
- package/dist/accounts/shared-manager.js +186 -0
- package/dist/accounts/shared-manager.js.map +1 -0
- package/dist/accounts/store.d.ts +30 -0
- package/dist/accounts/store.d.ts.map +1 -0
- package/dist/accounts/store.js +128 -0
- package/dist/accounts/store.js.map +1 -0
- package/dist/core/model-router.d.ts +30 -0
- package/dist/core/model-router.d.ts.map +1 -0
- package/dist/core/model-router.js +64 -0
- package/dist/core/model-router.js.map +1 -0
- package/dist/dashboard-server.d.ts +5 -0
- package/dist/dashboard-server.d.ts.map +1 -0
- package/dist/dashboard-server.js +387 -0
- package/dist/dashboard-server.js.map +1 -0
- package/dist/mcc.d.ts +8 -0
- package/dist/mcc.d.ts.map +1 -0
- package/dist/mcc.js +474 -0
- package/dist/mcc.js.map +1 -0
- package/dist/mcp/external-registry.d.ts +24 -0
- package/dist/mcp/external-registry.d.ts.map +1 -0
- package/dist/mcp/external-registry.js +99 -0
- package/dist/mcp/external-registry.js.map +1 -0
- package/dist/mcp/installer.d.ts +31 -0
- package/dist/mcp/installer.d.ts.map +1 -0
- package/dist/mcp/installer.js +273 -0
- package/dist/mcp/installer.js.map +1 -0
- package/dist/mcp/mcp-config.d.ts +86 -0
- package/dist/mcp/mcp-config.d.ts.map +1 -0
- package/dist/mcp/mcp-config.js +178 -0
- package/dist/mcp/mcp-config.js.map +1 -0
- package/dist/mcp/registry.d.ts +23 -0
- package/dist/mcp/registry.d.ts.map +1 -0
- package/dist/mcp/registry.js +100 -0
- package/dist/mcp/registry.js.map +1 -0
- package/dist/proxy/proxy-daemon.d.ts +27 -0
- package/dist/proxy/proxy-daemon.d.ts.map +1 -0
- package/dist/proxy/proxy-daemon.js +192 -0
- package/dist/proxy/proxy-daemon.js.map +1 -0
- package/dist/proxy/proxy-entry.d.ts +11 -0
- package/dist/proxy/proxy-entry.d.ts.map +1 -0
- package/dist/proxy/proxy-entry.js +74 -0
- package/dist/proxy/proxy-entry.js.map +1 -0
- package/dist/proxy/proxy-paths.d.ts +27 -0
- package/dist/proxy/proxy-paths.d.ts.map +1 -0
- package/dist/proxy/proxy-paths.js +125 -0
- package/dist/proxy/proxy-paths.js.map +1 -0
- package/dist/proxy/proxy-server.d.ts +20 -0
- package/dist/proxy/proxy-server.d.ts.map +1 -0
- package/dist/proxy/proxy-server.js +280 -0
- package/dist/proxy/proxy-server.js.map +1 -0
- package/dist/proxy/upstream-url.d.ts +7 -0
- package/dist/proxy/upstream-url.d.ts.map +1 -0
- package/dist/proxy/upstream-url.js +38 -0
- package/dist/proxy/upstream-url.js.map +1 -0
- package/dist/shared/logger.d.ts +23 -0
- package/dist/shared/logger.d.ts.map +1 -0
- package/dist/shared/logger.js +184 -0
- package/dist/shared/logger.js.map +1 -0
- package/dist/shared/provider-preset-catalog.d.ts +41 -0
- package/dist/shared/provider-preset-catalog.d.ts.map +1 -0
- package/dist/shared/provider-preset-catalog.js +299 -0
- package/dist/shared/provider-preset-catalog.js.map +1 -0
- package/docs/decisions.md +33 -0
- package/docs/lessons.md +8 -0
- package/docs/product.md +37 -0
- package/lib/mcp/mcc-image-analysis-server.cjs +454 -0
- package/lib/mcp/mcc-websearch-server.cjs +339 -0
- package/lib/mcp-hooks/image-analysis-runtime.cjs +510 -0
- package/lib/mcp-hooks/image-analyzer-transformer.cjs +526 -0
- package/lib/mcp-hooks/websearch-transformer.cjs +1421 -0
- package/lib/proxy/config/config-loader-facade.js +24 -0
- package/lib/proxy/glmt/delta-accumulator.js +363 -0
- package/lib/proxy/glmt/glmt-transformer.js +204 -0
- package/lib/proxy/glmt/index.js +41 -0
- package/lib/proxy/glmt/locale-enforcer.js +69 -0
- package/lib/proxy/glmt/pipeline/content-transformer.js +162 -0
- package/lib/proxy/glmt/pipeline/index.js +20 -0
- package/lib/proxy/glmt/pipeline/request-transformer.js +116 -0
- package/lib/proxy/glmt/pipeline/response-builder.js +205 -0
- package/lib/proxy/glmt/pipeline/stream-parser.js +234 -0
- package/lib/proxy/glmt/pipeline/tool-call-handler.js +78 -0
- package/lib/proxy/glmt/pipeline/types.js +6 -0
- package/lib/proxy/glmt/reasoning-enforcer.js +151 -0
- package/lib/proxy/glmt/sse-parser.js +102 -0
- package/lib/proxy/services/logging.js +13 -0
- package/lib/proxy/transformers/request-transformer.js +452 -0
- package/lib/proxy/transformers/sse-stream-transformer.js +199 -0
- package/lib/shared/logger.cjs +138 -0
- package/package.json +35 -0
- package/src/accounts/instance-manager.ts +58 -0
- package/src/accounts/shared-manager.ts +154 -0
- package/src/accounts/store.ts +111 -0
- package/src/core/model-router.ts +82 -0
- package/src/dashboard-server.ts +407 -0
- package/src/mcc.ts +474 -0
- package/src/mcp/external-registry.ts +73 -0
- package/src/mcp/installer.ts +258 -0
- package/src/mcp/mcp-config.ts +168 -0
- package/src/mcp/registry.ts +89 -0
- package/src/proxy/proxy-daemon.ts +184 -0
- package/src/proxy/proxy-entry.ts +63 -0
- package/src/proxy/proxy-paths.ts +97 -0
- package/src/proxy/proxy-server.ts +278 -0
- package/src/proxy/upstream-url.ts +38 -0
- package/src/shared/logger.ts +140 -0
- package/src/shared/provider-preset-catalog.ts +340 -0
- package/tsconfig.json +33 -0
- package/ui/.prettierrc +9 -0
- package/ui/index.html +12 -0
- package/ui/package.json +33 -0
- package/ui/postcss.config.js +6 -0
- package/ui/src/App.tsx +753 -0
- package/ui/src/components/ui/button.tsx +48 -0
- package/ui/src/components/ui/card.tsx +50 -0
- package/ui/src/components/ui/input.tsx +21 -0
- package/ui/src/components/ui/label.tsx +20 -0
- package/ui/src/components/ui/select.tsx +80 -0
- package/ui/src/components/ui/switch.tsx +26 -0
- package/ui/src/components/ui/tabs.tsx +52 -0
- package/ui/src/index.css +33 -0
- package/ui/src/lib/api.ts +185 -0
- package/ui/src/lib/utils.ts +6 -0
- package/ui/src/main.tsx +10 -0
- package/ui/src/vite-env.d.ts +1 -0
- package/ui/tailwind.config.js +49 -0
- package/ui/tsconfig.json +25 -0
- 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
|
|
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
|