@agiflowai/aicode-toolkit 1.0.3 → 1.0.5
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/dist/cli.cjs +8 -111
- package/dist/{cli.js → cli.mjs} +12 -110
- package/dist/index.cjs +1 -1
- package/dist/{index.d.ts → index.d.mts} +1 -1
- package/dist/index.mjs +4 -0
- package/dist/mcp-BgNkvV6h.mjs +3 -0
- package/dist/{mcp-BmhiAfeF.js → mcp-C3HR_ZNF.mjs} +1 -1
- package/dist/{services-zrdafWTg.js → services-BvnOGCDG.mjs} +296 -207
- package/dist/{services-C6lqyioO.cjs → services-DIUrmi_K.cjs} +294 -210
- package/package.json +7 -9
- package/dist/index.js +0 -4
- package/dist/mcp-CZIiB-6Y.js +0 -3
- /package/dist/{cli.d.ts → cli.d.mts} +0 -0
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { ProjectType, messages, print } from "@agiflowai/aicode-utils";
|
|
3
|
-
import * as fs$1 from "fs-extra";
|
|
1
|
+
import { CLAUDE_CODE, CODEX, CURSOR, ClaudeCodeService, CodexService, CursorService, GEMINI_CLI, GITHUB_COPILOT, GeminiCliService, GitHubCopilotService, NONE } from "@agiflowai/coding-agent-bridge";
|
|
2
|
+
import { ProjectType, copy, ensureDir, messages, mkdir, move, pathExists, print, readFile, readdir, remove, writeFile } from "@agiflowai/aicode-utils";
|
|
4
3
|
import chalk from "chalk";
|
|
5
4
|
import gradient from "gradient-string";
|
|
5
|
+
import path from "node:path";
|
|
6
6
|
import { execa } from "execa";
|
|
7
|
-
import { CLAUDE_CODE, CODEX, CURSOR, ClaudeCodeService, CodexService, CursorService, GEMINI_CLI, GITHUB_COPILOT, GeminiCliService, GitHubCopilotService, NONE } from "@agiflowai/coding-agent-bridge";
|
|
8
7
|
import fs from "node:fs/promises";
|
|
9
8
|
import { Liquid } from "liquidjs";
|
|
10
9
|
import os from "node:os";
|
|
@@ -70,6 +69,187 @@ const BANNER_GRADIENT = [
|
|
|
70
69
|
THEME.colors.secondary.dark
|
|
71
70
|
];
|
|
72
71
|
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/services/CodingAgentService.ts
|
|
74
|
+
/**
|
|
75
|
+
* CodingAgentService
|
|
76
|
+
*
|
|
77
|
+
* DESIGN PATTERNS:
|
|
78
|
+
* - Service pattern for business logic encapsulation
|
|
79
|
+
* - Strategy pattern for different agent configurations
|
|
80
|
+
* - Single responsibility: Handle MCP setup for coding agents
|
|
81
|
+
*
|
|
82
|
+
* CODING STANDARDS:
|
|
83
|
+
* - Use async/await for asynchronous operations
|
|
84
|
+
* - Throw descriptive errors for error cases
|
|
85
|
+
* - Document methods with JSDoc comments
|
|
86
|
+
*
|
|
87
|
+
* AVOID:
|
|
88
|
+
* - Direct UI interaction (no prompts in services)
|
|
89
|
+
* - Hard-coding agent configurations (use strategies)
|
|
90
|
+
*/
|
|
91
|
+
var CodingAgentService = class {
|
|
92
|
+
workspaceRoot;
|
|
93
|
+
constructor(workspaceRoot) {
|
|
94
|
+
this.workspaceRoot = workspaceRoot;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Detect which coding agent is enabled in the workspace
|
|
98
|
+
* Checks for Claude Code, Codex, Gemini CLI, GitHub Copilot, and Cursor installations
|
|
99
|
+
* @param workspaceRoot - The workspace root directory
|
|
100
|
+
* @returns Promise resolving to detected agent ID or null
|
|
101
|
+
*/
|
|
102
|
+
static async detectCodingAgent(workspaceRoot) {
|
|
103
|
+
if (await new ClaudeCodeService({ workspaceRoot }).isEnabled()) return CLAUDE_CODE;
|
|
104
|
+
if (await new CursorService({ workspaceRoot }).isEnabled()) return CURSOR;
|
|
105
|
+
if (await new GitHubCopilotService({ workspaceRoot }).isEnabled()) return GITHUB_COPILOT;
|
|
106
|
+
if (await new CodexService({ workspaceRoot }).isEnabled()) return CODEX;
|
|
107
|
+
if (await new GeminiCliService({ workspaceRoot }).isEnabled()) return GEMINI_CLI;
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get available coding agents with their descriptions
|
|
112
|
+
*/
|
|
113
|
+
static getAvailableAgents() {
|
|
114
|
+
return [
|
|
115
|
+
{
|
|
116
|
+
value: CLAUDE_CODE,
|
|
117
|
+
name: "Claude Code",
|
|
118
|
+
description: "Anthropic Claude Code CLI agent"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
value: CURSOR,
|
|
122
|
+
name: "Cursor",
|
|
123
|
+
description: "Cursor AI-first code editor"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
value: GITHUB_COPILOT,
|
|
127
|
+
name: "GitHub Copilot",
|
|
128
|
+
description: "GitHub Copilot coding agent and CLI"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
value: CODEX,
|
|
132
|
+
name: "Codex",
|
|
133
|
+
description: "OpenAI Codex CLI agent"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
value: GEMINI_CLI,
|
|
137
|
+
name: "Gemini CLI",
|
|
138
|
+
description: "Google Gemini CLI agent"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
value: NONE,
|
|
142
|
+
name: "Other",
|
|
143
|
+
description: "Other coding agent or skip MCP configuration"
|
|
144
|
+
}
|
|
145
|
+
];
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Get the coding agent service instance
|
|
149
|
+
* @param agent - The coding agent to get service for
|
|
150
|
+
* @returns The service instance or null if not supported
|
|
151
|
+
*/
|
|
152
|
+
getCodingAgentService(agent) {
|
|
153
|
+
if (agent === CLAUDE_CODE) return new ClaudeCodeService({ workspaceRoot: this.workspaceRoot });
|
|
154
|
+
if (agent === CURSOR) return new CursorService({ workspaceRoot: this.workspaceRoot });
|
|
155
|
+
if (agent === GITHUB_COPILOT) return new GitHubCopilotService({ workspaceRoot: this.workspaceRoot });
|
|
156
|
+
if (agent === CODEX) return new CodexService({ workspaceRoot: this.workspaceRoot });
|
|
157
|
+
if (agent === GEMINI_CLI) return new GeminiCliService({ workspaceRoot: this.workspaceRoot });
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Update custom instructions/prompts for the coding agent
|
|
162
|
+
* Appends custom instruction prompt to the agent's configuration
|
|
163
|
+
* @param agent - The coding agent to update
|
|
164
|
+
* @param instructionPrompt - The instruction prompt to append
|
|
165
|
+
* @param customInstructionFile - Optional custom file path to write instructions to (e.g., '.claude/aicode-instructions.md')
|
|
166
|
+
*/
|
|
167
|
+
async updateCustomInstructions(agent, instructionPrompt, customInstructionFile) {
|
|
168
|
+
if (agent === NONE) {
|
|
169
|
+
print.info("Skipping custom instruction update");
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
print.info(`\nUpdating custom instructions for ${agent}...`);
|
|
173
|
+
const service = this.getCodingAgentService(agent);
|
|
174
|
+
if (!service) {
|
|
175
|
+
print.info(`Custom instruction update for ${agent} is not yet supported.`);
|
|
176
|
+
print.info("Please manually add the instructions to your agent configuration.");
|
|
177
|
+
print.info("\nInstruction prompt to add:");
|
|
178
|
+
print.info(instructionPrompt);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
await service.updatePrompt({
|
|
182
|
+
systemPrompt: instructionPrompt,
|
|
183
|
+
customInstructionFile,
|
|
184
|
+
marker: true
|
|
185
|
+
});
|
|
186
|
+
if (customInstructionFile) print.success(`Custom instructions written to ${customInstructionFile} and referenced in CLAUDE.md and AGENTS.md`);
|
|
187
|
+
else print.success(`Custom instructions appended to CLAUDE.md and AGENTS.md`);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Setup MCP configuration for the selected coding agent
|
|
191
|
+
* @param agent - The coding agent to configure
|
|
192
|
+
*/
|
|
193
|
+
async setupMCP(agent) {
|
|
194
|
+
if (agent === NONE) {
|
|
195
|
+
print.info("Skipping MCP configuration");
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
print.info(`\nSetting up MCP for ${agent}...`);
|
|
199
|
+
const service = this.getCodingAgentService(agent);
|
|
200
|
+
let configLocation = "";
|
|
201
|
+
let restartInstructions = "";
|
|
202
|
+
if (agent === CLAUDE_CODE) {
|
|
203
|
+
configLocation = ".mcp.json";
|
|
204
|
+
restartInstructions = "Restart Claude Code to load the new MCP servers";
|
|
205
|
+
} else if (agent === CURSOR) {
|
|
206
|
+
configLocation = "~/.cursor/mcp.json (or .cursor/mcp.json for workspace)";
|
|
207
|
+
restartInstructions = "Restart Cursor to load the new MCP servers";
|
|
208
|
+
} else if (agent === GITHUB_COPILOT) {
|
|
209
|
+
configLocation = "~/.copilot/config.json (CLI) or GitHub UI (Coding Agent)";
|
|
210
|
+
restartInstructions = "Restart GitHub Copilot CLI or configure via GitHub repository settings";
|
|
211
|
+
} else if (agent === CODEX) {
|
|
212
|
+
configLocation = "~/.codex/config.toml";
|
|
213
|
+
restartInstructions = "Restart Codex CLI to load the new MCP servers";
|
|
214
|
+
} else if (agent === GEMINI_CLI) {
|
|
215
|
+
configLocation = "~/.gemini/settings.json";
|
|
216
|
+
restartInstructions = "Restart Gemini CLI to load the new MCP servers";
|
|
217
|
+
}
|
|
218
|
+
if (!service) {
|
|
219
|
+
print.info(`MCP configuration for ${agent} is not yet supported.`);
|
|
220
|
+
print.info("Please configure MCP servers manually for this coding agent.");
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
await service.updateMcpSettings({ servers: {
|
|
224
|
+
"scaffold-mcp": {
|
|
225
|
+
type: "stdio",
|
|
226
|
+
command: "npx",
|
|
227
|
+
args: [
|
|
228
|
+
"-y",
|
|
229
|
+
"@agiflowai/scaffold-mcp",
|
|
230
|
+
"mcp-serve"
|
|
231
|
+
],
|
|
232
|
+
disabled: false
|
|
233
|
+
},
|
|
234
|
+
"architect-mcp": {
|
|
235
|
+
type: "stdio",
|
|
236
|
+
command: "npx",
|
|
237
|
+
args: [
|
|
238
|
+
"-y",
|
|
239
|
+
"@agiflowai/architect-mcp",
|
|
240
|
+
"mcp-serve"
|
|
241
|
+
],
|
|
242
|
+
disabled: false
|
|
243
|
+
}
|
|
244
|
+
} });
|
|
245
|
+
print.success(`Added scaffold-mcp and architect-mcp to ${configLocation}`);
|
|
246
|
+
print.info("\nNext steps:");
|
|
247
|
+
print.indent(`1. ${restartInstructions}`);
|
|
248
|
+
print.indent("2. The scaffold-mcp and architect-mcp servers will be available");
|
|
249
|
+
print.success("\nMCP configuration completed!");
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
|
|
73
253
|
//#endregion
|
|
74
254
|
//#region src/utils/banner.ts
|
|
75
255
|
/**
|
|
@@ -145,8 +325,7 @@ async function findWorkspaceRoot(startPath = process.cwd()) {
|
|
|
145
325
|
let currentPath = path.resolve(startPath);
|
|
146
326
|
const rootPath = path.parse(currentPath).root;
|
|
147
327
|
while (true) {
|
|
148
|
-
|
|
149
|
-
if (await fs$1.pathExists(gitPath)) return currentPath;
|
|
328
|
+
if (await pathExists(path.join(currentPath, ".git"))) return currentPath;
|
|
150
329
|
if (currentPath === rootPath) return null;
|
|
151
330
|
currentPath = path.dirname(currentPath);
|
|
152
331
|
}
|
|
@@ -202,8 +381,7 @@ async function cloneSubdirectory(repoUrl, branch, subdirectory, targetFolder) {
|
|
|
202
381
|
"core.sparseCheckout",
|
|
203
382
|
"true"
|
|
204
383
|
], tempFolder);
|
|
205
|
-
|
|
206
|
-
await fs$1.writeFile(sparseCheckoutFile, `${subdirectory}\n`);
|
|
384
|
+
await writeFile(path.join(tempFolder, ".git", "info", "sparse-checkout"), `${subdirectory}\n`);
|
|
207
385
|
await execGit([
|
|
208
386
|
"pull",
|
|
209
387
|
"--depth=1",
|
|
@@ -211,12 +389,12 @@ async function cloneSubdirectory(repoUrl, branch, subdirectory, targetFolder) {
|
|
|
211
389
|
branch
|
|
212
390
|
], tempFolder);
|
|
213
391
|
const sourceDir = path.join(tempFolder, subdirectory);
|
|
214
|
-
if (!await
|
|
215
|
-
if (await
|
|
216
|
-
await
|
|
217
|
-
await
|
|
392
|
+
if (!await pathExists(sourceDir)) throw new Error(`Subdirectory '${subdirectory}' not found in repository at branch '${branch}'`);
|
|
393
|
+
if (await pathExists(targetFolder)) throw new Error(`Target folder already exists: ${targetFolder}`);
|
|
394
|
+
await move(sourceDir, targetFolder);
|
|
395
|
+
await remove(tempFolder);
|
|
218
396
|
} catch (error) {
|
|
219
|
-
if (await
|
|
397
|
+
if (await pathExists(tempFolder)) await remove(tempFolder);
|
|
220
398
|
throw error;
|
|
221
399
|
}
|
|
222
400
|
}
|
|
@@ -230,7 +408,7 @@ async function cloneRepository(repoUrl, targetFolder) {
|
|
|
230
408
|
targetFolder
|
|
231
409
|
]);
|
|
232
410
|
const gitFolder = path.join(targetFolder, ".git");
|
|
233
|
-
if (await
|
|
411
|
+
if (await pathExists(gitFolder)) await remove(gitFolder);
|
|
234
412
|
}
|
|
235
413
|
/**
|
|
236
414
|
* Fetch directory listing from GitHub API
|
|
@@ -251,172 +429,27 @@ async function fetchGitHubDirectoryContents(owner, repo, path$1, branch = "main"
|
|
|
251
429
|
}));
|
|
252
430
|
}
|
|
253
431
|
|
|
254
|
-
//#endregion
|
|
255
|
-
//#region src/services/CodingAgentService.ts
|
|
256
|
-
var CodingAgentService = class {
|
|
257
|
-
workspaceRoot;
|
|
258
|
-
constructor(workspaceRoot) {
|
|
259
|
-
this.workspaceRoot = workspaceRoot;
|
|
260
|
-
}
|
|
261
|
-
/**
|
|
262
|
-
* Detect which coding agent is enabled in the workspace
|
|
263
|
-
* Checks for Claude Code, Codex, Gemini CLI, GitHub Copilot, and Cursor installations
|
|
264
|
-
* @param workspaceRoot - The workspace root directory
|
|
265
|
-
* @returns Promise resolving to detected agent ID or null
|
|
266
|
-
*/
|
|
267
|
-
static async detectCodingAgent(workspaceRoot) {
|
|
268
|
-
if (await new ClaudeCodeService({ workspaceRoot }).isEnabled()) return CLAUDE_CODE;
|
|
269
|
-
if (await new CursorService({ workspaceRoot }).isEnabled()) return CURSOR;
|
|
270
|
-
if (await new GitHubCopilotService({ workspaceRoot }).isEnabled()) return GITHUB_COPILOT;
|
|
271
|
-
if (await new CodexService({ workspaceRoot }).isEnabled()) return CODEX;
|
|
272
|
-
if (await new GeminiCliService({ workspaceRoot }).isEnabled()) return GEMINI_CLI;
|
|
273
|
-
return null;
|
|
274
|
-
}
|
|
275
|
-
/**
|
|
276
|
-
* Get available coding agents with their descriptions
|
|
277
|
-
*/
|
|
278
|
-
static getAvailableAgents() {
|
|
279
|
-
return [
|
|
280
|
-
{
|
|
281
|
-
value: CLAUDE_CODE,
|
|
282
|
-
name: "Claude Code",
|
|
283
|
-
description: "Anthropic Claude Code CLI agent"
|
|
284
|
-
},
|
|
285
|
-
{
|
|
286
|
-
value: CURSOR,
|
|
287
|
-
name: "Cursor",
|
|
288
|
-
description: "Cursor AI-first code editor"
|
|
289
|
-
},
|
|
290
|
-
{
|
|
291
|
-
value: GITHUB_COPILOT,
|
|
292
|
-
name: "GitHub Copilot",
|
|
293
|
-
description: "GitHub Copilot coding agent and CLI"
|
|
294
|
-
},
|
|
295
|
-
{
|
|
296
|
-
value: CODEX,
|
|
297
|
-
name: "Codex",
|
|
298
|
-
description: "OpenAI Codex CLI agent"
|
|
299
|
-
},
|
|
300
|
-
{
|
|
301
|
-
value: GEMINI_CLI,
|
|
302
|
-
name: "Gemini CLI",
|
|
303
|
-
description: "Google Gemini CLI agent"
|
|
304
|
-
},
|
|
305
|
-
{
|
|
306
|
-
value: NONE,
|
|
307
|
-
name: "Other",
|
|
308
|
-
description: "Other coding agent or skip MCP configuration"
|
|
309
|
-
}
|
|
310
|
-
];
|
|
311
|
-
}
|
|
312
|
-
/**
|
|
313
|
-
* Get the coding agent service instance
|
|
314
|
-
* @param agent - The coding agent to get service for
|
|
315
|
-
* @returns The service instance or null if not supported
|
|
316
|
-
*/
|
|
317
|
-
getCodingAgentService(agent) {
|
|
318
|
-
if (agent === CLAUDE_CODE) return new ClaudeCodeService({ workspaceRoot: this.workspaceRoot });
|
|
319
|
-
if (agent === CURSOR) return new CursorService({ workspaceRoot: this.workspaceRoot });
|
|
320
|
-
if (agent === GITHUB_COPILOT) return new GitHubCopilotService({ workspaceRoot: this.workspaceRoot });
|
|
321
|
-
if (agent === CODEX) return new CodexService({ workspaceRoot: this.workspaceRoot });
|
|
322
|
-
if (agent === GEMINI_CLI) return new GeminiCliService({ workspaceRoot: this.workspaceRoot });
|
|
323
|
-
return null;
|
|
324
|
-
}
|
|
325
|
-
/**
|
|
326
|
-
* Update custom instructions/prompts for the coding agent
|
|
327
|
-
* Appends custom instruction prompt to the agent's configuration
|
|
328
|
-
* @param agent - The coding agent to update
|
|
329
|
-
* @param instructionPrompt - The instruction prompt to append
|
|
330
|
-
* @param customInstructionFile - Optional custom file path to write instructions to (e.g., '.claude/aicode-instructions.md')
|
|
331
|
-
*/
|
|
332
|
-
async updateCustomInstructions(agent, instructionPrompt, customInstructionFile) {
|
|
333
|
-
if (agent === NONE) {
|
|
334
|
-
print.info("Skipping custom instruction update");
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
337
|
-
print.info(`\nUpdating custom instructions for ${agent}...`);
|
|
338
|
-
const service = this.getCodingAgentService(agent);
|
|
339
|
-
if (!service) {
|
|
340
|
-
print.info(`Custom instruction update for ${agent} is not yet supported.`);
|
|
341
|
-
print.info("Please manually add the instructions to your agent configuration.");
|
|
342
|
-
print.info("\nInstruction prompt to add:");
|
|
343
|
-
print.info(instructionPrompt);
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
await service.updatePrompt({
|
|
347
|
-
systemPrompt: instructionPrompt,
|
|
348
|
-
customInstructionFile,
|
|
349
|
-
marker: true
|
|
350
|
-
});
|
|
351
|
-
if (customInstructionFile) print.success(`Custom instructions written to ${customInstructionFile} and referenced in CLAUDE.md and AGENTS.md`);
|
|
352
|
-
else print.success(`Custom instructions appended to CLAUDE.md and AGENTS.md`);
|
|
353
|
-
}
|
|
354
|
-
/**
|
|
355
|
-
* Setup MCP configuration for the selected coding agent
|
|
356
|
-
* @param agent - The coding agent to configure
|
|
357
|
-
*/
|
|
358
|
-
async setupMCP(agent) {
|
|
359
|
-
if (agent === NONE) {
|
|
360
|
-
print.info("Skipping MCP configuration");
|
|
361
|
-
return;
|
|
362
|
-
}
|
|
363
|
-
print.info(`\nSetting up MCP for ${agent}...`);
|
|
364
|
-
const service = this.getCodingAgentService(agent);
|
|
365
|
-
let configLocation = "";
|
|
366
|
-
let restartInstructions = "";
|
|
367
|
-
if (agent === CLAUDE_CODE) {
|
|
368
|
-
configLocation = ".mcp.json";
|
|
369
|
-
restartInstructions = "Restart Claude Code to load the new MCP servers";
|
|
370
|
-
} else if (agent === CURSOR) {
|
|
371
|
-
configLocation = "~/.cursor/mcp.json (or .cursor/mcp.json for workspace)";
|
|
372
|
-
restartInstructions = "Restart Cursor to load the new MCP servers";
|
|
373
|
-
} else if (agent === GITHUB_COPILOT) {
|
|
374
|
-
configLocation = "~/.copilot/config.json (CLI) or GitHub UI (Coding Agent)";
|
|
375
|
-
restartInstructions = "Restart GitHub Copilot CLI or configure via GitHub repository settings";
|
|
376
|
-
} else if (agent === CODEX) {
|
|
377
|
-
configLocation = "~/.codex/config.toml";
|
|
378
|
-
restartInstructions = "Restart Codex CLI to load the new MCP servers";
|
|
379
|
-
} else if (agent === GEMINI_CLI) {
|
|
380
|
-
configLocation = "~/.gemini/settings.json";
|
|
381
|
-
restartInstructions = "Restart Gemini CLI to load the new MCP servers";
|
|
382
|
-
}
|
|
383
|
-
if (!service) {
|
|
384
|
-
print.info(`MCP configuration for ${agent} is not yet supported.`);
|
|
385
|
-
print.info("Please configure MCP servers manually for this coding agent.");
|
|
386
|
-
return;
|
|
387
|
-
}
|
|
388
|
-
await service.updateMcpSettings({ servers: {
|
|
389
|
-
"scaffold-mcp": {
|
|
390
|
-
type: "stdio",
|
|
391
|
-
command: "npx",
|
|
392
|
-
args: [
|
|
393
|
-
"-y",
|
|
394
|
-
"@agiflowai/scaffold-mcp",
|
|
395
|
-
"mcp-serve"
|
|
396
|
-
],
|
|
397
|
-
disabled: false
|
|
398
|
-
},
|
|
399
|
-
"architect-mcp": {
|
|
400
|
-
type: "stdio",
|
|
401
|
-
command: "npx",
|
|
402
|
-
args: [
|
|
403
|
-
"-y",
|
|
404
|
-
"@agiflowai/architect-mcp",
|
|
405
|
-
"mcp-serve"
|
|
406
|
-
],
|
|
407
|
-
disabled: false
|
|
408
|
-
}
|
|
409
|
-
} });
|
|
410
|
-
print.success(`Added scaffold-mcp and architect-mcp to ${configLocation}`);
|
|
411
|
-
print.info("\nNext steps:");
|
|
412
|
-
print.indent(`1. ${restartInstructions}`);
|
|
413
|
-
print.indent("2. The scaffold-mcp and architect-mcp servers will be available");
|
|
414
|
-
print.success("\nMCP configuration completed!");
|
|
415
|
-
}
|
|
416
|
-
};
|
|
417
|
-
|
|
418
432
|
//#endregion
|
|
419
433
|
//#region src/services/NewProjectService.ts
|
|
434
|
+
/**
|
|
435
|
+
* NewProjectService
|
|
436
|
+
*
|
|
437
|
+
* DESIGN PATTERNS:
|
|
438
|
+
* - Service pattern for business logic encapsulation
|
|
439
|
+
* - Single responsibility principle
|
|
440
|
+
* - No UI interaction (prompts handled by CLI layer)
|
|
441
|
+
*
|
|
442
|
+
* CODING STANDARDS:
|
|
443
|
+
* - Use async/await for asynchronous operations
|
|
444
|
+
* - Throw descriptive errors for error cases
|
|
445
|
+
* - Keep methods focused and well-named
|
|
446
|
+
* - Document complex logic with comments
|
|
447
|
+
*
|
|
448
|
+
* AVOID:
|
|
449
|
+
* - Mixing concerns (keep focused on single domain)
|
|
450
|
+
* - Direct UI interaction (no @inquirer/prompts in services)
|
|
451
|
+
* - Direct tool implementation (services should be tool-agnostic)
|
|
452
|
+
*/
|
|
420
453
|
const RESERVED_PROJECT_NAMES = [
|
|
421
454
|
".",
|
|
422
455
|
"..",
|
|
@@ -490,7 +523,7 @@ var NewProjectService = class {
|
|
|
490
523
|
*/
|
|
491
524
|
async createProjectDirectory(projectPath, projectName) {
|
|
492
525
|
try {
|
|
493
|
-
await
|
|
526
|
+
await mkdir(projectPath, { recursive: false });
|
|
494
527
|
} catch (error) {
|
|
495
528
|
if (error.code === "EEXIST") throw new Error(`Directory '${projectName}' already exists. Please choose a different name.`);
|
|
496
529
|
throw error;
|
|
@@ -507,7 +540,7 @@ var NewProjectService = class {
|
|
|
507
540
|
if (parsed.isSubdirectory && parsed.branch && parsed.subdirectory) await cloneSubdirectory(parsed.repoUrl, parsed.branch, parsed.subdirectory, projectPath);
|
|
508
541
|
else await cloneRepository(parsed.repoUrl, projectPath);
|
|
509
542
|
} catch (error) {
|
|
510
|
-
await
|
|
543
|
+
await remove(projectPath);
|
|
511
544
|
throw new Error(`Failed to clone repository: ${error.message}`);
|
|
512
545
|
}
|
|
513
546
|
}
|
|
@@ -541,6 +574,18 @@ var openspec_default = "When working on this project, follow the OpenSpec spec-d
|
|
|
541
574
|
//#endregion
|
|
542
575
|
//#region src/specs/openspec.ts
|
|
543
576
|
/**
|
|
577
|
+
* OpenSpec Bridge Implementation
|
|
578
|
+
*
|
|
579
|
+
* DESIGN PATTERNS:
|
|
580
|
+
* - Bridge pattern implementation for OpenSpec
|
|
581
|
+
* - Singleton pattern for OpenSpec configuration
|
|
582
|
+
*
|
|
583
|
+
* CODING STANDARDS:
|
|
584
|
+
* - Implement ISpecBridge interface
|
|
585
|
+
* - Use async/await for I/O operations
|
|
586
|
+
* - Handle errors with descriptive messages
|
|
587
|
+
*/
|
|
588
|
+
/**
|
|
544
589
|
* OpenSpec configuration
|
|
545
590
|
*/
|
|
546
591
|
const OPENSPEC_CONFIG = {
|
|
@@ -606,6 +651,18 @@ var OpenSpecBridge = class {
|
|
|
606
651
|
//#endregion
|
|
607
652
|
//#region src/services/SpecToolService.ts
|
|
608
653
|
/**
|
|
654
|
+
* Spec Tool Service
|
|
655
|
+
*
|
|
656
|
+
* DESIGN PATTERNS:
|
|
657
|
+
* - Service pattern for spec tool detection and installation
|
|
658
|
+
* - Bridge pattern to abstract spec tool implementations
|
|
659
|
+
*
|
|
660
|
+
* CODING STANDARDS:
|
|
661
|
+
* - Use async/await for asynchronous operations
|
|
662
|
+
* - Handle errors with try/catch blocks
|
|
663
|
+
* - Use descriptive method names
|
|
664
|
+
*/
|
|
665
|
+
/**
|
|
609
666
|
* Available spec tools
|
|
610
667
|
*/
|
|
611
668
|
let SpecTool = /* @__PURE__ */ function(SpecTool$1) {
|
|
@@ -683,6 +740,22 @@ var SpecToolService = class {
|
|
|
683
740
|
|
|
684
741
|
//#endregion
|
|
685
742
|
//#region src/services/TemplateSelectionService.ts
|
|
743
|
+
/**
|
|
744
|
+
* TemplateSelectionService
|
|
745
|
+
*
|
|
746
|
+
* DESIGN PATTERNS:
|
|
747
|
+
* - Service pattern for business logic encapsulation
|
|
748
|
+
* - Single responsibility: Handle template download, listing, and selection
|
|
749
|
+
*
|
|
750
|
+
* CODING STANDARDS:
|
|
751
|
+
* - Use async/await for asynchronous operations
|
|
752
|
+
* - Throw descriptive errors for error cases
|
|
753
|
+
* - Document methods with JSDoc comments
|
|
754
|
+
*
|
|
755
|
+
* AVOID:
|
|
756
|
+
* - Direct UI interaction (no prompts in services)
|
|
757
|
+
* - Mixing concerns beyond template management
|
|
758
|
+
*/
|
|
686
759
|
var TemplateSelectionService = class {
|
|
687
760
|
tmpDir;
|
|
688
761
|
constructor(existingTmpDir) {
|
|
@@ -695,7 +768,7 @@ var TemplateSelectionService = class {
|
|
|
695
768
|
*/
|
|
696
769
|
async downloadTemplatesToTmp(repoConfig) {
|
|
697
770
|
try {
|
|
698
|
-
await
|
|
771
|
+
await ensureDir(this.tmpDir);
|
|
699
772
|
const contents = await fetchGitHubDirectoryContents(repoConfig.owner, repoConfig.repo, repoConfig.path, repoConfig.branch);
|
|
700
773
|
const templateDirs = contents.filter((item) => item.type === "dir");
|
|
701
774
|
const globalFiles = contents.filter((item) => item.type === "file" && item.name === "RULES.yaml");
|
|
@@ -713,8 +786,7 @@ var TemplateSelectionService = class {
|
|
|
713
786
|
const targetFile = path.join(this.tmpDir, "RULES.yaml");
|
|
714
787
|
const response = await fetch(rulesUrl);
|
|
715
788
|
if (response.ok) {
|
|
716
|
-
|
|
717
|
-
await fs$1.writeFile(targetFile, content, "utf-8");
|
|
789
|
+
await writeFile(targetFile, await response.text(), "utf-8");
|
|
718
790
|
print.success("Downloaded global RULES.yaml");
|
|
719
791
|
}
|
|
720
792
|
}
|
|
@@ -731,7 +803,7 @@ var TemplateSelectionService = class {
|
|
|
731
803
|
*/
|
|
732
804
|
async listTemplates() {
|
|
733
805
|
try {
|
|
734
|
-
const entries = await
|
|
806
|
+
const entries = await readdir(this.tmpDir, { withFileTypes: true });
|
|
735
807
|
const templates = [];
|
|
736
808
|
for (const entry of entries) if (entry.isDirectory()) {
|
|
737
809
|
const templatePath = path.join(this.tmpDir, entry.name);
|
|
@@ -757,27 +829,27 @@ var TemplateSelectionService = class {
|
|
|
757
829
|
async copyTemplates(templateNames, destinationPath, projectType, selectedMcpServers) {
|
|
758
830
|
try {
|
|
759
831
|
if (projectType === ProjectType.MONOLITH && templateNames.length > 1) throw new Error("Monolith projects can only use a single template");
|
|
760
|
-
await
|
|
832
|
+
await ensureDir(destinationPath);
|
|
761
833
|
print.info(`\nCopying templates to ${destinationPath}...`);
|
|
762
834
|
for (const templateName of templateNames) {
|
|
763
835
|
const sourcePath = path.join(this.tmpDir, templateName);
|
|
764
836
|
const targetPath = path.join(destinationPath, templateName);
|
|
765
|
-
if (!await
|
|
766
|
-
if (await
|
|
837
|
+
if (!await pathExists(sourcePath)) throw new Error(`Template '${templateName}' not found in downloaded templates`);
|
|
838
|
+
if (await pathExists(targetPath)) {
|
|
767
839
|
print.info(`Skipping ${templateName} (already exists)`);
|
|
768
840
|
continue;
|
|
769
841
|
}
|
|
770
842
|
print.info(`Copying ${templateName}...`);
|
|
771
843
|
if (selectedMcpServers && selectedMcpServers.length > 0) await this.copyTemplateWithMcpFilter(sourcePath, targetPath, selectedMcpServers);
|
|
772
|
-
else await
|
|
844
|
+
else await copy(sourcePath, targetPath);
|
|
773
845
|
print.success(`Copied ${templateName}`);
|
|
774
846
|
}
|
|
775
847
|
const globalRulesSource = path.join(this.tmpDir, "RULES.yaml");
|
|
776
848
|
const globalRulesTarget = path.join(destinationPath, "RULES.yaml");
|
|
777
|
-
if (await
|
|
778
|
-
if (!await
|
|
849
|
+
if (await pathExists(globalRulesSource)) {
|
|
850
|
+
if (!await pathExists(globalRulesTarget)) {
|
|
779
851
|
print.info("Copying global RULES.yaml...");
|
|
780
|
-
await
|
|
852
|
+
await copy(globalRulesSource, globalRulesTarget);
|
|
781
853
|
print.success("Copied global RULES.yaml");
|
|
782
854
|
}
|
|
783
855
|
}
|
|
@@ -793,23 +865,23 @@ var TemplateSelectionService = class {
|
|
|
793
865
|
* @param selectedMcpServers - Selected MCP servers
|
|
794
866
|
*/
|
|
795
867
|
async copyTemplateWithMcpFilter(sourcePath, targetPath, selectedMcpServers) {
|
|
796
|
-
const { MCPServer: MCPServer$1, MCP_CONFIG_FILES: MCP_CONFIG_FILES$1 } = await import("./mcp-
|
|
868
|
+
const { MCPServer: MCPServer$1, MCP_CONFIG_FILES: MCP_CONFIG_FILES$1 } = await import("./mcp-BgNkvV6h.mjs");
|
|
797
869
|
const architectFiles = MCP_CONFIG_FILES$1[MCPServer$1.ARCHITECT];
|
|
798
870
|
const hasArchitect = selectedMcpServers.includes(MCPServer$1.ARCHITECT);
|
|
799
871
|
const hasScaffold = selectedMcpServers.includes(MCPServer$1.SCAFFOLD);
|
|
800
|
-
await
|
|
801
|
-
const entries = await
|
|
872
|
+
await ensureDir(targetPath);
|
|
873
|
+
const entries = await readdir(sourcePath, { withFileTypes: true });
|
|
802
874
|
for (const entry of entries) {
|
|
803
875
|
const entrySourcePath = path.join(sourcePath, entry.name);
|
|
804
876
|
const entryTargetPath = path.join(targetPath, entry.name);
|
|
805
877
|
const isArchitectFile = architectFiles.includes(entry.name);
|
|
806
|
-
if (hasArchitect && hasScaffold) if (entry.isDirectory()) await
|
|
807
|
-
else await
|
|
878
|
+
if (hasArchitect && hasScaffold) if (entry.isDirectory()) await copy(entrySourcePath, entryTargetPath);
|
|
879
|
+
else await copy(entrySourcePath, entryTargetPath);
|
|
808
880
|
else if (hasArchitect && !hasScaffold) {
|
|
809
|
-
if (isArchitectFile) await
|
|
881
|
+
if (isArchitectFile) await copy(entrySourcePath, entryTargetPath);
|
|
810
882
|
} else if (!hasArchitect && hasScaffold) {
|
|
811
|
-
if (!isArchitectFile) if (entry.isDirectory()) await
|
|
812
|
-
else await
|
|
883
|
+
if (!isArchitectFile) if (entry.isDirectory()) await copy(entrySourcePath, entryTargetPath);
|
|
884
|
+
else await copy(entrySourcePath, entryTargetPath);
|
|
813
885
|
}
|
|
814
886
|
}
|
|
815
887
|
}
|
|
@@ -821,15 +893,15 @@ var TemplateSelectionService = class {
|
|
|
821
893
|
async readTemplateDescription(templatePath) {
|
|
822
894
|
try {
|
|
823
895
|
const scaffoldYamlPath = path.join(templatePath, "scaffold.yaml");
|
|
824
|
-
if (await
|
|
896
|
+
if (await pathExists(scaffoldYamlPath)) {
|
|
825
897
|
const yaml = await import("js-yaml");
|
|
826
|
-
const content = await
|
|
898
|
+
const content = await readFile(scaffoldYamlPath, "utf-8");
|
|
827
899
|
const scaffoldConfig = yaml.load(content);
|
|
828
900
|
if (scaffoldConfig?.description) return scaffoldConfig.description;
|
|
829
901
|
if (scaffoldConfig?.boilerplate?.[0]?.description) return scaffoldConfig.boilerplate[0].description;
|
|
830
902
|
}
|
|
831
903
|
const readmePath = path.join(templatePath, "README.md");
|
|
832
|
-
if (await
|
|
904
|
+
if (await pathExists(readmePath)) return (await readFile(readmePath, "utf-8")).split("\n\n")[0].substring(0, 200).trim();
|
|
833
905
|
return;
|
|
834
906
|
} catch {
|
|
835
907
|
return;
|
|
@@ -846,7 +918,7 @@ var TemplateSelectionService = class {
|
|
|
846
918
|
*/
|
|
847
919
|
async cleanup() {
|
|
848
920
|
try {
|
|
849
|
-
if (await
|
|
921
|
+
if (await pathExists(this.tmpDir)) await remove(this.tmpDir);
|
|
850
922
|
} catch (error) {
|
|
851
923
|
print.warning(`Warning: Failed to clean up tmp directory: ${error.message}`);
|
|
852
924
|
}
|
|
@@ -855,6 +927,23 @@ var TemplateSelectionService = class {
|
|
|
855
927
|
|
|
856
928
|
//#endregion
|
|
857
929
|
//#region src/services/TemplatesService.ts
|
|
930
|
+
/**
|
|
931
|
+
* TemplatesService
|
|
932
|
+
*
|
|
933
|
+
* DESIGN PATTERNS:
|
|
934
|
+
* - Service pattern for business logic encapsulation
|
|
935
|
+
* - Single responsibility principle
|
|
936
|
+
*
|
|
937
|
+
* CODING STANDARDS:
|
|
938
|
+
* - Use async/await for asynchronous operations
|
|
939
|
+
* - Throw descriptive errors for error cases
|
|
940
|
+
* - Keep methods focused and well-named
|
|
941
|
+
* - Document complex logic with comments
|
|
942
|
+
*
|
|
943
|
+
* AVOID:
|
|
944
|
+
* - Mixing concerns (keep focused on single domain)
|
|
945
|
+
* - Direct tool implementation (services should be tool-agnostic)
|
|
946
|
+
*/
|
|
858
947
|
var TemplatesService = class {
|
|
859
948
|
/**
|
|
860
949
|
* Download templates from a GitHub repository with UI feedback
|
|
@@ -874,7 +963,7 @@ var TemplatesService = class {
|
|
|
874
963
|
let _skipped = 0;
|
|
875
964
|
for (const template of templateDirs) {
|
|
876
965
|
const targetFolder = path.join(templatesPath, template.name);
|
|
877
|
-
if (await
|
|
966
|
+
if (await pathExists(targetFolder)) {
|
|
878
967
|
print.info(`Skipping ${template.name} (already exists)`);
|
|
879
968
|
_skipped++;
|
|
880
969
|
continue;
|
|
@@ -894,8 +983,8 @@ var TemplatesService = class {
|
|
|
894
983
|
* @param templatesPath - Path where templates folder should be created
|
|
895
984
|
*/
|
|
896
985
|
async initializeTemplatesFolder(templatesPath) {
|
|
897
|
-
await
|
|
898
|
-
await
|
|
986
|
+
await ensureDir(templatesPath);
|
|
987
|
+
await writeFile(path.join(templatesPath, "README.md"), `# Templates
|
|
899
988
|
|
|
900
989
|
This folder contains boilerplate templates and scaffolding methods for your projects.
|
|
901
990
|
|
|
@@ -933,4 +1022,4 @@ See existing templates for examples and documentation for more details.
|
|
|
933
1022
|
};
|
|
934
1023
|
|
|
935
1024
|
//#endregion
|
|
936
|
-
export {
|
|
1025
|
+
export { THEME as _, SpecToolService as a, cloneSubdirectory as c, gitInit as d, parseGitHubUrl as f, BANNER_GRADIENT as g, CodingAgentService as h, SpecTool as i, fetchGitHubDirectoryContents as l, displayCompactBanner as m, TemplateSelectionService as n, NewProjectService as o, displayBanner as p, SPEC_TOOL_INFO as r, cloneRepository as s, TemplatesService as t, findWorkspaceRoot as u };
|