ccg-workflow 1.7.82 → 1.7.84

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.
@@ -4,13 +4,77 @@ import { exec, spawn } from 'node:child_process';
4
4
  import { promisify } from 'node:util';
5
5
  import { homedir } from 'node:os';
6
6
  import { fileURLToPath } from 'node:url';
7
- import { join, dirname } from 'pathe';
7
+ import { dirname, join, basename } from 'pathe';
8
8
  import fs from 'fs-extra';
9
9
  import { parse, stringify } from 'smol-toml';
10
10
  import i18next from 'i18next';
11
11
  import ora from 'ora';
12
12
 
13
- const version = "1.7.82";
13
+ const version = "1.7.84";
14
+
15
+ function cmd(id, order, category, name, nameEn, description, descriptionEn, cmdOverride) {
16
+ return {
17
+ id,
18
+ name,
19
+ nameEn,
20
+ category,
21
+ commands: [cmdOverride ?? id],
22
+ defaultSelected: true,
23
+ order,
24
+ description,
25
+ descriptionEn
26
+ };
27
+ }
28
+ const WORKFLOW_CONFIGS = [
29
+ // ── Development ──────────────────────────────────────
30
+ cmd("workflow", 1, "development", "\u5B8C\u6574\u5F00\u53D1\u5DE5\u4F5C\u6D41", "Full Development Workflow", "\u5B8C\u65746\u9636\u6BB5\u5F00\u53D1\u5DE5\u4F5C\u6D41\uFF08\u7814\u7A76\u2192\u6784\u601D\u2192\u8BA1\u5212\u2192\u6267\u884C\u2192\u4F18\u5316\u2192\u8BC4\u5BA1\uFF09", "Full 6-phase development workflow"),
31
+ cmd("plan", 1.5, "development", "\u591A\u6A21\u578B\u534F\u4F5C\u89C4\u5212", "Multi-Model Planning", "\u4E0A\u4E0B\u6587\u68C0\u7D22 + \u53CC\u6A21\u578B\u5206\u6790 \u2192 \u751F\u6210 Step-by-step \u5B9E\u65BD\u8BA1\u5212", "Context retrieval + dual-model analysis \u2192 Step-by-step plan"),
32
+ cmd("execute", 1.6, "development", "\u591A\u6A21\u578B\u534F\u4F5C\u6267\u884C", "Multi-Model Execution", "\u6839\u636E\u8BA1\u5212\u83B7\u53D6\u539F\u578B \u2192 Claude \u91CD\u6784\u5B9E\u65BD \u2192 \u591A\u6A21\u578B\u5BA1\u8BA1\u4EA4\u4ED8", "Get prototype from plan \u2192 Claude refactor \u2192 Multi-model audit"),
33
+ cmd("team-research", 1.8, "development", "Agent Teams \u9700\u6C42\u7814\u7A76", "Agent Teams Research", "\u5E76\u884C\u63A2\u7D22\u4EE3\u7801\u5E93\uFF0C\u4EA7\u51FA\u7EA6\u675F\u96C6 + \u53EF\u9A8C\u8BC1\u6210\u529F\u5224\u636E", "Parallel codebase exploration, produces constraint sets + success criteria"),
34
+ cmd("team-plan", 1.85, "development", "Agent Teams \u89C4\u5212", "Agent Teams Planning", "Lead \u8C03\u7528 Codex/Gemini \u5E76\u884C\u5206\u6790\uFF0C\u4EA7\u51FA\u96F6\u51B3\u7B56\u5E76\u884C\u5B9E\u65BD\u8BA1\u5212", "Lead orchestrates Codex/Gemini analysis, produces zero-decision parallel plan"),
35
+ cmd("team-exec", 1.9, "development", "Agent Teams \u5E76\u884C\u5B9E\u65BD", "Agent Teams Parallel Execution", "\u8BFB\u53D6\u8BA1\u5212\u6587\u4EF6\uFF0Cspawn Builder teammates \u5E76\u884C\u5199\u4EE3\u7801\uFF0C\u9700\u542F\u7528 Agent Teams", "Read plan file, spawn Builder teammates for parallel implementation"),
36
+ cmd("team-review", 1.95, "development", "Agent Teams \u5BA1\u67E5", "Agent Teams Review", "\u53CC\u6A21\u578B\u4EA4\u53C9\u5BA1\u67E5\u5E76\u884C\u5B9E\u65BD\u4EA7\u51FA\uFF0C\u5206\u7EA7\u5904\u7406 Critical/Warning/Info", "Dual-model cross-review with severity classification"),
37
+ cmd("frontend", 2, "development", "\u524D\u7AEF\u4E13\u9879", "Frontend Tasks", "\u524D\u7AEF\u4E13\u9879\u4EFB\u52A1\uFF08Gemini\u4E3B\u5BFC\uFF0C\u66F4\u5FEB\u66F4\u7CBE\u51C6\uFF09", "Frontend tasks (Gemini-led, faster)"),
38
+ cmd("codex-exec", 2.5, "development", "Codex \u6267\u884C\u8BA1\u5212", "Codex Plan Executor", "\u8BFB\u53D6 /ccg:plan \u8BA1\u5212\u6587\u4EF6\uFF0CCodex \u5168\u6743\u6267\u884C + \u591A\u6A21\u578B\u5BA1\u6838", "Read plan file from /ccg:plan, Codex executes + multi-model review"),
39
+ cmd("context", 2.6, "development", "\u9879\u76EE\u4E0A\u4E0B\u6587\u7BA1\u7406", "Project Context Manager", "\u521D\u59CB\u5316 .context \u76EE\u5F55\u3001\u8BB0\u5F55\u51B3\u7B56\u65E5\u5FD7\u3001\u538B\u7F29\u5F52\u6863\u3001\u67E5\u770B\u5386\u53F2", "Init .context dir, log decisions, compress, view history"),
40
+ cmd("backend", 3, "development", "\u540E\u7AEF\u4E13\u9879", "Backend Tasks", "\u540E\u7AEF\u4E13\u9879\u4EFB\u52A1\uFF08Codex\u4E3B\u5BFC\uFF0C\u66F4\u5FEB\u66F4\u7CBE\u51C6\uFF09", "Backend tasks (Codex-led, faster)"),
41
+ cmd("feat", 4, "development", "\u667A\u80FD\u529F\u80FD\u5F00\u53D1", "Smart Feature Development", "\u667A\u80FD\u529F\u80FD\u5F00\u53D1 - \u81EA\u52A8\u89C4\u5212\u3001\u8BBE\u8BA1\u3001\u5B9E\u65BD", "Smart feature development - auto plan, design, implement"),
42
+ cmd("analyze", 5, "development", "\u6280\u672F\u5206\u6790", "Technical Analysis", "\u53CC\u6A21\u578B\u6280\u672F\u5206\u6790\uFF0C\u4EC5\u5206\u6790\u4E0D\u4FEE\u6539\u4EE3\u7801", "Dual-model technical analysis, analysis only"),
43
+ cmd("debug", 6, "development", "\u95EE\u9898\u8BCA\u65AD", "Debug", "\u591A\u6A21\u578B\u8BCA\u65AD + \u4FEE\u590D", "Multi-model diagnosis + fix"),
44
+ cmd("optimize", 7, "development", "\u6027\u80FD\u4F18\u5316", "Performance Optimization", "\u591A\u6A21\u578B\u6027\u80FD\u4F18\u5316", "Multi-model performance optimization"),
45
+ cmd("test", 8, "development", "\u6D4B\u8BD5\u751F\u6210", "Test Generation", "\u667A\u80FD\u8DEF\u7531\u6D4B\u8BD5\u751F\u6210", "Smart routing test generation"),
46
+ cmd("review", 9, "development", "\u4EE3\u7801\u5BA1\u67E5", "Code Review", "\u53CC\u6A21\u578B\u4EE3\u7801\u5BA1\u67E5\uFF0C\u65E0\u53C2\u6570\u65F6\u81EA\u52A8\u5BA1\u67E5 git diff", "Dual-model code review, auto-review git diff when no args"),
47
+ cmd("enhance", 9.5, "development", "Prompt \u589E\u5F3A", "Prompt Enhancement", "ace-tool Prompt \u589E\u5F3A\u5DE5\u5177", "ace-tool prompt enhancement"),
48
+ // ── Init ─────────────────────────────────────────────
49
+ cmd("init-project", 10, "init", "\u9879\u76EE\u521D\u59CB\u5316", "Project Init", "\u521D\u59CB\u5316\u9879\u76EE AI \u4E0A\u4E0B\u6587\uFF0C\u751F\u6210 CLAUDE.md", "Initialize project AI context, generate CLAUDE.md", "init"),
50
+ // ── Git ──────────────────────────────────────────────
51
+ cmd("commit", 20, "git", "Git \u63D0\u4EA4", "Git Commit", "\u667A\u80FD\u751F\u6210 conventional commit \u4FE1\u606F", "Smart conventional commit message generation"),
52
+ cmd("rollback", 21, "git", "Git \u56DE\u6EDA", "Git Rollback", "\u4EA4\u4E92\u5F0F\u56DE\u6EDA\u5206\u652F\u5230\u5386\u53F2\u7248\u672C", "Interactive rollback to historical version"),
53
+ cmd("clean-branches", 22, "git", "Git \u6E05\u7406\u5206\u652F", "Git Clean Branches", "\u5B89\u5168\u6E05\u7406\u5DF2\u5408\u5E76\u6216\u8FC7\u671F\u5206\u652F", "Safely clean merged or stale branches"),
54
+ cmd("worktree", 23, "git", "Git Worktree", "Git Worktree", "\u7BA1\u7406 Git worktree", "Manage Git worktree"),
55
+ // ── Spec (OpenSpec / OPSX) ───────────────────────────
56
+ cmd("spec-init", 30, "spec", "OpenSpec \u521D\u59CB\u5316", "OpenSpec Init", "\u521D\u59CB\u5316 OpenSpec \u73AF\u5883 + \u9A8C\u8BC1\u591A\u6A21\u578B MCP \u5DE5\u5177", "Initialize OpenSpec environment with multi-model MCP validation"),
57
+ cmd("spec-research", 31, "spec", "\u9700\u6C42\u7814\u7A76", "Spec Research", "\u9700\u6C42 \u2192 \u7EA6\u675F\u96C6\uFF08\u5E76\u884C\u63A2\u7D22 + OpenSpec \u63D0\u6848\uFF09", "Transform requirements into constraint sets via parallel exploration"),
58
+ cmd("spec-plan", 32, "spec", "\u96F6\u51B3\u7B56\u89C4\u5212", "Spec Plan", "\u591A\u6A21\u578B\u5206\u6790 \u2192 \u6D88\u9664\u6B67\u4E49 \u2192 \u96F6\u51B3\u7B56\u53EF\u6267\u884C\u8BA1\u5212", "Refine proposals into zero-decision executable plans"),
59
+ cmd("spec-impl", 33, "spec", "\u89C4\u8303\u9A71\u52A8\u5B9E\u73B0", "Spec Implementation", "\u6309\u89C4\u8303\u6267\u884C + \u591A\u6A21\u578B\u534F\u4F5C + \u5F52\u6863", "Execute changes via multi-model collaboration with spec compliance"),
60
+ cmd("spec-review", 34, "spec", "\u5F52\u6863\u524D\u5BA1\u67E5", "Spec Review", "\u53CC\u6A21\u578B\u4EA4\u53C9\u5BA1\u67E5 \u2192 Critical \u5FC5\u987B\u4FEE\u590D \u2192 \u5141\u8BB8\u5F52\u6863", "Multi-model compliance review before archiving")
61
+ ];
62
+ function getWorkflowConfigs() {
63
+ return WORKFLOW_CONFIGS.sort((a, b) => a.order - b.order);
64
+ }
65
+ function getWorkflowById(id) {
66
+ return WORKFLOW_CONFIGS.find((w) => w.id === id);
67
+ }
68
+ function getAllCommandIds() {
69
+ return WORKFLOW_CONFIGS.map((w) => w.id);
70
+ }
71
+ ({
72
+ full: {
73
+ description: `\u5168\u90E8\u547D\u4EE4\uFF08${WORKFLOW_CONFIGS.length}\u4E2A\uFF09`,
74
+ descriptionEn: `All commands (${WORKFLOW_CONFIGS.length})`,
75
+ workflows: WORKFLOW_CONFIGS.map((w) => w.id)
76
+ }
77
+ });
14
78
 
15
79
  function isWindows() {
16
80
  return process.platform === "win32";
@@ -23,6 +87,76 @@ function getMcpCommand(command) {
23
87
  return [command];
24
88
  }
25
89
 
90
+ const __filename$2 = fileURLToPath(import.meta.url);
91
+ const __dirname$2 = dirname(__filename$2);
92
+ function findPackageRoot$1(startDir) {
93
+ let dir = startDir;
94
+ for (let i = 0; i < 5; i++) {
95
+ if (fs.existsSync(join(dir, "package.json"))) {
96
+ return dir;
97
+ }
98
+ dir = dirname(dir);
99
+ }
100
+ return startDir;
101
+ }
102
+ const PACKAGE_ROOT$1 = findPackageRoot$1(__dirname$2);
103
+ const MCP_PROVIDERS = {
104
+ "ace-tool": { tool: "mcp__ace-tool__search_context", param: "query" },
105
+ "ace-tool-rs": { tool: "mcp__ace-tool__search_context", param: "query" },
106
+ "contextweaver": { tool: "mcp__contextweaver__codebase-retrieval", param: "information_request" },
107
+ "fast-context": { tool: "mcp__fast-context__fast_context_search", param: "query" }
108
+ };
109
+ function injectConfigVariables(content, config) {
110
+ let processed = content;
111
+ const routing = config.routing || {};
112
+ const frontendModels = routing.frontend?.models || ["gemini"];
113
+ const frontendPrimary = routing.frontend?.primary || "gemini";
114
+ processed = processed.replace(/\{\{FRONTEND_MODELS\}\}/g, JSON.stringify(frontendModels));
115
+ processed = processed.replace(/\{\{FRONTEND_PRIMARY\}\}/g, frontendPrimary);
116
+ const backendModels = routing.backend?.models || ["codex"];
117
+ const backendPrimary = routing.backend?.primary || "codex";
118
+ processed = processed.replace(/\{\{BACKEND_MODELS\}\}/g, JSON.stringify(backendModels));
119
+ processed = processed.replace(/\{\{BACKEND_PRIMARY\}\}/g, backendPrimary);
120
+ const reviewModels = routing.review?.models || ["codex", "gemini"];
121
+ processed = processed.replace(/\{\{REVIEW_MODELS\}\}/g, JSON.stringify(reviewModels));
122
+ const routingMode = routing.mode || "smart";
123
+ processed = processed.replace(/\{\{ROUTING_MODE\}\}/g, routingMode);
124
+ const liteModeFlag = config.liteMode ? "--lite " : "";
125
+ processed = processed.replace(/\{\{LITE_MODE_FLAG\}\}/g, liteModeFlag);
126
+ const mcpProvider = config.mcpProvider || "ace-tool";
127
+ if (mcpProvider === "skip") {
128
+ processed = processed.replace(/,\s*\{\{MCP_SEARCH_TOOL\}\}/g, "");
129
+ processed = processed.replace(
130
+ /```\n\{\{MCP_SEARCH_TOOL\}\}[\s\S]*?\n```/g,
131
+ "> MCP \u672A\u914D\u7F6E\u3002\u4F7F\u7528 `Glob` \u5B9A\u4F4D\u6587\u4EF6 + `Grep` \u641C\u7D22\u5173\u952E\u7B26\u53F7 + `Read` \u8BFB\u53D6\u6587\u4EF6\u5185\u5BB9\u3002"
132
+ );
133
+ processed = processed.replace(/`\{\{MCP_SEARCH_TOOL\}\}`/g, "`Glob + Grep`\uFF08MCP \u672A\u914D\u7F6E\uFF09");
134
+ processed = processed.replace(/\{\{MCP_SEARCH_TOOL\}\}/g, "Glob + Grep");
135
+ processed = processed.replace(/\{\{MCP_SEARCH_PARAM\}\}/g, "");
136
+ } else {
137
+ const provider = MCP_PROVIDERS[mcpProvider] ?? MCP_PROVIDERS["ace-tool"];
138
+ processed = processed.replace(/\{\{MCP_SEARCH_TOOL\}\}/g, provider.tool);
139
+ processed = processed.replace(/\{\{MCP_SEARCH_PARAM\}\}/g, provider.param);
140
+ }
141
+ return processed;
142
+ }
143
+ function replaceHomePathsInTemplate(content, installDir) {
144
+ const userHome = homedir();
145
+ const ccgDir = join(installDir, ".ccg");
146
+ const binDir = join(installDir, "bin");
147
+ const claudeDir = installDir;
148
+ const toForwardSlash = (path) => path.replace(/\\/g, "/");
149
+ let processed = content;
150
+ processed = processed.replace(/~\/\.claude\/\.ccg/g, toForwardSlash(ccgDir));
151
+ const wrapperName = isWindows() ? "codeagent-wrapper.exe" : "codeagent-wrapper";
152
+ const wrapperPath = `${toForwardSlash(binDir)}/${wrapperName}`;
153
+ processed = processed.replace(/~\/\.claude\/bin\/codeagent-wrapper/g, wrapperPath);
154
+ processed = processed.replace(/~\/\.claude\/bin/g, toForwardSlash(binDir));
155
+ processed = processed.replace(/~\/\.claude/g, toForwardSlash(claudeDir));
156
+ processed = processed.replace(/~\//g, `${toForwardSlash(userHome)}/`);
157
+ return processed;
158
+ }
159
+
26
160
  function getClaudeCodeConfigPath() {
27
161
  return join(homedir(), ".claude.json");
28
162
  }
@@ -156,896 +290,75 @@ async function diagnoseMcpConfig() {
156
290
  return issues;
157
291
  }
158
292
 
159
- const GITHUB_REPO = "fengshao1227/ccg-workflow";
160
- const RELEASE_TAG = "preset";
161
- const BINARY_DOWNLOAD_URL = `https://github.com/${GITHUB_REPO}/releases/download/${RELEASE_TAG}`;
162
- const __filename$2 = fileURLToPath(import.meta.url);
163
- const __dirname$2 = dirname(__filename$2);
164
- function findPackageRoot$1(startDir) {
165
- let dir = startDir;
166
- for (let i = 0; i < 5; i++) {
167
- if (fs.existsSync(join(dir, "package.json"))) {
168
- return dir;
293
+ async function configureMcpInClaude(serverId, serverConfig, label) {
294
+ try {
295
+ let existingConfig = await readClaudeCodeConfig();
296
+ if (!existingConfig) {
297
+ existingConfig = { mcpServers: {} };
169
298
  }
170
- dir = dirname(dir);
171
- }
172
- return startDir;
173
- }
174
- const PACKAGE_ROOT$1 = findPackageRoot$1(__dirname$2);
175
- async function downloadBinaryFromRelease(binaryName, destPath) {
176
- const url = `${BINARY_DOWNLOAD_URL}/${binaryName}`;
177
- const MAX_ATTEMPTS = 3;
178
- const TIMEOUT_MS = 6e4;
179
- for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
180
- try {
181
- const controller = new AbortController();
182
- const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
183
- const response = await fetch(url, { redirect: "follow", signal: controller.signal });
184
- if (!response.ok) {
185
- clearTimeout(timer);
186
- if (attempt < MAX_ATTEMPTS) {
187
- await new Promise((resolve) => setTimeout(resolve, attempt * 2e3));
188
- continue;
189
- }
190
- return false;
191
- }
192
- const buffer = Buffer.from(await response.arrayBuffer());
193
- clearTimeout(timer);
194
- await fs.writeFile(destPath, buffer);
195
- if (process.platform !== "win32") {
196
- await fs.chmod(destPath, 493);
197
- }
198
- return true;
199
- } catch {
200
- if (attempt < MAX_ATTEMPTS) {
201
- await new Promise((resolve) => setTimeout(resolve, attempt * 2e3));
202
- continue;
299
+ if (existingConfig.mcpServers && Object.keys(existingConfig.mcpServers).length > 0) {
300
+ const backupPath = await backupClaudeCodeConfig();
301
+ if (backupPath) {
302
+ console.log(` \u2713 Backup created: ${backupPath}`);
203
303
  }
204
- return false;
205
304
  }
305
+ let mergedConfig = mergeMcpServers(existingConfig, {
306
+ [serverId]: serverConfig
307
+ });
308
+ if (isWindows()) {
309
+ mergedConfig = fixWindowsMcpConfig(mergedConfig);
310
+ console.log(" \u2713 Applied Windows MCP configuration fixes");
311
+ }
312
+ await writeClaudeCodeConfig(mergedConfig);
313
+ return {
314
+ success: true,
315
+ message: isWindows() ? `${label} configured successfully with Windows compatibility` : `${label} configured successfully`,
316
+ configPath: join(homedir(), ".claude.json")
317
+ };
318
+ } catch (error) {
319
+ return {
320
+ success: false,
321
+ message: `Failed to configure ${label}: ${error}`
322
+ };
206
323
  }
207
- return false;
208
324
  }
209
- const WORKFLOW_CONFIGS = [
210
- {
211
- id: "workflow",
212
- name: "\u5B8C\u6574\u5F00\u53D1\u5DE5\u4F5C\u6D41",
213
- nameEn: "Full Development Workflow",
214
- category: "development",
215
- commands: ["workflow"],
216
- defaultSelected: true,
217
- order: 1,
218
- description: "\u5B8C\u65746\u9636\u6BB5\u5F00\u53D1\u5DE5\u4F5C\u6D41\uFF08\u7814\u7A76\u2192\u6784\u601D\u2192\u8BA1\u5212\u2192\u6267\u884C\u2192\u4F18\u5316\u2192\u8BC4\u5BA1\uFF09",
219
- descriptionEn: "Full 6-phase development workflow"
220
- },
221
- {
222
- id: "plan",
223
- name: "\u591A\u6A21\u578B\u534F\u4F5C\u89C4\u5212",
224
- nameEn: "Multi-Model Planning",
225
- category: "development",
226
- commands: ["plan"],
227
- defaultSelected: true,
228
- order: 1.5,
229
- description: "\u4E0A\u4E0B\u6587\u68C0\u7D22 + \u53CC\u6A21\u578B\u5206\u6790 \u2192 \u751F\u6210 Step-by-step \u5B9E\u65BD\u8BA1\u5212",
230
- descriptionEn: "Context retrieval + dual-model analysis \u2192 Step-by-step plan"
231
- },
232
- {
233
- id: "execute",
234
- name: "\u591A\u6A21\u578B\u534F\u4F5C\u6267\u884C",
235
- nameEn: "Multi-Model Execution",
236
- category: "development",
237
- commands: ["execute"],
238
- defaultSelected: true,
239
- order: 1.6,
240
- description: "\u6839\u636E\u8BA1\u5212\u83B7\u53D6\u539F\u578B \u2192 Claude \u91CD\u6784\u5B9E\u65BD \u2192 \u591A\u6A21\u578B\u5BA1\u8BA1\u4EA4\u4ED8",
241
- descriptionEn: "Get prototype from plan \u2192 Claude refactor \u2192 Multi-model audit"
242
- },
243
- {
244
- id: "frontend",
245
- name: "\u524D\u7AEF\u4E13\u9879",
246
- nameEn: "Frontend Tasks",
247
- category: "development",
248
- commands: ["frontend"],
249
- defaultSelected: true,
250
- order: 2,
251
- description: "\u524D\u7AEF\u4E13\u9879\u4EFB\u52A1\uFF08Gemini\u4E3B\u5BFC\uFF0C\u66F4\u5FEB\u66F4\u7CBE\u51C6\uFF09",
252
- descriptionEn: "Frontend tasks (Gemini-led, faster)"
253
- },
254
- {
255
- id: "backend",
256
- name: "\u540E\u7AEF\u4E13\u9879",
257
- nameEn: "Backend Tasks",
258
- category: "development",
259
- commands: ["backend"],
260
- defaultSelected: true,
261
- order: 3,
262
- description: "\u540E\u7AEF\u4E13\u9879\u4EFB\u52A1\uFF08Codex\u4E3B\u5BFC\uFF0C\u66F4\u5FEB\u66F4\u7CBE\u51C6\uFF09",
263
- descriptionEn: "Backend tasks (Codex-led, faster)"
264
- },
265
- {
266
- id: "feat",
267
- name: "\u667A\u80FD\u529F\u80FD\u5F00\u53D1",
268
- nameEn: "Smart Feature Development",
269
- category: "development",
270
- commands: ["feat"],
271
- defaultSelected: true,
272
- order: 4,
273
- description: "\u667A\u80FD\u529F\u80FD\u5F00\u53D1 - \u81EA\u52A8\u89C4\u5212\u3001\u8BBE\u8BA1\u3001\u5B9E\u65BD",
274
- descriptionEn: "Smart feature development - auto plan, design, implement"
275
- },
276
- {
277
- id: "analyze",
278
- name: "\u6280\u672F\u5206\u6790",
279
- nameEn: "Technical Analysis",
280
- category: "development",
281
- commands: ["analyze"],
282
- defaultSelected: true,
283
- order: 5,
284
- description: "\u53CC\u6A21\u578B\u6280\u672F\u5206\u6790\uFF0C\u4EC5\u5206\u6790\u4E0D\u4FEE\u6539\u4EE3\u7801",
285
- descriptionEn: "Dual-model technical analysis, analysis only"
286
- },
287
- {
288
- id: "debug",
289
- name: "\u95EE\u9898\u8BCA\u65AD",
290
- nameEn: "Debug",
291
- category: "development",
292
- commands: ["debug"],
293
- defaultSelected: true,
294
- order: 6,
295
- description: "\u591A\u6A21\u578B\u8BCA\u65AD + \u4FEE\u590D",
296
- descriptionEn: "Multi-model diagnosis + fix"
297
- },
298
- {
299
- id: "optimize",
300
- name: "\u6027\u80FD\u4F18\u5316",
301
- nameEn: "Performance Optimization",
302
- category: "development",
303
- commands: ["optimize"],
304
- defaultSelected: true,
305
- order: 7,
306
- description: "\u591A\u6A21\u578B\u6027\u80FD\u4F18\u5316",
307
- descriptionEn: "Multi-model performance optimization"
308
- },
309
- {
310
- id: "test",
311
- name: "\u6D4B\u8BD5\u751F\u6210",
312
- nameEn: "Test Generation",
313
- category: "development",
314
- commands: ["test"],
315
- defaultSelected: true,
316
- order: 8,
317
- description: "\u667A\u80FD\u8DEF\u7531\u6D4B\u8BD5\u751F\u6210",
318
- descriptionEn: "Smart routing test generation"
319
- },
320
- {
321
- id: "review",
322
- name: "\u4EE3\u7801\u5BA1\u67E5",
323
- nameEn: "Code Review",
324
- category: "development",
325
- commands: ["review"],
326
- defaultSelected: true,
327
- order: 9,
328
- description: "\u53CC\u6A21\u578B\u4EE3\u7801\u5BA1\u67E5\uFF0C\u65E0\u53C2\u6570\u65F6\u81EA\u52A8\u5BA1\u67E5 git diff",
329
- descriptionEn: "Dual-model code review, auto-review git diff when no args"
330
- },
331
- {
332
- id: "enhance",
333
- name: "Prompt \u589E\u5F3A",
334
- nameEn: "Prompt Enhancement",
335
- category: "development",
336
- commands: ["enhance"],
337
- defaultSelected: true,
338
- order: 9.5,
339
- description: "ace-tool Prompt \u589E\u5F3A\u5DE5\u5177",
340
- descriptionEn: "ace-tool prompt enhancement"
341
- },
342
- {
343
- id: "init-project",
344
- name: "\u9879\u76EE\u521D\u59CB\u5316",
345
- nameEn: "Project Init",
346
- category: "init",
347
- commands: ["init"],
348
- defaultSelected: true,
349
- order: 10,
350
- description: "\u521D\u59CB\u5316\u9879\u76EE AI \u4E0A\u4E0B\u6587\uFF0C\u751F\u6210 CLAUDE.md",
351
- descriptionEn: "Initialize project AI context, generate CLAUDE.md"
352
- },
353
- {
354
- id: "commit",
355
- name: "Git \u63D0\u4EA4",
356
- nameEn: "Git Commit",
357
- category: "git",
358
- commands: ["commit"],
359
- defaultSelected: true,
360
- order: 20,
361
- description: "\u667A\u80FD\u751F\u6210 conventional commit \u4FE1\u606F",
362
- descriptionEn: "Smart conventional commit message generation"
363
- },
364
- {
365
- id: "rollback",
366
- name: "Git \u56DE\u6EDA",
367
- nameEn: "Git Rollback",
368
- category: "git",
369
- commands: ["rollback"],
370
- defaultSelected: true,
371
- order: 21,
372
- description: "\u4EA4\u4E92\u5F0F\u56DE\u6EDA\u5206\u652F\u5230\u5386\u53F2\u7248\u672C",
373
- descriptionEn: "Interactive rollback to historical version"
374
- },
375
- {
376
- id: "clean-branches",
377
- name: "Git \u6E05\u7406\u5206\u652F",
378
- nameEn: "Git Clean Branches",
379
- category: "git",
380
- commands: ["clean-branches"],
381
- defaultSelected: true,
382
- order: 22,
383
- description: "\u5B89\u5168\u6E05\u7406\u5DF2\u5408\u5E76\u6216\u8FC7\u671F\u5206\u652F",
384
- descriptionEn: "Safely clean merged or stale branches"
385
- },
386
- {
387
- id: "worktree",
388
- name: "Git Worktree",
389
- nameEn: "Git Worktree",
390
- category: "git",
391
- commands: ["worktree"],
392
- defaultSelected: true,
393
- order: 23,
394
- description: "\u7BA1\u7406 Git worktree",
395
- descriptionEn: "Manage Git worktree"
396
- },
397
- {
398
- id: "spec-init",
399
- name: "OpenSpec \u521D\u59CB\u5316",
400
- nameEn: "OpenSpec Init",
401
- category: "spec",
402
- commands: ["spec-init"],
403
- defaultSelected: true,
404
- order: 30,
405
- description: "\u521D\u59CB\u5316 OpenSpec \u73AF\u5883 + \u9A8C\u8BC1\u591A\u6A21\u578B MCP \u5DE5\u5177",
406
- descriptionEn: "Initialize OpenSpec environment with multi-model MCP validation"
407
- },
408
- {
409
- id: "spec-research",
410
- name: "\u9700\u6C42\u7814\u7A76",
411
- nameEn: "Spec Research",
412
- category: "spec",
413
- commands: ["spec-research"],
414
- defaultSelected: true,
415
- order: 31,
416
- description: "\u9700\u6C42 \u2192 \u7EA6\u675F\u96C6\uFF08\u5E76\u884C\u63A2\u7D22 + OpenSpec \u63D0\u6848\uFF09",
417
- descriptionEn: "Transform requirements into constraint sets via parallel exploration"
418
- },
419
- {
420
- id: "spec-plan",
421
- name: "\u96F6\u51B3\u7B56\u89C4\u5212",
422
- nameEn: "Spec Plan",
423
- category: "spec",
424
- commands: ["spec-plan"],
425
- defaultSelected: true,
426
- order: 32,
427
- description: "\u591A\u6A21\u578B\u5206\u6790 \u2192 \u6D88\u9664\u6B67\u4E49 \u2192 \u96F6\u51B3\u7B56\u53EF\u6267\u884C\u8BA1\u5212",
428
- descriptionEn: "Refine proposals into zero-decision executable plans"
429
- },
430
- {
431
- id: "spec-impl",
432
- name: "\u89C4\u8303\u9A71\u52A8\u5B9E\u73B0",
433
- nameEn: "Spec Implementation",
434
- category: "spec",
435
- commands: ["spec-impl"],
436
- defaultSelected: true,
437
- order: 33,
438
- description: "\u6309\u89C4\u8303\u6267\u884C + \u591A\u6A21\u578B\u534F\u4F5C + \u5F52\u6863",
439
- descriptionEn: "Execute changes via multi-model collaboration with spec compliance"
440
- },
441
- {
442
- id: "spec-review",
443
- name: "\u5F52\u6863\u524D\u5BA1\u67E5",
444
- nameEn: "Spec Review",
445
- category: "spec",
446
- commands: ["spec-review"],
447
- defaultSelected: true,
448
- order: 34,
449
- description: "\u53CC\u6A21\u578B\u4EA4\u53C9\u5BA1\u67E5 \u2192 Critical \u5FC5\u987B\u4FEE\u590D \u2192 \u5141\u8BB8\u5F52\u6863",
450
- descriptionEn: "Multi-model compliance review before archiving"
451
- },
452
- {
453
- id: "team-research",
454
- name: "Agent Teams \u9700\u6C42\u7814\u7A76",
455
- nameEn: "Agent Teams Research",
456
- category: "development",
457
- commands: ["team-research"],
458
- defaultSelected: true,
459
- order: 1.8,
460
- description: "\u5E76\u884C\u63A2\u7D22\u4EE3\u7801\u5E93\uFF0C\u4EA7\u51FA\u7EA6\u675F\u96C6 + \u53EF\u9A8C\u8BC1\u6210\u529F\u5224\u636E",
461
- descriptionEn: "Parallel codebase exploration, produces constraint sets + success criteria"
462
- },
463
- {
464
- id: "team-plan",
465
- name: "Agent Teams \u89C4\u5212",
466
- nameEn: "Agent Teams Planning",
467
- category: "development",
468
- commands: ["team-plan"],
469
- defaultSelected: true,
470
- order: 2,
471
- description: "Lead \u8C03\u7528 Codex/Gemini \u5E76\u884C\u5206\u6790\uFF0C\u4EA7\u51FA\u96F6\u51B3\u7B56\u5E76\u884C\u5B9E\u65BD\u8BA1\u5212",
472
- descriptionEn: "Lead orchestrates Codex/Gemini analysis, produces zero-decision parallel plan"
473
- },
474
- {
475
- id: "team-exec",
476
- name: "Agent Teams \u5E76\u884C\u5B9E\u65BD",
477
- nameEn: "Agent Teams Parallel Execution",
478
- category: "development",
479
- commands: ["team-exec"],
480
- defaultSelected: true,
481
- order: 2.5,
482
- description: "\u8BFB\u53D6\u8BA1\u5212\u6587\u4EF6\uFF0Cspawn Builder teammates \u5E76\u884C\u5199\u4EE3\u7801\uFF0C\u9700\u542F\u7528 Agent Teams",
483
- descriptionEn: "Read plan file, spawn Builder teammates for parallel implementation"
484
- },
485
- {
486
- id: "team-review",
487
- name: "Agent Teams \u5BA1\u67E5",
488
- nameEn: "Agent Teams Review",
489
- category: "development",
490
- commands: ["team-review"],
491
- defaultSelected: true,
492
- order: 3,
493
- description: "\u53CC\u6A21\u578B\u4EA4\u53C9\u5BA1\u67E5\u5E76\u884C\u5B9E\u65BD\u4EA7\u51FA\uFF0C\u5206\u7EA7\u5904\u7406 Critical/Warning/Info",
494
- descriptionEn: "Dual-model cross-review with severity classification"
495
- },
496
- {
497
- id: "codex-exec",
498
- name: "Codex \u6267\u884C\u8BA1\u5212",
499
- nameEn: "Codex Plan Executor",
500
- category: "development",
501
- commands: ["codex-exec"],
502
- defaultSelected: true,
503
- order: 3,
504
- description: "\u8BFB\u53D6 /ccg:plan \u8BA1\u5212\u6587\u4EF6\uFF0CCodex \u5168\u6743\u6267\u884C + \u591A\u6A21\u578B\u5BA1\u6838",
505
- descriptionEn: "Read plan file from /ccg:plan, Codex executes + multi-model review"
506
- },
507
- {
508
- id: "context",
509
- name: "\u9879\u76EE\u4E0A\u4E0B\u6587\u7BA1\u7406",
510
- nameEn: "Project Context Manager",
511
- category: "development",
512
- commands: ["context"],
513
- defaultSelected: true,
514
- order: 4,
515
- description: "\u521D\u59CB\u5316 .context \u76EE\u5F55\u3001\u8BB0\u5F55\u51B3\u7B56\u65E5\u5FD7\u3001\u538B\u7F29\u5F52\u6863\u3001\u67E5\u770B\u5386\u53F2",
516
- descriptionEn: "Init .context dir, log decisions, compress, view history"
325
+ async function uninstallAceTool() {
326
+ try {
327
+ const existingConfig = await readClaudeCodeConfig();
328
+ if (!existingConfig) {
329
+ return { success: true, message: "No ~/.claude.json found, nothing to remove" };
330
+ }
331
+ if (!existingConfig.mcpServers || !existingConfig.mcpServers["ace-tool"]) {
332
+ return { success: true, message: "ace-tool MCP not found in config" };
333
+ }
334
+ await backupClaudeCodeConfig();
335
+ delete existingConfig.mcpServers["ace-tool"];
336
+ await writeClaudeCodeConfig(existingConfig);
337
+ return { success: true, message: "ace-tool MCP removed from ~/.claude.json" };
338
+ } catch (error) {
339
+ return { success: false, message: `Failed to uninstall ace-tool: ${error}` };
517
340
  }
518
- ];
519
- function getWorkflowConfigs() {
520
- return WORKFLOW_CONFIGS.sort((a, b) => a.order - b.order);
521
341
  }
522
- function getWorkflowById(id) {
523
- return WORKFLOW_CONFIGS.find((w) => w.id === id);
342
+ async function installAceTool(config) {
343
+ const { baseUrl, token } = config;
344
+ const args = ["-y", "ace-tool@latest"];
345
+ if (baseUrl) args.push("--base-url", baseUrl);
346
+ if (token) args.push("--token", token);
347
+ const serverConfig = buildMcpServerConfig({ type: "stdio", command: "npx", args });
348
+ return configureMcpInClaude("ace-tool", serverConfig, "ace-tool MCP");
524
349
  }
525
- function getAllCommandIds() {
526
- return WORKFLOW_CONFIGS.map((w) => w.id);
527
- }
528
- ({
529
- full: {
530
- workflows: WORKFLOW_CONFIGS.map((w) => w.id)
531
- }
532
- });
533
- function injectConfigVariables(content, config) {
534
- let processed = content;
535
- const routing = config.routing || {};
536
- const frontendModels = routing.frontend?.models || ["gemini"];
537
- const frontendPrimary = routing.frontend?.primary || "gemini";
538
- processed = processed.replace(/\{\{FRONTEND_MODELS\}\}/g, JSON.stringify(frontendModels));
539
- processed = processed.replace(/\{\{FRONTEND_PRIMARY\}\}/g, frontendPrimary);
540
- const backendModels = routing.backend?.models || ["codex"];
541
- const backendPrimary = routing.backend?.primary || "codex";
542
- processed = processed.replace(/\{\{BACKEND_MODELS\}\}/g, JSON.stringify(backendModels));
543
- processed = processed.replace(/\{\{BACKEND_PRIMARY\}\}/g, backendPrimary);
544
- const reviewModels = routing.review?.models || ["codex", "gemini"];
545
- processed = processed.replace(/\{\{REVIEW_MODELS\}\}/g, JSON.stringify(reviewModels));
546
- const routingMode = routing.mode || "smart";
547
- processed = processed.replace(/\{\{ROUTING_MODE\}\}/g, routingMode);
548
- const liteModeFlag = config.liteMode ? "--lite " : "";
549
- processed = processed.replace(/\{\{LITE_MODE_FLAG\}\}/g, liteModeFlag);
550
- const mcpProvider = config.mcpProvider || "ace-tool";
551
- if (mcpProvider === "skip") {
552
- processed = processed.replace(/,\s*\{\{MCP_SEARCH_TOOL\}\}/g, "");
553
- processed = processed.replace(
554
- /```\n\{\{MCP_SEARCH_TOOL\}\}[\s\S]*?\n```/g,
555
- "> MCP \u672A\u914D\u7F6E\u3002\u4F7F\u7528 `Glob` \u5B9A\u4F4D\u6587\u4EF6 + `Grep` \u641C\u7D22\u5173\u952E\u7B26\u53F7 + `Read` \u8BFB\u53D6\u6587\u4EF6\u5185\u5BB9\u3002"
556
- );
557
- processed = processed.replace(
558
- /`\{\{MCP_SEARCH_TOOL\}\}`/g,
559
- "`Glob + Grep`\uFF08MCP \u672A\u914D\u7F6E\uFF09"
560
- );
561
- processed = processed.replace(/\{\{MCP_SEARCH_TOOL\}\}/g, "Glob + Grep");
562
- processed = processed.replace(/\{\{MCP_SEARCH_PARAM\}\}/g, "");
563
- } else if (mcpProvider === "contextweaver") {
564
- processed = processed.replace(/\{\{MCP_SEARCH_TOOL\}\}/g, "mcp__contextweaver__codebase-retrieval");
565
- processed = processed.replace(/\{\{MCP_SEARCH_PARAM\}\}/g, "information_request");
566
- } else if (mcpProvider === "fast-context") {
567
- processed = processed.replace(/\{\{MCP_SEARCH_TOOL\}\}/g, "mcp__fast-context__fast_context_search");
568
- processed = processed.replace(/\{\{MCP_SEARCH_PARAM\}\}/g, "query");
569
- } else {
570
- processed = processed.replace(/\{\{MCP_SEARCH_TOOL\}\}/g, "mcp__ace-tool__search_context");
571
- processed = processed.replace(/\{\{MCP_SEARCH_PARAM\}\}/g, "query");
572
- }
573
- return processed;
574
- }
575
- function replaceHomePathsInTemplate(content, installDir) {
576
- const userHome = homedir();
577
- const ccgDir = join(installDir, ".ccg");
578
- const binDir = join(installDir, "bin");
579
- const claudeDir = installDir;
580
- const normalizePath2 = (path) => path.replace(/\\/g, "/");
581
- let processed = content;
582
- processed = processed.replace(/~\/\.claude\/\.ccg/g, normalizePath2(ccgDir));
583
- const wrapperName = isWindows() ? "codeagent-wrapper.exe" : "codeagent-wrapper";
584
- const wrapperPath = `${normalizePath2(binDir)}/${wrapperName}`;
585
- processed = processed.replace(/~\/\.claude\/bin\/codeagent-wrapper/g, wrapperPath);
586
- processed = processed.replace(/~\/\.claude\/bin/g, normalizePath2(binDir));
587
- processed = processed.replace(/~\/\.claude/g, normalizePath2(claudeDir));
588
- processed = processed.replace(/~\//g, `${normalizePath2(userHome)}/`);
589
- return processed;
590
- }
591
- async function countInstalledSkills(skillsDir, depth = 0) {
592
- let count = 0;
593
- try {
594
- const entries = await fs.readdir(skillsDir, { withFileTypes: true });
595
- for (const entry of entries) {
596
- const fullPath = join(skillsDir, entry.name);
597
- if (entry.isDirectory()) {
598
- count += await countInstalledSkills(fullPath, depth + 1);
599
- } else if (entry.name === "SKILL.md" && depth > 0) {
600
- count++;
601
- }
602
- }
603
- } catch {
604
- }
605
- return count;
606
- }
607
- async function installWorkflows(workflowIds, installDir, force = false, config) {
608
- const installConfig = {
609
- routing: config?.routing || {
610
- mode: "smart",
611
- frontend: { models: ["gemini"], primary: "gemini" },
612
- backend: { models: ["codex"], primary: "codex" },
613
- review: { models: ["codex", "gemini"] }
614
- },
615
- liteMode: config?.liteMode || false,
616
- mcpProvider: config?.mcpProvider || "ace-tool"
617
- };
618
- const result = {
619
- success: true,
620
- installedCommands: [],
621
- installedPrompts: [],
622
- errors: [],
623
- configPath: ""
624
- };
625
- const commandsDir = join(installDir, "commands", "ccg");
626
- const ccgConfigDir = join(installDir, ".ccg");
627
- const promptsDir = join(ccgConfigDir, "prompts");
628
- await fs.ensureDir(commandsDir);
629
- await fs.ensureDir(ccgConfigDir);
630
- await fs.ensureDir(promptsDir);
631
- const templateDir = join(PACKAGE_ROOT$1, "templates");
632
- for (const workflowId of workflowIds) {
633
- const workflow = getWorkflowById(workflowId);
634
- if (!workflow) {
635
- result.errors.push(`Unknown workflow: ${workflowId}`);
636
- continue;
637
- }
638
- for (const cmd of workflow.commands) {
639
- const srcFile = join(templateDir, "commands", `${cmd}.md`);
640
- const destFile = join(commandsDir, `${cmd}.md`);
641
- try {
642
- if (await fs.pathExists(srcFile)) {
643
- if (force || !await fs.pathExists(destFile)) {
644
- let templateContent = await fs.readFile(srcFile, "utf-8");
645
- templateContent = injectConfigVariables(templateContent, installConfig);
646
- const processedContent = replaceHomePathsInTemplate(templateContent, installDir);
647
- await fs.writeFile(destFile, processedContent, "utf-8");
648
- result.installedCommands.push(cmd);
649
- }
650
- } else {
651
- const placeholder = `---
652
- description: "${workflow.descriptionEn}"
653
- ---
654
-
655
- # /ccg:${cmd}
656
-
657
- ${workflow.description}
658
-
659
- > This command is part of CCG multi-model collaboration system.
660
- `;
661
- await fs.writeFile(destFile, placeholder, "utf-8");
662
- result.installedCommands.push(cmd);
663
- }
664
- } catch (error) {
665
- result.errors.push(`Failed to install ${cmd}: ${error}`);
666
- result.success = false;
667
- }
668
- }
669
- }
670
- const agentsSrcDir = join(templateDir, "commands", "agents");
671
- const agentsDestDir = join(installDir, "agents", "ccg");
672
- if (await fs.pathExists(agentsSrcDir)) {
673
- try {
674
- await fs.ensureDir(agentsDestDir);
675
- const agentFiles = await fs.readdir(agentsSrcDir);
676
- for (const file of agentFiles) {
677
- if (file.endsWith(".md")) {
678
- const srcFile = join(agentsSrcDir, file);
679
- const destFile = join(agentsDestDir, file);
680
- if (force || !await fs.pathExists(destFile)) {
681
- let templateContent = await fs.readFile(srcFile, "utf-8");
682
- templateContent = injectConfigVariables(templateContent, installConfig);
683
- const processedContent = replaceHomePathsInTemplate(templateContent, installDir);
684
- await fs.writeFile(destFile, processedContent, "utf-8");
685
- }
686
- }
687
- }
688
- } catch (error) {
689
- result.errors.push(`Failed to install agents: ${error}`);
690
- result.success = false;
691
- }
692
- }
693
- const promptsTemplateDir = join(templateDir, "prompts");
694
- if (await fs.pathExists(promptsTemplateDir)) {
695
- const modelDirs = ["codex", "gemini", "claude"];
696
- for (const model of modelDirs) {
697
- const srcModelDir = join(promptsTemplateDir, model);
698
- const destModelDir = join(promptsDir, model);
699
- if (await fs.pathExists(srcModelDir)) {
700
- try {
701
- await fs.ensureDir(destModelDir);
702
- const files = await fs.readdir(srcModelDir);
703
- for (const file of files) {
704
- if (file.endsWith(".md")) {
705
- const srcFile = join(srcModelDir, file);
706
- const destFile = join(destModelDir, file);
707
- if (force || !await fs.pathExists(destFile)) {
708
- const templateContent = await fs.readFile(srcFile, "utf-8");
709
- const processedContent = replaceHomePathsInTemplate(templateContent, installDir);
710
- await fs.writeFile(destFile, processedContent, "utf-8");
711
- result.installedPrompts.push(`${model}/${file.replace(".md", "")}`);
712
- }
713
- }
714
- }
715
- } catch (error) {
716
- result.errors.push(`Failed to install ${model} prompts: ${error}`);
717
- result.success = false;
718
- }
719
- }
720
- }
721
- }
722
- const skillsTemplateDir = join(templateDir, "skills");
723
- const skillsDestDir = join(installDir, "skills", "ccg");
724
- if (await fs.pathExists(skillsTemplateDir)) {
725
- try {
726
- const oldSkillsRoot = join(installDir, "skills");
727
- const ccgLegacyItems = ["tools", "orchestration", "SKILL.md", "run_skill.js"];
728
- const needsMigration = !await fs.pathExists(skillsDestDir) && await fs.pathExists(join(oldSkillsRoot, "tools"));
729
- if (needsMigration) {
730
- await fs.ensureDir(skillsDestDir);
731
- for (const item of ccgLegacyItems) {
732
- const oldPath = join(oldSkillsRoot, item);
733
- const newPath = join(skillsDestDir, item);
734
- if (await fs.pathExists(oldPath)) {
735
- await fs.move(oldPath, newPath, { overwrite: true });
736
- }
737
- }
738
- }
739
- await fs.copy(skillsTemplateDir, skillsDestDir, {
740
- overwrite: force,
741
- errorOnExist: false
742
- });
743
- const replacePathsInDir = async (dir) => {
744
- const entries = await fs.readdir(dir, { withFileTypes: true });
745
- for (const entry of entries) {
746
- const fullPath = join(dir, entry.name);
747
- if (entry.isDirectory()) {
748
- await replacePathsInDir(fullPath);
749
- } else if (entry.name.endsWith(".md")) {
750
- const content = await fs.readFile(fullPath, "utf-8");
751
- const processed = replaceHomePathsInTemplate(content, installDir);
752
- if (processed !== content) {
753
- await fs.writeFile(fullPath, processed, "utf-8");
754
- }
755
- }
756
- }
757
- };
758
- await replacePathsInDir(skillsDestDir);
759
- result.installedSkills = await countInstalledSkills(skillsDestDir);
760
- } catch (error) {
761
- result.errors.push(`Failed to install skills: ${error}`);
762
- result.success = false;
763
- }
764
- }
765
- const rulesTemplateDir = join(templateDir, "rules");
766
- const rulesDestDir = join(installDir, "rules");
767
- if (await fs.pathExists(rulesTemplateDir)) {
768
- try {
769
- await fs.ensureDir(rulesDestDir);
770
- const rulesFiles = await fs.readdir(rulesTemplateDir);
771
- for (const file of rulesFiles) {
772
- if (file.endsWith(".md")) {
773
- const srcFile = join(rulesTemplateDir, file);
774
- const destFile = join(rulesDestDir, file);
775
- if (force || !await fs.pathExists(destFile)) {
776
- const templateContent = await fs.readFile(srcFile, "utf-8");
777
- const processedContent = replaceHomePathsInTemplate(templateContent, installDir);
778
- await fs.writeFile(destFile, processedContent, "utf-8");
779
- }
780
- }
781
- }
782
- result.installedRules = true;
783
- } catch (error) {
784
- result.errors.push(`Failed to install rules: ${error}`);
785
- }
786
- }
787
- try {
788
- const binDir = join(installDir, "bin");
789
- await fs.ensureDir(binDir);
790
- const platform = process.platform;
791
- const arch = process.arch;
792
- let binaryName;
793
- if (platform === "darwin") {
794
- binaryName = arch === "arm64" ? "codeagent-wrapper-darwin-arm64" : "codeagent-wrapper-darwin-amd64";
795
- } else if (platform === "linux") {
796
- binaryName = arch === "arm64" ? "codeagent-wrapper-linux-arm64" : "codeagent-wrapper-linux-amd64";
797
- } else if (platform === "win32") {
798
- binaryName = arch === "arm64" ? "codeagent-wrapper-windows-arm64.exe" : "codeagent-wrapper-windows-amd64.exe";
799
- } else {
800
- result.errors.push(`Unsupported platform: ${platform}`);
801
- result.success = false;
802
- result.configPath = commandsDir;
803
- return result;
804
- }
805
- const destBinary = join(binDir, platform === "win32" ? "codeagent-wrapper.exe" : "codeagent-wrapper");
806
- const installed = await downloadBinaryFromRelease(binaryName, destBinary);
807
- if (installed) {
808
- try {
809
- const { execSync } = await import('node:child_process');
810
- execSync(`"${destBinary}" --version`, { stdio: "pipe" });
811
- result.binPath = binDir;
812
- result.binInstalled = true;
813
- } catch (verifyError) {
814
- result.errors.push(`Binary verification failed (non-blocking): ${verifyError}`);
815
- }
816
- } else {
817
- result.errors.push(`Failed to download binary: ${binaryName} from GitHub Release (after 3 attempts). Check network or visit https://github.com/${GITHUB_REPO}/releases/tag/${RELEASE_TAG}`);
818
- }
819
- } catch (error) {
820
- result.errors.push(`Failed to install codeagent-wrapper (non-blocking): ${error}`);
821
- }
822
- result.configPath = commandsDir;
823
- return result;
824
- }
825
- async function uninstallWorkflows(installDir) {
826
- const result = {
827
- success: true,
828
- removedCommands: [],
829
- removedPrompts: [],
830
- removedAgents: [],
831
- removedSkills: [],
832
- removedRules: false,
833
- removedBin: false,
834
- errors: []
835
- };
836
- const commandsDir = join(installDir, "commands", "ccg");
837
- join(installDir, ".ccg", "prompts");
838
- const agentsDir = join(installDir, "agents", "ccg");
839
- const skillsDir = join(installDir, "skills", "ccg");
840
- const rulesDir = join(installDir, "rules");
841
- const binDir = join(installDir, "bin");
842
- const ccgConfigDir = join(installDir, ".ccg");
843
- if (await fs.pathExists(commandsDir)) {
844
- try {
845
- const files = await fs.readdir(commandsDir);
846
- for (const file of files) {
847
- if (file.endsWith(".md")) {
848
- result.removedCommands.push(file.replace(".md", ""));
849
- }
850
- }
851
- await fs.remove(commandsDir);
852
- } catch (error) {
853
- result.errors.push(`Failed to remove commands directory: ${error}`);
854
- result.success = false;
855
- }
856
- }
857
- if (await fs.pathExists(agentsDir)) {
858
- try {
859
- const files = await fs.readdir(agentsDir);
860
- for (const file of files) {
861
- result.removedAgents.push(file.replace(".md", ""));
862
- }
863
- await fs.remove(agentsDir);
864
- } catch (error) {
865
- result.errors.push(`Failed to remove agents directory: ${error}`);
866
- result.success = false;
867
- }
868
- }
869
- if (await fs.pathExists(skillsDir)) {
870
- try {
871
- const collectSkillNames = async (dir, depth) => {
872
- const names = [];
873
- const entries = await fs.readdir(dir, { withFileTypes: true });
874
- for (const entry of entries) {
875
- if (entry.isDirectory()) {
876
- names.push(...await collectSkillNames(join(dir, entry.name), depth + 1));
877
- } else if (entry.name === "SKILL.md" && depth > 0) {
878
- const parts = dir.split("/");
879
- names.push(parts[parts.length - 1]);
880
- }
881
- }
882
- return names;
883
- };
884
- result.removedSkills = await collectSkillNames(skillsDir, 0);
885
- await fs.remove(skillsDir);
886
- } catch (error) {
887
- result.errors.push(`Failed to remove skills: ${error}`);
888
- result.success = false;
889
- }
890
- }
891
- if (await fs.pathExists(rulesDir)) {
892
- try {
893
- const ccgRuleFiles = ["ccg-skills.md", "ccg-grok-search.md"];
894
- for (const ruleFile of ccgRuleFiles) {
895
- const rulePath = join(rulesDir, ruleFile);
896
- if (await fs.pathExists(rulePath)) {
897
- await fs.remove(rulePath);
898
- result.removedRules = true;
899
- }
900
- }
901
- } catch (error) {
902
- result.errors.push(`Failed to remove rules: ${error}`);
903
- }
904
- }
905
- if (await fs.pathExists(binDir)) {
906
- try {
907
- const wrapperName = process.platform === "win32" ? "codeagent-wrapper.exe" : "codeagent-wrapper";
908
- const wrapperPath = join(binDir, wrapperName);
909
- if (await fs.pathExists(wrapperPath)) {
910
- await fs.remove(wrapperPath);
911
- result.removedBin = true;
912
- }
913
- } catch (error) {
914
- result.errors.push(`Failed to remove binary: ${error}`);
915
- result.success = false;
916
- }
917
- }
918
- if (await fs.pathExists(ccgConfigDir)) {
919
- try {
920
- await fs.remove(ccgConfigDir);
921
- result.removedPrompts.push("ALL_PROMPTS_AND_CONFIGS");
922
- } catch (error) {
923
- result.errors.push(`Failed to remove .ccg directory: ${error}`);
924
- }
925
- }
926
- return result;
927
- }
928
- async function uninstallAceTool() {
929
- try {
930
- const existingConfig = await readClaudeCodeConfig();
931
- if (!existingConfig) {
932
- return {
933
- success: true,
934
- message: "No ~/.claude.json found, nothing to remove"
935
- };
936
- }
937
- if (!existingConfig.mcpServers || !existingConfig.mcpServers["ace-tool"]) {
938
- return {
939
- success: true,
940
- message: "ace-tool MCP not found in config"
941
- };
942
- }
943
- await backupClaudeCodeConfig();
944
- delete existingConfig.mcpServers["ace-tool"];
945
- await writeClaudeCodeConfig(existingConfig);
946
- return {
947
- success: true,
948
- message: "ace-tool MCP removed from ~/.claude.json"
949
- };
950
- } catch (error) {
951
- return {
952
- success: false,
953
- message: `Failed to uninstall ace-tool: ${error}`
954
- };
955
- }
956
- }
957
- async function installAceTool(config) {
958
- const { baseUrl, token } = config;
959
- try {
960
- let existingConfig = await readClaudeCodeConfig();
961
- if (!existingConfig) {
962
- existingConfig = { mcpServers: {} };
963
- }
964
- if (existingConfig.mcpServers && Object.keys(existingConfig.mcpServers).length > 0) {
965
- const backupPath = await backupClaudeCodeConfig();
966
- if (backupPath) {
967
- console.log(` \u2713 Backup created: ${backupPath}`);
968
- }
969
- }
970
- const args = ["-y", "ace-tool@latest"];
971
- if (baseUrl) {
972
- args.push("--base-url", baseUrl);
973
- }
974
- if (token) {
975
- args.push("--token", token);
976
- }
977
- const aceToolConfig = buildMcpServerConfig({
978
- type: "stdio",
979
- command: "npx",
980
- args
981
- });
982
- let mergedConfig = mergeMcpServers(existingConfig, {
983
- "ace-tool": aceToolConfig
984
- });
985
- if (isWindows()) {
986
- mergedConfig = fixWindowsMcpConfig(mergedConfig);
987
- console.log(" \u2713 Applied Windows MCP configuration fixes");
988
- }
989
- await writeClaudeCodeConfig(mergedConfig);
990
- return {
991
- success: true,
992
- message: isWindows() ? "ace-tool MCP configured successfully with Windows compatibility" : "ace-tool MCP configured successfully",
993
- configPath: join(homedir(), ".claude.json")
994
- };
995
- } catch (error) {
996
- return {
997
- success: false,
998
- message: `Failed to configure ace-tool: ${error}`
999
- };
1000
- }
1001
- }
1002
- async function installAceToolRs(config) {
1003
- const { baseUrl, token } = config;
1004
- try {
1005
- let existingConfig = await readClaudeCodeConfig();
1006
- if (!existingConfig) {
1007
- existingConfig = { mcpServers: {} };
1008
- }
1009
- if (existingConfig.mcpServers && Object.keys(existingConfig.mcpServers).length > 0) {
1010
- const backupPath = await backupClaudeCodeConfig();
1011
- if (backupPath) {
1012
- console.log(` \u2713 Backup created: ${backupPath}`);
1013
- }
1014
- }
1015
- const args = ["ace-tool-rs"];
1016
- if (baseUrl) {
1017
- args.push("--base-url", baseUrl);
1018
- }
1019
- if (token) {
1020
- args.push("--token", token);
1021
- }
1022
- const aceToolRsConfig = buildMcpServerConfig({
1023
- type: "stdio",
1024
- command: "npx",
1025
- args,
1026
- env: {
1027
- RUST_LOG: "info"
1028
- }
1029
- });
1030
- let mergedConfig = mergeMcpServers(existingConfig, {
1031
- "ace-tool": aceToolRsConfig
1032
- });
1033
- if (isWindows()) {
1034
- mergedConfig = fixWindowsMcpConfig(mergedConfig);
1035
- console.log(" \u2713 Applied Windows MCP configuration fixes");
1036
- }
1037
- await writeClaudeCodeConfig(mergedConfig);
1038
- return {
1039
- success: true,
1040
- message: isWindows() ? "ace-tool-rs MCP configured successfully with Windows compatibility" : "ace-tool-rs MCP configured successfully",
1041
- configPath: join(homedir(), ".claude.json")
1042
- };
1043
- } catch (error) {
1044
- return {
1045
- success: false,
1046
- message: `Failed to configure ace-tool-rs: ${error}`
1047
- };
1048
- }
350
+ async function installAceToolRs(config) {
351
+ const { baseUrl, token } = config;
352
+ const args = ["ace-tool-rs"];
353
+ if (baseUrl) args.push("--base-url", baseUrl);
354
+ if (token) args.push("--token", token);
355
+ const serverConfig = buildMcpServerConfig({
356
+ type: "stdio",
357
+ command: "npx",
358
+ args,
359
+ env: { RUST_LOG: "info" }
360
+ });
361
+ return configureMcpInClaude("ace-tool", serverConfig, "ace-tool-rs MCP");
1049
362
  }
1050
363
  async function installContextWeaver(config) {
1051
364
  const { siliconflowApiKey } = config;
@@ -1085,122 +398,151 @@ RERANK_MODEL=Qwen/Qwen3-Reranker-8B
1085
398
  RERANK_TOP_N=20
1086
399
  `;
1087
400
  await fs.writeFile(join(contextWeaverDir, ".env"), envContent, "utf-8");
1088
- let existingConfig = await readClaudeCodeConfig();
1089
- if (!existingConfig) {
1090
- existingConfig = { mcpServers: {} };
1091
- }
1092
- if (existingConfig.mcpServers && Object.keys(existingConfig.mcpServers).length > 0) {
1093
- const backupPath = await backupClaudeCodeConfig();
1094
- if (backupPath) {
1095
- console.log(` \u2713 Backup created: ${backupPath}`);
1096
- }
1097
- }
1098
- const contextWeaverMcpConfig = buildMcpServerConfig({
401
+ const serverConfig = buildMcpServerConfig({
1099
402
  type: "stdio",
1100
403
  command: "contextweaver",
1101
404
  args: ["mcp"]
1102
405
  });
1103
- let mergedConfig = mergeMcpServers(existingConfig, {
1104
- contextweaver: contextWeaverMcpConfig
1105
- });
1106
- if (isWindows()) {
1107
- mergedConfig = fixWindowsMcpConfig(mergedConfig);
1108
- }
1109
- await writeClaudeCodeConfig(mergedConfig);
1110
- return {
1111
- success: true,
1112
- message: "ContextWeaver MCP configured successfully",
1113
- configPath: join(homedir(), ".claude.json")
1114
- };
406
+ return await configureMcpInClaude("contextweaver", serverConfig, "ContextWeaver MCP");
1115
407
  } catch (error) {
1116
- return {
1117
- success: false,
1118
- message: `Failed to configure ContextWeaver: ${error}`
1119
- };
408
+ return { success: false, message: `Failed to configure ContextWeaver: ${error}` };
1120
409
  }
1121
410
  }
1122
- async function uninstallContextWeaver() {
411
+ function uninstallContextWeaver() {
412
+ return uninstallMcpServer("contextweaver");
413
+ }
414
+ async function installFastContext(config) {
415
+ const { apiKey, includeSnippets } = config;
416
+ const env = {};
417
+ if (apiKey) env.WINDSURF_API_KEY = apiKey;
418
+ if (includeSnippets) env.FC_INCLUDE_SNIPPETS = "true";
419
+ const serverConfig = buildMcpServerConfig({
420
+ type: "stdio",
421
+ command: "npx",
422
+ args: ["-y", "--prefer-online", "fast-context-mcp@latest"],
423
+ ...Object.keys(env).length > 0 ? { env } : {}
424
+ });
425
+ return configureMcpInClaude("fast-context", serverConfig, "fast-context MCP");
426
+ }
427
+ function uninstallFastContext() {
428
+ return uninstallMcpServer("fast-context");
429
+ }
430
+ async function installMcpServer(id, command, args, env = {}) {
431
+ const serverConfig = buildMcpServerConfig({ type: "stdio", command, args, env });
432
+ return configureMcpInClaude(id, serverConfig, id);
433
+ }
434
+ async function uninstallMcpServer(id) {
1123
435
  try {
1124
436
  const existingConfig = await readClaudeCodeConfig();
1125
- if (existingConfig?.mcpServers?.contextweaver) {
1126
- delete existingConfig.mcpServers.contextweaver;
437
+ if (existingConfig?.mcpServers?.[id]) {
438
+ delete existingConfig.mcpServers[id];
1127
439
  await writeClaudeCodeConfig(existingConfig);
1128
440
  }
1129
- return {
1130
- success: true,
1131
- message: "ContextWeaver MCP uninstalled successfully"
1132
- };
441
+ return { success: true, message: `${id} MCP uninstalled successfully` };
1133
442
  } catch (error) {
1134
- return {
1135
- success: false,
1136
- message: `Failed to uninstall ContextWeaver: ${error}`
1137
- };
443
+ return { success: false, message: `Failed to uninstall ${id}: ${error}` };
1138
444
  }
1139
445
  }
1140
- async function installFastContext(config) {
1141
- const { apiKey, includeSnippets } = config;
1142
- try {
1143
- let existingConfig = await readClaudeCodeConfig();
1144
- if (!existingConfig) {
1145
- existingConfig = { mcpServers: {} };
446
+ const CCG_MCP_IDS = /* @__PURE__ */ new Set([
447
+ "grok-search",
448
+ "context7",
449
+ "ace-tool",
450
+ "ace-tool-rs",
451
+ "contextweaver",
452
+ "fast-context"
453
+ ]);
454
+ async function getCcgMcpServersFromClaude() {
455
+ const claudeConfig = await readClaudeCodeConfig();
456
+ const claudeMcpServers = claudeConfig?.mcpServers || {};
457
+ const serversToSync = {};
458
+ for (const [id, config] of Object.entries(claudeMcpServers)) {
459
+ if (CCG_MCP_IDS.has(id) && config) {
460
+ serversToSync[id] = config;
1146
461
  }
1147
- if (existingConfig.mcpServers && Object.keys(existingConfig.mcpServers).length > 0) {
1148
- const backupPath = await backupClaudeCodeConfig();
1149
- if (backupPath) {
1150
- console.log(` \u2713 Backup created: ${backupPath}`);
1151
- }
462
+ }
463
+ return serversToSync;
464
+ }
465
+ function mirrorCcgServers(serversToSync, targetServers) {
466
+ const synced = [];
467
+ const removed = [];
468
+ for (const [id, claudeServer] of Object.entries(serversToSync)) {
469
+ targetServers[id] = claudeServer;
470
+ synced.push(id);
471
+ }
472
+ for (const id of CCG_MCP_IDS) {
473
+ if (!serversToSync[id] && targetServers[id]) {
474
+ delete targetServers[id];
475
+ removed.push(id);
476
+ }
477
+ }
478
+ return { synced, removed };
479
+ }
480
+ function formatSyncMessage(target, synced, removed) {
481
+ const parts = [];
482
+ if (synced.length > 0) parts.push(`synced: ${synced.join(", ")}`);
483
+ if (removed.length > 0) parts.push(`removed: ${removed.join(", ")}`);
484
+ return `${target} MCP mirror complete (${parts.join("; ")})`;
485
+ }
486
+ async function syncMcpToCodex() {
487
+ try {
488
+ const serversToSync = await getCcgMcpServersFromClaude();
489
+ const codexConfigDir = join(homedir(), ".codex");
490
+ const codexConfigPath = join(codexConfigDir, "config.toml");
491
+ await fs.ensureDir(codexConfigDir);
492
+ let codexConfig = {};
493
+ if (await fs.pathExists(codexConfigPath)) {
494
+ const content = await fs.readFile(codexConfigPath, "utf-8");
495
+ codexConfig = parse(content);
1152
496
  }
1153
- const env = {};
1154
- if (apiKey) {
1155
- env.WINDSURF_API_KEY = apiKey;
497
+ if (!codexConfig.mcp_servers) {
498
+ codexConfig.mcp_servers = {};
1156
499
  }
1157
- if (includeSnippets) {
1158
- env.FC_INCLUDE_SNIPPETS = "true";
500
+ const codexServersToSync = {};
501
+ for (const [id, server] of Object.entries(serversToSync)) {
502
+ const entry = {};
503
+ for (const [key, value] of Object.entries(server)) {
504
+ if (value !== null && value !== void 0) {
505
+ entry[key] = value;
506
+ }
507
+ }
508
+ codexServersToSync[id] = entry;
1159
509
  }
1160
- const fastContextConfig = buildMcpServerConfig({
1161
- type: "stdio",
1162
- command: "npx",
1163
- args: ["-y", "--prefer-online", "fast-context-mcp@latest"],
1164
- ...Object.keys(env).length > 0 ? { env } : {}
1165
- });
1166
- let mergedConfig = mergeMcpServers(existingConfig, {
1167
- "fast-context": fastContextConfig
1168
- });
1169
- if (isWindows()) {
1170
- mergedConfig = fixWindowsMcpConfig(mergedConfig);
1171
- console.log(" \u2713 Applied Windows MCP configuration fixes");
510
+ const { synced, removed } = mirrorCcgServers(codexServersToSync, codexConfig.mcp_servers);
511
+ if (synced.length === 0 && removed.length === 0) {
512
+ return { success: true, message: "No CCG MCP servers to sync or remove", synced: [], removed: [] };
1172
513
  }
1173
- await writeClaudeCodeConfig(mergedConfig);
1174
- return {
1175
- success: true,
1176
- message: isWindows() ? "fast-context MCP configured successfully with Windows compatibility" : "fast-context MCP configured successfully",
1177
- configPath: join(homedir(), ".claude.json")
1178
- };
514
+ const tmpPath = `${codexConfigPath}.tmp`;
515
+ await fs.writeFile(tmpPath, stringify(codexConfig), "utf-8");
516
+ await fs.rename(tmpPath, codexConfigPath);
517
+ return { success: true, message: formatSyncMessage("Codex", synced, removed), synced, removed };
1179
518
  } catch (error) {
1180
- return {
1181
- success: false,
1182
- message: `Failed to configure fast-context: ${error}`
1183
- };
519
+ return { success: false, message: `Failed to sync MCP to Codex: ${error}`, synced: [], removed: [] };
1184
520
  }
1185
521
  }
1186
- async function uninstallFastContext() {
522
+ async function syncMcpToGemini() {
1187
523
  try {
1188
- const existingConfig = await readClaudeCodeConfig();
1189
- if (existingConfig?.mcpServers?.["fast-context"]) {
1190
- delete existingConfig.mcpServers["fast-context"];
1191
- await writeClaudeCodeConfig(existingConfig);
524
+ const serversToSync = await getCcgMcpServersFromClaude();
525
+ const geminiDir = join(homedir(), ".gemini");
526
+ const geminiSettingsPath = join(geminiDir, "settings.json");
527
+ await fs.ensureDir(geminiDir);
528
+ let geminiSettings = {};
529
+ if (await fs.pathExists(geminiSettingsPath)) {
530
+ geminiSettings = await fs.readJSON(geminiSettingsPath);
1192
531
  }
1193
- return {
1194
- success: true,
1195
- message: "fast-context MCP uninstalled successfully"
1196
- };
532
+ if (!geminiSettings.mcpServers) {
533
+ geminiSettings.mcpServers = {};
534
+ }
535
+ const { synced, removed } = mirrorCcgServers(serversToSync, geminiSettings.mcpServers);
536
+ if (synced.length === 0 && removed.length === 0) {
537
+ return { success: true, message: "No CCG MCP servers to sync to Gemini", synced: [], removed: [] };
538
+ }
539
+ await fs.writeJSON(geminiSettingsPath, geminiSettings, { spaces: 2 });
540
+ return { success: true, message: formatSyncMessage("Gemini", synced, removed), synced, removed };
1197
541
  } catch (error) {
1198
- return {
1199
- success: false,
1200
- message: `Failed to uninstall fast-context: ${error}`
1201
- };
542
+ return { success: false, message: `Failed to sync MCP to Gemini: ${error}`, synced: [], removed: [] };
1202
543
  }
1203
544
  }
545
+
1204
546
  const FAST_CONTEXT_PROMPT = `# fast-context MCP \u5DE5\u5177\u4F7F\u7528\u6307\u5357
1205
547
 
1206
548
  ## \u6838\u5FC3\u539F\u5219
@@ -1248,199 +590,471 @@ ${markerStart}
1248
590
  ${FAST_CONTEXT_PROMPT}
1249
591
  ${markerEnd}
1250
592
  `;
1251
- const markerRegex = new RegExp(
1252
- `\\n?${markerStart.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${markerEnd.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`
1253
- );
1254
- async function injectIntoFile(filePath) {
1255
- const dir = dirname(filePath);
1256
- await fs.ensureDir(dir);
1257
- if (await fs.pathExists(filePath)) {
1258
- let content = await fs.readFile(filePath, "utf-8");
1259
- if (content.includes(markerStart)) {
1260
- content = content.replace(markerRegex, markedBlock);
1261
- } else {
1262
- content += markedBlock;
593
+ const markerRegex = new RegExp(
594
+ `\\n?${markerStart.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${markerEnd.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`
595
+ );
596
+ async function injectIntoFile(filePath) {
597
+ const dir = dirname(filePath);
598
+ await fs.ensureDir(dir);
599
+ if (await fs.pathExists(filePath)) {
600
+ let content = await fs.readFile(filePath, "utf-8");
601
+ if (content.includes(markerStart)) {
602
+ content = content.replace(markerRegex, markedBlock);
603
+ } else {
604
+ content += markedBlock;
605
+ }
606
+ await fs.writeFile(filePath, content, "utf-8");
607
+ } else {
608
+ await fs.writeFile(filePath, markedBlock.trim() + "\n", "utf-8");
609
+ }
610
+ }
611
+ const rulesDir = join(homedir(), ".claude", "rules");
612
+ await fs.ensureDir(rulesDir);
613
+ await fs.writeFile(join(rulesDir, "ccg-fast-context.md"), FAST_CONTEXT_PROMPT, "utf-8");
614
+ await injectIntoFile(join(homedir(), ".codex", "AGENTS.md"));
615
+ await injectIntoFile(join(homedir(), ".gemini", "GEMINI.md"));
616
+ }
617
+ async function removeFastContextPrompt() {
618
+ const markerRegex = new RegExp(
619
+ `\\n?${FC_MARKER_START.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${FC_MARKER_END.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`
620
+ );
621
+ async function removeFromFile(filePath) {
622
+ if (await fs.pathExists(filePath)) {
623
+ let content = await fs.readFile(filePath, "utf-8");
624
+ if (content.includes(FC_MARKER_START)) {
625
+ content = content.replace(markerRegex, "");
626
+ await fs.writeFile(filePath, content, "utf-8");
627
+ }
628
+ }
629
+ }
630
+ const rulePath = join(homedir(), ".claude", "rules", "ccg-fast-context.md");
631
+ if (await fs.pathExists(rulePath)) {
632
+ await fs.remove(rulePath);
633
+ }
634
+ await removeFromFile(join(homedir(), ".codex", "AGENTS.md"));
635
+ await removeFromFile(join(homedir(), ".gemini", "GEMINI.md"));
636
+ }
637
+
638
+ const GITHUB_REPO = "fengshao1227/ccg-workflow";
639
+ const RELEASE_TAG = "preset";
640
+ const BINARY_DOWNLOAD_URL = `https://github.com/${GITHUB_REPO}/releases/download/${RELEASE_TAG}`;
641
+ async function downloadBinaryFromRelease(binaryName, destPath) {
642
+ const url = `${BINARY_DOWNLOAD_URL}/${binaryName}`;
643
+ const MAX_ATTEMPTS = 3;
644
+ const TIMEOUT_MS = 6e4;
645
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
646
+ try {
647
+ const controller = new AbortController();
648
+ const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
649
+ const response = await fetch(url, { redirect: "follow", signal: controller.signal });
650
+ if (!response.ok) {
651
+ clearTimeout(timer);
652
+ if (attempt < MAX_ATTEMPTS) {
653
+ await new Promise((resolve) => setTimeout(resolve, attempt * 2e3));
654
+ continue;
655
+ }
656
+ return false;
657
+ }
658
+ const buffer = Buffer.from(await response.arrayBuffer());
659
+ clearTimeout(timer);
660
+ await fs.writeFile(destPath, buffer);
661
+ if (process.platform !== "win32") {
662
+ await fs.chmod(destPath, 493);
663
+ }
664
+ return true;
665
+ } catch {
666
+ if (attempt < MAX_ATTEMPTS) {
667
+ await new Promise((resolve) => setTimeout(resolve, attempt * 2e3));
668
+ continue;
669
+ }
670
+ return false;
671
+ }
672
+ }
673
+ return false;
674
+ }
675
+ async function copyMdTemplates(ctx, srcDir, destDir, options = {}) {
676
+ const installed = [];
677
+ if (!await fs.pathExists(srcDir)) return installed;
678
+ await fs.ensureDir(destDir);
679
+ const files = await fs.readdir(srcDir);
680
+ for (const file of files) {
681
+ if (!file.endsWith(".md")) continue;
682
+ const destFile = join(destDir, file);
683
+ if (ctx.force || !await fs.pathExists(destFile)) {
684
+ let content = await fs.readFile(join(srcDir, file), "utf-8");
685
+ if (options.inject) content = injectConfigVariables(content, ctx.config);
686
+ content = replaceHomePathsInTemplate(content, ctx.installDir);
687
+ await fs.writeFile(destFile, content, "utf-8");
688
+ installed.push(file.replace(".md", ""));
689
+ }
690
+ }
691
+ return installed;
692
+ }
693
+ async function installCommandFiles(ctx, workflowIds) {
694
+ const commandsDir = join(ctx.installDir, "commands", "ccg");
695
+ for (const workflowId of workflowIds) {
696
+ const workflow = getWorkflowById(workflowId);
697
+ if (!workflow) {
698
+ ctx.result.errors.push(`Unknown workflow: ${workflowId}`);
699
+ continue;
700
+ }
701
+ for (const cmd of workflow.commands) {
702
+ const srcFile = join(ctx.templateDir, "commands", `${cmd}.md`);
703
+ const destFile = join(commandsDir, `${cmd}.md`);
704
+ try {
705
+ if (await fs.pathExists(srcFile)) {
706
+ if (ctx.force || !await fs.pathExists(destFile)) {
707
+ let content = await fs.readFile(srcFile, "utf-8");
708
+ content = injectConfigVariables(content, ctx.config);
709
+ content = replaceHomePathsInTemplate(content, ctx.installDir);
710
+ await fs.writeFile(destFile, content, "utf-8");
711
+ ctx.result.installedCommands.push(cmd);
712
+ }
713
+ } else {
714
+ const placeholder = `---
715
+ description: "${workflow.descriptionEn}"
716
+ ---
717
+
718
+ # /ccg:${cmd}
719
+
720
+ ${workflow.description}
721
+
722
+ > This command is part of CCG multi-model collaboration system.
723
+ `;
724
+ await fs.writeFile(destFile, placeholder, "utf-8");
725
+ ctx.result.installedCommands.push(cmd);
726
+ }
727
+ } catch (error) {
728
+ ctx.result.errors.push(`Failed to install ${cmd}: ${error}`);
729
+ ctx.result.success = false;
1263
730
  }
1264
- await fs.writeFile(filePath, content, "utf-8");
1265
- } else {
1266
- await fs.writeFile(filePath, markedBlock.trim() + "\n", "utf-8");
1267
731
  }
1268
732
  }
1269
- const rulesDir = join(homedir(), ".claude", "rules");
1270
- await fs.ensureDir(rulesDir);
1271
- await fs.writeFile(join(rulesDir, "ccg-fast-context.md"), FAST_CONTEXT_PROMPT, "utf-8");
1272
- await injectIntoFile(join(homedir(), ".codex", "AGENTS.md"));
1273
- await injectIntoFile(join(homedir(), ".gemini", "GEMINI.md"));
1274
733
  }
1275
- async function removeFastContextPrompt() {
1276
- const markerRegex = new RegExp(
1277
- `\\n?${FC_MARKER_START.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${FC_MARKER_END.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`
1278
- );
1279
- async function removeFromFile(filePath) {
1280
- if (await fs.pathExists(filePath)) {
1281
- let content = await fs.readFile(filePath, "utf-8");
1282
- if (content.includes(FC_MARKER_START)) {
1283
- content = content.replace(markerRegex, "");
1284
- await fs.writeFile(filePath, content, "utf-8");
734
+ async function installAgentFiles(ctx) {
735
+ try {
736
+ await copyMdTemplates(
737
+ ctx,
738
+ join(ctx.templateDir, "commands", "agents"),
739
+ join(ctx.installDir, "agents", "ccg"),
740
+ { inject: true }
741
+ );
742
+ } catch (error) {
743
+ ctx.result.errors.push(`Failed to install agents: ${error}`);
744
+ ctx.result.success = false;
745
+ }
746
+ }
747
+ async function installPromptFiles(ctx) {
748
+ const promptsTemplateDir = join(ctx.templateDir, "prompts");
749
+ const promptsDir = join(ctx.installDir, ".ccg", "prompts");
750
+ if (!await fs.pathExists(promptsTemplateDir)) return;
751
+ for (const model of ["codex", "gemini", "claude"]) {
752
+ try {
753
+ const installed = await copyMdTemplates(
754
+ ctx,
755
+ join(promptsTemplateDir, model),
756
+ join(promptsDir, model)
757
+ );
758
+ for (const name of installed) {
759
+ ctx.result.installedPrompts.push(`${model}/${name}`);
1285
760
  }
761
+ } catch (error) {
762
+ ctx.result.errors.push(`Failed to install ${model} prompts: ${error}`);
763
+ ctx.result.success = false;
1286
764
  }
1287
765
  }
1288
- const rulePath = join(homedir(), ".claude", "rules", "ccg-fast-context.md");
1289
- if (await fs.pathExists(rulePath)) {
1290
- await fs.remove(rulePath);
766
+ }
767
+ async function collectSkillNames(dir, depth = 0) {
768
+ const names = [];
769
+ try {
770
+ const entries = await fs.readdir(dir, { withFileTypes: true });
771
+ for (const entry of entries) {
772
+ if (entry.isDirectory()) {
773
+ names.push(...await collectSkillNames(join(dir, entry.name), depth + 1));
774
+ } else if (entry.name === "SKILL.md" && depth > 0) {
775
+ names.push(basename(dir));
776
+ }
777
+ }
778
+ } catch {
1291
779
  }
1292
- await removeFromFile(join(homedir(), ".codex", "AGENTS.md"));
1293
- await removeFromFile(join(homedir(), ".gemini", "GEMINI.md"));
780
+ return names;
1294
781
  }
1295
- async function installMcpServer(id, command, args, env = {}) {
782
+ async function removeDirCollectMdNames(dir) {
783
+ if (!await fs.pathExists(dir)) return [];
784
+ const files = await fs.readdir(dir);
785
+ const names = files.filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
786
+ await fs.remove(dir);
787
+ return names;
788
+ }
789
+ async function installSkillFiles(ctx) {
790
+ const skillsTemplateDir = join(ctx.templateDir, "skills");
791
+ const skillsDestDir = join(ctx.installDir, "skills", "ccg");
792
+ if (!await fs.pathExists(skillsTemplateDir)) return;
1296
793
  try {
1297
- await backupClaudeCodeConfig();
1298
- const existingConfig = await readClaudeCodeConfig();
1299
- const serverConfig = buildMcpServerConfig({ type: "stdio", command, args, env });
1300
- let mergedConfig = mergeMcpServers(existingConfig, { [id]: serverConfig });
1301
- if (isWindows()) {
1302
- mergedConfig = fixWindowsMcpConfig(mergedConfig);
794
+ const oldSkillsRoot = join(ctx.installDir, "skills");
795
+ const ccgLegacyItems = ["tools", "orchestration", "SKILL.md", "run_skill.js"];
796
+ const needsMigration = !await fs.pathExists(skillsDestDir) && await fs.pathExists(join(oldSkillsRoot, "tools"));
797
+ if (needsMigration) {
798
+ await fs.ensureDir(skillsDestDir);
799
+ for (const item of ccgLegacyItems) {
800
+ const oldPath = join(oldSkillsRoot, item);
801
+ const newPath = join(skillsDestDir, item);
802
+ if (await fs.pathExists(oldPath)) {
803
+ await fs.move(oldPath, newPath, { overwrite: true });
804
+ }
805
+ }
1303
806
  }
1304
- await writeClaudeCodeConfig(mergedConfig);
1305
- return { success: true, message: `${id} MCP installed successfully` };
807
+ await fs.copy(skillsTemplateDir, skillsDestDir, {
808
+ overwrite: ctx.force,
809
+ errorOnExist: false
810
+ });
811
+ const replacePathsInDir = async (dir) => {
812
+ const entries = await fs.readdir(dir, { withFileTypes: true });
813
+ for (const entry of entries) {
814
+ const fullPath = join(dir, entry.name);
815
+ if (entry.isDirectory()) {
816
+ await replacePathsInDir(fullPath);
817
+ } else if (entry.name.endsWith(".md")) {
818
+ const content = await fs.readFile(fullPath, "utf-8");
819
+ const processed = replaceHomePathsInTemplate(content, ctx.installDir);
820
+ if (processed !== content) {
821
+ await fs.writeFile(fullPath, processed, "utf-8");
822
+ }
823
+ }
824
+ }
825
+ };
826
+ await replacePathsInDir(skillsDestDir);
827
+ ctx.result.installedSkills = (await collectSkillNames(skillsDestDir)).length;
1306
828
  } catch (error) {
1307
- return { success: false, message: `Failed to install ${id}: ${error}` };
829
+ ctx.result.errors.push(`Failed to install skills: ${error}`);
830
+ ctx.result.success = false;
1308
831
  }
1309
832
  }
1310
- async function uninstallMcpServer(id) {
833
+ async function installRuleFiles(ctx) {
1311
834
  try {
1312
- const existingConfig = await readClaudeCodeConfig();
1313
- if (existingConfig?.mcpServers?.[id]) {
1314
- delete existingConfig.mcpServers[id];
1315
- await writeClaudeCodeConfig(existingConfig);
1316
- }
1317
- return { success: true, message: `${id} MCP uninstalled successfully` };
835
+ const installed = await copyMdTemplates(
836
+ ctx,
837
+ join(ctx.templateDir, "rules"),
838
+ join(ctx.installDir, "rules")
839
+ );
840
+ if (installed.length > 0) ctx.result.installedRules = true;
1318
841
  } catch (error) {
1319
- return { success: false, message: `Failed to uninstall ${id}: ${error}` };
842
+ ctx.result.errors.push(`Failed to install rules: ${error}`);
1320
843
  }
1321
844
  }
1322
- const CCG_MCP_IDS = /* @__PURE__ */ new Set([
1323
- "grok-search",
1324
- "context7",
1325
- "ace-tool",
1326
- "ace-tool-rs",
1327
- "contextweaver",
1328
- "fast-context"
1329
- ]);
1330
- async function syncMcpToCodex() {
1331
- const synced = [];
1332
- const removed = [];
845
+ function getBinaryName() {
846
+ const osMap = { darwin: "darwin", linux: "linux", win32: "windows" };
847
+ const os = osMap[process.platform];
848
+ if (!os) return null;
849
+ const arch = process.arch === "arm64" ? "arm64" : "amd64";
850
+ const ext = process.platform === "win32" ? ".exe" : "";
851
+ return `codeagent-wrapper-${os}-${arch}${ext}`;
852
+ }
853
+ async function verifyBinary(installDir) {
854
+ const binDir = join(installDir, "bin");
855
+ const wrapperName = process.platform === "win32" ? "codeagent-wrapper.exe" : "codeagent-wrapper";
856
+ const wrapperPath = join(binDir, wrapperName);
857
+ if (!await fs.pathExists(wrapperPath)) return false;
1333
858
  try {
1334
- const claudeConfig = await readClaudeCodeConfig();
1335
- const claudeMcpServers = claudeConfig?.mcpServers || {};
1336
- const serversToSync = {};
1337
- for (const [id, config] of Object.entries(claudeMcpServers)) {
1338
- if (CCG_MCP_IDS.has(id) && config) {
1339
- serversToSync[id] = config;
1340
- }
1341
- }
1342
- const codexConfigDir = join(homedir(), ".codex");
1343
- const codexConfigPath = join(codexConfigDir, "config.toml");
1344
- await fs.ensureDir(codexConfigDir);
1345
- let codexConfig = {};
1346
- if (await fs.pathExists(codexConfigPath)) {
1347
- const content = await fs.readFile(codexConfigPath, "utf-8");
1348
- codexConfig = parse(content);
1349
- }
1350
- if (!codexConfig.mcp_servers) {
1351
- codexConfig.mcp_servers = {};
859
+ const { execSync } = await import('node:child_process');
860
+ execSync(`"${wrapperPath}" --version`, { stdio: "pipe" });
861
+ return true;
862
+ } catch {
863
+ return false;
864
+ }
865
+ }
866
+ function showBinaryDownloadWarning(binDir) {
867
+ const binaryExt = process.platform === "win32" ? ".exe" : "";
868
+ const platformLabel = process.platform === "darwin" ? process.arch === "arm64" ? "darwin-arm64" : "darwin-amd64" : process.platform === "linux" ? process.arch === "arm64" ? "linux-arm64" : "linux-amd64" : process.arch === "arm64" ? "windows-arm64" : "windows-amd64";
869
+ const binaryFileName = `codeagent-wrapper-${platformLabel}${binaryExt}`;
870
+ const destFileName = `codeagent-wrapper${binaryExt}`;
871
+ const releaseUrl = `https://github.com/${GITHUB_REPO}/releases/tag/${RELEASE_TAG}`;
872
+ console.log();
873
+ console.log(ansis.red.bold(` \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557`));
874
+ console.log(ansis.red.bold(` \u2551 \u26A0 codeagent-wrapper \u4E0B\u8F7D\u5931\u8D25 \u2551`));
875
+ console.log(ansis.red.bold(` \u2551 Binary download failed (network issue) \u2551`));
876
+ console.log(ansis.red.bold(` \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D`));
877
+ console.log();
878
+ console.log(ansis.yellow(` \u591A\u6A21\u578B\u534F\u4F5C\u547D\u4EE4 (/ccg:workflow, /ccg:plan \u7B49) \u9700\u8981\u6B64\u6587\u4EF6\u624D\u80FD\u5DE5\u4F5C\u3002`));
879
+ console.log(ansis.yellow(` Multi-model commands require this binary to work.`));
880
+ console.log();
881
+ console.log(ansis.cyan(` \u624B\u52A8\u4FEE\u590D / Manual fix:`));
882
+ console.log();
883
+ console.log(ansis.white(` 1. \u4E0B\u8F7D / Download:`));
884
+ console.log(ansis.cyan(` ${releaseUrl}`));
885
+ console.log(ansis.gray(` \u2192 \u627E\u5230 ${ansis.white(binaryFileName)} \u5E76\u4E0B\u8F7D`));
886
+ console.log();
887
+ console.log(ansis.white(` 2. \u653E\u5230 / Place at:`));
888
+ console.log(ansis.cyan(` ${binDir}/${destFileName}`));
889
+ console.log();
890
+ if (process.platform !== "win32") {
891
+ console.log(ansis.white(` 3. \u52A0\u6743\u9650 / Make executable:`));
892
+ console.log(ansis.cyan(` chmod +x "${binDir}/${destFileName}"`));
893
+ console.log();
894
+ }
895
+ console.log(ansis.white(` \u6216\u91CD\u65B0\u5B89\u88C5 / Or re-install:`));
896
+ console.log(ansis.cyan(` npx ccg-workflow@latest`));
897
+ console.log();
898
+ }
899
+ async function installBinaryFile(ctx) {
900
+ try {
901
+ const binDir = join(ctx.installDir, "bin");
902
+ await fs.ensureDir(binDir);
903
+ const binaryName = getBinaryName();
904
+ if (!binaryName) {
905
+ ctx.result.errors.push(`Unsupported platform: ${process.platform}`);
906
+ ctx.result.success = false;
907
+ return;
1352
908
  }
1353
- for (const [id, claudeServer] of Object.entries(serversToSync)) {
1354
- const server = claudeServer;
1355
- const codexEntry = {};
1356
- for (const [key, value] of Object.entries(server)) {
1357
- if (value !== null && value !== void 0) {
1358
- codexEntry[key] = value;
1359
- }
909
+ const destBinary = join(binDir, process.platform === "win32" ? "codeagent-wrapper.exe" : "codeagent-wrapper");
910
+ if (await fs.pathExists(destBinary)) {
911
+ try {
912
+ const { execSync } = await import('node:child_process');
913
+ execSync(`"${destBinary}" --version`, { stdio: "pipe" });
914
+ ctx.result.binPath = binDir;
915
+ ctx.result.binInstalled = true;
916
+ return;
917
+ } catch {
1360
918
  }
1361
- codexConfig.mcp_servers[id] = codexEntry;
1362
- synced.push(id);
1363
919
  }
1364
- for (const id of CCG_MCP_IDS) {
1365
- if (!serversToSync[id] && codexConfig.mcp_servers[id]) {
1366
- delete codexConfig.mcp_servers[id];
1367
- removed.push(id);
920
+ const installed = await downloadBinaryFromRelease(binaryName, destBinary);
921
+ if (installed) {
922
+ try {
923
+ const { execSync } = await import('node:child_process');
924
+ execSync(`"${destBinary}" --version`, { stdio: "pipe" });
925
+ ctx.result.binPath = binDir;
926
+ ctx.result.binInstalled = true;
927
+ } catch (verifyError) {
928
+ ctx.result.errors.push(`Binary verification failed (non-blocking): ${verifyError}`);
1368
929
  }
930
+ } else {
931
+ ctx.result.errors.push(`Failed to download binary: ${binaryName} from GitHub Release (after 3 attempts). Check network or visit https://github.com/${GITHUB_REPO}/releases/tag/${RELEASE_TAG}`);
1369
932
  }
1370
- if (synced.length === 0 && removed.length === 0) {
1371
- return {
1372
- success: true,
1373
- message: "No CCG MCP servers to sync or remove",
1374
- synced: [],
1375
- removed: []
1376
- };
1377
- }
1378
- const tmpPath = `${codexConfigPath}.tmp`;
1379
- await fs.writeFile(tmpPath, stringify(codexConfig), "utf-8");
1380
- await fs.rename(tmpPath, codexConfigPath);
1381
- const parts = [];
1382
- if (synced.length > 0) parts.push(`synced: ${synced.join(", ")}`);
1383
- if (removed.length > 0) parts.push(`removed: ${removed.join(", ")}`);
1384
- return {
1385
- success: true,
1386
- message: `Codex MCP mirror complete (${parts.join("; ")})`,
1387
- synced,
1388
- removed
1389
- };
1390
933
  } catch (error) {
1391
- return {
1392
- success: false,
1393
- message: `Failed to sync MCP to Codex: ${error}`,
1394
- synced,
1395
- removed
1396
- };
934
+ ctx.result.errors.push(`Failed to install codeagent-wrapper (non-blocking): ${error}`);
1397
935
  }
1398
936
  }
1399
- async function syncMcpToGemini() {
1400
- const synced = [];
1401
- const removed = [];
1402
- try {
1403
- const claudeConfig = await readClaudeCodeConfig();
1404
- const claudeMcpServers = claudeConfig?.mcpServers || {};
1405
- const serversToSync = {};
1406
- for (const [id, config] of Object.entries(claudeMcpServers)) {
1407
- if (CCG_MCP_IDS.has(id) && config) {
1408
- serversToSync[id] = config;
1409
- }
1410
- }
1411
- const geminiDir = join(homedir(), ".gemini");
1412
- const geminiSettingsPath = join(geminiDir, "settings.json");
1413
- await fs.ensureDir(geminiDir);
1414
- let geminiSettings = {};
1415
- if (await fs.pathExists(geminiSettingsPath)) {
1416
- geminiSettings = await fs.readJSON(geminiSettingsPath);
937
+ async function installWorkflows(workflowIds, installDir, force = false, config) {
938
+ const ctx = {
939
+ installDir,
940
+ force,
941
+ config: {
942
+ routing: config?.routing || {
943
+ mode: "smart",
944
+ frontend: { models: ["gemini"], primary: "gemini" },
945
+ backend: { models: ["codex"], primary: "codex" },
946
+ review: { models: ["codex", "gemini"] }
947
+ },
948
+ liteMode: config?.liteMode || false,
949
+ mcpProvider: config?.mcpProvider || "ace-tool"
950
+ },
951
+ templateDir: join(PACKAGE_ROOT$1, "templates"),
952
+ result: {
953
+ success: true,
954
+ installedCommands: [],
955
+ installedPrompts: [],
956
+ errors: [],
957
+ configPath: ""
1417
958
  }
1418
- if (!geminiSettings.mcpServers) {
1419
- geminiSettings.mcpServers = {};
959
+ };
960
+ await fs.ensureDir(join(installDir, "commands", "ccg"));
961
+ await fs.ensureDir(join(installDir, ".ccg"));
962
+ await fs.ensureDir(join(installDir, ".ccg", "prompts"));
963
+ await installCommandFiles(ctx, workflowIds);
964
+ await installAgentFiles(ctx);
965
+ await installPromptFiles(ctx);
966
+ await installSkillFiles(ctx);
967
+ await installRuleFiles(ctx);
968
+ await installBinaryFile(ctx);
969
+ ctx.result.configPath = join(installDir, "commands", "ccg");
970
+ return ctx.result;
971
+ }
972
+ async function uninstallWorkflows(installDir, options) {
973
+ const result = {
974
+ success: true,
975
+ removedCommands: [],
976
+ removedPrompts: [],
977
+ removedAgents: [],
978
+ removedSkills: [],
979
+ removedRules: false,
980
+ removedBin: false,
981
+ errors: []
982
+ };
983
+ const commandsDir = join(installDir, "commands", "ccg");
984
+ const agentsDir = join(installDir, "agents", "ccg");
985
+ const skillsDir = join(installDir, "skills", "ccg");
986
+ const rulesDir = join(installDir, "rules");
987
+ const binDir = join(installDir, "bin");
988
+ const ccgConfigDir = join(installDir, ".ccg");
989
+ try {
990
+ result.removedCommands = await removeDirCollectMdNames(commandsDir);
991
+ } catch (error) {
992
+ result.errors.push(`Failed to remove commands directory: ${error}`);
993
+ result.success = false;
994
+ }
995
+ try {
996
+ result.removedAgents = await removeDirCollectMdNames(agentsDir);
997
+ } catch (error) {
998
+ result.errors.push(`Failed to remove agents directory: ${error}`);
999
+ result.success = false;
1000
+ }
1001
+ if (await fs.pathExists(skillsDir)) {
1002
+ try {
1003
+ result.removedSkills = await collectSkillNames(skillsDir);
1004
+ await fs.remove(skillsDir);
1005
+ } catch (error) {
1006
+ result.errors.push(`Failed to remove skills: ${error}`);
1007
+ result.success = false;
1420
1008
  }
1421
- for (const [id, claudeServer] of Object.entries(serversToSync)) {
1422
- geminiSettings.mcpServers[id] = claudeServer;
1423
- synced.push(id);
1009
+ }
1010
+ if (await fs.pathExists(rulesDir)) {
1011
+ try {
1012
+ for (const ruleFile of ["ccg-skills.md", "ccg-grok-search.md"]) {
1013
+ const rulePath = join(rulesDir, ruleFile);
1014
+ if (await fs.pathExists(rulePath)) {
1015
+ await fs.remove(rulePath);
1016
+ result.removedRules = true;
1017
+ }
1018
+ }
1019
+ } catch (error) {
1020
+ result.errors.push(`Failed to remove rules: ${error}`);
1424
1021
  }
1425
- for (const id of CCG_MCP_IDS) {
1426
- if (!serversToSync[id] && geminiSettings.mcpServers[id]) {
1427
- delete geminiSettings.mcpServers[id];
1428
- removed.push(id);
1022
+ }
1023
+ if (!options?.preserveBinary && await fs.pathExists(binDir)) {
1024
+ try {
1025
+ const wrapperName = process.platform === "win32" ? "codeagent-wrapper.exe" : "codeagent-wrapper";
1026
+ const wrapperPath = join(binDir, wrapperName);
1027
+ if (await fs.pathExists(wrapperPath)) {
1028
+ await fs.remove(wrapperPath);
1029
+ result.removedBin = true;
1429
1030
  }
1031
+ } catch (error) {
1032
+ result.errors.push(`Failed to remove binary: ${error}`);
1033
+ result.success = false;
1430
1034
  }
1431
- if (synced.length === 0 && removed.length === 0) {
1432
- return { success: true, message: "No CCG MCP servers to sync to Gemini", synced: [], removed: [] };
1035
+ }
1036
+ if (await fs.pathExists(ccgConfigDir)) {
1037
+ try {
1038
+ await fs.remove(ccgConfigDir);
1039
+ result.removedPrompts.push("ALL_PROMPTS_AND_CONFIGS");
1040
+ } catch (error) {
1041
+ result.errors.push(`Failed to remove .ccg directory: ${error}`);
1433
1042
  }
1434
- await fs.writeJSON(geminiSettingsPath, geminiSettings, { spaces: 2 });
1435
- const parts = [];
1436
- if (synced.length > 0) parts.push(`synced: ${synced.join(", ")}`);
1437
- if (removed.length > 0) parts.push(`removed: ${removed.join(", ")}`);
1438
- return { success: true, message: `Gemini MCP mirror complete (${parts.join("; ")})`, synced, removed };
1439
- } catch (error) {
1440
- return { success: false, message: `Failed to sync MCP to Gemini: ${error}`, synced, removed };
1441
1043
  }
1044
+ return result;
1442
1045
  }
1443
1046
 
1047
+ async function syncMcpMirrors() {
1048
+ const [codex, gemini] = await Promise.all([syncMcpToCodex(), syncMcpToGemini()]);
1049
+ const synced = [];
1050
+ if (codex.success && codex.synced.length > 0) synced.push(`Codex(${codex.synced.join(",")})`);
1051
+ if (gemini.success && gemini.synced.length > 0) synced.push(`Gemini(${gemini.synced.join(",")})`);
1052
+ if (synced.length > 0) {
1053
+ console.log(ansis.green(`\u2713 MCP \u5DF2\u540C\u6B65\u5230 ${synced.join(" + ")}`));
1054
+ }
1055
+ if (!codex.success) console.log(ansis.yellow(`\u26A0 Codex \u540C\u6B65\u5931\u8D25: ${codex.message}`));
1056
+ if (!gemini.success) console.log(ansis.yellow(`\u26A0 Gemini \u540C\u6B65\u5931\u8D25: ${gemini.message}`));
1057
+ }
1444
1058
  async function configMcp() {
1445
1059
  console.log();
1446
1060
  console.log(ansis.cyan.bold(` \u914D\u7F6E MCP \u5DE5\u5177`));
@@ -1516,6 +1130,7 @@ async function handleInstallAceTool(isRs) {
1516
1130
  console.log();
1517
1131
  if (result.success) {
1518
1132
  console.log(ansis.green(`\u2713 ${toolName} MCP \u914D\u7F6E\u6210\u529F\uFF01`));
1133
+ await syncMcpMirrors();
1519
1134
  console.log(ansis.gray(` \u91CD\u542F Claude Code CLI \u4F7F\u914D\u7F6E\u751F\u6548`));
1520
1135
  } else {
1521
1136
  console.log(ansis.red(`\u2717 ${toolName} MCP \u914D\u7F6E\u5931\u8D25: ${result.message}`));
@@ -1541,6 +1156,7 @@ async function handleInstallContextWeaver() {
1541
1156
  console.log();
1542
1157
  if (result.success) {
1543
1158
  console.log(ansis.green("\u2713 ContextWeaver MCP \u914D\u7F6E\u6210\u529F\uFF01"));
1159
+ await syncMcpMirrors();
1544
1160
  console.log(ansis.gray(" \u91CD\u542F Claude Code CLI \u4F7F\u914D\u7F6E\u751F\u6548"));
1545
1161
  } else {
1546
1162
  console.log(ansis.red(`\u2717 ContextWeaver MCP \u914D\u7F6E\u5931\u8D25: ${result.message}`));
@@ -1574,6 +1190,7 @@ async function handleInstallFastContext() {
1574
1190
  await writeFastContextPrompt();
1575
1191
  console.log(ansis.green("\u2713 fast-context MCP \u914D\u7F6E\u6210\u529F\uFF01"));
1576
1192
  console.log(ansis.green("\u2713 \u641C\u7D22\u63D0\u793A\u8BCD\u5DF2\u5199\u5165 ~/.claude/rules/ + ~/.codex/AGENTS.md + ~/.gemini/GEMINI.md"));
1193
+ await syncMcpMirrors();
1577
1194
  console.log(ansis.gray(" \u91CD\u542F Claude Code CLI \u4F7F\u914D\u7F6E\u751F\u6548"));
1578
1195
  } else {
1579
1196
  console.log(ansis.red(`\u2717 fast-context MCP \u914D\u7F6E\u5931\u8D25: ${result.message}`));
@@ -1666,6 +1283,7 @@ async function handleGrokSearch() {
1666
1283
  await writeGrokPromptToRules();
1667
1284
  console.log(ansis.green("\u2713 grok-search MCP \u914D\u7F6E\u6210\u529F\uFF01"));
1668
1285
  console.log(ansis.green("\u2713 \u5168\u5C40\u641C\u7D22\u63D0\u793A\u8BCD\u5DF2\u5199\u5165 ~/.claude/rules/ccg-grok-search.md"));
1286
+ await syncMcpMirrors();
1669
1287
  console.log(ansis.gray(" \u91CD\u542F Claude Code CLI \u4F7F\u914D\u7F6E\u751F\u6548"));
1670
1288
  } else {
1671
1289
  console.log(ansis.red(`\u2717 grok-search MCP \u5B89\u88C5\u5931\u8D25: ${result.message}`));
@@ -1718,6 +1336,7 @@ async function handleAuxiliary() {
1718
1336
  }
1719
1337
  }
1720
1338
  console.log();
1339
+ await syncMcpMirrors();
1721
1340
  console.log(ansis.gray("\u91CD\u542F Claude Code CLI \u4F7F\u914D\u7F6E\u751F\u6548"));
1722
1341
  }
1723
1342
  async function handleUninstall() {
@@ -1759,6 +1378,7 @@ async function handleUninstall() {
1759
1378
  console.log(ansis.red(`\u2717 ${target} \u5378\u8F7D\u5931\u8D25: ${result.message}`));
1760
1379
  }
1761
1380
  }
1381
+ await syncMcpMirrors();
1762
1382
  console.log();
1763
1383
  }
1764
1384
 
@@ -3556,38 +3176,7 @@ ${exportCommand}
3556
3176
  }
3557
3177
  }
3558
3178
  } else {
3559
- const binDest = join(installDir, "bin");
3560
- const binaryExt = process.platform === "win32" ? ".exe" : "";
3561
- const platformLabel = process.platform === "darwin" ? process.arch === "arm64" ? "darwin-arm64" : "darwin-amd64" : process.platform === "linux" ? process.arch === "arm64" ? "linux-arm64" : "linux-amd64" : process.arch === "arm64" ? "windows-arm64" : "windows-amd64";
3562
- const binaryFileName = `codeagent-wrapper-${platformLabel}${binaryExt}`;
3563
- const destFileName = `codeagent-wrapper${binaryExt}`;
3564
- const releaseUrl = `https://github.com/fengshao1227/ccg-workflow/releases/tag/preset`;
3565
- console.log();
3566
- console.log(ansis.red.bold(` \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557`));
3567
- console.log(ansis.red.bold(` \u2551 \u26A0 codeagent-wrapper \u4E0B\u8F7D\u5931\u8D25 \u2551`));
3568
- console.log(ansis.red.bold(` \u2551 Binary download failed (network issue) \u2551`));
3569
- console.log(ansis.red.bold(` \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D`));
3570
- console.log();
3571
- console.log(ansis.yellow(` \u591A\u6A21\u578B\u534F\u4F5C\u547D\u4EE4 (/ccg:workflow, /ccg:plan \u7B49) \u9700\u8981\u6B64\u6587\u4EF6\u624D\u80FD\u5DE5\u4F5C\u3002`));
3572
- console.log(ansis.yellow(` Multi-model commands require this binary to work.`));
3573
- console.log();
3574
- console.log(ansis.cyan(` \u624B\u52A8\u4FEE\u590D / Manual fix:`));
3575
- console.log();
3576
- console.log(ansis.white(` 1. \u4E0B\u8F7D / Download:`));
3577
- console.log(ansis.cyan(` ${releaseUrl}`));
3578
- console.log(ansis.gray(` \u2192 \u627E\u5230 ${ansis.white(binaryFileName)} \u5E76\u4E0B\u8F7D`));
3579
- console.log();
3580
- console.log(ansis.white(` 2. \u653E\u5230 / Place at:`));
3581
- console.log(ansis.cyan(` ${binDest}/${destFileName}`));
3582
- console.log();
3583
- if (process.platform !== "win32") {
3584
- console.log(ansis.white(` 3. \u52A0\u6743\u9650 / Make executable:`));
3585
- console.log(ansis.cyan(` chmod +x "${binDest}/${destFileName}"`));
3586
- console.log();
3587
- }
3588
- console.log(ansis.white(` \u6216\u91CD\u65B0\u5B89\u88C5 / Or re-install:`));
3589
- console.log(ansis.cyan(` npx ccg-workflow@latest`));
3590
- console.log();
3179
+ showBinaryDownloadWarning(join(installDir, "bin"));
3591
3180
  }
3592
3181
  if (mcpProvider === "skip" || (mcpProvider === "ace-tool" || mcpProvider === "ace-tool-rs") && !aceToolToken || mcpProvider === "contextweaver" && !contextWeaverApiKey) {
3593
3182
  console.log();
@@ -3829,18 +3418,8 @@ async function performUpdate(fromVersion, toVersion, isNewVersion) {
3829
3418
  }
3830
3419
  spinner = ora(i18n.t("update:removingOld")).start();
3831
3420
  const installDir = join(homedir(), ".claude");
3832
- const binDir = join(installDir, "bin");
3833
- const wrapperName = process.platform === "win32" ? "codeagent-wrapper.exe" : "codeagent-wrapper";
3834
- const wrapperPath = join(binDir, wrapperName);
3835
- const wrapperBackup = join(binDir, `${wrapperName}.bak`);
3836
- let binaryBackedUp = false;
3837
3421
  try {
3838
- const fsExtra = await import('fs-extra');
3839
- if (await fsExtra.pathExists(wrapperPath)) {
3840
- await fsExtra.copy(wrapperPath, wrapperBackup);
3841
- binaryBackedUp = true;
3842
- }
3843
- const uninstallResult = await uninstallWorkflows(installDir);
3422
+ const uninstallResult = await uninstallWorkflows(installDir, { preserveBinary: true });
3844
3423
  if (uninstallResult.success) {
3845
3424
  spinner.succeed(i18n.t("update:oldRemoved"));
3846
3425
  } else {
@@ -3863,11 +3442,6 @@ async function performUpdate(fromVersion, toVersion, isNewVersion) {
3863
3442
  }
3864
3443
  });
3865
3444
  spinner.succeed(i18n.t("update:installDone"));
3866
- if (binaryBackedUp) {
3867
- const fsExtra = await import('fs-extra');
3868
- await fsExtra.remove(wrapperBackup).catch(() => {
3869
- });
3870
- }
3871
3445
  const config = await readCcgConfig();
3872
3446
  if (config?.workflows?.installed) {
3873
3447
  console.log();
@@ -3876,19 +3450,11 @@ async function performUpdate(fromVersion, toVersion, isNewVersion) {
3876
3450
  console.log(` ${ansis.gray("\u2022")} /ccg:${cmd}`);
3877
3451
  }
3878
3452
  }
3453
+ if (!await verifyBinary(installDir)) {
3454
+ showBinaryDownloadWarning(join(installDir, "bin"));
3455
+ }
3879
3456
  } catch (error) {
3880
3457
  spinner.fail(i18n.t("update:installFailed"));
3881
- if (binaryBackedUp) {
3882
- try {
3883
- const fsExtra = await import('fs-extra');
3884
- if (await fsExtra.pathExists(wrapperBackup)) {
3885
- await fsExtra.ensureDir(binDir);
3886
- await fsExtra.move(wrapperBackup, wrapperPath, { overwrite: true });
3887
- console.log(ansis.yellow(` \u2022 codeagent-wrapper restored from backup`));
3888
- }
3889
- } catch {
3890
- }
3891
- }
3892
3458
  console.log(ansis.red(`${i18n.t("common:error")}: ${error}`));
3893
3459
  console.log();
3894
3460
  console.log(ansis.yellow(i18n.t("update:manualRetry")));