@agiflowai/aicode-toolkit 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,859 @@
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
+ get: ((k) => from[k]).bind(null, key),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
+ });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
+ value: mod,
20
+ enumerable: true
21
+ }) : target, mod));
22
+
23
+ //#endregion
24
+ let node_path = require("node:path");
25
+ node_path = __toESM(node_path);
26
+ let __agiflowai_aicode_utils = require("@agiflowai/aicode-utils");
27
+ __agiflowai_aicode_utils = __toESM(__agiflowai_aicode_utils);
28
+ let fs_extra = require("fs-extra");
29
+ fs_extra = __toESM(fs_extra);
30
+ let chalk = require("chalk");
31
+ chalk = __toESM(chalk);
32
+ let gradient_string = require("gradient-string");
33
+ gradient_string = __toESM(gradient_string);
34
+ let execa = require("execa");
35
+ execa = __toESM(execa);
36
+ let __agiflowai_coding_agent_bridge = require("@agiflowai/coding-agent-bridge");
37
+ __agiflowai_coding_agent_bridge = __toESM(__agiflowai_coding_agent_bridge);
38
+ let node_os = require("node:os");
39
+ node_os = __toESM(node_os);
40
+
41
+ //#region src/constants/theme.ts
42
+ /**
43
+ * Theme color constants for AICode Toolkit
44
+ * Defines the brand color palette used throughout the CLI
45
+ */
46
+ const THEME = { colors: {
47
+ primary: {
48
+ default: "#10b981",
49
+ dark: "#059669",
50
+ text: "#ffffff"
51
+ },
52
+ secondary: {
53
+ default: "#0d9488",
54
+ dark: "#0f766e",
55
+ light: "#14b8a6",
56
+ text: "#ffffff"
57
+ },
58
+ accent: {
59
+ default: "#c44569",
60
+ dark: "#c44569",
61
+ text: "#2a0b14"
62
+ },
63
+ semantic: {
64
+ info: "#5fb3d4",
65
+ success: "#5fb368",
66
+ error: "#d45959",
67
+ alert: "#d4b359"
68
+ },
69
+ cta: {
70
+ from: "#10b981",
71
+ to: "#0d9488",
72
+ text: "#ffffff"
73
+ },
74
+ transparent: "rgba(0, 0, 0, 0)",
75
+ white: "#c4cccf",
76
+ black: "#424549",
77
+ background: {
78
+ dark: {
79
+ default: "#0f0f0f",
80
+ shade: "#141414",
81
+ dark: "#0a0a0a",
82
+ light: "#1a1a1a"
83
+ },
84
+ light: {
85
+ default: "#fff",
86
+ shade: "#EAEAEA",
87
+ dark: "#17202a",
88
+ light: "#EAEAEA"
89
+ }
90
+ }
91
+ } };
92
+ /**
93
+ * Gradient colors for banner (primary green -> secondary teal)
94
+ */
95
+ const BANNER_GRADIENT = [
96
+ THEME.colors.primary.default,
97
+ THEME.colors.primary.dark,
98
+ THEME.colors.secondary.default,
99
+ THEME.colors.secondary.dark
100
+ ];
101
+
102
+ //#endregion
103
+ //#region src/utils/banner.ts
104
+ /**
105
+ * ASCII art for AICode Toolkit - simple and highly readable design
106
+ * Uses clean block style with clear spacing
107
+ */
108
+ const ASCII_ART = `
109
+ █████╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗
110
+ ██╔══██╗██║██╔════╝██╔═══██╗██╔══██╗██╔════╝
111
+ ███████║██║██║ ██║ ██║██║ ██║█████╗
112
+ ██╔══██║██║██║ ██║ ██║██║ ██║██╔══╝
113
+ ██║ ██║██║╚██████╗╚██████╔╝██████╔╝███████╗
114
+ ╚═╝ ╚═╝╚═╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝
115
+
116
+ ████████╗ ██████╗ ██████╗ ██╗ ██╗ ██╗██╗████████╗
117
+ ╚══██╔══╝██╔═══██╗██╔═══██╗██║ ██║ ██╔╝██║╚══██╔══╝
118
+ ██║ ██║ ██║██║ ██║██║ █████╔╝ ██║ ██║
119
+ ██║ ██║ ██║██║ ██║██║ ██╔═██╗ ██║ ██║
120
+ ██║ ╚██████╔╝╚██████╔╝███████╗██║ ██╗██║ ██║
121
+ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝
122
+ `;
123
+ /**
124
+ * Displays the AICode Toolkit banner with gradient effect
125
+ * Uses gradient-string with theme colors (primary green -> secondary teal)
126
+ */
127
+ function displayBanner() {
128
+ const bannerGradient = (0, gradient_string.default)(BANNER_GRADIENT);
129
+ console.log(bannerGradient.multiline(ASCII_ART));
130
+ console.log(bannerGradient(" AI-Powered Code Toolkit for Modern Development"));
131
+ console.log(chalk.default.dim(" v0.6.0"));
132
+ console.log();
133
+ }
134
+ /**
135
+ * Simplified banner for compact display
136
+ */
137
+ function displayCompactBanner() {
138
+ const titleGradient = (0, gradient_string.default)(BANNER_GRADIENT);
139
+ console.log();
140
+ console.log(chalk.default.bold("▸ ") + titleGradient("AICode Toolkit") + chalk.default.dim(" v0.6.0"));
141
+ console.log(chalk.default.dim(" AI-Powered Code Toolkit"));
142
+ console.log();
143
+ }
144
+
145
+ //#endregion
146
+ //#region src/utils/git.ts
147
+ /**
148
+ * Execute a git command safely using execa to prevent command injection
149
+ */
150
+ async function execGit(args, cwd) {
151
+ try {
152
+ await (0, execa.execa)("git", args, { cwd });
153
+ } catch (error) {
154
+ const execaError = error;
155
+ throw new Error(`Git command failed: ${execaError.stderr || execaError.message}`);
156
+ }
157
+ }
158
+ /**
159
+ * Execute git init safely using execa to prevent command injection
160
+ */
161
+ async function gitInit(projectPath) {
162
+ try {
163
+ await (0, execa.execa)("git", ["init", projectPath]);
164
+ } catch (error) {
165
+ const execaError = error;
166
+ throw new Error(`Git init failed: ${execaError.stderr || execaError.message}`);
167
+ }
168
+ }
169
+ /**
170
+ * Find the workspace root by searching upwards for .git folder
171
+ * Returns null if no .git folder is found (indicating a new project setup is needed)
172
+ */
173
+ async function findWorkspaceRoot(startPath = process.cwd()) {
174
+ let currentPath = node_path.default.resolve(startPath);
175
+ const rootPath = node_path.default.parse(currentPath).root;
176
+ while (true) {
177
+ const gitPath = node_path.default.join(currentPath, ".git");
178
+ if (await fs_extra.pathExists(gitPath)) return currentPath;
179
+ if (currentPath === rootPath) return null;
180
+ currentPath = node_path.default.dirname(currentPath);
181
+ }
182
+ }
183
+ /**
184
+ * Parse GitHub URL to detect if it's a subdirectory
185
+ * Supports formats:
186
+ * - https://github.com/user/repo
187
+ * - https://github.com/user/repo/tree/branch/path/to/dir
188
+ * - https://github.com/user/repo/tree/main/path/to/dir
189
+ */
190
+ function parseGitHubUrl(url) {
191
+ const treeMatch = url.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)\/(.+)$/);
192
+ const blobMatch = url.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/blob\/([^/]+)\/(.+)$/);
193
+ const rootMatch = url.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/);
194
+ if (treeMatch || blobMatch) {
195
+ const match = treeMatch || blobMatch;
196
+ return {
197
+ owner: match[1],
198
+ repo: match[2],
199
+ repoUrl: `https://github.com/${match[1]}/${match[2]}.git`,
200
+ branch: match[3],
201
+ subdirectory: match[4],
202
+ isSubdirectory: true
203
+ };
204
+ }
205
+ if (rootMatch) return {
206
+ owner: rootMatch[1],
207
+ repo: rootMatch[2],
208
+ repoUrl: `https://github.com/${rootMatch[1]}/${rootMatch[2]}.git`,
209
+ isSubdirectory: false
210
+ };
211
+ return {
212
+ repoUrl: url,
213
+ isSubdirectory: false
214
+ };
215
+ }
216
+ /**
217
+ * Clone a subdirectory from a git repository using sparse checkout
218
+ */
219
+ async function cloneSubdirectory(repoUrl, branch, subdirectory, targetFolder) {
220
+ const tempFolder = `${targetFolder}.tmp`;
221
+ try {
222
+ await execGit(["init", tempFolder]);
223
+ await execGit([
224
+ "remote",
225
+ "add",
226
+ "origin",
227
+ repoUrl
228
+ ], tempFolder);
229
+ await execGit([
230
+ "config",
231
+ "core.sparseCheckout",
232
+ "true"
233
+ ], tempFolder);
234
+ const sparseCheckoutFile = node_path.default.join(tempFolder, ".git", "info", "sparse-checkout");
235
+ await fs_extra.writeFile(sparseCheckoutFile, `${subdirectory}\n`);
236
+ await execGit([
237
+ "pull",
238
+ "--depth=1",
239
+ "origin",
240
+ branch
241
+ ], tempFolder);
242
+ const sourceDir = node_path.default.join(tempFolder, subdirectory);
243
+ if (!await fs_extra.pathExists(sourceDir)) throw new Error(`Subdirectory '${subdirectory}' not found in repository at branch '${branch}'`);
244
+ if (await fs_extra.pathExists(targetFolder)) throw new Error(`Target folder already exists: ${targetFolder}`);
245
+ await fs_extra.move(sourceDir, targetFolder);
246
+ await fs_extra.remove(tempFolder);
247
+ } catch (error) {
248
+ if (await fs_extra.pathExists(tempFolder)) await fs_extra.remove(tempFolder);
249
+ throw error;
250
+ }
251
+ }
252
+ /**
253
+ * Clone entire repository
254
+ */
255
+ async function cloneRepository(repoUrl, targetFolder) {
256
+ await execGit([
257
+ "clone",
258
+ repoUrl,
259
+ targetFolder
260
+ ]);
261
+ const gitFolder = node_path.default.join(targetFolder, ".git");
262
+ if (await fs_extra.pathExists(gitFolder)) await fs_extra.remove(gitFolder);
263
+ }
264
+ /**
265
+ * Fetch directory listing from GitHub API
266
+ */
267
+ async function fetchGitHubDirectoryContents(owner, repo, path$3, branch = "main") {
268
+ const url = `https://api.github.com/repos/${owner}/${repo}/contents/${path$3}?ref=${branch}`;
269
+ const response = await fetch(url, { headers: {
270
+ Accept: "application/vnd.github.v3+json",
271
+ "User-Agent": "scaffold-mcp"
272
+ } });
273
+ if (!response.ok) throw new Error(`Failed to fetch directory contents: ${response.statusText}`);
274
+ const data = await response.json();
275
+ if (!Array.isArray(data)) throw new Error("Expected directory but got file");
276
+ return data.map((item) => ({
277
+ name: item.name,
278
+ type: item.type,
279
+ path: item.path
280
+ }));
281
+ }
282
+
283
+ //#endregion
284
+ //#region src/services/CodingAgentService.ts
285
+ var CodingAgentService = class {
286
+ workspaceRoot;
287
+ constructor(workspaceRoot) {
288
+ this.workspaceRoot = workspaceRoot;
289
+ }
290
+ /**
291
+ * Detect which coding agent is enabled in the workspace
292
+ * Checks for Claude Code, Codex, and Gemini CLI installations
293
+ * @param workspaceRoot - The workspace root directory
294
+ * @returns Promise resolving to detected agent ID or null
295
+ */
296
+ static async detectCodingAgent(workspaceRoot) {
297
+ if (await new __agiflowai_coding_agent_bridge.ClaudeCodeService({ workspaceRoot }).isEnabled()) return __agiflowai_coding_agent_bridge.CLAUDE_CODE;
298
+ if (await new __agiflowai_coding_agent_bridge.CodexService({ workspaceRoot }).isEnabled()) return __agiflowai_coding_agent_bridge.CODEX;
299
+ if (await new __agiflowai_coding_agent_bridge.GeminiCliService({ workspaceRoot }).isEnabled()) return __agiflowai_coding_agent_bridge.GEMINI_CLI;
300
+ return null;
301
+ }
302
+ /**
303
+ * Get available coding agents with their descriptions
304
+ */
305
+ static getAvailableAgents() {
306
+ return [
307
+ {
308
+ value: __agiflowai_coding_agent_bridge.CLAUDE_CODE,
309
+ name: "Claude Code",
310
+ description: "Anthropic Claude Code CLI agent"
311
+ },
312
+ {
313
+ value: __agiflowai_coding_agent_bridge.CODEX,
314
+ name: "Codex",
315
+ description: "OpenAI Codex CLI agent"
316
+ },
317
+ {
318
+ value: __agiflowai_coding_agent_bridge.GEMINI_CLI,
319
+ name: "Gemini CLI",
320
+ description: "Google Gemini CLI agent"
321
+ },
322
+ {
323
+ value: __agiflowai_coding_agent_bridge.NONE,
324
+ name: "Other",
325
+ description: "Other coding agent or skip MCP configuration"
326
+ }
327
+ ];
328
+ }
329
+ /**
330
+ * Setup MCP configuration for the selected coding agent
331
+ * @param agent - The coding agent to configure
332
+ */
333
+ async setupMCP(agent) {
334
+ if (agent === __agiflowai_coding_agent_bridge.NONE) {
335
+ __agiflowai_aicode_utils.print.info("Skipping MCP configuration");
336
+ return;
337
+ }
338
+ __agiflowai_aicode_utils.print.info(`\nSetting up MCP for ${agent}...`);
339
+ let service = null;
340
+ let configLocation = "";
341
+ let restartInstructions = "";
342
+ if (agent === __agiflowai_coding_agent_bridge.CLAUDE_CODE) {
343
+ service = new __agiflowai_coding_agent_bridge.ClaudeCodeService({ workspaceRoot: this.workspaceRoot });
344
+ configLocation = ".mcp.json";
345
+ restartInstructions = "Restart Claude Code to load the new MCP servers";
346
+ } else if (agent === __agiflowai_coding_agent_bridge.CODEX) {
347
+ service = new __agiflowai_coding_agent_bridge.CodexService({ workspaceRoot: this.workspaceRoot });
348
+ configLocation = "~/.codex/config.toml";
349
+ restartInstructions = "Restart Codex CLI to load the new MCP servers";
350
+ } else if (agent === __agiflowai_coding_agent_bridge.GEMINI_CLI) {
351
+ service = new __agiflowai_coding_agent_bridge.GeminiCliService({ workspaceRoot: this.workspaceRoot });
352
+ configLocation = "~/.gemini/settings.json";
353
+ restartInstructions = "Restart Gemini CLI to load the new MCP servers";
354
+ }
355
+ if (!service) {
356
+ __agiflowai_aicode_utils.print.info(`MCP configuration for ${agent} is not yet supported.`);
357
+ __agiflowai_aicode_utils.print.info("Please configure MCP servers manually for this coding agent.");
358
+ return;
359
+ }
360
+ await service.updateMcpSettings({ servers: {
361
+ "scaffold-mcp": {
362
+ type: "stdio",
363
+ command: "npx",
364
+ args: [
365
+ "-y",
366
+ "@agiflowai/scaffold-mcp",
367
+ "mcp-serve"
368
+ ],
369
+ disabled: false
370
+ },
371
+ "architect-mcp": {
372
+ type: "stdio",
373
+ command: "npx",
374
+ args: [
375
+ "-y",
376
+ "@agiflowai/architect-mcp",
377
+ "mcp-serve"
378
+ ],
379
+ disabled: false
380
+ }
381
+ } });
382
+ __agiflowai_aicode_utils.print.success(`Added scaffold-mcp and architect-mcp to ${configLocation}`);
383
+ __agiflowai_aicode_utils.print.info("\nNext steps:");
384
+ __agiflowai_aicode_utils.print.indent(`1. ${restartInstructions}`);
385
+ __agiflowai_aicode_utils.print.indent("2. The scaffold-mcp and architect-mcp servers will be available");
386
+ __agiflowai_aicode_utils.print.success("\nMCP configuration completed!");
387
+ }
388
+ };
389
+
390
+ //#endregion
391
+ //#region src/services/NewProjectService.ts
392
+ const RESERVED_PROJECT_NAMES = [
393
+ ".",
394
+ "..",
395
+ "CON",
396
+ "PRN",
397
+ "AUX",
398
+ "NUL",
399
+ "COM1",
400
+ "COM2",
401
+ "COM3",
402
+ "COM4",
403
+ "COM5",
404
+ "COM6",
405
+ "COM7",
406
+ "COM8",
407
+ "COM9",
408
+ "LPT1",
409
+ "LPT2",
410
+ "LPT3",
411
+ "LPT4",
412
+ "LPT5",
413
+ "LPT6",
414
+ "LPT7",
415
+ "LPT8",
416
+ "LPT9"
417
+ ];
418
+ var NewProjectService = class {
419
+ providedName;
420
+ providedProjectType;
421
+ constructor(providedName, providedProjectType) {
422
+ this.providedName = providedName;
423
+ this.providedProjectType = providedProjectType;
424
+ }
425
+ /**
426
+ * Validate project name against naming rules
427
+ * @param value - Project name to validate
428
+ * @returns true if valid, error message string if invalid
429
+ */
430
+ validateProjectName(value) {
431
+ const trimmed = value.trim();
432
+ if (!trimmed) return "Project name is required";
433
+ if (!/^[a-zA-Z0-9]/.test(trimmed)) return "Project name must start with a letter or number";
434
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(trimmed)) return "Project name can only contain letters, numbers, hyphens, and underscores";
435
+ if (RESERVED_PROJECT_NAMES.includes(trimmed.toUpperCase())) return "Project name uses a reserved name";
436
+ return true;
437
+ }
438
+ /**
439
+ * Validate project type
440
+ * @param projectType - Project type to validate
441
+ * @throws Error if invalid project type
442
+ */
443
+ validateProjectType(projectType) {
444
+ if (projectType !== __agiflowai_aicode_utils.ProjectType.MONOLITH && projectType !== __agiflowai_aicode_utils.ProjectType.MONOREPO) throw new Error(`Invalid project type '${projectType}'. Must be '${__agiflowai_aicode_utils.ProjectType.MONOLITH}' or '${__agiflowai_aicode_utils.ProjectType.MONOREPO}'`);
445
+ }
446
+ /**
447
+ * Get the provided name from constructor
448
+ */
449
+ getProvidedName() {
450
+ return this.providedName;
451
+ }
452
+ /**
453
+ * Get the provided project type from constructor
454
+ */
455
+ getProvidedProjectType() {
456
+ return this.providedProjectType;
457
+ }
458
+ /**
459
+ * Create project directory atomically
460
+ * @param projectPath - Full path where project should be created
461
+ * @param projectName - Name of the project (for error messages)
462
+ */
463
+ async createProjectDirectory(projectPath, projectName) {
464
+ try {
465
+ await fs_extra.mkdir(projectPath, { recursive: false });
466
+ __agiflowai_aicode_utils.print.success(`Created project directory: ${projectPath}`);
467
+ } catch (error) {
468
+ if (error.code === "EEXIST") throw new Error(`Directory '${projectName}' already exists. Please choose a different name.`);
469
+ throw error;
470
+ }
471
+ }
472
+ /**
473
+ * Clone an existing Git repository
474
+ * @param repoUrl - Repository URL to clone
475
+ * @param projectPath - Destination path for the cloned repository
476
+ */
477
+ async cloneExistingRepository(repoUrl, projectPath) {
478
+ __agiflowai_aicode_utils.print.info("Cloning repository...");
479
+ try {
480
+ const parsed = parseGitHubUrl(repoUrl.trim());
481
+ if (parsed.isSubdirectory && parsed.branch && parsed.subdirectory) await cloneSubdirectory(parsed.repoUrl, parsed.branch, parsed.subdirectory, projectPath);
482
+ else await cloneRepository(parsed.repoUrl, projectPath);
483
+ __agiflowai_aicode_utils.print.success("Repository cloned successfully");
484
+ } catch (error) {
485
+ await fs_extra.remove(projectPath);
486
+ throw new Error(`Failed to clone repository: ${error.message}`);
487
+ }
488
+ }
489
+ /**
490
+ * Initialize a new Git repository
491
+ * @param projectPath - Path where git repository should be initialized
492
+ */
493
+ async initializeGitRepository(projectPath) {
494
+ __agiflowai_aicode_utils.print.info("Initializing Git repository...");
495
+ try {
496
+ await gitInit(projectPath);
497
+ __agiflowai_aicode_utils.print.success("Git repository initialized");
498
+ } catch (error) {
499
+ __agiflowai_aicode_utils.messages.warning(`Failed to initialize Git: ${error.message}`);
500
+ }
501
+ }
502
+ /**
503
+ * Validate repository URL format
504
+ * @param value - Repository URL to validate
505
+ * @returns true if valid, error message string if invalid
506
+ */
507
+ validateRepositoryUrl(value) {
508
+ if (!value.trim()) return "Repository URL is required";
509
+ if (!value.match(/^(https?:\/\/|git@)/)) return "Please enter a valid Git repository URL";
510
+ return true;
511
+ }
512
+ };
513
+
514
+ //#endregion
515
+ //#region src/services/TemplateSelectionService.ts
516
+ var TemplateSelectionService = class {
517
+ tmpDir;
518
+ constructor(existingTmpDir) {
519
+ this.tmpDir = existingTmpDir || node_path.default.join(node_os.default.tmpdir(), `aicode-templates-${Date.now()}`);
520
+ }
521
+ /**
522
+ * Download templates to OS tmp directory
523
+ * @param repoConfig - Repository configuration
524
+ * @returns Path to the tmp directory containing templates
525
+ */
526
+ async downloadTemplatesToTmp(repoConfig) {
527
+ __agiflowai_aicode_utils.print.info(`Downloading templates from ${repoConfig.owner}/${repoConfig.repo}...`);
528
+ try {
529
+ await fs_extra.ensureDir(this.tmpDir);
530
+ const contents = await fetchGitHubDirectoryContents(repoConfig.owner, repoConfig.repo, repoConfig.path, repoConfig.branch);
531
+ const templateDirs = contents.filter((item) => item.type === "dir");
532
+ const globalFiles = contents.filter((item) => item.type === "file" && item.name === "RULES.yaml");
533
+ if (templateDirs.length === 0) throw new Error("No templates found in repository");
534
+ __agiflowai_aicode_utils.print.info(`Found ${templateDirs.length} template(s), downloading...`);
535
+ for (const template of templateDirs) {
536
+ const targetFolder = node_path.default.join(this.tmpDir, template.name);
537
+ __agiflowai_aicode_utils.print.info(`Downloading ${template.name}...`);
538
+ await cloneSubdirectory(`https://github.com/${repoConfig.owner}/${repoConfig.repo}.git`, repoConfig.branch, template.path, targetFolder);
539
+ __agiflowai_aicode_utils.print.success(`Downloaded ${template.name}`);
540
+ }
541
+ if (globalFiles.length > 0) {
542
+ __agiflowai_aicode_utils.print.info("Downloading global RULES.yaml...");
543
+ const rulesUrl = `https://raw.githubusercontent.com/${repoConfig.owner}/${repoConfig.repo}/${repoConfig.branch}/${repoConfig.path}/RULES.yaml`;
544
+ const targetFile = node_path.default.join(this.tmpDir, "RULES.yaml");
545
+ const response = await fetch(rulesUrl);
546
+ if (response.ok) {
547
+ const content = await response.text();
548
+ await fs_extra.writeFile(targetFile, content, "utf-8");
549
+ __agiflowai_aicode_utils.print.success("Downloaded global RULES.yaml");
550
+ }
551
+ }
552
+ __agiflowai_aicode_utils.print.success(`\nAll templates downloaded to ${this.tmpDir}`);
553
+ return this.tmpDir;
554
+ } catch (error) {
555
+ await this.cleanup();
556
+ throw new Error(`Failed to download templates: ${error.message}`);
557
+ }
558
+ }
559
+ /**
560
+ * List available templates in the tmp directory
561
+ * @returns Array of template information
562
+ */
563
+ async listTemplates() {
564
+ try {
565
+ const entries = await fs_extra.readdir(this.tmpDir, { withFileTypes: true });
566
+ const templates = [];
567
+ for (const entry of entries) if (entry.isDirectory()) {
568
+ const templatePath = node_path.default.join(this.tmpDir, entry.name);
569
+ const description = await this.readTemplateDescription(templatePath);
570
+ templates.push({
571
+ name: entry.name,
572
+ path: templatePath,
573
+ description
574
+ });
575
+ }
576
+ return templates;
577
+ } catch (error) {
578
+ throw new Error(`Failed to list templates: ${error.message}`);
579
+ }
580
+ }
581
+ /**
582
+ * Copy selected templates to destination
583
+ * @param templateNames - Names of templates to copy
584
+ * @param destinationPath - Destination templates folder path
585
+ * @param projectType - Project type (monolith allows only single template)
586
+ * @param selectedMcpServers - Optional array of selected MCP servers to filter files
587
+ */
588
+ async copyTemplates(templateNames, destinationPath, projectType, selectedMcpServers) {
589
+ try {
590
+ if (projectType === __agiflowai_aicode_utils.ProjectType.MONOLITH && templateNames.length > 1) throw new Error("Monolith projects can only use a single template");
591
+ await fs_extra.ensureDir(destinationPath);
592
+ __agiflowai_aicode_utils.print.info(`\nCopying templates to ${destinationPath}...`);
593
+ for (const templateName of templateNames) {
594
+ const sourcePath = node_path.default.join(this.tmpDir, templateName);
595
+ const targetPath = node_path.default.join(destinationPath, templateName);
596
+ if (!await fs_extra.pathExists(sourcePath)) throw new Error(`Template '${templateName}' not found in downloaded templates`);
597
+ if (await fs_extra.pathExists(targetPath)) {
598
+ __agiflowai_aicode_utils.print.info(`Skipping ${templateName} (already exists)`);
599
+ continue;
600
+ }
601
+ __agiflowai_aicode_utils.print.info(`Copying ${templateName}...`);
602
+ if (selectedMcpServers && selectedMcpServers.length > 0) await this.copyTemplateWithMcpFilter(sourcePath, targetPath, selectedMcpServers);
603
+ else await fs_extra.copy(sourcePath, targetPath);
604
+ __agiflowai_aicode_utils.print.success(`Copied ${templateName}`);
605
+ }
606
+ const globalRulesSource = node_path.default.join(this.tmpDir, "RULES.yaml");
607
+ const globalRulesTarget = node_path.default.join(destinationPath, "RULES.yaml");
608
+ if (await fs_extra.pathExists(globalRulesSource)) {
609
+ if (!await fs_extra.pathExists(globalRulesTarget)) {
610
+ __agiflowai_aicode_utils.print.info("Copying global RULES.yaml...");
611
+ await fs_extra.copy(globalRulesSource, globalRulesTarget);
612
+ __agiflowai_aicode_utils.print.success("Copied global RULES.yaml");
613
+ }
614
+ }
615
+ __agiflowai_aicode_utils.print.success("\nTemplates copied successfully!");
616
+ } catch (error) {
617
+ throw new Error(`Failed to copy templates: ${error.message}`);
618
+ }
619
+ }
620
+ /**
621
+ * Copy template files with MCP server filtering
622
+ * @param sourcePath - Source template path
623
+ * @param targetPath - Target template path
624
+ * @param selectedMcpServers - Selected MCP servers
625
+ */
626
+ async copyTemplateWithMcpFilter(sourcePath, targetPath, selectedMcpServers) {
627
+ const { MCPServer: MCPServer$1, MCP_CONFIG_FILES: MCP_CONFIG_FILES$1 } = await Promise.resolve().then(() => require("./mcp-Bdxvi2Ej.cjs"));
628
+ const architectFiles = MCP_CONFIG_FILES$1[MCPServer$1.ARCHITECT];
629
+ const hasArchitect = selectedMcpServers.includes(MCPServer$1.ARCHITECT);
630
+ const hasScaffold = selectedMcpServers.includes(MCPServer$1.SCAFFOLD);
631
+ await fs_extra.ensureDir(targetPath);
632
+ const entries = await fs_extra.readdir(sourcePath, { withFileTypes: true });
633
+ for (const entry of entries) {
634
+ const entrySourcePath = node_path.default.join(sourcePath, entry.name);
635
+ const entryTargetPath = node_path.default.join(targetPath, entry.name);
636
+ const isArchitectFile = architectFiles.includes(entry.name);
637
+ if (hasArchitect && hasScaffold) if (entry.isDirectory()) await fs_extra.copy(entrySourcePath, entryTargetPath);
638
+ else await fs_extra.copy(entrySourcePath, entryTargetPath);
639
+ else if (hasArchitect && !hasScaffold) {
640
+ if (isArchitectFile) await fs_extra.copy(entrySourcePath, entryTargetPath);
641
+ } else if (!hasArchitect && hasScaffold) {
642
+ if (!isArchitectFile) if (entry.isDirectory()) await fs_extra.copy(entrySourcePath, entryTargetPath);
643
+ else await fs_extra.copy(entrySourcePath, entryTargetPath);
644
+ }
645
+ }
646
+ }
647
+ /**
648
+ * Read template description from README or scaffold.yaml
649
+ * @param templatePath - Path to the template directory
650
+ * @returns Description string or undefined
651
+ */
652
+ async readTemplateDescription(templatePath) {
653
+ try {
654
+ const scaffoldYamlPath = node_path.default.join(templatePath, "scaffold.yaml");
655
+ if (await fs_extra.pathExists(scaffoldYamlPath)) {
656
+ const yaml = await import("js-yaml");
657
+ const content = await fs_extra.readFile(scaffoldYamlPath, "utf-8");
658
+ const scaffoldConfig = yaml.load(content);
659
+ if (scaffoldConfig?.description) return scaffoldConfig.description;
660
+ if (scaffoldConfig?.boilerplate?.[0]?.description) return scaffoldConfig.boilerplate[0].description;
661
+ }
662
+ const readmePath = node_path.default.join(templatePath, "README.md");
663
+ if (await fs_extra.pathExists(readmePath)) return (await fs_extra.readFile(readmePath, "utf-8")).split("\n\n")[0].substring(0, 200).trim();
664
+ return;
665
+ } catch {
666
+ return;
667
+ }
668
+ }
669
+ /**
670
+ * Get the tmp directory path
671
+ */
672
+ getTmpDir() {
673
+ return this.tmpDir;
674
+ }
675
+ /**
676
+ * Clean up tmp directory
677
+ */
678
+ async cleanup() {
679
+ try {
680
+ if (await fs_extra.pathExists(this.tmpDir)) {
681
+ await fs_extra.remove(this.tmpDir);
682
+ __agiflowai_aicode_utils.print.info("Cleaned up temporary files");
683
+ }
684
+ } catch (error) {
685
+ __agiflowai_aicode_utils.print.warning(`Warning: Failed to clean up tmp directory: ${error.message}`);
686
+ }
687
+ }
688
+ };
689
+
690
+ //#endregion
691
+ //#region src/services/TemplatesService.ts
692
+ var TemplatesService = class {
693
+ /**
694
+ * Download templates from a GitHub repository with UI feedback
695
+ * @param templatesPath - Local path where templates should be downloaded
696
+ * @param repoConfig - Repository configuration (owner, repo, branch, path)
697
+ */
698
+ async downloadTemplates(templatesPath, repoConfig) {
699
+ __agiflowai_aicode_utils.print.info(`Fetching templates from ${repoConfig.owner}/${repoConfig.repo}...`);
700
+ try {
701
+ const templateDirs = (await fetchGitHubDirectoryContents(repoConfig.owner, repoConfig.repo, repoConfig.path, repoConfig.branch)).filter((item) => item.type === "dir");
702
+ if (templateDirs.length === 0) {
703
+ __agiflowai_aicode_utils.messages.warning("No templates found in repository");
704
+ return;
705
+ }
706
+ __agiflowai_aicode_utils.print.info(`Found ${templateDirs.length} template(s)`);
707
+ let downloaded = 0;
708
+ let skipped = 0;
709
+ for (const template of templateDirs) {
710
+ const targetFolder = node_path.default.join(templatesPath, template.name);
711
+ if (await fs_extra.pathExists(targetFolder)) {
712
+ __agiflowai_aicode_utils.print.info(`Skipping ${template.name} (already exists)`);
713
+ skipped++;
714
+ continue;
715
+ }
716
+ __agiflowai_aicode_utils.print.info(`Downloading ${template.name}...`);
717
+ await cloneSubdirectory(`https://github.com/${repoConfig.owner}/${repoConfig.repo}.git`, repoConfig.branch, template.path, targetFolder);
718
+ __agiflowai_aicode_utils.print.success(`Downloaded ${template.name}`);
719
+ downloaded++;
720
+ }
721
+ __agiflowai_aicode_utils.print.success("\nAll templates downloaded successfully!");
722
+ } catch (error) {
723
+ throw new Error(`Failed to download templates: ${error.message}`);
724
+ }
725
+ }
726
+ /**
727
+ * Initialize templates folder with README
728
+ * @param templatesPath - Path where templates folder should be created
729
+ */
730
+ async initializeTemplatesFolder(templatesPath) {
731
+ await fs_extra.ensureDir(templatesPath);
732
+ await fs_extra.writeFile(node_path.default.join(templatesPath, "README.md"), `# Templates
733
+
734
+ This folder contains boilerplate templates and scaffolding methods for your projects.
735
+
736
+ ## Templates
737
+
738
+ Templates are organized by framework/technology and include configuration files (\`scaffold.yaml\`) that define:
739
+ - Boilerplates: Full project starter templates
740
+ - Features: Code scaffolding methods for adding new features to existing projects
741
+
742
+ ## Adding More Templates
743
+
744
+ Use the \`add\` command to add templates from remote repositories:
745
+
746
+ \`\`\`bash
747
+ scaffold-mcp add --name my-template --url https://github.com/user/template
748
+ \`\`\`
749
+
750
+ Or add templates from subdirectories:
751
+
752
+ \`\`\`bash
753
+ scaffold-mcp add --name nextjs-template --url https://github.com/user/repo/tree/main/templates/nextjs
754
+ \`\`\`
755
+
756
+ ## Creating Custom Templates
757
+
758
+ Each template should have a \`scaffold.yaml\` configuration file defining:
759
+ - \`boilerplate\`: Array of boilerplate configurations
760
+ - \`features\`: Array of feature scaffold configurations
761
+
762
+ Template files use Liquid syntax for variable placeholders: \`{{ variableName }}\`
763
+
764
+ See existing templates for examples and documentation for more details.
765
+ `);
766
+ }
767
+ };
768
+
769
+ //#endregion
770
+ Object.defineProperty(exports, 'BANNER_GRADIENT', {
771
+ enumerable: true,
772
+ get: function () {
773
+ return BANNER_GRADIENT;
774
+ }
775
+ });
776
+ Object.defineProperty(exports, 'CodingAgentService', {
777
+ enumerable: true,
778
+ get: function () {
779
+ return CodingAgentService;
780
+ }
781
+ });
782
+ Object.defineProperty(exports, 'NewProjectService', {
783
+ enumerable: true,
784
+ get: function () {
785
+ return NewProjectService;
786
+ }
787
+ });
788
+ Object.defineProperty(exports, 'THEME', {
789
+ enumerable: true,
790
+ get: function () {
791
+ return THEME;
792
+ }
793
+ });
794
+ Object.defineProperty(exports, 'TemplateSelectionService', {
795
+ enumerable: true,
796
+ get: function () {
797
+ return TemplateSelectionService;
798
+ }
799
+ });
800
+ Object.defineProperty(exports, 'TemplatesService', {
801
+ enumerable: true,
802
+ get: function () {
803
+ return TemplatesService;
804
+ }
805
+ });
806
+ Object.defineProperty(exports, '__toESM', {
807
+ enumerable: true,
808
+ get: function () {
809
+ return __toESM;
810
+ }
811
+ });
812
+ Object.defineProperty(exports, 'cloneRepository', {
813
+ enumerable: true,
814
+ get: function () {
815
+ return cloneRepository;
816
+ }
817
+ });
818
+ Object.defineProperty(exports, 'cloneSubdirectory', {
819
+ enumerable: true,
820
+ get: function () {
821
+ return cloneSubdirectory;
822
+ }
823
+ });
824
+ Object.defineProperty(exports, 'displayBanner', {
825
+ enumerable: true,
826
+ get: function () {
827
+ return displayBanner;
828
+ }
829
+ });
830
+ Object.defineProperty(exports, 'displayCompactBanner', {
831
+ enumerable: true,
832
+ get: function () {
833
+ return displayCompactBanner;
834
+ }
835
+ });
836
+ Object.defineProperty(exports, 'fetchGitHubDirectoryContents', {
837
+ enumerable: true,
838
+ get: function () {
839
+ return fetchGitHubDirectoryContents;
840
+ }
841
+ });
842
+ Object.defineProperty(exports, 'findWorkspaceRoot', {
843
+ enumerable: true,
844
+ get: function () {
845
+ return findWorkspaceRoot;
846
+ }
847
+ });
848
+ Object.defineProperty(exports, 'gitInit', {
849
+ enumerable: true,
850
+ get: function () {
851
+ return gitInit;
852
+ }
853
+ });
854
+ Object.defineProperty(exports, 'parseGitHubUrl', {
855
+ enumerable: true,
856
+ get: function () {
857
+ return parseGitHubUrl;
858
+ }
859
+ });