@agiflowai/aicode-toolkit 0.6.0 → 1.0.2

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.js CHANGED
@@ -1,17 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  import { MCPServer, MCP_SERVER_INFO } from "./mcp-BmhiAfeF.js";
3
- import { CodingAgentService, NewProjectService, TemplateSelectionService, cloneRepository, cloneSubdirectory, displayBanner, findWorkspaceRoot, parseGitHubUrl } from "./services-DNldrNnu.js";
3
+ import { CodingAgentService, NewProjectService, SPEC_TOOL_INFO, SpecTool, SpecToolService, TemplateSelectionService, cloneRepository, cloneSubdirectory, displayBanner, findWorkspaceRoot, parseGitHubUrl } from "./services-zrdafWTg.js";
4
4
  import { Command } from "commander";
5
5
  import path from "node:path";
6
- import { ProjectType, TemplatesManagerService, icons, messages, print, sections } from "@agiflowai/aicode-utils";
6
+ import { ProjectType, TemplatesManagerService, detectProjectType, icons, messages, print, sections } from "@agiflowai/aicode-utils";
7
7
  import * as fs from "fs-extra";
8
8
  import { confirm, input, select } from "@inquirer/prompts";
9
+ import ora from "ora";
9
10
  import { assign, createActor, createMachine, fromPromise } from "xstate";
10
11
 
11
12
  //#region package.json
12
13
  var name = "@agiflowai/aicode-toolkit";
13
14
  var description = "AI-powered code toolkit CLI for scaffolding, architecture management, and development workflows";
14
- var version = "0.6.0";
15
+ var version = "1.0.1";
15
16
  var license = "AGPL-3.0";
16
17
  var author = "AgiflowIO";
17
18
  var repository = {
@@ -57,6 +58,7 @@ var dependencies = {
57
58
  "gradient-string": "^3.0.0",
58
59
  "js-yaml": "4.1.0",
59
60
  "liquidjs": "10.21.1",
61
+ "ora": "^9.0.0",
60
62
  "pino": "^10.0.0",
61
63
  "pino-pretty": "^13.1.1",
62
64
  "xstate": "^5.23.0",
@@ -68,7 +70,8 @@ var devDependencies = {
68
70
  "@types/js-yaml": "^4.0.9",
69
71
  "@types/node": "^22.0.0",
70
72
  "tsdown": "^0.15.6",
71
- "typescript": "5.9.3"
73
+ "typescript": "5.9.3",
74
+ "unplugin-raw": "^0.6.3"
72
75
  };
73
76
  var publishConfig = { "access": "public" };
74
77
  var type = "module";
@@ -180,7 +183,6 @@ const initMachine = createMachine({
180
183
  projectType: void 0,
181
184
  projectPath: void 0,
182
185
  repositoryExists: false,
183
- detectionConfidence: void 0,
184
186
  detectionIndicators: void 0,
185
187
  templatesPath: void 0,
186
188
  tmpTemplatesPath: void 0,
@@ -188,6 +190,8 @@ const initMachine = createMachine({
188
190
  selectedMcpServers: void 0,
189
191
  detectedCodingAgent: void 0,
190
192
  codingAgent: void 0,
193
+ detectedSpecTool: void 0,
194
+ useSpecDrivenApproach: void 0,
191
195
  options: input$1.options,
192
196
  error: void 0
193
197
  }),
@@ -216,18 +220,14 @@ const initMachine = createMachine({
216
220
  input: ({ context }) => ({ workspaceRoot: context.workspaceRoot }),
217
221
  onDone: [{
218
222
  target: "checkingSkipTemplates",
219
- guard: ({ event }) => event.output.confidence === "high",
223
+ guard: ({ event }) => event.output.projectType !== void 0,
220
224
  actions: assign({
221
225
  projectType: ({ event }) => event.output.projectType,
222
- detectionConfidence: ({ event }) => event.output.confidence,
223
226
  detectionIndicators: ({ event }) => event.output.indicators
224
227
  })
225
228
  }, {
226
229
  target: "promptingProjectType",
227
- actions: assign({
228
- detectionConfidence: ({ event }) => event.output.confidence,
229
- detectionIndicators: ({ event }) => event.output.indicators
230
- })
230
+ actions: assign({ detectionIndicators: ({ event }) => event.output.indicators })
231
231
  }],
232
232
  onError: {
233
233
  target: "failed",
@@ -238,6 +238,7 @@ const initMachine = createMachine({
238
238
  src: "promptProjectType",
239
239
  input: ({ context }) => ({
240
240
  providedProjectType: context.options.projectType,
241
+ detectedProjectType: context.projectType,
241
242
  detectionIndicators: context.detectionIndicators
242
243
  }),
243
244
  onDone: {
@@ -309,7 +310,7 @@ const initMachine = createMachine({
309
310
  promptingMcpSelection: { invoke: {
310
311
  src: "promptMcpSelection",
311
312
  onDone: {
312
- target: "downloadingTemplates",
313
+ target: "checkingTemplatesFolder",
313
314
  actions: assign({ selectedMcpServers: ({ event }) => event.output })
314
315
  },
315
316
  onError: {
@@ -317,6 +318,29 @@ const initMachine = createMachine({
317
318
  actions: assign({ error: ({ event }) => event.error })
318
319
  }
319
320
  } },
321
+ checkingTemplatesFolder: { invoke: {
322
+ src: "checkTemplatesFolder",
323
+ input: ({ context }) => ({ workspaceRoot: context.workspaceRoot }),
324
+ onDone: [{
325
+ target: "creatingConfig",
326
+ guard: ({ event }) => event.output.skipDownload === true,
327
+ actions: assign({
328
+ templatesPath: ({ event }) => event.output.templatesPath,
329
+ skipDownload: ({ event }) => event.output.skipDownload,
330
+ selectedTemplates: ({ event }) => event.output.existingTemplates
331
+ })
332
+ }, {
333
+ target: "downloadingTemplates",
334
+ actions: assign({
335
+ templatesPath: ({ event }) => event.output.templatesPath,
336
+ skipDownload: ({ event }) => event.output.skipDownload
337
+ })
338
+ }],
339
+ onError: {
340
+ target: "failed",
341
+ actions: assign({ error: ({ event }) => event.error })
342
+ }
343
+ } },
320
344
  downloadingTemplates: { invoke: {
321
345
  src: "downloadTemplates",
322
346
  onDone: {
@@ -357,14 +381,12 @@ const initMachine = createMachine({
357
381
  input: ({ context }) => ({
358
382
  tmpTemplatesPath: context.tmpTemplatesPath,
359
383
  workspaceRoot: context.workspaceRoot,
384
+ templatesPath: context.templatesPath,
360
385
  selectedTemplates: context.selectedTemplates,
361
386
  projectType: context.projectType,
362
387
  selectedMcpServers: context.selectedMcpServers
363
388
  }),
364
- onDone: {
365
- target: "creatingConfig",
366
- actions: assign({ templatesPath: ({ event }) => event.output })
367
- },
389
+ onDone: { target: "creatingConfig" },
368
390
  onError: {
369
391
  target: "failed",
370
392
  actions: assign({ error: ({ event }) => event.error })
@@ -375,6 +397,7 @@ const initMachine = createMachine({
375
397
  input: ({ context }) => ({
376
398
  workspaceRoot: context.workspaceRoot,
377
399
  projectType: context.projectType,
400
+ templatesPath: context.templatesPath,
378
401
  selectedTemplates: context.selectedTemplates
379
402
  }),
380
403
  onDone: { target: "checkingSkipMcp" },
@@ -386,7 +409,7 @@ const initMachine = createMachine({
386
409
  checkingSkipMcp: { always: [{
387
410
  target: "detectingCodingAgent",
388
411
  guard: ({ context }) => !context.options.skipMcp
389
- }, { target: "cleaningUp" }] },
412
+ }, { target: "detectingSpecTool" }] },
390
413
  detectingCodingAgent: { invoke: {
391
414
  src: "detectCodingAgent",
392
415
  input: ({ context }) => ({ workspaceRoot: context.workspaceRoot }),
@@ -417,6 +440,66 @@ const initMachine = createMachine({
417
440
  workspaceRoot: context.workspaceRoot,
418
441
  codingAgent: context.codingAgent
419
442
  }),
443
+ onDone: { target: "detectingSpecTool" },
444
+ onError: {
445
+ target: "failed",
446
+ actions: assign({ error: ({ event }) => event.error })
447
+ }
448
+ } },
449
+ detectingSpecTool: { invoke: {
450
+ src: "detectSpecTool",
451
+ input: ({ context }) => ({ workspaceRoot: context.workspaceRoot }),
452
+ onDone: {
453
+ target: "promptingSpecDrivenApproach",
454
+ actions: assign({ detectedSpecTool: ({ event }) => event.output })
455
+ },
456
+ onError: { target: "cleaningUp" }
457
+ } },
458
+ promptingSpecDrivenApproach: { invoke: {
459
+ src: "promptSpecDrivenApproach",
460
+ input: ({ context }) => ({ detectedSpecTool: context.detectedSpecTool }),
461
+ onDone: [{
462
+ target: "settingUpSpec",
463
+ guard: ({ event }) => event.output === true,
464
+ actions: assign({ useSpecDrivenApproach: () => true })
465
+ }, {
466
+ target: "cleaningUp",
467
+ actions: assign({ useSpecDrivenApproach: () => false })
468
+ }],
469
+ onError: { target: "cleaningUp" }
470
+ } },
471
+ settingUpSpec: { invoke: {
472
+ src: "setupSpec",
473
+ input: ({ context }) => ({
474
+ workspaceRoot: context.workspaceRoot,
475
+ isAlreadyInstalled: context.detectedSpecTool !== null,
476
+ selectedMcpServers: context.selectedMcpServers,
477
+ codingAgent: context.codingAgent
478
+ }),
479
+ onDone: [{
480
+ target: "promptingSpecInstructions",
481
+ guard: ({ context }) => context.detectedSpecTool === null
482
+ }, { target: "cleaningUp" }],
483
+ onError: {
484
+ target: "failed",
485
+ actions: assign({ error: ({ event }) => event.error })
486
+ }
487
+ } },
488
+ promptingSpecInstructions: { invoke: {
489
+ src: "promptSpecInstructions",
490
+ onDone: [{
491
+ target: "updatingSpecInstructions",
492
+ guard: ({ event }) => event.output === true
493
+ }, { target: "cleaningUp" }],
494
+ onError: { target: "cleaningUp" }
495
+ } },
496
+ updatingSpecInstructions: { invoke: {
497
+ src: "updateSpecInstructions",
498
+ input: ({ context }) => ({
499
+ workspaceRoot: context.workspaceRoot,
500
+ selectedMcpServers: context.selectedMcpServers,
501
+ codingAgent: context.codingAgent
502
+ }),
420
503
  onDone: { target: "cleaningUp" },
421
504
  onError: {
422
505
  target: "failed",
@@ -452,6 +535,7 @@ const initActors = {
452
535
  checkWorkspaceExists: fromPromise(async () => {
453
536
  const workspaceRoot = await findWorkspaceRoot();
454
537
  if (workspaceRoot) {
538
+ print.divider();
455
539
  print.info(`Found workspace at: ${workspaceRoot}`);
456
540
  return {
457
541
  exists: true,
@@ -461,62 +545,11 @@ const initActors = {
461
545
  return { exists: false };
462
546
  }),
463
547
  detectProjectType: fromPromise(async ({ input: input$1 }) => {
464
- print.info("\nDetecting project type...");
465
- const nxJsonPath = path.join(input$1.workspaceRoot, "nx.json");
466
- const lernaJsonPath = path.join(input$1.workspaceRoot, "lerna.json");
467
- const pnpmWorkspacePath = path.join(input$1.workspaceRoot, "pnpm-workspace.yaml");
468
- const turboJsonPath = path.join(input$1.workspaceRoot, "turbo.json");
469
- const indicators = [];
470
- let projectType;
471
- let confidence = "low";
472
- if (await fs.pathExists(nxJsonPath)) {
473
- indicators.push("nx.json found");
474
- projectType = ProjectType.MONOREPO;
475
- confidence = "high";
476
- }
477
- if (await fs.pathExists(lernaJsonPath)) {
478
- indicators.push("lerna.json found");
479
- projectType = ProjectType.MONOREPO;
480
- confidence = "high";
481
- }
482
- if (await fs.pathExists(pnpmWorkspacePath)) {
483
- indicators.push("pnpm-workspace.yaml found");
484
- projectType = ProjectType.MONOREPO;
485
- confidence = "high";
486
- }
487
- if (await fs.pathExists(turboJsonPath)) {
488
- indicators.push("turbo.json found");
489
- projectType = ProjectType.MONOREPO;
490
- confidence = "high";
491
- }
492
- const packageJsonPath = path.join(input$1.workspaceRoot, "package.json");
493
- if (await fs.pathExists(packageJsonPath)) {
494
- if ((await fs.readJson(packageJsonPath)).workspaces) {
495
- indicators.push("package.json with workspaces found");
496
- projectType = ProjectType.MONOREPO;
497
- if (confidence !== "high") confidence = "medium";
498
- }
499
- }
500
- const toolkitYamlPath = path.join(input$1.workspaceRoot, "toolkit.yaml");
501
- if (await fs.pathExists(toolkitYamlPath)) {
502
- const toolkitConfig = await TemplatesManagerService.readToolkitConfig(input$1.workspaceRoot);
503
- if (toolkitConfig?.projectType) {
504
- indicators.push(`toolkit.yaml specifies ${toolkitConfig.projectType}`);
505
- projectType = toolkitConfig.projectType;
506
- confidence = "high";
507
- }
508
- }
509
- if (!projectType) {
510
- projectType = ProjectType.MONOLITH;
511
- indicators.push("No monorepo indicators found, assuming monolith");
512
- confidence = "low";
513
- }
514
- if (confidence === "high") print.success(`Detected ${projectType} project (high confidence)`);
515
- return {
516
- projectType,
517
- confidence,
518
- indicators
519
- };
548
+ print.divider();
549
+ print.info("Detecting project type...");
550
+ const result = await detectProjectType(input$1.workspaceRoot);
551
+ if (result.projectType) print.success(`Detected ${result.projectType} project`);
552
+ return result;
520
553
  }),
521
554
  promptProjectType: fromPromise(async ({ input: actorInput }) => {
522
555
  if (actorInput.providedProjectType) {
@@ -524,23 +557,30 @@ const initActors = {
524
557
  print.info(`Project type: ${projectType}`);
525
558
  return projectType;
526
559
  }
560
+ if (actorInput.detectedProjectType) {
561
+ print.info(`Using detected project type: ${actorInput.detectedProjectType}`);
562
+ return actorInput.detectedProjectType;
563
+ }
527
564
  if (actorInput.detectionIndicators && actorInput.detectionIndicators.length > 0) {
528
565
  print.info("\nDetection results:");
529
566
  for (const indicator of actorInput.detectionIndicators) print.indent(`• ${indicator}`);
530
567
  print.newline();
531
568
  }
532
- return await select({
569
+ print.divider();
570
+ const result = await select({
533
571
  message: "Select project type:",
534
572
  choices: [{
535
- name: "Monolith - Single application structure",
573
+ name: "Monolith Single application structure",
536
574
  value: ProjectType.MONOLITH,
537
- description: "Traditional single-application project structure"
575
+ description: "\n Traditional single-application project structure"
538
576
  }, {
539
- name: "Monorepo - Multiple packages/apps in one repository",
577
+ name: "Monorepo Multiple packages/apps in one repository",
540
578
  value: ProjectType.MONOREPO,
541
- description: "Multiple packages managed together (uses workspaces)"
579
+ description: "\n Multiple packages managed together (uses workspaces)"
542
580
  }]
543
581
  });
582
+ print.info("");
583
+ return result;
544
584
  }),
545
585
  promptProjectName: fromPromise(async ({ input: actorInput }) => {
546
586
  const newProjectService = new NewProjectService(actorInput.providedName, void 0);
@@ -552,49 +592,171 @@ const initActors = {
552
592
  print.info(`Project name: ${trimmedName}`);
553
593
  return trimmedName;
554
594
  }
555
- return await input({
556
- message: "Enter your project name:",
557
- validate: (value) => newProjectService.validateProjectName(value)
595
+ print.divider();
596
+ const result = await input({
597
+ message: "Enter your project name (press Enter to use current directory):",
598
+ validate: (value) => {
599
+ if (!value || value.trim() === "") return true;
600
+ return newProjectService.validateProjectName(value);
601
+ }
558
602
  });
603
+ print.info("");
604
+ if (!result || result.trim() === "") return ".";
605
+ return result;
559
606
  }),
560
607
  createProjectDirectory: fromPromise(async ({ input: actorInput }) => {
561
- const projectPath = path.join(process.cwd(), actorInput.projectName.trim());
562
- await new NewProjectService(void 0, void 0).createProjectDirectory(projectPath, actorInput.projectName);
563
- return { projectPath };
608
+ if (actorInput.projectName === ".") {
609
+ const projectPath = process.cwd();
610
+ print.success(`Using current directory: ${projectPath}`);
611
+ return { projectPath };
612
+ }
613
+ const spinner = ora("Creating project directory...").start();
614
+ try {
615
+ const projectPath = path.join(process.cwd(), actorInput.projectName.trim());
616
+ await new NewProjectService(void 0, void 0).createProjectDirectory(projectPath, actorInput.projectName);
617
+ spinner.succeed(`Created project directory: ${projectPath}`);
618
+ return { projectPath };
619
+ } catch (error) {
620
+ spinner.fail("Failed to create project directory");
621
+ throw error;
622
+ }
564
623
  }),
565
624
  promptGitSetup: fromPromise(async ({ input: actorInput }) => {
566
625
  const newProjectService = new NewProjectService(void 0, void 0);
567
- if (await confirm({
626
+ print.divider();
627
+ const hasExistingRepo = await confirm({
568
628
  message: "Do you have an existing Git repository you want to use?",
569
629
  default: false
570
- })) {
630
+ });
631
+ print.info("");
632
+ if (hasExistingRepo) {
633
+ print.divider();
571
634
  const repoUrl = await input({
572
- message: "Enter Git repository URL:",
573
- validate: (value) => newProjectService.validateRepositoryUrl(value)
635
+ message: "Enter Git repository URL (press Enter to skip):",
636
+ validate: (value) => {
637
+ if (!value || value.trim() === "") return true;
638
+ return newProjectService.validateRepositoryUrl(value);
639
+ }
574
640
  });
575
- await newProjectService.cloneExistingRepository(repoUrl.trim(), actorInput.projectPath);
576
- } else if (await confirm({
577
- message: "Initialize a new Git repository?",
578
- default: true
579
- })) await newProjectService.initializeGitRepository(actorInput.projectPath);
641
+ print.info("");
642
+ if (repoUrl && repoUrl.trim() !== "") {
643
+ const spinner = ora("Cloning repository...").start();
644
+ try {
645
+ await newProjectService.cloneExistingRepository(repoUrl.trim(), actorInput.projectPath);
646
+ spinner.succeed("Repository cloned successfully");
647
+ } catch (error) {
648
+ spinner.fail("Failed to clone repository");
649
+ throw error;
650
+ }
651
+ } else print.info("Skipped cloning repository");
652
+ } else {
653
+ print.divider();
654
+ const initGit = await confirm({
655
+ message: "Initialize a new Git repository?",
656
+ default: true
657
+ });
658
+ print.info("");
659
+ if (initGit) {
660
+ const spinner = ora("Initializing Git repository...").start();
661
+ try {
662
+ await newProjectService.initializeGitRepository(actorInput.projectPath);
663
+ spinner.succeed("Git repository initialized");
664
+ } catch (error) {
665
+ spinner.fail("Failed to initialize Git repository");
666
+ throw error;
667
+ }
668
+ }
669
+ }
580
670
  }),
581
671
  promptMcpSelection: fromPromise(async () => {
582
- return await (await import("@inquirer/prompts").then((m) => m.checkbox))({
672
+ const checkbox = await import("@inquirer/prompts").then((m) => m.checkbox);
673
+ const choices = Object.values(MCPServer).map((server) => ({
674
+ name: MCP_SERVER_INFO[server].name,
675
+ value: server,
676
+ description: `\n ${MCP_SERVER_INFO[server].description}`,
677
+ checked: true
678
+ }));
679
+ print.divider();
680
+ const selected = await checkbox({
583
681
  message: "Select MCP servers to configure:",
584
- choices: Object.values(MCPServer).map((server) => ({
585
- name: MCP_SERVER_INFO[server].name,
586
- value: server,
587
- description: MCP_SERVER_INFO[server].description,
588
- checked: true
589
- })),
682
+ choices,
590
683
  validate: (answer) => {
591
684
  if (answer.length === 0) return "Please select at least one MCP server";
592
685
  return true;
593
686
  }
594
687
  });
688
+ print.info("");
689
+ return selected;
690
+ }),
691
+ checkTemplatesFolder: fromPromise(async ({ input: actorInput }) => {
692
+ try {
693
+ const fs$1 = await import("node:fs/promises");
694
+ const defaultTemplatesPath = path.join(actorInput.workspaceRoot, "templates");
695
+ let templatesExists = false;
696
+ try {
697
+ await fs$1.access(defaultTemplatesPath);
698
+ templatesExists = true;
699
+ } catch {
700
+ templatesExists = false;
701
+ }
702
+ let finalTemplatesPath = defaultTemplatesPath;
703
+ let skipDownload = false;
704
+ if (templatesExists) {
705
+ print.divider();
706
+ print.info(`Templates folder already exists at: ${defaultTemplatesPath}`);
707
+ const useDifferentDir = await confirm({
708
+ message: "Would you like to use a different directory for templates?",
709
+ default: false
710
+ });
711
+ print.info("");
712
+ if (useDifferentDir) {
713
+ print.divider();
714
+ const customDir = await input({
715
+ message: "Enter custom templates directory path (relative to workspace root):",
716
+ default: "templates",
717
+ validate: (value) => {
718
+ if (!value || value.trim() === "") return "Please enter a valid directory path";
719
+ return true;
720
+ }
721
+ });
722
+ print.info("");
723
+ finalTemplatesPath = path.join(actorInput.workspaceRoot, customDir.trim());
724
+ try {
725
+ await fs$1.mkdir(finalTemplatesPath, { recursive: true });
726
+ print.success(`Created templates directory at: ${finalTemplatesPath}`);
727
+ } catch (error) {
728
+ throw new Error(`Failed to create templates directory at ${finalTemplatesPath}: ${error.message}`);
729
+ }
730
+ } else {
731
+ skipDownload = true;
732
+ print.info("Using existing templates folder");
733
+ }
734
+ }
735
+ let existingTemplates;
736
+ if (skipDownload) try {
737
+ existingTemplates = (await new TemplateSelectionService(finalTemplatesPath).listTemplates()).map((t) => t.name);
738
+ } catch (_error) {
739
+ print.warning("Could not read existing templates, will proceed anyway");
740
+ }
741
+ return {
742
+ templatesPath: finalTemplatesPath,
743
+ skipDownload,
744
+ existingTemplates
745
+ };
746
+ } catch (error) {
747
+ throw new Error(`Failed to check templates folder: ${error.message}`);
748
+ }
595
749
  }),
596
750
  downloadTemplates: fromPromise(async () => {
597
- return await new TemplateSelectionService().downloadTemplatesToTmp(DEFAULT_TEMPLATE_REPO);
751
+ const spinner = ora("Downloading templates from AgiFlow/aicode-toolkit...").start();
752
+ try {
753
+ const tmpPath = await new TemplateSelectionService().downloadTemplatesToTmp(DEFAULT_TEMPLATE_REPO);
754
+ spinner.succeed("Templates downloaded successfully");
755
+ return tmpPath;
756
+ } catch (error) {
757
+ spinner.fail("Failed to download templates");
758
+ throw error;
759
+ }
598
760
  }),
599
761
  listTemplates: fromPromise(async ({ input: input$1 }) => {
600
762
  const templates = await new TemplateSelectionService(input$1.tmpTemplatesPath).listTemplates();
@@ -605,37 +767,52 @@ const initActors = {
605
767
  promptTemplateSelection: fromPromise(async ({ input: actorInput }) => {
606
768
  const templates = await new TemplateSelectionService(actorInput.tmpTemplatesPath).listTemplates();
607
769
  if (templates.length === 0) throw new Error("No templates available");
608
- if (actorInput.projectType === ProjectType.MONOLITH) return [await select({
609
- message: "Select template (monolith allows only one):",
610
- choices: templates.map((t) => ({
770
+ if (actorInput.projectType === ProjectType.MONOLITH) {
771
+ const choices$1 = templates.map((t) => ({
611
772
  name: t.name,
612
773
  value: t.name,
613
- description: t.description
614
- }))
615
- })];
616
- const selected = await (await import("@inquirer/prompts").then((m) => m.checkbox))({
774
+ description: t.description ? `\n ${t.description}` : void 0
775
+ }));
776
+ print.divider();
777
+ const selected$1 = await select({
778
+ message: "Select template (monolith allows only one):",
779
+ choices: choices$1
780
+ });
781
+ print.info("");
782
+ return [selected$1];
783
+ }
784
+ const checkbox = await import("@inquirer/prompts").then((m) => m.checkbox);
785
+ const choices = templates.map((t) => ({
786
+ name: t.name,
787
+ value: t.name,
788
+ description: t.description ? `\n ${t.description}` : void 0,
789
+ checked: true
790
+ }));
791
+ print.divider();
792
+ const selected = await checkbox({
617
793
  message: "Select templates (use space to select, enter to confirm):",
618
- choices: templates.map((t) => ({
619
- name: t.name,
620
- value: t.name,
621
- description: t.description,
622
- checked: true
623
- }))
794
+ choices
624
795
  });
796
+ print.info("");
625
797
  if (selected.length === 0) throw new Error("Please select at least one template");
626
798
  return selected;
627
799
  }),
628
800
  copyTemplates: fromPromise(async ({ input: actorInput }) => {
629
- const templateSelectionService = new TemplateSelectionService(actorInput.tmpTemplatesPath);
630
- const templatesPath = path.join(actorInput.workspaceRoot, "templates");
631
- await templateSelectionService.copyTemplates(actorInput.selectedTemplates, templatesPath, actorInput.projectType, actorInput.selectedMcpServers);
632
- return templatesPath;
801
+ const spinner = ora("Copying templates to workspace...").start();
802
+ try {
803
+ await new TemplateSelectionService(actorInput.tmpTemplatesPath).copyTemplates(actorInput.selectedTemplates, actorInput.templatesPath, actorInput.projectType, actorInput.selectedMcpServers);
804
+ spinner.succeed(`Templates copied to ${actorInput.templatesPath}`);
805
+ return actorInput.templatesPath;
806
+ } catch (error) {
807
+ spinner.fail("Failed to copy templates");
808
+ throw error;
809
+ }
633
810
  }),
634
811
  createConfig: fromPromise(async ({ input: actorInput }) => {
635
812
  if (actorInput.projectType === ProjectType.MONOLITH) {
636
813
  const toolkitConfig = {
637
814
  version: "1.0",
638
- templatesPath: "templates",
815
+ templatesPath: path.relative(actorInput.workspaceRoot, actorInput.templatesPath) || "templates",
639
816
  projectType: "monolith",
640
817
  sourceTemplate: actorInput.selectedTemplates[0]
641
818
  };
@@ -656,20 +833,120 @@ const initActors = {
656
833
  print.info(`Using detected coding agent: ${actorInput.detectedAgent}`);
657
834
  return actorInput.detectedAgent;
658
835
  }
659
- return await select({
836
+ const agents = CodingAgentService.getAvailableAgents();
837
+ print.divider();
838
+ const selected = await select({
660
839
  message: "Select coding agent for MCP configuration:",
661
- choices: CodingAgentService.getAvailableAgents().map((agent) => ({
840
+ choices: agents.map((agent) => ({
662
841
  name: agent.name,
663
842
  value: agent.value,
664
- description: agent.description
843
+ description: `\n ${agent.description}`
665
844
  }))
666
845
  });
846
+ print.info("");
847
+ return selected;
667
848
  }),
668
849
  configureMCP: fromPromise(async ({ input: actorInput }) => {
669
- await new CodingAgentService(actorInput.workspaceRoot).setupMCP(actorInput.codingAgent);
850
+ const spinner = ora(`Setting up MCP for ${actorInput.codingAgent}...`).start();
851
+ try {
852
+ await new CodingAgentService(actorInput.workspaceRoot).setupMCP(actorInput.codingAgent);
853
+ spinner.succeed("MCP configuration completed");
854
+ } catch (error) {
855
+ spinner.fail("Failed to configure MCP");
856
+ throw error;
857
+ }
670
858
  }),
671
859
  cleanup: fromPromise(async ({ input: input$1 }) => {
672
- if (input$1.tmpTemplatesPath) await new TemplateSelectionService(input$1.tmpTemplatesPath).cleanup();
860
+ if (input$1.tmpTemplatesPath) {
861
+ const spinner = ora("Cleaning up temporary files...").start();
862
+ try {
863
+ await new TemplateSelectionService(input$1.tmpTemplatesPath).cleanup();
864
+ spinner.succeed("Cleaned up temporary files");
865
+ } catch (_error) {
866
+ spinner.warn("Could not clean up all temporary files");
867
+ }
868
+ }
869
+ }),
870
+ detectSpecTool: fromPromise(async ({ input: input$1 }) => {
871
+ print.info("\nDetecting spec tools...");
872
+ const detectedTool = await new SpecToolService(input$1.workspaceRoot).detectSpecTool();
873
+ if (detectedTool) print.success(`Detected ${SPEC_TOOL_INFO[detectedTool].name} in workspace`);
874
+ else print.info("No spec tool detected");
875
+ return detectedTool;
876
+ }),
877
+ promptSpecDrivenApproach: fromPromise(async ({ input: actorInput }) => {
878
+ if (actorInput.detectedSpecTool) {
879
+ print.divider();
880
+ const result$1 = await confirm({
881
+ message: `${SPEC_TOOL_INFO[actorInput.detectedSpecTool].name} is installed. Would you like to update the agent instructions for spec-driven development?`,
882
+ default: true
883
+ });
884
+ print.info("");
885
+ return result$1;
886
+ }
887
+ print.divider();
888
+ const result = await confirm({
889
+ message: "Would you like to install OpenSpec for spec-driven development? This helps AI assistants agree on what to build before writing code.",
890
+ default: false
891
+ });
892
+ print.info("");
893
+ return result;
894
+ }),
895
+ setupSpec: fromPromise(async ({ input: input$1 }) => {
896
+ const codingAgentService = new CodingAgentService(input$1.workspaceRoot);
897
+ const specToolService = new SpecToolService(input$1.workspaceRoot, SpecTool.OPENSPEC, codingAgentService);
898
+ if (input$1.isAlreadyInstalled) {
899
+ const spinner = ora("Updating OpenSpec agent instructions...").start();
900
+ try {
901
+ const enabledMcps = {
902
+ scaffoldMcp: input$1.selectedMcpServers?.includes(MCPServer.SCAFFOLD) ?? false,
903
+ architectMcp: input$1.selectedMcpServers?.includes(MCPServer.ARCHITECT) ?? false,
904
+ projectType: input$1.projectType
905
+ };
906
+ await specToolService.updateInstructions(enabledMcps, input$1.codingAgent);
907
+ spinner.succeed("OpenSpec agent instructions updated");
908
+ } catch (error) {
909
+ spinner.fail("Failed to update OpenSpec instructions");
910
+ throw error;
911
+ }
912
+ } else {
913
+ const spinner = ora("Initializing OpenSpec...").start();
914
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
915
+ spinner.stop();
916
+ try {
917
+ await specToolService.initializeSpec();
918
+ print.success("OpenSpec initialized successfully");
919
+ } catch (error) {
920
+ print.error("Failed to initialize OpenSpec");
921
+ throw error;
922
+ }
923
+ }
924
+ }),
925
+ promptSpecInstructions: fromPromise(async () => {
926
+ print.divider();
927
+ const result = await confirm({
928
+ message: "Would you like to update the agent instructions with OpenSpec workflow guidance?",
929
+ default: true
930
+ });
931
+ print.info("");
932
+ return result;
933
+ }),
934
+ updateSpecInstructions: fromPromise(async ({ input: input$1 }) => {
935
+ const spinner = ora("Updating OpenSpec agent instructions...").start();
936
+ try {
937
+ const codingAgentService = new CodingAgentService(input$1.workspaceRoot);
938
+ const specToolService = new SpecToolService(input$1.workspaceRoot, SpecTool.OPENSPEC, codingAgentService);
939
+ const enabledMcps = {
940
+ scaffoldMcp: input$1.selectedMcpServers?.includes(MCPServer.SCAFFOLD) ?? false,
941
+ architectMcp: input$1.selectedMcpServers?.includes(MCPServer.ARCHITECT) ?? false,
942
+ projectType: input$1.projectType
943
+ };
944
+ await specToolService.updateInstructions(enabledMcps, input$1.codingAgent);
945
+ spinner.succeed("OpenSpec agent instructions updated");
946
+ } catch (error) {
947
+ spinner.fail("Failed to update OpenSpec instructions");
948
+ throw error;
949
+ }
673
950
  })
674
951
  };
675
952
  /**