ai-engineering-kit 0.3.0 → 0.4.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.
Files changed (2) hide show
  1. package/dist/cli.js +108 -55
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { basename as basename2 } from "path";
5
- import { intro, outro, confirm, select, isCancel as isCancel2, cancel as cancel2, log } from "@clack/prompts";
4
+ import { basename as basename3 } from "path";
5
+ import { intro, outro, confirm, select as select2, isCancel as isCancel2, cancel as cancel2, note } from "@clack/prompts";
6
6
 
7
7
  // src/catalog/load.ts
8
8
  import { readdirSync, readFileSync } from "fs";
@@ -162,6 +162,31 @@ function resolveSelection(catalog, selection) {
162
162
  });
163
163
  }
164
164
 
165
+ // src/menu/summary.ts
166
+ import { basename } from "path";
167
+ var labelOf = (id) => COMPONENTS.find((c) => c.id === id)?.label ?? id;
168
+ function summarize(files, plan) {
169
+ const byPath = new Map(files.map((f) => [f.targetPath, f]));
170
+ const groups = /* @__PURE__ */ new Map();
171
+ const conflicts = [];
172
+ let written = 0;
173
+ for (const action of plan.actions) {
174
+ if (action.type === "conflict") {
175
+ conflicts.push(action.path);
176
+ continue;
177
+ }
178
+ if (action.type !== "add" && action.type !== "refresh") continue;
179
+ const file = byPath.get(action.path);
180
+ if (!file) continue;
181
+ written++;
182
+ const group = groups.get(file.component) ?? groups.set(file.component, { component: file.component, label: labelOf(file.component), count: 0, names: [] }).get(file.component);
183
+ group.count++;
184
+ if (file.component === "entry-files") group.names.push(basename(file.targetPath));
185
+ }
186
+ const ordered = COMPONENTS.map((c) => groups.get(c.id)).filter((g) => g !== void 0);
187
+ return { groups: ordered, conflicts, written };
188
+ }
189
+
165
190
  // src/planner/planner.ts
166
191
  function planInstall(input) {
167
192
  const files = resolveSelection(input.catalog, input.selection);
@@ -338,8 +363,8 @@ On Windows, enable Developer Mode (or run as admin) and re-run, or point Claude
338
363
  }
339
364
 
340
365
  // src/menu/prompt.ts
341
- import { basename } from "path";
342
- import { multiselect, text, isCancel, cancel } from "@clack/prompts";
366
+ import { basename as basename2 } from "path";
367
+ import { select, multiselect, text, isCancel, cancel } from "@clack/prompts";
343
368
 
344
369
  // src/menu/menu.ts
345
370
  function componentOptions() {
@@ -351,6 +376,13 @@ function categoryOptions() {
351
376
  function agentOptions() {
352
377
  return AGENTS.map((a) => ({ value: a.id, label: a.label, hint: a.hint }));
353
378
  }
379
+ function presetSelection(preset) {
380
+ return {
381
+ components: COMPONENTS.map((c) => c.id),
382
+ categories: preset === "everything" ? CATEGORIES.map((c) => c.id) : ["core-workflow"],
383
+ agents: AGENTS.map((a) => a.id)
384
+ };
385
+ }
354
386
  function buildSelection(answers) {
355
387
  return {
356
388
  components: answers.components,
@@ -367,35 +399,31 @@ function required(value) {
367
399
  }
368
400
  return value;
369
401
  }
370
- var allValues = (options) => options.map((o) => o.value);
371
- async function pickAll(message, options) {
372
- return required(
373
- await multiselect({
374
- message: `${message} (everything is pre-selected \u2014 toggle off what you don't want)`,
375
- options,
376
- initialValues: allValues(options),
377
- required: true
378
- })
379
- );
402
+ async function pick(message, options) {
403
+ return required(await multiselect({ message, options, required: true }));
404
+ }
405
+ async function chooseSelection() {
406
+ const components = await pick("Select parts to install", componentOptions());
407
+ const categories = components.includes("skills") ? await pick("Select skill categories", categoryOptions()) : [];
408
+ const agents = components.includes("entry-files") ? await pick("Select coding agents", agentOptions()) : [];
409
+ return buildSelection({ components, categories, agents });
380
410
  }
381
411
  async function promptForSelection(projectDir) {
382
- const components = await pickAll("Which parts do you want to install?", componentOptions());
383
- let categories = [];
384
- if (components.includes("skills")) {
385
- categories = await pickAll("Which skill categories?", categoryOptions());
386
- }
387
- let agents = [];
388
- if (components.includes("entry-files")) {
389
- agents = await pickAll("Which coding agents?", agentOptions());
390
- }
391
- const projectName = required(
392
- await text({
393
- message: "Project name",
394
- defaultValue: basename(projectDir),
395
- placeholder: basename(projectDir)
412
+ const mode = required(
413
+ await select({
414
+ message: `How do you want to set up ${basename2(projectDir)}?`,
415
+ options: [
416
+ { value: "everything", label: "Everything", hint: "all skills, docs, and workspace \u2014 Claude Code + Codex" },
417
+ { value: "core", label: "Core skills only", hint: "core-workflow skills, docs, and workspace \u2014 both agents" },
418
+ { value: "choose", label: "Choose what to install\u2026", hint: "pick components, skill categories, and agents" }
419
+ ]
396
420
  })
397
421
  );
398
- return { selection: buildSelection({ components, categories, agents }), projectName };
422
+ const selection = mode === "choose" ? await chooseSelection() : presetSelection(mode);
423
+ const projectName = selection.components.includes("entry-files") ? required(
424
+ await text({ message: "Project name", defaultValue: basename2(projectDir), placeholder: basename2(projectDir) })
425
+ ) : basename2(projectDir);
426
+ return { selection, projectName };
399
427
  }
400
428
 
401
429
  // src/applier/update.ts
@@ -547,7 +575,29 @@ function planFor(projectDir, selection) {
547
575
  onDisk: readOnDiskHashes(projectDir, files),
548
576
  manifest: readManifest(projectDir)
549
577
  });
550
- return { catalog, plan };
578
+ return { catalog, files, plan };
579
+ }
580
+ function formatSummary(summary) {
581
+ if (summary.written === 0 && summary.conflicts.length === 0) return "Everything is already up to date.";
582
+ const width = Math.max(0, ...summary.groups.map((g) => g.label.length));
583
+ const lines = summary.groups.map((g) => {
584
+ const detail = g.names.length > 0 ? g.names.join(", ") : String(g.count);
585
+ return ` ${g.label.padEnd(width)} ${detail}`;
586
+ });
587
+ if (summary.conflicts.length > 0) {
588
+ lines.push("", `Conflicts \u2014 you edited these (you'll choose per file):`);
589
+ for (const path of summary.conflicts) lines.push(` ${path}`);
590
+ }
591
+ return lines.join("\n");
592
+ }
593
+ function successOutro(verb, written, selection) {
594
+ if (selection.components.includes("skills")) {
595
+ note(
596
+ "Open your coding agent in this project and run a skill:\n setup-foundations fill in docs/foundations\n kickoff brainstorm an MVP",
597
+ "Next steps"
598
+ );
599
+ }
600
+ outro(`${verb} ${written} file${written === 1 ? "" : "s"}.`);
551
601
  }
552
602
  function reportConflict(error) {
553
603
  if (!(error instanceof ApplyConflictError)) return false;
@@ -567,7 +617,7 @@ function dryRun(args) {
567
617
  function init(args) {
568
618
  const projectDir = process.cwd();
569
619
  const selection = selectionFromArgs(args);
570
- const projectName = parseValue(args, "--name") ?? basename2(projectDir);
620
+ const projectName = parseValue(args, "--name") ?? basename3(projectDir);
571
621
  try {
572
622
  const result = runInit({ projectDir, selection, projectName });
573
623
  console.log(`ai-engineering-kit ${kitVersion()} installed into ${projectDir}`);
@@ -587,7 +637,7 @@ async function runReRun(projectDir) {
587
637
  const manifest = readManifest(projectDir);
588
638
  intro(`ai-engineering-kit ${kitVersion()} \u2014 already installed`);
589
639
  const mode = bail(
590
- await select({
640
+ await select2({
591
641
  message: "What would you like to do?",
592
642
  options: [
593
643
  { value: "update", label: "Update installed parts to latest" },
@@ -597,7 +647,7 @@ async function runReRun(projectDir) {
597
647
  })
598
648
  );
599
649
  let addParts = EMPTY_SELECTION;
600
- let projectName = basename2(projectDir);
650
+ let projectName = basename3(projectDir);
601
651
  if (mode === "add" || mode === "both") {
602
652
  const picked = await promptForSelection(projectDir);
603
653
  addParts = picked.selection;
@@ -607,24 +657,27 @@ async function runReRun(projectDir) {
607
657
  const applicable = {
608
658
  actions: prepared.plan.actions.filter((a) => prepared.applyTypes.has(a.type))
609
659
  };
610
- log.message(formatPlan(applicable));
660
+ const pending = applicable.actions.filter((a) => a.type !== "skip").length;
661
+ if (pending === 0) {
662
+ outro("Already up to date \u2014 nothing to do.");
663
+ return;
664
+ }
665
+ note(formatSummary(summarize(prepared.files, applicable)), mode === "add" ? "This will add" : "This will update");
611
666
  const resolutions = /* @__PURE__ */ new Map();
612
- if (prepared.applyTypes.has("conflict")) {
613
- for (const action of applicable.actions.filter((a) => a.type === "conflict")) {
614
- const choice = bail(
615
- await select({
616
- message: `You edited ${action.path}. A newer version exists.`,
617
- options: [
618
- { value: "keep-mine", label: "Keep mine" },
619
- { value: "overwrite", label: "Overwrite with the new version" },
620
- { value: "keep-theirs", label: "Keep mine, save theirs as .new" }
621
- ]
622
- })
623
- );
624
- resolutions.set(action.path, choice);
625
- }
667
+ const conflicts = applicable.actions.filter((a) => a.type === "conflict");
668
+ for (const [index, action] of conflicts.entries()) {
669
+ const choice = bail(
670
+ await select2({
671
+ message: `Conflict ${index + 1}/${conflicts.length}: you edited ${action.path}, and a newer version exists.`,
672
+ options: [
673
+ { value: "keep-mine", label: "Keep mine", hint: "discard the update for this file" },
674
+ { value: "overwrite", label: "Use the new version", hint: "replace your file" },
675
+ { value: "keep-theirs", label: "Keep mine + save theirs", hint: `writes ${basename3(action.path)}.new` }
676
+ ]
677
+ })
678
+ );
679
+ resolutions.set(action.path, choice);
626
680
  }
627
- const pending = applicable.actions.filter((a) => a.type !== "skip").length;
628
681
  const proceed = await confirm({ message: `Apply ${pending} changes in ${projectDir}?` });
629
682
  if (isCancel2(proceed) || !proceed) {
630
683
  outro("Nothing written.");
@@ -642,24 +695,24 @@ async function runReRun(projectDir) {
642
695
  projectName,
643
696
  applyTypes: prepared.applyTypes
644
697
  });
645
- outro(`Done. ${result.written.length} files written.`);
698
+ successOutro(mode === "add" ? "Added" : "Updated", result.written.length, prepared.selection);
646
699
  }
647
700
  async function runInteractive() {
648
701
  const projectDir = process.cwd();
649
702
  if (readManifest(projectDir)) return runReRun(projectDir);
650
703
  intro(`ai-engineering-kit ${kitVersion()}`);
651
704
  const { selection, projectName } = await promptForSelection(projectDir);
652
- const { plan } = planFor(projectDir, selection);
653
- log.message(formatPlan(plan));
705
+ const { files, plan } = planFor(projectDir, selection);
706
+ note(formatSummary(summarize(files, plan)), "This will add");
654
707
  const pending = plan.actions.filter((a) => a.type !== "skip").length;
655
- const proceed = await confirm({ message: `Scaffold ${pending} files into ${projectDir}?` });
708
+ const proceed = await confirm({ message: `Install ${pending} files into ${projectDir}?` });
656
709
  if (isCancel2(proceed) || !proceed) {
657
710
  outro("Nothing written.");
658
711
  return;
659
712
  }
660
713
  try {
661
714
  const result = runInit({ projectDir, selection, projectName });
662
- outro(`Installed ${result.written.length} files into ${projectDir}.`);
715
+ successOutro("Installed", result.written.length, selection);
663
716
  } catch (error) {
664
717
  if (!reportConflict(error)) throw error;
665
718
  }
@@ -682,7 +735,7 @@ function performReRun(projectDir, mode, addParts) {
682
735
  priorManifest: manifest,
683
736
  selection: prepared.selection,
684
737
  version: prepared.version,
685
- projectName: basename2(projectDir),
738
+ projectName: basename3(projectDir),
686
739
  applyTypes: prepared.applyTypes
687
740
  });
688
741
  console.log(`${result.written.length} files written. Edited files were kept (use the interactive flow to resolve).`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-engineering-kit",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "An opinionated, agent-agnostic AI-development kit — disciplined skills plus a structured workspace — installed and updated through one npx command.",
5
5
  "type": "module",
6
6
  "license": "MIT",