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.
- package/README.md +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +28 -27
- package/dist/index.d.ts +28 -27
- package/dist/index.mjs +1 -1
- package/dist/shared/{ccg-workflow.C7wxgYrc.mjs → ccg-workflow.DPGNdJGO.mjs} +755 -1189
- package/package.json +9 -2
|
@@ -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,
|
|
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.
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
210
|
-
{
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
|
523
|
-
|
|
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
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
(
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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?.
|
|
1126
|
-
delete existingConfig.mcpServers
|
|
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
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
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
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
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
|
-
|
|
1154
|
-
|
|
1155
|
-
env.WINDSURF_API_KEY = apiKey;
|
|
497
|
+
if (!codexConfig.mcp_servers) {
|
|
498
|
+
codexConfig.mcp_servers = {};
|
|
1156
499
|
}
|
|
1157
|
-
|
|
1158
|
-
|
|
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
|
|
1161
|
-
|
|
1162
|
-
|
|
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
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
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
|
|
522
|
+
async function syncMcpToGemini() {
|
|
1187
523
|
try {
|
|
1188
|
-
const
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
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
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
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
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
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
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
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
|
-
|
|
1293
|
-
await removeFromFile(join(homedir(), ".gemini", "GEMINI.md"));
|
|
780
|
+
return names;
|
|
1294
781
|
}
|
|
1295
|
-
async function
|
|
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
|
-
|
|
1298
|
-
const
|
|
1299
|
-
const
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
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
|
|
1305
|
-
|
|
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
|
-
|
|
829
|
+
ctx.result.errors.push(`Failed to install skills: ${error}`);
|
|
830
|
+
ctx.result.success = false;
|
|
1308
831
|
}
|
|
1309
832
|
}
|
|
1310
|
-
async function
|
|
833
|
+
async function installRuleFiles(ctx) {
|
|
1311
834
|
try {
|
|
1312
|
-
const
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
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
|
-
|
|
842
|
+
ctx.result.errors.push(`Failed to install rules: ${error}`);
|
|
1320
843
|
}
|
|
1321
844
|
}
|
|
1322
|
-
|
|
1323
|
-
"
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
"
|
|
1327
|
-
"
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
async function
|
|
1331
|
-
const
|
|
1332
|
-
const
|
|
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
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
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
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
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
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
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
|
-
|
|
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
|
|
1400
|
-
const
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
}
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
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
|
-
|
|
1419
|
-
|
|
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
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
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
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
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
|
-
|
|
1432
|
-
|
|
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
|
-
|
|
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
|
|
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")));
|