thomas-agentkit 0.6.0 → 0.6.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.
Files changed (2) hide show
  1. package/dist/cli.js +188 -243
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -212,49 +212,68 @@ function assertKnownKeys(value, validKeys, name) {
212
212
  }
213
213
  }
214
214
  }
215
+ function optionalConfigString(rawConfig, key) {
216
+ const value = rawConfig[key];
217
+ if (value === undefined) {
218
+ return undefined;
219
+ }
220
+ if (typeof value !== "string") {
221
+ throw new Error(`${configFileName} ${key} must be a string.`);
222
+ }
223
+ return value;
224
+ }
225
+ function readConfigAiTools(value) {
226
+ if (value === undefined) {
227
+ return undefined;
228
+ }
229
+ if (!Array.isArray(value)) {
230
+ throw new Error(`${configFileName} aiTools must be an array.`);
231
+ }
232
+ return value.map((aiTool) => {
233
+ if (typeof aiTool !== "string" || !isAiToolName(aiTool)) {
234
+ throw new Error(`Unknown AI tool "${String(aiTool)}". Valid AI tools: ${validAiTools.join(", ")}.`);
235
+ }
236
+ return aiTool;
237
+ });
238
+ }
239
+ function readConfigPersonalization(value) {
240
+ if (value === undefined) {
241
+ return undefined;
242
+ }
243
+ assertPlainObject(value, `${configFileName} personalization`);
244
+ assertKnownKeys(value, personalizationKeys, `${configFileName} personalization`);
245
+ const personalization = {};
246
+ for (const [key, entry] of Object.entries(value)) {
247
+ if (typeof entry !== "string") {
248
+ throw new Error(`${configFileName} personalization.${key} must be a string.`);
249
+ }
250
+ personalization[key] = entry;
251
+ }
252
+ return personalization;
253
+ }
215
254
  function parseConfig(rawConfig, configPath) {
216
255
  assertPlainObject(rawConfig, configFileName);
217
256
  assertKnownKeys(rawConfig, configKeys, configFileName);
257
+ const preset = optionalConfigString(rawConfig, "preset");
258
+ const templateSet = optionalConfigString(rawConfig, "templateSet");
259
+ const designSystem = optionalConfigString(rawConfig, "designSystem");
218
260
  const config = {};
219
- if (rawConfig.preset !== undefined) {
220
- if (typeof rawConfig.preset !== "string") {
221
- throw new Error(`${configFileName} preset must be a string.`);
222
- }
223
- config.preset = resolvePreset(rawConfig.preset);
261
+ if (preset !== undefined) {
262
+ config.preset = resolvePreset(preset);
224
263
  }
225
- if (rawConfig.templateSet !== undefined) {
226
- if (typeof rawConfig.templateSet !== "string") {
227
- throw new Error(`${configFileName} templateSet must be a string.`);
228
- }
229
- config.templateSet = resolveTemplateSet(rawConfig.templateSet);
264
+ if (templateSet !== undefined) {
265
+ config.templateSet = resolveTemplateSet(templateSet);
230
266
  }
231
- if (rawConfig.aiTools !== undefined) {
232
- if (!Array.isArray(rawConfig.aiTools)) {
233
- throw new Error(`${configFileName} aiTools must be an array.`);
234
- }
235
- config.aiTools = rawConfig.aiTools.map((aiTool) => {
236
- if (typeof aiTool !== "string" || !isAiToolName(aiTool)) {
237
- throw new Error(`Unknown AI tool "${String(aiTool)}". Valid AI tools: ${validAiTools.join(", ")}.`);
238
- }
239
- return aiTool;
240
- });
267
+ const aiTools = readConfigAiTools(rawConfig.aiTools);
268
+ if (aiTools !== undefined) {
269
+ config.aiTools = aiTools;
241
270
  }
242
- if (rawConfig.designSystem !== undefined) {
243
- if (typeof rawConfig.designSystem !== "string") {
244
- throw new Error(`${configFileName} designSystem must be a string.`);
245
- }
246
- config.designSystem = resolveDesignSystem(rawConfig.designSystem);
247
- }
248
- if (rawConfig.personalization !== undefined) {
249
- assertPlainObject(rawConfig.personalization, `${configFileName} personalization`);
250
- assertKnownKeys(rawConfig.personalization, personalizationKeys, `${configFileName} personalization`);
251
- config.personalization = {};
252
- for (const [key, value] of Object.entries(rawConfig.personalization)) {
253
- if (typeof value !== "string") {
254
- throw new Error(`${configFileName} personalization.${key} must be a string.`);
255
- }
256
- config.personalization[key] = value;
257
- }
271
+ if (designSystem !== undefined) {
272
+ config.designSystem = resolveDesignSystem(designSystem);
273
+ }
274
+ const personalization = readConfigPersonalization(rawConfig.personalization);
275
+ if (personalization !== undefined) {
276
+ config.personalization = personalization;
258
277
  }
259
278
  if (config.preset === undefined && rawConfig.preset !== undefined) {
260
279
  throw new Error(`Invalid preset in ${configPath}.`);
@@ -495,161 +514,84 @@ export function shouldPromptForInit(options, streams) {
495
514
  }
496
515
  return Boolean(streams.stdin?.isTTY || streams.stdout?.isTTY);
497
516
  }
517
+ function resolvePrompt(value) {
518
+ if (isCancel(value)) {
519
+ process.exit(130);
520
+ }
521
+ return value;
522
+ }
523
+ async function promptForTextValue(message, placeholder, defaultValue) {
524
+ return resolvePrompt(await text({ message, placeholder, defaultValue }));
525
+ }
498
526
  async function promptForPersonalization(defaults) {
499
- const shouldPersonalize = await confirm({
527
+ const shouldPersonalize = resolvePrompt(await confirm({
500
528
  message: "Personalize template placeholders?",
501
529
  initialValue: Boolean(defaults),
502
- });
503
- if (isCancel(shouldPersonalize)) {
504
- process.exit(130);
505
- }
530
+ }));
506
531
  if (!shouldPersonalize) {
507
532
  return undefined;
508
533
  }
509
- const projectName = await text({
510
- message: "Project name",
511
- placeholder: "[Project Name]",
512
- defaultValue: defaults?.projectName,
513
- });
514
- if (isCancel(projectName)) {
515
- process.exit(130);
516
- }
517
- const projectDescription = await text({
518
- message: "Short project description",
519
- placeholder: "[short project description]",
520
- defaultValue: defaults?.projectDescription,
521
- });
522
- if (isCancel(projectDescription)) {
523
- process.exit(130);
524
- }
525
- const issueTracker = await text({
526
- message: "Issue tracker name",
527
- placeholder: "Linear or GitHub Issues",
528
- defaultValue: defaults?.issueTracker,
529
- });
530
- if (isCancel(issueTracker)) {
531
- process.exit(130);
532
- }
533
- const designSystemPath = await text({
534
- message: "Design system path",
535
- placeholder: "docs/design-system.md",
536
- defaultValue: defaults?.designSystemPath,
537
- });
538
- if (isCancel(designSystemPath)) {
539
- process.exit(130);
540
- }
541
- const briefsPath = await text({
542
- message: "Briefs path",
543
- placeholder: "docs/briefs",
544
- defaultValue: defaults?.briefsPath,
545
- });
546
- if (isCancel(briefsPath)) {
547
- process.exit(130);
548
- }
549
- const testCommand = await text({
550
- message: "Test command",
551
- placeholder: "npm test",
552
- defaultValue: defaults?.testCommand,
553
- });
554
- if (isCancel(testCommand)) {
555
- process.exit(130);
556
- }
557
- const lintCommand = await text({
558
- message: "Lint command",
559
- placeholder: "npm run lint",
560
- defaultValue: defaults?.lintCommand,
561
- });
562
- if (isCancel(lintCommand)) {
563
- process.exit(130);
564
- }
565
- const buildCommand = await text({
566
- message: "Build/check command",
567
- placeholder: "npm run build",
568
- defaultValue: defaults?.buildCommand,
569
- });
570
- if (isCancel(buildCommand)) {
571
- process.exit(130);
572
- }
573
- const stackSummary = await text({
574
- message: "Stack summary",
575
- placeholder: "Next.js, TypeScript, Tailwind CSS, Vitest",
576
- defaultValue: defaults?.stackSummary,
577
- });
578
- if (isCancel(stackSummary)) {
579
- process.exit(130);
580
- }
581
534
  return {
582
- projectName,
583
- projectDescription,
584
- issueTracker,
585
- designSystemPath,
586
- briefsPath,
587
- testCommand,
588
- lintCommand,
589
- buildCommand,
590
- stackSummary,
535
+ projectName: await promptForTextValue("Project name", "[Project Name]", defaults?.projectName),
536
+ projectDescription: await promptForTextValue("Short project description", "[short project description]", defaults?.projectDescription),
537
+ issueTracker: await promptForTextValue("Issue tracker name", "Linear or GitHub Issues", defaults?.issueTracker),
538
+ designSystemPath: await promptForTextValue("Design system path", "docs/design-system.md", defaults?.designSystemPath),
539
+ briefsPath: await promptForTextValue("Briefs path", "docs/briefs", defaults?.briefsPath),
540
+ testCommand: await promptForTextValue("Test command", "npm test", defaults?.testCommand),
541
+ lintCommand: await promptForTextValue("Lint command", "npm run lint", defaults?.lintCommand),
542
+ buildCommand: await promptForTextValue("Build/check command", "npm run build", defaults?.buildCommand),
543
+ stackSummary: await promptForTextValue("Stack summary", "Next.js, TypeScript, Tailwind CSS, Vitest", defaults?.stackSummary),
591
544
  };
592
545
  }
593
546
  async function buildInitTemplateContent(file, preset, personalization, designSystem) {
594
547
  const content = await buildTemplateContent(file, preset, designSystem);
595
548
  return personalizeTemplateContent(file, content, personalization);
596
549
  }
550
+ async function installFileIfAllowed(targetDir, file, options, result, getContent) {
551
+ const destination = path.join(targetDir, file);
552
+ if ((await exists(destination)) && !options.force) {
553
+ result.skipped.push(file);
554
+ return;
555
+ }
556
+ result.created.push(file);
557
+ if (options.dryRun) {
558
+ return;
559
+ }
560
+ await mkdir(path.dirname(destination), { recursive: true });
561
+ await writeFile(destination, await getContent());
562
+ }
563
+ async function resolveInitTemplateFiles(options) {
564
+ const allTemplateFiles = await getTemplateFiles();
565
+ if (options.files) {
566
+ return options.files;
567
+ }
568
+ if (options.templateSet || options.aiTools) {
569
+ return getSelectedTemplateFiles(options.templateSet ?? "full", options.aiTools ?? [], allTemplateFiles);
570
+ }
571
+ return allTemplateFiles;
572
+ }
597
573
  async function installTemplates(targetArg, options) {
598
574
  const targetDir = path.resolve(process.cwd(), targetArg || ".");
599
- const allTemplateFiles = await getTemplateFiles();
600
- const files = options.files ??
601
- (options.templateSet || options.aiTools
602
- ? getSelectedTemplateFiles(options.templateSet ?? "full", options.aiTools ?? [], allTemplateFiles)
603
- : allTemplateFiles);
575
+ const files = await resolveInitTemplateFiles(options);
604
576
  const preset = resolvePreset(options.preset);
605
577
  const designSystem = effectiveDesignSystem(options.designSystem);
606
- const created = [];
607
- const skipped = [];
578
+ const result = { targetDir, created: [], skipped: [] };
608
579
  if (!options.dryRun) {
609
580
  await mkdir(targetDir, { recursive: true });
610
581
  }
611
582
  if (options.writeConfig) {
612
- const destination = path.join(targetDir, configFileName);
613
- const destinationExists = await exists(destination);
614
- if (destinationExists && !options.force) {
615
- skipped.push(configFileName);
616
- }
617
- else {
618
- created.push(configFileName);
619
- if (!options.dryRun) {
620
- await writeFile(destination, serializeConfig(getResolvedConfig(options)));
621
- }
622
- }
583
+ await installFileIfAllowed(targetDir, configFileName, options, result, () => serializeConfig(getResolvedConfig(options)));
623
584
  }
624
585
  for (const file of files) {
625
- const destination = path.join(targetDir, file);
626
- const destinationExists = await exists(destination);
627
- if (destinationExists && !options.force) {
628
- skipped.push(file);
629
- continue;
630
- }
631
- created.push(file);
632
- if (!options.dryRun) {
633
- await mkdir(path.dirname(destination), { recursive: true });
586
+ await installFileIfAllowed(targetDir, file, options, result, async () => {
634
587
  const content = await buildInitTemplateContent(file, preset, options.personalization, designSystem);
635
- await writeFile(destination, wrapManagedBlock(file, content));
636
- }
588
+ return wrapManagedBlock(file, content);
589
+ });
637
590
  }
638
591
  if (preset) {
639
- const stackFile = "STACK.md";
640
- const destination = path.join(targetDir, stackFile);
641
- const destinationExists = await exists(destination);
642
- if (destinationExists && !options.force) {
643
- skipped.push(stackFile);
644
- }
645
- else {
646
- created.push(stackFile);
647
- if (!options.dryRun) {
648
- await writeFile(destination, wrapManagedBlock(stackFile, getStackGuidance(preset)));
649
- }
650
- }
592
+ await installFileIfAllowed(targetDir, "STACK.md", options, result, () => wrapManagedBlock("STACK.md", getStackGuidance(preset)));
651
593
  }
652
- return { targetDir, created, skipped };
594
+ return result;
653
595
  }
654
596
  function replaceManagedBlock(file, existingContent, nextContent) {
655
597
  const id = getTemplateId(file);
@@ -770,110 +712,113 @@ function printUpdateResult(result, dryRun = false) {
770
712
  console.log("All managed AgentKit files are current.");
771
713
  }
772
714
  }
773
- async function resolveInteractiveTarget(target, options) {
774
- const providedPreset = resolvePreset(options.preset);
775
- const shouldPrompt = shouldPromptForInit(options, process);
776
- if (!shouldPrompt) {
715
+ async function promptForTarget(target) {
716
+ if (target && target !== ".") {
777
717
  return target;
778
718
  }
779
- intro("Welcome to AgentKit");
780
- let resolvedTarget = target;
781
- if (!target || target === ".") {
782
- const targetResponse = await text({
783
- message: "Where should AgentKit install files?",
784
- placeholder: ".",
785
- defaultValue: ".",
786
- });
787
- if (isCancel(targetResponse)) {
788
- process.exit(130);
789
- }
790
- resolvedTarget = targetResponse || ".";
791
- }
792
- if (!providedPreset) {
793
- const projectTypeResponse = await select({
794
- message: "What type of project is this?",
795
- initialValue: "generic",
796
- options: [
797
- { label: "Generic TypeScript project", value: "generic" },
798
- { label: "Next.js app", value: "next" },
799
- { label: "SvelteKit app", value: "sveltekit" },
800
- { label: "Express API", value: "express" },
801
- { label: "Convex app", value: "convex" },
802
- { label: "Fullstack app", value: "fullstack" },
803
- ],
804
- });
805
- if (isCancel(projectTypeResponse)) {
806
- process.exit(130);
807
- }
808
- options.preset = resolveProjectPreset(projectTypeResponse);
809
- }
810
- const aiToolResponse = await multiselect({
719
+ return (resolvePrompt(await text({
720
+ message: "Where should AgentKit install files?",
721
+ placeholder: ".",
722
+ defaultValue: ".",
723
+ })) || ".");
724
+ }
725
+ async function promptForProjectPreset() {
726
+ const projectType = resolvePrompt(await select({
727
+ message: "What type of project is this?",
728
+ initialValue: "generic",
729
+ options: [
730
+ { label: "Generic TypeScript project", value: "generic" },
731
+ { label: "Next.js app", value: "next" },
732
+ { label: "SvelteKit app", value: "sveltekit" },
733
+ { label: "Express API", value: "express" },
734
+ { label: "Convex app", value: "convex" },
735
+ { label: "Fullstack app", value: "fullstack" },
736
+ ],
737
+ }));
738
+ return resolveProjectPreset(projectType);
739
+ }
740
+ async function promptForAiTools(defaults) {
741
+ return resolvePrompt(await multiselect({
811
742
  message: "Which AI tools do you use?",
812
- initialValues: options.aiTools ?? ["codex", "cursor", "claude"],
743
+ initialValues: defaults ?? ["codex", "cursor", "claude"],
813
744
  options: [
814
745
  { label: "Codex", value: "codex" },
815
746
  { label: "Cursor", value: "cursor" },
816
747
  { label: "Claude Code", value: "claude" },
817
748
  { label: "GitHub Copilot", value: "copilot" },
818
749
  ],
819
- });
820
- if (isCancel(aiToolResponse)) {
821
- process.exit(130);
822
- }
823
- const templateSetResponse = await select({
750
+ }));
751
+ }
752
+ async function promptForTemplateSet(defaultValue) {
753
+ return resolvePrompt(await select({
824
754
  message: "Which template set do you want?",
825
- initialValue: options.templateSet ?? "standard",
755
+ initialValue: defaultValue ?? "standard",
826
756
  options: [
827
757
  { label: "Minimal", value: "minimal" },
828
758
  { label: "Standard", value: "standard" },
829
759
  { label: "Full", value: "full" },
830
760
  ],
831
- });
832
- if (isCancel(templateSetResponse)) {
833
- process.exit(130);
761
+ }));
762
+ }
763
+ async function promptForDesignSystem(defaultValue) {
764
+ return resolvePrompt(await select({
765
+ message: "Which design system guidance?",
766
+ initialValue: effectiveDesignSystem(defaultValue),
767
+ options: validDesignSystems.map((value) => ({ label: designSystemLabels[value], value })),
768
+ }));
769
+ }
770
+ async function findExistingInstallFiles(target, files) {
771
+ const targetDir = path.resolve(process.cwd(), target || ".");
772
+ const existingFiles = [];
773
+ for (const file of files) {
774
+ if (await exists(path.join(targetDir, file))) {
775
+ existingFiles.push(file);
776
+ }
777
+ }
778
+ return existingFiles;
779
+ }
780
+ async function promptForConflictStrategy(existingFiles) {
781
+ return resolvePrompt(await select({
782
+ message: `Existing files found: ${existingFiles.join(", ")}. How should AgentKit handle conflicts?`,
783
+ initialValue: "skip",
784
+ options: [
785
+ { label: "Skip existing files", value: "skip" },
786
+ { label: "Overwrite existing files", value: "overwrite" },
787
+ ],
788
+ }));
789
+ }
790
+ async function applyInteractiveSelections(resolvedTarget, providedPreset, options) {
791
+ if (!providedPreset) {
792
+ options.preset = await promptForProjectPreset();
834
793
  }
794
+ const aiTools = await promptForAiTools(options.aiTools);
795
+ const templateSet = await promptForTemplateSet(options.templateSet);
835
796
  const templateFiles = await getTemplateFiles();
836
- options.templateSet = templateSetResponse;
837
- options.aiTools = aiToolResponse;
838
- options.files = getSelectedTemplateFiles(templateSetResponse, aiToolResponse, templateFiles);
839
- if (templateSetResponse === "standard" || templateSetResponse === "full") {
840
- const designSystemResponse = await select({
841
- message: "Which design system guidance?",
842
- initialValue: effectiveDesignSystem(options.designSystem),
843
- options: validDesignSystems.map((value) => ({ label: designSystemLabels[value], value })),
844
- });
845
- if (isCancel(designSystemResponse)) {
846
- process.exit(130);
847
- }
848
- options.designSystem = designSystemResponse;
797
+ options.templateSet = templateSet;
798
+ options.aiTools = aiTools;
799
+ options.files = getSelectedTemplateFiles(templateSet, aiTools, templateFiles);
800
+ if (templateSet === "standard" || templateSet === "full") {
801
+ options.designSystem = await promptForDesignSystem(options.designSystem);
849
802
  }
850
803
  if (!options.force) {
851
804
  const preset = resolvePreset(options.preset);
852
805
  const installFiles = preset ? [...options.files, "STACK.md"] : options.files;
853
- const targetDir = path.resolve(process.cwd(), resolvedTarget || ".");
854
- const existingFiles = [];
855
- for (const file of installFiles) {
856
- if (await exists(path.join(targetDir, file))) {
857
- existingFiles.push(file);
858
- }
859
- }
806
+ const existingFiles = await findExistingInstallFiles(resolvedTarget, installFiles);
860
807
  if (existingFiles.length > 0) {
861
- const conflictResponse = await select({
862
- message: `Existing files found: ${existingFiles.join(", ")}. How should AgentKit handle conflicts?`,
863
- initialValue: "skip",
864
- options: [
865
- { label: "Skip existing files", value: "skip" },
866
- { label: "Overwrite existing files", value: "overwrite" },
867
- ],
868
- });
869
- if (isCancel(conflictResponse)) {
870
- process.exit(130);
871
- }
872
- options.force = conflictResponse === "overwrite";
808
+ options.force = (await promptForConflictStrategy(existingFiles)) === "overwrite";
873
809
  }
874
810
  }
811
+ }
812
+ async function resolveInteractiveTarget(target, options) {
813
+ const providedPreset = resolvePreset(options.preset);
814
+ if (!shouldPromptForInit(options, process)) {
815
+ return target;
816
+ }
817
+ intro("Welcome to AgentKit");
818
+ const resolvedTarget = await promptForTarget(target);
819
+ await applyInteractiveSelections(resolvedTarget, providedPreset, options);
875
820
  options.personalization = await promptForPersonalization(options.personalization);
876
- return resolvedTarget || ".";
821
+ return resolvedTarget;
877
822
  }
878
823
  async function main() {
879
824
  if (process.argv.slice(2).includes("--list")) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thomas-agentkit",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "Install AI-agent-ready development templates into a project.",
5
5
  "license": "MIT",
6
6
  "repository": {