@agiflowai/aicode-toolkit 0.6.0 → 1.0.1

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.
@@ -1,10 +1,12 @@
1
1
  import path from "node:path";
2
2
  import { ProjectType, messages, print } from "@agiflowai/aicode-utils";
3
- import * as fs from "fs-extra";
3
+ import * as fs$1 from "fs-extra";
4
4
  import chalk from "chalk";
5
5
  import gradient from "gradient-string";
6
6
  import { execa } from "execa";
7
- import { CLAUDE_CODE, CODEX, ClaudeCodeService, CodexService, GEMINI_CLI, GeminiCliService, NONE } from "@agiflowai/coding-agent-bridge";
7
+ import { CLAUDE_CODE, CODEX, CURSOR, ClaudeCodeService, CodexService, CursorService, GEMINI_CLI, GITHUB_COPILOT, GeminiCliService, GitHubCopilotService, NONE } from "@agiflowai/coding-agent-bridge";
8
+ import fs from "node:fs/promises";
9
+ import { Liquid } from "liquidjs";
8
10
  import os from "node:os";
9
11
 
10
12
  //#region src/constants/theme.ts
@@ -144,7 +146,7 @@ async function findWorkspaceRoot(startPath = process.cwd()) {
144
146
  const rootPath = path.parse(currentPath).root;
145
147
  while (true) {
146
148
  const gitPath = path.join(currentPath, ".git");
147
- if (await fs.pathExists(gitPath)) return currentPath;
149
+ if (await fs$1.pathExists(gitPath)) return currentPath;
148
150
  if (currentPath === rootPath) return null;
149
151
  currentPath = path.dirname(currentPath);
150
152
  }
@@ -163,11 +165,11 @@ function parseGitHubUrl(url) {
163
165
  if (treeMatch || blobMatch) {
164
166
  const match = treeMatch || blobMatch;
165
167
  return {
166
- owner: match[1],
167
- repo: match[2],
168
- repoUrl: `https://github.com/${match[1]}/${match[2]}.git`,
169
- branch: match[3],
170
- subdirectory: match[4],
168
+ owner: match?.[1],
169
+ repo: match?.[2],
170
+ repoUrl: `https://github.com/${match?.[1]}/${match?.[2]}.git`,
171
+ branch: match?.[3],
172
+ subdirectory: match?.[4],
171
173
  isSubdirectory: true
172
174
  };
173
175
  }
@@ -201,7 +203,7 @@ async function cloneSubdirectory(repoUrl, branch, subdirectory, targetFolder) {
201
203
  "true"
202
204
  ], tempFolder);
203
205
  const sparseCheckoutFile = path.join(tempFolder, ".git", "info", "sparse-checkout");
204
- await fs.writeFile(sparseCheckoutFile, `${subdirectory}\n`);
206
+ await fs$1.writeFile(sparseCheckoutFile, `${subdirectory}\n`);
205
207
  await execGit([
206
208
  "pull",
207
209
  "--depth=1",
@@ -209,12 +211,12 @@ async function cloneSubdirectory(repoUrl, branch, subdirectory, targetFolder) {
209
211
  branch
210
212
  ], tempFolder);
211
213
  const sourceDir = path.join(tempFolder, subdirectory);
212
- if (!await fs.pathExists(sourceDir)) throw new Error(`Subdirectory '${subdirectory}' not found in repository at branch '${branch}'`);
213
- if (await fs.pathExists(targetFolder)) throw new Error(`Target folder already exists: ${targetFolder}`);
214
- await fs.move(sourceDir, targetFolder);
215
- await fs.remove(tempFolder);
214
+ if (!await fs$1.pathExists(sourceDir)) throw new Error(`Subdirectory '${subdirectory}' not found in repository at branch '${branch}'`);
215
+ if (await fs$1.pathExists(targetFolder)) throw new Error(`Target folder already exists: ${targetFolder}`);
216
+ await fs$1.move(sourceDir, targetFolder);
217
+ await fs$1.remove(tempFolder);
216
218
  } catch (error) {
217
- if (await fs.pathExists(tempFolder)) await fs.remove(tempFolder);
219
+ if (await fs$1.pathExists(tempFolder)) await fs$1.remove(tempFolder);
218
220
  throw error;
219
221
  }
220
222
  }
@@ -228,7 +230,7 @@ async function cloneRepository(repoUrl, targetFolder) {
228
230
  targetFolder
229
231
  ]);
230
232
  const gitFolder = path.join(targetFolder, ".git");
231
- if (await fs.pathExists(gitFolder)) await fs.remove(gitFolder);
233
+ if (await fs$1.pathExists(gitFolder)) await fs$1.remove(gitFolder);
232
234
  }
233
235
  /**
234
236
  * Fetch directory listing from GitHub API
@@ -258,12 +260,14 @@ var CodingAgentService = class {
258
260
  }
259
261
  /**
260
262
  * Detect which coding agent is enabled in the workspace
261
- * Checks for Claude Code, Codex, and Gemini CLI installations
263
+ * Checks for Claude Code, Codex, Gemini CLI, GitHub Copilot, and Cursor installations
262
264
  * @param workspaceRoot - The workspace root directory
263
265
  * @returns Promise resolving to detected agent ID or null
264
266
  */
265
267
  static async detectCodingAgent(workspaceRoot) {
266
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;
267
271
  if (await new CodexService({ workspaceRoot }).isEnabled()) return CODEX;
268
272
  if (await new GeminiCliService({ workspaceRoot }).isEnabled()) return GEMINI_CLI;
269
273
  return null;
@@ -278,6 +282,16 @@ var CodingAgentService = class {
278
282
  name: "Claude Code",
279
283
  description: "Anthropic Claude Code CLI agent"
280
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
+ },
281
295
  {
282
296
  value: CODEX,
283
297
  name: "Codex",
@@ -296,6 +310,48 @@ var CodingAgentService = class {
296
310
  ];
297
311
  }
298
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
+ /**
299
355
  * Setup MCP configuration for the selected coding agent
300
356
  * @param agent - The coding agent to configure
301
357
  */
@@ -305,19 +361,22 @@ var CodingAgentService = class {
305
361
  return;
306
362
  }
307
363
  print.info(`\nSetting up MCP for ${agent}...`);
308
- let service = null;
364
+ const service = this.getCodingAgentService(agent);
309
365
  let configLocation = "";
310
366
  let restartInstructions = "";
311
367
  if (agent === CLAUDE_CODE) {
312
- service = new ClaudeCodeService({ workspaceRoot: this.workspaceRoot });
313
368
  configLocation = ".mcp.json";
314
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";
315
376
  } else if (agent === CODEX) {
316
- service = new CodexService({ workspaceRoot: this.workspaceRoot });
317
377
  configLocation = "~/.codex/config.toml";
318
378
  restartInstructions = "Restart Codex CLI to load the new MCP servers";
319
379
  } else if (agent === GEMINI_CLI) {
320
- service = new GeminiCliService({ workspaceRoot: this.workspaceRoot });
321
380
  configLocation = "~/.gemini/settings.json";
322
381
  restartInstructions = "Restart Gemini CLI to load the new MCP servers";
323
382
  }
@@ -431,8 +490,7 @@ var NewProjectService = class {
431
490
  */
432
491
  async createProjectDirectory(projectPath, projectName) {
433
492
  try {
434
- await fs.mkdir(projectPath, { recursive: false });
435
- print.success(`Created project directory: ${projectPath}`);
493
+ await fs$1.mkdir(projectPath, { recursive: false });
436
494
  } catch (error) {
437
495
  if (error.code === "EEXIST") throw new Error(`Directory '${projectName}' already exists. Please choose a different name.`);
438
496
  throw error;
@@ -444,14 +502,12 @@ var NewProjectService = class {
444
502
  * @param projectPath - Destination path for the cloned repository
445
503
  */
446
504
  async cloneExistingRepository(repoUrl, projectPath) {
447
- print.info("Cloning repository...");
448
505
  try {
449
506
  const parsed = parseGitHubUrl(repoUrl.trim());
450
507
  if (parsed.isSubdirectory && parsed.branch && parsed.subdirectory) await cloneSubdirectory(parsed.repoUrl, parsed.branch, parsed.subdirectory, projectPath);
451
508
  else await cloneRepository(parsed.repoUrl, projectPath);
452
- print.success("Repository cloned successfully");
453
509
  } catch (error) {
454
- await fs.remove(projectPath);
510
+ await fs$1.remove(projectPath);
455
511
  throw new Error(`Failed to clone repository: ${error.message}`);
456
512
  }
457
513
  }
@@ -460,10 +516,8 @@ var NewProjectService = class {
460
516
  * @param projectPath - Path where git repository should be initialized
461
517
  */
462
518
  async initializeGitRepository(projectPath) {
463
- print.info("Initializing Git repository...");
464
519
  try {
465
520
  await gitInit(projectPath);
466
- print.success("Git repository initialized");
467
521
  } catch (error) {
468
522
  messages.warning(`Failed to initialize Git: ${error.message}`);
469
523
  }
@@ -480,6 +534,153 @@ var NewProjectService = class {
480
534
  }
481
535
  };
482
536
 
537
+ //#endregion
538
+ //#region src/instructions/specs/openspec.md?raw
539
+ var openspec_default = "When working on this project, follow the OpenSpec spec-driven development workflow{% if scaffoldMcp or architectMcp %} integrated with MCP tools{% endif %}.\n\n{% if scaffoldMcp %}\n\n## {% if scaffoldMcp %}1{% else %}1{% endif %}. Create Proposals with scaffold-mcp\n\nWhen implementing new features or changes, use scaffold-mcp MCP tools:\n\n**For new projects/features:**\n1. Use `list-boilerplates` MCP tool to see available templates\n2. Use `use-boilerplate` MCP tool to scaffold new projects{% if projectType == 'monolith' %} (set `monolith: true`){% elsif projectType == 'monorepo' %} (omit `monolith` parameter){% endif %}\n3. Use `list-scaffolding-methods` MCP tool to understand which methods can be used to add features\n4. Create OpenSpec proposal with available scaffolding methods in mind: \"Create an OpenSpec proposal for [feature description]\"\n\n**For adding features to existing code:**\n1. Use `list-scaffolding-methods` MCP tool with projectPath to see available features{% if projectType == 'monolith' %} (`projectPath` = workspace root){% elsif projectType == 'monorepo' %} (`projectPath` = project directory with project.json){% endif %}\n2. Review available methods and plan which ones to use for the feature\n3. Use `use-scaffold-method` MCP tool to generate boilerplate code\n4. Create OpenSpec proposal to capture the specs\n{% if projectType %}\n\n**Path Mapping ({% if projectType == 'monolith' %}Monolith{% else %}Monorepo{% endif %} Project):**\n{% if projectType == 'monolith' %}\n*This is a monolith project:*\n- Single project at workspace root\n- Config file: `toolkit.yaml` at root\n- All code in workspace root (src/, lib/, etc.)\n- When scaffolding: Use `monolith: true` parameter\n- When using scaffold methods: `projectPath` = workspace root\n\nExample: For workspace at `/path/to/project`:\n- Project config: `/path/to/project/toolkit.yaml`\n- Source code: `/path/to/project/src/`\n- OpenSpec: `/path/to/project/openspec/`\n{% else %}\n*This is a monorepo project:*\n- Multiple projects in subdirectories\n- Config file: `project.json` in each project\n- Projects in apps/, packages/, libs/, etc.\n- When scaffolding: Omit `monolith` parameter (defaults to false)\n- When using scaffold methods: `projectPath` = path to specific project\n\nExample: For workspace at `/path/to/workspace`:\n- App config: `/path/to/workspace/apps/my-app/project.json`\n- App source: `/path/to/workspace/apps/my-app/src/`\n- OpenSpec: `/path/to/workspace/openspec/` (workspace-level)\n{% endif %}\n{% endif %}\n\nAI will scaffold: openspec/changes/[feature-name]/ with proposal.md, tasks.md, and spec deltas\n{% endif %}\n{% if architectMcp %}\n\n## {% if scaffoldMcp %}2{% else %}1{% endif %}. Review & Validate with architect-mcp\n\nBefore and after editing files, use architect-mcp MCP tools:\n\n**Before editing:**\n- Use `get-file-design-pattern` MCP tool to understand:\n - Applicable design patterns from architect.yaml\n - Coding rules from RULES.yaml (must_do, should_do, must_not_do)\n - Code examples showing the patterns\n\n**After editing:**\n- Use `review-code-change` MCP tool to check for:\n - Must not do violations (critical issues)\n - Must do missing (required patterns not followed)\n - Should do suggestions (best practices)\n\n**Validate OpenSpec specs:**\n- Use `openspec validate [feature-name]` to check spec formatting\n- Iterate with AI until specs are agreed upon\n{% endif %}\n\n## {% if scaffoldMcp and architectMcp %}3{% elsif scaffoldMcp or architectMcp %}2{% else %}1{% endif %}. Implement{% if architectMcp %} with MCP-Guided Development{% endif %}\n\nDuring implementation:\n1. Ask AI to implement: \"Apply the OpenSpec change [feature-name]\"\n{% if architectMcp %}\n2. **Before each file edit**: Use `get-file-design-pattern` to understand patterns\n3. AI implements tasks from tasks.md following design patterns\n4. **After each file edit**: Use `review-code-change` to verify compliance\n5. Fix any violations before proceeding\n{% else %}\n2. AI implements tasks from tasks.md following the agreed specs\n{% endif %}\n\n## {% if scaffoldMcp and architectMcp %}4{% elsif scaffoldMcp or architectMcp %}3{% else %}2{% endif %}. Archive Completed Changes\n{% if scaffoldMcp or architectMcp %}\n\n## MCP Tools Reference\n{% endif %}\n{% if scaffoldMcp %}\n\n### scaffold-mcp\n- `list-boilerplates` - List available project templates\n- `use-boilerplate` - Create new project from template\n- `list-scaffolding-methods` - List features for existing project\n- `use-scaffold-method` - Add feature to existing project\n{% endif %}\n{% if architectMcp %}\n\n### architect-mcp\n- `get-file-design-pattern` - Get design patterns for file\n- `review-code-change` - Review code for violations\n{% endif %}\n\n## Workflow Summary\n\n{% if scaffoldMcp %}\n1. **Plan**: Use scaffold-mcp to generate boilerplate + OpenSpec proposal for specs\n{% else %}\n1. **Plan**: Create OpenSpec proposal with specs\n{% endif %}\n{% if architectMcp %}\n{% if scaffoldMcp %}2{% else %}2{% endif %}. **Design**: Use architect-mcp to understand patterns before editing\n{% endif %}\n{% if scaffoldMcp and architectMcp %}3{% elsif scaffoldMcp or architectMcp %}{% if architectMcp %}3{% else %}2{% endif %}{% else %}2{% endif %}. **Implement**: Follow specs{% if architectMcp %} and patterns{% endif %}\n{% if architectMcp %}\n{% if scaffoldMcp %}4{% else %}4{% endif %}. **Review**: Use architect-mcp to validate code quality\n{% endif %}\n{% if scaffoldMcp and architectMcp %}5{% elsif scaffoldMcp or architectMcp %}{% if architectMcp %}5{% else %}3{% endif %}{% else %}3{% endif %}. **Archive**: Merge specs into source of truth\n";
540
+
541
+ //#endregion
542
+ //#region src/specs/openspec.ts
543
+ /**
544
+ * OpenSpec configuration
545
+ */
546
+ const OPENSPEC_CONFIG = {
547
+ folderName: "openspec",
548
+ packageName: "@fission-ai/openspec",
549
+ name: "OpenSpec",
550
+ description: "Spec-driven development for AI coding assistants"
551
+ };
552
+ /**
553
+ * Bridge implementation for OpenSpec
554
+ *
555
+ * Provides integration with OpenSpec spec tool through a standardized interface.
556
+ */
557
+ var OpenSpecBridge = class {
558
+ /**
559
+ * Check if OpenSpec is enabled/installed in the workspace
560
+ *
561
+ * @param workspaceRoot - Absolute path to the workspace root directory
562
+ * @returns Promise resolving to true if OpenSpec folder exists, false otherwise
563
+ */
564
+ async isEnabled(workspaceRoot) {
565
+ try {
566
+ const openspecPath = path.join(workspaceRoot, OPENSPEC_CONFIG.folderName);
567
+ await fs.access(openspecPath);
568
+ return true;
569
+ } catch {
570
+ return false;
571
+ }
572
+ }
573
+ /**
574
+ * Initialize OpenSpec in the workspace
575
+ *
576
+ * Runs `npx @fission-ai/openspec init` interactively, allowing the user
577
+ * to configure OpenSpec during setup.
578
+ *
579
+ * @param workspaceRoot - Absolute path to the workspace root directory
580
+ * @throws Error if initialization fails
581
+ */
582
+ async initialize(workspaceRoot) {
583
+ try {
584
+ await execa("npx", [OPENSPEC_CONFIG.packageName, "init"], {
585
+ cwd: workspaceRoot,
586
+ stdio: "inherit"
587
+ });
588
+ } catch (error) {
589
+ throw new Error(`Failed to initialize ${OPENSPEC_CONFIG.name}: ${error.message}`);
590
+ }
591
+ }
592
+ /**
593
+ * Generate agent instruction prompt for OpenSpec with MCP integration
594
+ *
595
+ * Creates a comprehensive instruction prompt that guides AI agents to use
596
+ * spec-driven development workflow with OpenSpec, leveraging enabled MCP servers.
597
+ *
598
+ * @param enabledMcps - Configuration of which MCP servers are enabled
599
+ * @returns Promise resolving to the instruction prompt string
600
+ */
601
+ async updateInstruction(enabledMcps) {
602
+ return (await new Liquid().parseAndRender(openspec_default, enabledMcps)).trim();
603
+ }
604
+ };
605
+
606
+ //#endregion
607
+ //#region src/services/SpecToolService.ts
608
+ /**
609
+ * Available spec tools
610
+ */
611
+ let SpecTool = /* @__PURE__ */ function(SpecTool$1) {
612
+ SpecTool$1["OPENSPEC"] = "openspec";
613
+ return SpecTool$1;
614
+ }({});
615
+ /**
616
+ * Spec tool information
617
+ */
618
+ const SPEC_TOOL_INFO = { [SpecTool.OPENSPEC]: {
619
+ name: "OpenSpec",
620
+ description: "Spec-driven development for AI coding assistants",
621
+ docUrl: "https://github.com/Fission-AI/OpenSpec"
622
+ } };
623
+ /**
624
+ * Service for managing spec tools (e.g., OpenSpec)
625
+ *
626
+ * This service acts as a facade for spec tool operations, using bridge
627
+ * implementations to interact with specific spec tools.
628
+ */
629
+ var SpecToolService = class {
630
+ bridge;
631
+ constructor(workspaceRoot, specTool = SpecTool.OPENSPEC, codingAgentService) {
632
+ this.workspaceRoot = workspaceRoot;
633
+ this.codingAgentService = codingAgentService;
634
+ this.bridge = this.createBridge(specTool);
635
+ }
636
+ /**
637
+ * Create a bridge instance for the specified spec tool
638
+ */
639
+ createBridge(specTool) {
640
+ switch (specTool) {
641
+ case SpecTool.OPENSPEC: return new OpenSpecBridge();
642
+ default: throw new Error(`Unsupported spec tool: ${specTool}`);
643
+ }
644
+ }
645
+ /**
646
+ * Detect if a spec tool is installed in the workspace
647
+ */
648
+ async detectSpecTool() {
649
+ try {
650
+ return await this.bridge.isEnabled(this.workspaceRoot) ? SpecTool.OPENSPEC : null;
651
+ } catch (error) {
652
+ print.error(`Error detecting spec tool: ${error.message}`);
653
+ return null;
654
+ }
655
+ }
656
+ /**
657
+ * Initialize the spec tool in the workspace
658
+ */
659
+ async initializeSpec() {
660
+ await this.bridge.initialize(this.workspaceRoot);
661
+ }
662
+ /**
663
+ * Update spec tool agent instructions
664
+ *
665
+ * Generates instruction prompt and updates the coding agent's custom instructions
666
+ *
667
+ * @param enabledMcps - Configuration of which MCP servers are enabled
668
+ * @param codingAgent - The coding agent to update instructions for
669
+ * @param customInstructionFile - Optional custom file to write instructions to (e.g., '.claude/aicode-instructions.md')
670
+ * @returns Promise resolving to the instruction prompt string
671
+ */
672
+ async updateInstructions(enabledMcps, codingAgent, customInstructionFile) {
673
+ const prompt = await this.bridge.updateInstruction(enabledMcps);
674
+ if (this.codingAgentService && codingAgent) await this.codingAgentService.updateCustomInstructions(codingAgent, prompt, customInstructionFile ?? ".claude/aicode-instructions.md");
675
+ else {
676
+ print.info("\nGenerated OpenSpec instruction prompt:");
677
+ print.info("\nYou can append this to your CLAUDE.md or agent config file:");
678
+ print.info(`\n${prompt}`);
679
+ }
680
+ return prompt;
681
+ }
682
+ };
683
+
483
684
  //#endregion
484
685
  //#region src/services/TemplateSelectionService.ts
485
686
  var TemplateSelectionService = class {
@@ -493,9 +694,8 @@ var TemplateSelectionService = class {
493
694
  * @returns Path to the tmp directory containing templates
494
695
  */
495
696
  async downloadTemplatesToTmp(repoConfig) {
496
- print.info(`Downloading templates from ${repoConfig.owner}/${repoConfig.repo}...`);
497
697
  try {
498
- await fs.ensureDir(this.tmpDir);
698
+ await fs$1.ensureDir(this.tmpDir);
499
699
  const contents = await fetchGitHubDirectoryContents(repoConfig.owner, repoConfig.repo, repoConfig.path, repoConfig.branch);
500
700
  const templateDirs = contents.filter((item) => item.type === "dir");
501
701
  const globalFiles = contents.filter((item) => item.type === "file" && item.name === "RULES.yaml");
@@ -514,7 +714,7 @@ var TemplateSelectionService = class {
514
714
  const response = await fetch(rulesUrl);
515
715
  if (response.ok) {
516
716
  const content = await response.text();
517
- await fs.writeFile(targetFile, content, "utf-8");
717
+ await fs$1.writeFile(targetFile, content, "utf-8");
518
718
  print.success("Downloaded global RULES.yaml");
519
719
  }
520
720
  }
@@ -531,7 +731,7 @@ var TemplateSelectionService = class {
531
731
  */
532
732
  async listTemplates() {
533
733
  try {
534
- const entries = await fs.readdir(this.tmpDir, { withFileTypes: true });
734
+ const entries = await fs$1.readdir(this.tmpDir, { withFileTypes: true });
535
735
  const templates = [];
536
736
  for (const entry of entries) if (entry.isDirectory()) {
537
737
  const templatePath = path.join(this.tmpDir, entry.name);
@@ -557,27 +757,27 @@ var TemplateSelectionService = class {
557
757
  async copyTemplates(templateNames, destinationPath, projectType, selectedMcpServers) {
558
758
  try {
559
759
  if (projectType === ProjectType.MONOLITH && templateNames.length > 1) throw new Error("Monolith projects can only use a single template");
560
- await fs.ensureDir(destinationPath);
760
+ await fs$1.ensureDir(destinationPath);
561
761
  print.info(`\nCopying templates to ${destinationPath}...`);
562
762
  for (const templateName of templateNames) {
563
763
  const sourcePath = path.join(this.tmpDir, templateName);
564
764
  const targetPath = path.join(destinationPath, templateName);
565
- if (!await fs.pathExists(sourcePath)) throw new Error(`Template '${templateName}' not found in downloaded templates`);
566
- if (await fs.pathExists(targetPath)) {
765
+ if (!await fs$1.pathExists(sourcePath)) throw new Error(`Template '${templateName}' not found in downloaded templates`);
766
+ if (await fs$1.pathExists(targetPath)) {
567
767
  print.info(`Skipping ${templateName} (already exists)`);
568
768
  continue;
569
769
  }
570
770
  print.info(`Copying ${templateName}...`);
571
771
  if (selectedMcpServers && selectedMcpServers.length > 0) await this.copyTemplateWithMcpFilter(sourcePath, targetPath, selectedMcpServers);
572
- else await fs.copy(sourcePath, targetPath);
772
+ else await fs$1.copy(sourcePath, targetPath);
573
773
  print.success(`Copied ${templateName}`);
574
774
  }
575
775
  const globalRulesSource = path.join(this.tmpDir, "RULES.yaml");
576
776
  const globalRulesTarget = path.join(destinationPath, "RULES.yaml");
577
- if (await fs.pathExists(globalRulesSource)) {
578
- if (!await fs.pathExists(globalRulesTarget)) {
777
+ if (await fs$1.pathExists(globalRulesSource)) {
778
+ if (!await fs$1.pathExists(globalRulesTarget)) {
579
779
  print.info("Copying global RULES.yaml...");
580
- await fs.copy(globalRulesSource, globalRulesTarget);
780
+ await fs$1.copy(globalRulesSource, globalRulesTarget);
581
781
  print.success("Copied global RULES.yaml");
582
782
  }
583
783
  }
@@ -597,19 +797,19 @@ var TemplateSelectionService = class {
597
797
  const architectFiles = MCP_CONFIG_FILES$1[MCPServer$1.ARCHITECT];
598
798
  const hasArchitect = selectedMcpServers.includes(MCPServer$1.ARCHITECT);
599
799
  const hasScaffold = selectedMcpServers.includes(MCPServer$1.SCAFFOLD);
600
- await fs.ensureDir(targetPath);
601
- const entries = await fs.readdir(sourcePath, { withFileTypes: true });
800
+ await fs$1.ensureDir(targetPath);
801
+ const entries = await fs$1.readdir(sourcePath, { withFileTypes: true });
602
802
  for (const entry of entries) {
603
803
  const entrySourcePath = path.join(sourcePath, entry.name);
604
804
  const entryTargetPath = path.join(targetPath, entry.name);
605
805
  const isArchitectFile = architectFiles.includes(entry.name);
606
- if (hasArchitect && hasScaffold) if (entry.isDirectory()) await fs.copy(entrySourcePath, entryTargetPath);
607
- else await fs.copy(entrySourcePath, entryTargetPath);
806
+ if (hasArchitect && hasScaffold) if (entry.isDirectory()) await fs$1.copy(entrySourcePath, entryTargetPath);
807
+ else await fs$1.copy(entrySourcePath, entryTargetPath);
608
808
  else if (hasArchitect && !hasScaffold) {
609
- if (isArchitectFile) await fs.copy(entrySourcePath, entryTargetPath);
809
+ if (isArchitectFile) await fs$1.copy(entrySourcePath, entryTargetPath);
610
810
  } else if (!hasArchitect && hasScaffold) {
611
- if (!isArchitectFile) if (entry.isDirectory()) await fs.copy(entrySourcePath, entryTargetPath);
612
- else await fs.copy(entrySourcePath, entryTargetPath);
811
+ if (!isArchitectFile) if (entry.isDirectory()) await fs$1.copy(entrySourcePath, entryTargetPath);
812
+ else await fs$1.copy(entrySourcePath, entryTargetPath);
613
813
  }
614
814
  }
615
815
  }
@@ -621,15 +821,15 @@ var TemplateSelectionService = class {
621
821
  async readTemplateDescription(templatePath) {
622
822
  try {
623
823
  const scaffoldYamlPath = path.join(templatePath, "scaffold.yaml");
624
- if (await fs.pathExists(scaffoldYamlPath)) {
824
+ if (await fs$1.pathExists(scaffoldYamlPath)) {
625
825
  const yaml = await import("js-yaml");
626
- const content = await fs.readFile(scaffoldYamlPath, "utf-8");
826
+ const content = await fs$1.readFile(scaffoldYamlPath, "utf-8");
627
827
  const scaffoldConfig = yaml.load(content);
628
828
  if (scaffoldConfig?.description) return scaffoldConfig.description;
629
829
  if (scaffoldConfig?.boilerplate?.[0]?.description) return scaffoldConfig.boilerplate[0].description;
630
830
  }
631
831
  const readmePath = path.join(templatePath, "README.md");
632
- if (await fs.pathExists(readmePath)) return (await fs.readFile(readmePath, "utf-8")).split("\n\n")[0].substring(0, 200).trim();
832
+ if (await fs$1.pathExists(readmePath)) return (await fs$1.readFile(readmePath, "utf-8")).split("\n\n")[0].substring(0, 200).trim();
633
833
  return;
634
834
  } catch {
635
835
  return;
@@ -646,10 +846,7 @@ var TemplateSelectionService = class {
646
846
  */
647
847
  async cleanup() {
648
848
  try {
649
- if (await fs.pathExists(this.tmpDir)) {
650
- await fs.remove(this.tmpDir);
651
- print.info("Cleaned up temporary files");
652
- }
849
+ if (await fs$1.pathExists(this.tmpDir)) await fs$1.remove(this.tmpDir);
653
850
  } catch (error) {
654
851
  print.warning(`Warning: Failed to clean up tmp directory: ${error.message}`);
655
852
  }
@@ -673,19 +870,19 @@ var TemplatesService = class {
673
870
  return;
674
871
  }
675
872
  print.info(`Found ${templateDirs.length} template(s)`);
676
- let downloaded = 0;
677
- let skipped = 0;
873
+ let _downloaded = 0;
874
+ let _skipped = 0;
678
875
  for (const template of templateDirs) {
679
876
  const targetFolder = path.join(templatesPath, template.name);
680
- if (await fs.pathExists(targetFolder)) {
877
+ if (await fs$1.pathExists(targetFolder)) {
681
878
  print.info(`Skipping ${template.name} (already exists)`);
682
- skipped++;
879
+ _skipped++;
683
880
  continue;
684
881
  }
685
882
  print.info(`Downloading ${template.name}...`);
686
883
  await cloneSubdirectory(`https://github.com/${repoConfig.owner}/${repoConfig.repo}.git`, repoConfig.branch, template.path, targetFolder);
687
884
  print.success(`Downloaded ${template.name}`);
688
- downloaded++;
885
+ _downloaded++;
689
886
  }
690
887
  print.success("\nAll templates downloaded successfully!");
691
888
  } catch (error) {
@@ -697,8 +894,8 @@ var TemplatesService = class {
697
894
  * @param templatesPath - Path where templates folder should be created
698
895
  */
699
896
  async initializeTemplatesFolder(templatesPath) {
700
- await fs.ensureDir(templatesPath);
701
- await fs.writeFile(path.join(templatesPath, "README.md"), `# Templates
897
+ await fs$1.ensureDir(templatesPath);
898
+ await fs$1.writeFile(path.join(templatesPath, "README.md"), `# Templates
702
899
 
703
900
  This folder contains boilerplate templates and scaffolding methods for your projects.
704
901
 
@@ -736,4 +933,4 @@ See existing templates for examples and documentation for more details.
736
933
  };
737
934
 
738
935
  //#endregion
739
- export { BANNER_GRADIENT, CodingAgentService, NewProjectService, THEME, TemplateSelectionService, TemplatesService, cloneRepository, cloneSubdirectory, displayBanner, displayCompactBanner, fetchGitHubDirectoryContents, findWorkspaceRoot, gitInit, parseGitHubUrl };
936
+ export { BANNER_GRADIENT, CodingAgentService, NewProjectService, SPEC_TOOL_INFO, SpecTool, SpecToolService, THEME, TemplateSelectionService, TemplatesService, cloneRepository, cloneSubdirectory, displayBanner, displayCompactBanner, fetchGitHubDirectoryContents, findWorkspaceRoot, gitInit, parseGitHubUrl };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@agiflowai/aicode-toolkit",
3
3
  "description": "AI-powered code toolkit CLI for scaffolding, architecture management, and development workflows",
4
- "version": "0.6.0",
4
+ "version": "1.0.1",
5
5
  "license": "AGPL-3.0",
6
6
  "author": "AgiflowIO",
7
7
  "repository": {
@@ -46,12 +46,13 @@
46
46
  "gradient-string": "^3.0.0",
47
47
  "js-yaml": "4.1.0",
48
48
  "liquidjs": "10.21.1",
49
+ "ora": "^9.0.0",
49
50
  "pino": "^10.0.0",
50
51
  "pino-pretty": "^13.1.1",
51
52
  "xstate": "^5.23.0",
52
53
  "zod": "3.25.76",
53
- "@agiflowai/aicode-utils": "1.0.0",
54
- "@agiflowai/coding-agent-bridge": "1.0.0"
54
+ "@agiflowai/coding-agent-bridge": "1.0.1",
55
+ "@agiflowai/aicode-utils": "1.0.1"
55
56
  },
56
57
  "devDependencies": {
57
58
  "@types/express": "^5.0.0",
@@ -59,7 +60,8 @@
59
60
  "@types/js-yaml": "^4.0.9",
60
61
  "@types/node": "^22.0.0",
61
62
  "tsdown": "^0.15.6",
62
- "typescript": "5.9.3"
63
+ "typescript": "5.9.3",
64
+ "unplugin-raw": "^0.6.3"
63
65
  },
64
66
  "publishConfig": {
65
67
  "access": "public"