@kitnai/cli 0.1.7 → 0.1.9

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/index.js CHANGED
@@ -215,7 +215,7 @@ async function resolveDependencies(names, fetchItem) {
215
215
  const visited = /* @__PURE__ */ new Set();
216
216
  const items = /* @__PURE__ */ new Map();
217
217
  const edges = [];
218
- async function resolve(name) {
218
+ async function resolve3(name) {
219
219
  if (visited.has(name)) return;
220
220
  visited.add(name);
221
221
  const item = await fetchItem(name);
@@ -223,11 +223,11 @@ async function resolveDependencies(names, fetchItem) {
223
223
  const deps = item.registryDependencies ?? [];
224
224
  for (const dep of deps) {
225
225
  edges.push([dep, name]);
226
- await resolve(dep);
226
+ await resolve3(dep);
227
227
  }
228
228
  }
229
229
  for (const name of names) {
230
- await resolve(name);
230
+ await resolve3(name);
231
231
  }
232
232
  return topologicalSort(items, edges);
233
233
  }
@@ -326,25 +326,119 @@ var init_dep_installer = __esm({
326
326
  }
327
327
  });
328
328
 
329
- // src/installers/env-checker.ts
329
+ // src/installers/env-writer.ts
330
+ import * as p2 from "@clack/prompts";
330
331
  import pc3 from "picocolors";
331
- function checkEnvVars(envVars) {
332
- const missing = [];
333
- for (const [key, description] of Object.entries(envVars)) {
334
- if (!process.env[key]) {
335
- missing.push(` ${pc3.yellow(key)}: ${description}`);
332
+ import { readFile as readFile4, writeFile as writeFile4, access as access3 } from "fs/promises";
333
+ import { join as join4 } from "path";
334
+ function parseEnvKeys(content) {
335
+ const keys = /* @__PURE__ */ new Set();
336
+ for (const line of content.split("\n")) {
337
+ const trimmed = line.trim();
338
+ if (!trimmed || trimmed.startsWith("#")) continue;
339
+ const eqIndex = trimmed.indexOf("=");
340
+ if (eqIndex > 0) {
341
+ keys.add(trimmed.slice(0, eqIndex).trim());
342
+ }
343
+ }
344
+ return keys;
345
+ }
346
+ async function readEnvFile(path) {
347
+ try {
348
+ return await readFile4(path, "utf-8");
349
+ } catch {
350
+ return "";
351
+ }
352
+ }
353
+ function collectEnvVars(items) {
354
+ const merged = {};
355
+ for (const item of items) {
356
+ if (item.envVars) {
357
+ Object.assign(merged, item.envVars);
336
358
  }
337
359
  }
338
- return missing;
360
+ return merged;
339
361
  }
340
- var init_env_checker = __esm({
341
- "src/installers/env-checker.ts"() {
362
+ async function handleEnvVars(cwd, envVars) {
363
+ const keys = Object.keys(envVars);
364
+ if (keys.length === 0) return;
365
+ const envPath = join4(cwd, ".env");
366
+ const examplePath = join4(cwd, ".env.example");
367
+ const envContent = await readEnvFile(envPath);
368
+ const exampleContent = await readEnvFile(examplePath);
369
+ const envKeys = parseEnvKeys(envContent);
370
+ const exampleKeys = parseEnvKeys(exampleContent);
371
+ const missingFromExample = keys.filter((k) => !exampleKeys.has(k));
372
+ const missingFromEnv = keys.filter((k) => !envKeys.has(k) && !process.env[k]);
373
+ if (missingFromExample.length > 0) {
374
+ const lines = [];
375
+ if (exampleContent && !exampleContent.endsWith("\n")) lines.push("");
376
+ for (const key of missingFromExample) {
377
+ const config = envVars[key];
378
+ lines.push(`# ${config.description}${config.url ? ` (${config.url})` : ""}`);
379
+ lines.push(`${key}=`);
380
+ }
381
+ await writeFile4(examplePath, exampleContent + lines.join("\n") + "\n");
382
+ p2.log.info(`Updated ${pc3.cyan(".env.example")} with ${missingFromExample.length} variable(s)`);
383
+ }
384
+ if (missingFromEnv.length === 0) return;
385
+ p2.log.message("");
386
+ p2.log.warn(
387
+ `${missingFromEnv.length} environment variable(s) needed:`
388
+ );
389
+ for (const key of missingFromEnv) {
390
+ const config = envVars[key];
391
+ const req = config.required !== false ? pc3.red("*") : "";
392
+ p2.log.message(` ${pc3.yellow(key)}${req}: ${config.description}${config.url ? pc3.dim(` -> ${config.url}`) : ""}`);
393
+ }
394
+ const shouldPrompt = await p2.confirm({
395
+ message: "Would you like to enter values now?",
396
+ initialValue: true
397
+ });
398
+ if (p2.isCancel(shouldPrompt) || !shouldPrompt) {
399
+ p2.log.info(`Add them to ${pc3.cyan(".env")} when ready.`);
400
+ return;
401
+ }
402
+ const newEntries = [];
403
+ for (const key of missingFromEnv) {
404
+ const config = envVars[key];
405
+ const isSecret = config.secret !== false;
406
+ let value;
407
+ if (isSecret) {
408
+ value = await p2.password({
409
+ message: `${key}:`
410
+ });
411
+ } else {
412
+ value = await p2.text({
413
+ message: `${key}:`,
414
+ placeholder: config.description
415
+ });
416
+ }
417
+ if (p2.isCancel(value)) {
418
+ p2.log.info(`Skipped remaining variables. Add them to ${pc3.cyan(".env")} when ready.`);
419
+ break;
420
+ }
421
+ if (value) {
422
+ newEntries.push(`${key}=${value}`);
423
+ }
424
+ }
425
+ if (newEntries.length > 0) {
426
+ const existingEnv = await readEnvFile(envPath);
427
+ const lines = [];
428
+ if (existingEnv && !existingEnv.endsWith("\n")) lines.push("");
429
+ lines.push(...newEntries);
430
+ await writeFile4(envPath, existingEnv + lines.join("\n") + "\n");
431
+ p2.log.success(`Wrote ${newEntries.length} variable(s) to ${pc3.cyan(".env")}`);
432
+ }
433
+ }
434
+ var init_env_writer = __esm({
435
+ "src/installers/env-writer.ts"() {
342
436
  "use strict";
343
437
  }
344
438
  });
345
439
 
346
440
  // src/installers/import-rewriter.ts
347
- import { relative, join as join4 } from "path";
441
+ import { relative, join as join5 } from "path";
348
442
  function rewriteKitnImports(content, fileType, fileName, aliases) {
349
443
  const sourceAliasKey = TYPE_TO_ALIAS_KEY[fileType];
350
444
  if (!sourceAliasKey) return content;
@@ -356,7 +450,7 @@ function rewriteKitnImports(content, fileType, fileName, aliases) {
356
450
  return `${prefix}@kitn/${type}/${targetPath}${quote}`;
357
451
  }
358
452
  const targetDir = aliases[type];
359
- const targetFile = join4(targetDir, targetPath);
453
+ const targetFile = join5(targetDir, targetPath);
360
454
  let rel = relative(sourceDir, targetFile);
361
455
  rel = rel.split("\\").join("/");
362
456
  if (!rel.startsWith(".")) {
@@ -381,10 +475,10 @@ var init_import_rewriter = __esm({
381
475
  });
382
476
 
383
477
  // src/installers/tsconfig-patcher.ts
384
- import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
385
- import { join as join5 } from "path";
386
- function stripJsonc(text2) {
387
- return text2.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/,\s*([}\]])/g, "$1");
478
+ import { readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
479
+ import { join as join6 } from "path";
480
+ function stripJsonc(text3) {
481
+ return text3.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/,\s*([}\]])/g, "$1");
388
482
  }
389
483
  function patchTsconfig(tsconfigContent, paths) {
390
484
  const config = JSON.parse(stripJsonc(tsconfigContent));
@@ -400,15 +494,15 @@ function patchTsconfig(tsconfigContent, paths) {
400
494
  return JSON.stringify(config, null, 2) + "\n";
401
495
  }
402
496
  async function patchProjectTsconfig(projectDir, paths) {
403
- const tsconfigPath = join5(projectDir, "tsconfig.json");
497
+ const tsconfigPath = join6(projectDir, "tsconfig.json");
404
498
  let content;
405
499
  try {
406
- content = await readFile4(tsconfigPath, "utf-8");
500
+ content = await readFile5(tsconfigPath, "utf-8");
407
501
  } catch {
408
502
  content = "{}";
409
503
  }
410
504
  const patched = patchTsconfig(content, paths);
411
- await writeFile4(tsconfigPath, patched);
505
+ await writeFile5(tsconfigPath, patched);
412
506
  }
413
507
  var init_tsconfig_patcher = __esm({
414
508
  "src/installers/tsconfig-patcher.ts"() {
@@ -416,6 +510,40 @@ var init_tsconfig_patcher = __esm({
416
510
  }
417
511
  });
418
512
 
513
+ // src/installers/barrel-manager.ts
514
+ function createBarrelFile() {
515
+ return `${BARREL_COMMENT}
516
+ ${EXPORT_LINE}
517
+ `;
518
+ }
519
+ function addImportToBarrel(content, importPath) {
520
+ const importLine = `import "${importPath}";`;
521
+ if (content.includes(importLine)) return content;
522
+ const exportIndex = content.indexOf(EXPORT_LINE);
523
+ if (exportIndex === -1) {
524
+ return `${content.trimEnd()}
525
+ ${importLine}
526
+ ${EXPORT_LINE}
527
+ `;
528
+ }
529
+ const before = content.slice(0, exportIndex);
530
+ const after = content.slice(exportIndex);
531
+ return `${before}${importLine}
532
+ ${after}`;
533
+ }
534
+ function removeImportFromBarrel(content, importPath) {
535
+ const importLine = `import "${importPath}";`;
536
+ return content.split("\n").filter((line) => line.trim() !== importLine).join("\n");
537
+ }
538
+ var EXPORT_LINE, BARREL_COMMENT;
539
+ var init_barrel_manager = __esm({
540
+ "src/installers/barrel-manager.ts"() {
541
+ "use strict";
542
+ EXPORT_LINE = 'export { registerWithPlugin } from "@kitnai/core";';
543
+ BARREL_COMMENT = "// Managed by kitn CLI \u2014 components auto-imported below";
544
+ }
545
+ });
546
+
419
547
  // src/utils/hash.ts
420
548
  import { createHash } from "crypto";
421
549
  function contentHash(content) {
@@ -457,7 +585,7 @@ var init_parse_ref = __esm({
457
585
 
458
586
  // src/registry/schema.ts
459
587
  import { z as z2 } from "zod";
460
- var componentType2, registryFileSchema, changelogEntrySchema, registryItemSchema, registryIndexItemSchema, registryIndexSchema, typeToDir;
588
+ var componentType2, registryFileSchema, changelogEntrySchema, envVarConfigSchema, componentConfigSchema, registryItemSchema, registryIndexItemSchema, registryIndexSchema, typeToDir;
461
589
  var init_schema = __esm({
462
590
  "src/registry/schema.ts"() {
463
591
  "use strict";
@@ -473,6 +601,31 @@ var init_schema = __esm({
473
601
  type: z2.enum(["feature", "fix", "breaking", "initial"]),
474
602
  note: z2.string()
475
603
  });
604
+ envVarConfigSchema = z2.object({
605
+ description: z2.string(),
606
+ required: z2.boolean().optional(),
607
+ secret: z2.boolean().optional(),
608
+ url: z2.string().optional()
609
+ });
610
+ componentConfigSchema = z2.object({
611
+ $schema: z2.string().optional(),
612
+ type: componentType2,
613
+ name: z2.string().optional(),
614
+ version: z2.string().optional(),
615
+ description: z2.string().optional(),
616
+ dependencies: z2.array(z2.string()).optional(),
617
+ devDependencies: z2.array(z2.string()).optional(),
618
+ registryDependencies: z2.array(z2.string()).optional(),
619
+ files: z2.array(z2.string()).optional(),
620
+ sourceDir: z2.string().optional(),
621
+ exclude: z2.array(z2.string()).optional(),
622
+ installDir: z2.string().optional(),
623
+ tsconfig: z2.record(z2.string(), z2.array(z2.string())).optional(),
624
+ envVars: z2.record(z2.string(), envVarConfigSchema).optional(),
625
+ categories: z2.array(z2.string()).optional(),
626
+ docs: z2.string().optional(),
627
+ changelog: z2.array(changelogEntrySchema).optional()
628
+ });
476
629
  registryItemSchema = z2.object({
477
630
  $schema: z2.string().optional(),
478
631
  name: z2.string(),
@@ -481,7 +634,7 @@ var init_schema = __esm({
481
634
  dependencies: z2.array(z2.string()).optional(),
482
635
  devDependencies: z2.array(z2.string()).optional(),
483
636
  registryDependencies: z2.array(z2.string()).optional(),
484
- envVars: z2.record(z2.string(), z2.string()).optional(),
637
+ envVars: z2.record(z2.string(), envVarConfigSchema).optional(),
485
638
  files: z2.array(registryFileSchema),
486
639
  installDir: z2.string().optional(),
487
640
  tsconfig: z2.record(z2.string(), z2.array(z2.string())).optional(),
@@ -521,19 +674,22 @@ var add_exports = {};
521
674
  __export(add_exports, {
522
675
  addCommand: () => addCommand
523
676
  });
524
- import * as p2 from "@clack/prompts";
677
+ import * as p3 from "@clack/prompts";
525
678
  import pc4 from "picocolors";
526
- import { join as join6 } from "path";
679
+ import { join as join7 } from "path";
680
+ import { existsSync } from "fs";
681
+ import { readFile as readFile6, writeFile as writeFile6, mkdir as mkdir3 } from "fs/promises";
682
+ import { relative as relative2 } from "path";
527
683
  async function addCommand(components, opts) {
528
- p2.intro(pc4.bgCyan(pc4.black(" kitn add ")));
684
+ p3.intro(pc4.bgCyan(pc4.black(" kitn add ")));
529
685
  const cwd = process.cwd();
530
686
  const config = await readConfig(cwd);
531
687
  if (!config) {
532
- p2.log.error("No kitn.json found. Run `kitn init` first.");
688
+ p3.log.error("No kitn.json found. Run `kitn init` first.");
533
689
  process.exit(1);
534
690
  }
535
691
  if (components.length === 0) {
536
- p2.log.error("Please specify at least one component to add.");
692
+ p3.log.error("Please specify at least one component to add.");
537
693
  process.exit(1);
538
694
  }
539
695
  const resolvedComponents = components.map((c) => {
@@ -545,7 +701,7 @@ async function addCommand(components, opts) {
545
701
  });
546
702
  const refs = resolvedComponents.map(parseComponentRef);
547
703
  const fetcher = new RegistryFetcher(config.registries);
548
- const s = p2.spinner();
704
+ const s = p3.spinner();
549
705
  s.start("Resolving dependencies...");
550
706
  let resolved;
551
707
  try {
@@ -559,31 +715,27 @@ async function addCommand(components, opts) {
559
715
  });
560
716
  } catch (err) {
561
717
  s.stop(pc4.red("Failed to resolve dependencies"));
562
- p2.log.error(err.message);
718
+ p3.log.error(err.message);
563
719
  process.exit(1);
564
720
  }
565
721
  s.stop(`Resolved ${resolved.length} component(s)`);
566
- p2.log.info("Components to install:");
722
+ p3.log.info("Components to install:");
567
723
  for (const item of resolved) {
568
724
  const isExplicit = resolvedComponents.includes(item.name) || components.includes(item.name);
569
725
  const label = isExplicit ? item.name : `${item.name} ${pc4.dim("(dependency)")}`;
570
- p2.log.message(` ${pc4.cyan(label)}`);
726
+ p3.log.message(` ${pc4.cyan(label)}`);
571
727
  }
572
728
  const created = [];
573
729
  const updated = [];
574
730
  const skipped = [];
575
731
  const allDeps = [];
576
- const allEnvWarnings = [];
577
732
  for (const item of resolved) {
578
733
  if (item.dependencies) allDeps.push(...item.dependencies);
579
- if (item.envVars) {
580
- allEnvWarnings.push(...checkEnvVars(item.envVars));
581
- }
582
734
  if (item.type === "kitn:package") {
583
- const baseDir = config.aliases.base ?? "src/ai";
735
+ const baseDir2 = config.aliases.base ?? "src/ai";
584
736
  for (const file of item.files) {
585
- const targetPath = join6(cwd, baseDir, file.path);
586
- const relativePath = join6(baseDir, file.path);
737
+ const targetPath = join7(cwd, baseDir2, file.path);
738
+ const relativePath = join7(baseDir2, file.path);
587
739
  const status = await checkFileStatus(targetPath, file.content);
588
740
  switch (status) {
589
741
  case "new" /* New */:
@@ -600,15 +752,15 @@ async function addCommand(components, opts) {
600
752
  } else {
601
753
  const existing = await readExistingFile(targetPath);
602
754
  const diff = generateDiff(relativePath, existing ?? "", file.content);
603
- p2.log.message(pc4.dim(diff));
604
- const action = await p2.select({
755
+ p3.log.message(pc4.dim(diff));
756
+ const action = await p3.select({
605
757
  message: `${relativePath} already exists and differs. What to do?`,
606
758
  options: [
607
759
  { value: "skip", label: "Keep local version" },
608
760
  { value: "overwrite", label: "Overwrite with registry version" }
609
761
  ]
610
762
  });
611
- if (!p2.isCancel(action) && action === "overwrite") {
763
+ if (!p3.isCancel(action) && action === "overwrite") {
612
764
  await writeComponentFile(targetPath, file.content);
613
765
  updated.push(relativePath);
614
766
  } else {
@@ -622,10 +774,10 @@ async function addCommand(components, opts) {
622
774
  const resolvedPaths = {};
623
775
  const installDir = item.installDir ?? item.name;
624
776
  for (const [key, values] of Object.entries(item.tsconfig)) {
625
- resolvedPaths[key] = values.map((v) => `./${join6(baseDir, installDir, v)}`);
777
+ resolvedPaths[key] = values.map((v) => `./${join7(baseDir2, installDir, v)}`);
626
778
  }
627
779
  await patchProjectTsconfig(cwd, resolvedPaths);
628
- p2.log.info(`Patched tsconfig.json with paths: ${Object.keys(resolvedPaths).join(", ")}`);
780
+ p3.log.info(`Patched tsconfig.json with paths: ${Object.keys(resolvedPaths).join(", ")}`);
629
781
  }
630
782
  const installed = config.installed ?? {};
631
783
  const allContent = item.files.map((f) => f.content).join("\n");
@@ -635,7 +787,7 @@ async function addCommand(components, opts) {
635
787
  registry: ref.namespace,
636
788
  version: item.version ?? "1.0.0",
637
789
  installedAt: (/* @__PURE__ */ new Date()).toISOString(),
638
- files: item.files.map((f) => join6(baseDir, f.path)),
790
+ files: item.files.map((f) => join7(baseDir2, f.path)),
639
791
  hash: contentHash(allContent)
640
792
  };
641
793
  config.installed = installed;
@@ -654,8 +806,8 @@ async function addCommand(components, opts) {
654
806
  }
655
807
  })();
656
808
  const fileName = file.path.split("/").pop();
657
- const targetPath = join6(cwd, config.aliases[aliasKey], fileName);
658
- const relativePath = join6(config.aliases[aliasKey], fileName);
809
+ const targetPath = join7(cwd, config.aliases[aliasKey], fileName);
810
+ const relativePath = join7(config.aliases[aliasKey], fileName);
659
811
  const content = rewriteKitnImports(file.content, item.type, fileName, config.aliases);
660
812
  const status = await checkFileStatus(targetPath, content);
661
813
  switch (status) {
@@ -673,15 +825,15 @@ async function addCommand(components, opts) {
673
825
  } else {
674
826
  const existing = await readExistingFile(targetPath);
675
827
  const diff = generateDiff(relativePath, existing ?? "", content);
676
- p2.log.message(pc4.dim(diff));
677
- const action = await p2.select({
828
+ p3.log.message(pc4.dim(diff));
829
+ const action = await p3.select({
678
830
  message: `${relativePath} already exists and differs. What to do?`,
679
831
  options: [
680
832
  { value: "skip", label: "Keep local version" },
681
833
  { value: "overwrite", label: "Overwrite with registry version" }
682
834
  ]
683
835
  });
684
- if (!p2.isCancel(action) && action === "overwrite") {
836
+ if (!p3.isCancel(action) && action === "overwrite") {
685
837
  await writeComponentFile(targetPath, content);
686
838
  updated.push(relativePath);
687
839
  } else {
@@ -716,13 +868,68 @@ async function addCommand(components, opts) {
716
868
  }
717
869
  })();
718
870
  const fileName = f.path.split("/").pop();
719
- return join6(config.aliases[aliasKey], fileName);
871
+ return join7(config.aliases[aliasKey], fileName);
720
872
  }),
721
873
  hash: contentHash(allContent)
722
874
  };
723
875
  config.installed = installed;
724
876
  }
725
877
  }
878
+ const BARREL_ELIGIBLE = /* @__PURE__ */ new Set(["kitn:agent", "kitn:tool", "kitn:skill"]);
879
+ const baseDir = config.aliases.base ?? "src/ai";
880
+ const barrelPath = join7(cwd, baseDir, "index.ts");
881
+ const barrelDir = join7(cwd, baseDir);
882
+ const barrelImports = [];
883
+ for (const item of resolved) {
884
+ if (!BARREL_ELIGIBLE.has(item.type)) continue;
885
+ for (const file of item.files) {
886
+ const aliasKey = (() => {
887
+ switch (item.type) {
888
+ case "kitn:agent":
889
+ return "agents";
890
+ case "kitn:tool":
891
+ return "tools";
892
+ case "kitn:skill":
893
+ return "skills";
894
+ }
895
+ })();
896
+ const fileName = file.path.split("/").pop();
897
+ const filePath = join7(cwd, config.aliases[aliasKey], fileName);
898
+ const importPath = "./" + relative2(barrelDir, filePath).replace(/\\/g, "/");
899
+ barrelImports.push(importPath);
900
+ }
901
+ }
902
+ if (barrelImports.length > 0) {
903
+ const barrelExisted = existsSync(barrelPath);
904
+ let barrelContent;
905
+ if (barrelExisted) {
906
+ barrelContent = await readFile6(barrelPath, "utf-8");
907
+ } else {
908
+ await mkdir3(barrelDir, { recursive: true });
909
+ barrelContent = createBarrelFile();
910
+ }
911
+ for (const importPath of barrelImports) {
912
+ barrelContent = addImportToBarrel(barrelContent, importPath);
913
+ }
914
+ await writeFile6(barrelPath, barrelContent);
915
+ p3.log.info(`Updated barrel file: ${join7(baseDir, "index.ts")}`);
916
+ if (!barrelExisted) {
917
+ p3.note(
918
+ [
919
+ `import { createAIPlugin } from "@kitnai/hono";`,
920
+ `import { registerWithPlugin } from "./ai";`,
921
+ ``,
922
+ `const plugin = createAIPlugin({`,
923
+ ` model: (model) => yourProvider(model ?? "default-model"),`,
924
+ `});`,
925
+ ``,
926
+ `registerWithPlugin(plugin);`,
927
+ `app.route("/api", plugin.app);`
928
+ ].join("\n"),
929
+ "Add this to your app setup"
930
+ );
931
+ }
932
+ }
726
933
  await writeConfig(cwd, config);
727
934
  const uniqueDeps = [...new Set(allDeps)];
728
935
  if (uniqueDeps.length > 0) {
@@ -738,24 +945,22 @@ async function addCommand(components, opts) {
738
945
  }
739
946
  }
740
947
  if (created.length > 0) {
741
- p2.log.success(`Created ${created.length} file(s):`);
742
- for (const f of created) p2.log.message(` ${pc4.green("+")} ${f}`);
948
+ p3.log.success(`Created ${created.length} file(s):`);
949
+ for (const f of created) p3.log.message(` ${pc4.green("+")} ${f}`);
743
950
  }
744
951
  if (updated.length > 0) {
745
- p2.log.success(`Updated ${updated.length} file(s):`);
746
- for (const f of updated) p2.log.message(` ${pc4.yellow("~")} ${f}`);
952
+ p3.log.success(`Updated ${updated.length} file(s):`);
953
+ for (const f of updated) p3.log.message(` ${pc4.yellow("~")} ${f}`);
747
954
  }
748
955
  if (skipped.length > 0) {
749
- p2.log.info(`Skipped ${skipped.length} file(s):`);
750
- for (const f of skipped) p2.log.message(` ${pc4.dim("-")} ${f}`);
751
- }
752
- if (allEnvWarnings.length > 0) {
753
- p2.log.warn("Missing environment variables:");
754
- for (const w of allEnvWarnings) p2.log.message(w);
956
+ p3.log.info(`Skipped ${skipped.length} file(s):`);
957
+ for (const f of skipped) p3.log.message(` ${pc4.dim("-")} ${f}`);
755
958
  }
959
+ const allEnvVars = collectEnvVars(resolved);
960
+ await handleEnvVars(cwd, allEnvVars);
756
961
  for (const item of resolved) {
757
962
  if (item.docs) {
758
- p2.log.info(`${pc4.bold(item.name)}: ${item.docs}`);
963
+ p3.log.info(`${pc4.bold(item.name)}: ${item.docs}`);
759
964
  }
760
965
  }
761
966
  const installedNames = new Set(resolved.map((r) => r.name));
@@ -773,7 +978,7 @@ async function addCommand(components, opts) {
773
978
  hints.push(pc4.dim(` import { yourProvider } from "your-ai-provider";`));
774
979
  hints.push(pc4.dim(``));
775
980
  hints.push(pc4.dim(` const plugin = createAIPlugin({`));
776
- hints.push(pc4.dim(` getModel: (id) => yourProvider(id ?? "default-model"),`));
981
+ hints.push(pc4.dim(` model: (model) => yourProvider(model ?? "default-model"),`));
777
982
  hints.push(pc4.dim(` });`));
778
983
  hints.push(pc4.dim(``));
779
984
  hints.push(pc4.dim(` const app = new Hono();`));
@@ -783,10 +988,10 @@ async function addCommand(components, opts) {
783
988
  }
784
989
  }
785
990
  if (hints.length > 0) {
786
- p2.log.message(pc4.bold("\nNext steps:"));
787
- for (const hint of hints) p2.log.message(hint);
991
+ p3.log.message(pc4.bold("\nNext steps:"));
992
+ for (const hint of hints) p3.log.message(hint);
788
993
  }
789
- p2.outro(pc4.green("Done!"));
994
+ p3.outro(pc4.green("Done!"));
790
995
  }
791
996
  var init_add = __esm({
792
997
  "src/commands/add.ts"() {
@@ -797,9 +1002,10 @@ var init_add = __esm({
797
1002
  init_resolver();
798
1003
  init_file_writer();
799
1004
  init_dep_installer();
800
- init_env_checker();
1005
+ init_env_writer();
801
1006
  init_import_rewriter();
802
1007
  init_tsconfig_patcher();
1008
+ init_barrel_manager();
803
1009
  init_hash();
804
1010
  init_parse_ref();
805
1011
  init_schema();
@@ -811,22 +1017,22 @@ var list_exports = {};
811
1017
  __export(list_exports, {
812
1018
  listCommand: () => listCommand
813
1019
  });
814
- import * as p3 from "@clack/prompts";
1020
+ import * as p4 from "@clack/prompts";
815
1021
  import pc5 from "picocolors";
816
1022
  async function listCommand(opts) {
817
1023
  const cwd = process.cwd();
818
1024
  const config = await readConfig(cwd);
819
1025
  if (!config) {
820
- p3.log.error("No kitn.json found. Run `kitn init` first.");
1026
+ p4.log.error("No kitn.json found. Run `kitn init` first.");
821
1027
  process.exit(1);
822
1028
  }
823
1029
  const fetcher = new RegistryFetcher(config.registries);
824
1030
  const namespacesToFetch = opts.registry ? [opts.registry] : Object.keys(config.registries);
825
1031
  if (opts.registry && !config.registries[opts.registry]) {
826
- p3.log.error(`Registry ${pc5.bold(opts.registry)} is not configured. Run ${pc5.bold("kitn registry list")} to see configured registries.`);
1032
+ p4.log.error(`Registry ${pc5.bold(opts.registry)} is not configured. Run ${pc5.bold("kitn registry list")} to see configured registries.`);
827
1033
  process.exit(1);
828
1034
  }
829
- const s = p3.spinner();
1035
+ const s = p4.spinner();
830
1036
  s.start("Fetching registry index...");
831
1037
  const allItems = [];
832
1038
  const errors = [];
@@ -842,12 +1048,12 @@ async function listCommand(opts) {
842
1048
  }
843
1049
  if (allItems.length === 0 && errors.length > 0) {
844
1050
  s.stop(pc5.red("Failed to fetch registries"));
845
- for (const e of errors) p3.log.error(e);
1051
+ for (const e of errors) p4.log.error(e);
846
1052
  process.exit(1);
847
1053
  }
848
1054
  s.stop(`Found ${allItems.length} components across ${namespacesToFetch.length - errors.length} ${namespacesToFetch.length - errors.length === 1 ? "registry" : "registries"}`);
849
1055
  for (const e of errors) {
850
- p3.log.warn(`${pc5.yellow("\u26A0")} Failed to fetch ${e}`);
1056
+ p4.log.warn(`${pc5.yellow("\u26A0")} Failed to fetch ${e}`);
851
1057
  }
852
1058
  const installed = config.installed ?? {};
853
1059
  const typeGroups = /* @__PURE__ */ new Map();
@@ -860,7 +1066,7 @@ async function listCommand(opts) {
860
1066
  let installedCount = 0;
861
1067
  let updateCount = 0;
862
1068
  for (const [group, items] of typeGroups) {
863
- p3.log.message(pc5.bold(`
1069
+ p4.log.message(pc5.bold(`
864
1070
  ${group.charAt(0).toUpperCase() + group.slice(1)}s:`));
865
1071
  for (const item of items) {
866
1072
  const displayName = item.namespace === "@kitn" ? item.name : `${item.namespace}/${item.name}`;
@@ -873,18 +1079,18 @@ ${group.charAt(0).toUpperCase() + group.slice(1)}s:`));
873
1079
  const hasUpdate = item.version && inst.version !== item.version;
874
1080
  const updateTag = hasUpdate ? pc5.yellow(` \u2B06 v${item.version} available`) : "";
875
1081
  if (hasUpdate) updateCount++;
876
- p3.log.message(` ${status} ${displayName.padEnd(20)} ${version} ${pc5.dim(item.description)}${updateTag}`);
1082
+ p4.log.message(` ${status} ${displayName.padEnd(20)} ${version} ${pc5.dim(item.description)}${updateTag}`);
877
1083
  } else {
878
1084
  const status = pc5.dim("\u25CB");
879
- p3.log.message(` ${status} ${displayName.padEnd(20)} ${version} ${pc5.dim(item.description)}`);
1085
+ p4.log.message(` ${status} ${displayName.padEnd(20)} ${version} ${pc5.dim(item.description)}`);
880
1086
  }
881
1087
  }
882
1088
  }
883
1089
  const availableCount = allItems.length - installedCount;
884
1090
  const parts = [`${installedCount} installed`, `${availableCount} available`];
885
1091
  if (updateCount > 0) parts.push(`${updateCount} update${updateCount === 1 ? "" : "s"} available`);
886
- p3.log.message("");
887
- p3.log.message(pc5.dim(` ${parts.join(", ")}`));
1092
+ p4.log.message("");
1093
+ p4.log.message(pc5.dim(` ${parts.join(", ")}`));
888
1094
  }
889
1095
  var init_list = __esm({
890
1096
  "src/commands/list.ts"() {
@@ -899,13 +1105,13 @@ var diff_exports = {};
899
1105
  __export(diff_exports, {
900
1106
  diffCommand: () => diffCommand
901
1107
  });
902
- import * as p4 from "@clack/prompts";
903
- import { join as join7 } from "path";
1108
+ import * as p5 from "@clack/prompts";
1109
+ import { join as join8 } from "path";
904
1110
  async function diffCommand(componentName) {
905
1111
  const cwd = process.cwd();
906
1112
  const config = await readConfig(cwd);
907
1113
  if (!config) {
908
- p4.log.error("No kitn.json found. Run `kitn init` first.");
1114
+ p5.log.error("No kitn.json found. Run `kitn init` first.");
909
1115
  process.exit(1);
910
1116
  }
911
1117
  const input = componentName === "routes" ? config.framework ?? "hono" : componentName;
@@ -913,7 +1119,7 @@ async function diffCommand(componentName) {
913
1119
  const installedKey = ref.namespace === "@kitn" ? ref.name : `${ref.namespace}/${ref.name}`;
914
1120
  const installed = config.installed?.[installedKey];
915
1121
  if (!installed) {
916
- p4.log.error(`Component '${ref.name}' is not installed.`);
1122
+ p5.log.error(`Component '${ref.name}' is not installed.`);
917
1123
  process.exit(1);
918
1124
  }
919
1125
  const namespace = installed.registry ?? ref.namespace;
@@ -921,7 +1127,7 @@ async function diffCommand(componentName) {
921
1127
  const index = await fetcher.fetchIndex(namespace);
922
1128
  const indexItem = index.items.find((i) => i.name === ref.name);
923
1129
  if (!indexItem) {
924
- p4.log.error(`Component '${ref.name}' not found in ${namespace} registry.`);
1130
+ p5.log.error(`Component '${ref.name}' not found in ${namespace} registry.`);
925
1131
  process.exit(1);
926
1132
  }
927
1133
  const dir = typeToDir[indexItem.type];
@@ -930,11 +1136,11 @@ async function diffCommand(componentName) {
930
1136
  for (const file of registryItem.files) {
931
1137
  if (indexItem.type === "kitn:package") {
932
1138
  const baseDir = config.aliases.base ?? "src/ai";
933
- const localPath = join7(cwd, baseDir, file.path);
934
- const relativePath = join7(baseDir, file.path);
1139
+ const localPath = join8(cwd, baseDir, file.path);
1140
+ const relativePath = join8(baseDir, file.path);
935
1141
  const localContent = await readExistingFile(localPath);
936
1142
  if (localContent === null) {
937
- p4.log.warn(`${relativePath}: file missing locally`);
1143
+ p5.log.warn(`${relativePath}: file missing locally`);
938
1144
  hasDiff = true;
939
1145
  } else if (localContent !== file.content) {
940
1146
  const diff = generateDiff(relativePath, localContent, file.content);
@@ -955,10 +1161,10 @@ async function diffCommand(componentName) {
955
1161
  return "storage";
956
1162
  }
957
1163
  })();
958
- const localPath = join7(cwd, config.aliases[aliasKey], fileName);
1164
+ const localPath = join8(cwd, config.aliases[aliasKey], fileName);
959
1165
  const localContent = await readExistingFile(localPath);
960
1166
  if (localContent === null) {
961
- p4.log.warn(`${fileName}: file missing locally`);
1167
+ p5.log.warn(`${fileName}: file missing locally`);
962
1168
  hasDiff = true;
963
1169
  } else if (localContent !== file.content) {
964
1170
  const diff = generateDiff(fileName, localContent, file.content);
@@ -968,7 +1174,7 @@ async function diffCommand(componentName) {
968
1174
  }
969
1175
  }
970
1176
  if (!hasDiff) {
971
- p4.log.success(`${ref.name}: up to date, no differences.`);
1177
+ p5.log.success(`${ref.name}: up to date, no differences.`);
972
1178
  }
973
1179
  }
974
1180
  var init_diff = __esm({
@@ -987,15 +1193,16 @@ var remove_exports = {};
987
1193
  __export(remove_exports, {
988
1194
  removeCommand: () => removeCommand
989
1195
  });
990
- import * as p5 from "@clack/prompts";
1196
+ import * as p6 from "@clack/prompts";
991
1197
  import pc6 from "picocolors";
992
- import { join as join8 } from "path";
993
- import { unlink } from "fs/promises";
1198
+ import { join as join9, relative as relative3, dirname as dirname3 } from "path";
1199
+ import { unlink, readFile as readFile7, writeFile as writeFile7 } from "fs/promises";
1200
+ import { existsSync as existsSync2 } from "fs";
994
1201
  async function removeCommand(componentName) {
995
1202
  const cwd = process.cwd();
996
1203
  const config = await readConfig(cwd);
997
1204
  if (!config) {
998
- p5.log.error("No kitn.json found. Run `kitn init` first.");
1205
+ p6.log.error("No kitn.json found. Run `kitn init` first.");
999
1206
  process.exit(1);
1000
1207
  }
1001
1208
  const input = componentName === "routes" ? config.framework ?? "hono" : componentName;
@@ -1003,24 +1210,50 @@ async function removeCommand(componentName) {
1003
1210
  const installedKey = ref.namespace === "@kitn" ? ref.name : `${ref.namespace}/${ref.name}`;
1004
1211
  const installed = config.installed?.[installedKey];
1005
1212
  if (!installed) {
1006
- p5.log.error(`Component '${ref.name}' is not installed.`);
1213
+ p6.log.error(`Component '${ref.name}' is not installed.`);
1007
1214
  process.exit(1);
1008
1215
  }
1009
- const shouldRemove = await p5.confirm({
1216
+ const shouldRemove = await p6.confirm({
1010
1217
  message: `Remove ${ref.name}? This will delete ${installed.files.length} file(s).`,
1011
1218
  initialValue: false
1012
1219
  });
1013
- if (p5.isCancel(shouldRemove) || !shouldRemove) {
1014
- p5.cancel("Remove cancelled.");
1220
+ if (p6.isCancel(shouldRemove) || !shouldRemove) {
1221
+ p6.cancel("Remove cancelled.");
1015
1222
  process.exit(0);
1016
1223
  }
1017
1224
  const deleted = [];
1018
1225
  for (const filePath of installed.files) {
1019
1226
  try {
1020
- await unlink(join8(cwd, filePath));
1227
+ await unlink(join9(cwd, filePath));
1021
1228
  deleted.push(filePath);
1022
1229
  } catch {
1023
- p5.log.warn(`Could not delete ${filePath} (may have been moved or renamed)`);
1230
+ p6.log.warn(`Could not delete ${filePath} (may have been moved or renamed)`);
1231
+ }
1232
+ }
1233
+ const baseDir = config.aliases.base ?? "src/ai";
1234
+ const barrelPath = join9(cwd, baseDir, "index.ts");
1235
+ const barrelDir = join9(cwd, baseDir);
1236
+ const barrelEligibleDirs = /* @__PURE__ */ new Set([
1237
+ config.aliases.agents,
1238
+ config.aliases.tools,
1239
+ config.aliases.skills
1240
+ ]);
1241
+ if (existsSync2(barrelPath) && deleted.length > 0) {
1242
+ let barrelContent = await readFile7(barrelPath, "utf-8");
1243
+ let barrelChanged = false;
1244
+ for (const filePath of deleted) {
1245
+ const fileDir = dirname3(filePath);
1246
+ if (!barrelEligibleDirs.has(fileDir)) continue;
1247
+ const importPath = "./" + relative3(barrelDir, join9(cwd, filePath)).replace(/\\/g, "/");
1248
+ const updated = removeImportFromBarrel(barrelContent, importPath);
1249
+ if (updated !== barrelContent) {
1250
+ barrelContent = updated;
1251
+ barrelChanged = true;
1252
+ }
1253
+ }
1254
+ if (barrelChanged) {
1255
+ await writeFile7(barrelPath, barrelContent);
1256
+ p6.log.info(`Updated barrel file: ${join9(baseDir, "index.ts")}`);
1024
1257
  }
1025
1258
  }
1026
1259
  delete config.installed[installedKey];
@@ -1029,8 +1262,8 @@ async function removeCommand(componentName) {
1029
1262
  }
1030
1263
  await writeConfig(cwd, config);
1031
1264
  if (deleted.length > 0) {
1032
- p5.log.success(`Removed ${ref.name}:`);
1033
- for (const f of deleted) p5.log.message(` ${pc6.red("-")} ${f}`);
1265
+ p6.log.success(`Removed ${ref.name}:`);
1266
+ for (const f of deleted) p6.log.message(` ${pc6.red("-")} ${f}`);
1034
1267
  }
1035
1268
  }
1036
1269
  var init_remove = __esm({
@@ -1038,6 +1271,7 @@ var init_remove = __esm({
1038
1271
  "use strict";
1039
1272
  init_config();
1040
1273
  init_parse_ref();
1274
+ init_barrel_manager();
1041
1275
  }
1042
1276
  });
1043
1277
 
@@ -1046,18 +1280,18 @@ var update_exports = {};
1046
1280
  __export(update_exports, {
1047
1281
  updateCommand: () => updateCommand
1048
1282
  });
1049
- import * as p6 from "@clack/prompts";
1283
+ import * as p7 from "@clack/prompts";
1050
1284
  async function updateCommand(components) {
1051
1285
  if (components.length === 0) {
1052
1286
  const cwd = process.cwd();
1053
1287
  const config = await readConfig(cwd);
1054
1288
  if (!config) {
1055
- p6.log.error("No kitn.json found. Run `kitn init` first.");
1289
+ p7.log.error("No kitn.json found. Run `kitn init` first.");
1056
1290
  process.exit(1);
1057
1291
  }
1058
1292
  const installed = config.installed;
1059
1293
  if (!installed || Object.keys(installed).length === 0) {
1060
- p6.log.info("No installed components to update.");
1294
+ p7.log.info("No installed components to update.");
1061
1295
  return;
1062
1296
  }
1063
1297
  components = Object.keys(installed);
@@ -1072,36 +1306,603 @@ var init_update = __esm({
1072
1306
  }
1073
1307
  });
1074
1308
 
1309
+ // src/registry/build-output.ts
1310
+ import { readdir, writeFile as writeFile8, mkdir as mkdir4, access as access4 } from "fs/promises";
1311
+ import { join as join10, resolve } from "path";
1312
+ async function fileExists(path) {
1313
+ try {
1314
+ await access4(path);
1315
+ return true;
1316
+ } catch {
1317
+ return false;
1318
+ }
1319
+ }
1320
+ async function walkForRegistryJson(dir) {
1321
+ const results = [];
1322
+ let entries;
1323
+ try {
1324
+ entries = await readdir(dir, { withFileTypes: true });
1325
+ } catch {
1326
+ return results;
1327
+ }
1328
+ if (await fileExists(join10(dir, "registry.json"))) {
1329
+ results.push(dir);
1330
+ return results;
1331
+ }
1332
+ for (const entry of entries) {
1333
+ if (entry.isDirectory() && !SKIP_DIRS.has(entry.name)) {
1334
+ const subResults = await walkForRegistryJson(join10(dir, entry.name));
1335
+ results.push(...subResults);
1336
+ }
1337
+ }
1338
+ return results;
1339
+ }
1340
+ async function scanForComponents(cwd, paths) {
1341
+ const resolvedCwd = resolve(cwd);
1342
+ if (paths && paths.length > 0) {
1343
+ const results = [];
1344
+ for (const p12 of paths) {
1345
+ const absPath = resolve(resolvedCwd, p12);
1346
+ if (await fileExists(join10(absPath, "registry.json"))) {
1347
+ results.push(absPath);
1348
+ continue;
1349
+ }
1350
+ let entries;
1351
+ try {
1352
+ entries = await readdir(absPath, { withFileTypes: true });
1353
+ } catch {
1354
+ continue;
1355
+ }
1356
+ for (const entry of entries) {
1357
+ if (entry.isDirectory()) {
1358
+ const subDir = join10(absPath, entry.name);
1359
+ if (await fileExists(join10(subDir, "registry.json"))) {
1360
+ results.push(subDir);
1361
+ }
1362
+ }
1363
+ }
1364
+ }
1365
+ return results;
1366
+ }
1367
+ return walkForRegistryJson(resolvedCwd);
1368
+ }
1369
+ function parseVersionFromFilename(name, componentName) {
1370
+ const prefix = `${componentName}@`;
1371
+ const suffix = ".json";
1372
+ if (name.startsWith(prefix) && name.endsWith(suffix)) {
1373
+ return name.slice(prefix.length, -suffix.length);
1374
+ }
1375
+ return null;
1376
+ }
1377
+ async function writeRegistryOutput(outputDir, items) {
1378
+ const written = [];
1379
+ const skipped = [];
1380
+ const resolvedOutput = resolve(outputDir);
1381
+ const indexItems = [];
1382
+ for (const item of items) {
1383
+ const dir = typeToDir[item.type];
1384
+ const typeDir = join10(resolvedOutput, dir);
1385
+ await mkdir4(typeDir, { recursive: true });
1386
+ const itemJson = JSON.stringify(item, null, 2);
1387
+ const latestPath = join10(typeDir, `${item.name}.json`);
1388
+ const latestRelative = `${dir}/${item.name}.json`;
1389
+ await writeFile8(latestPath, itemJson, "utf-8");
1390
+ written.push(latestRelative);
1391
+ if (item.version) {
1392
+ const versionedFilename = `${item.name}@${item.version}.json`;
1393
+ const versionedPath = join10(typeDir, versionedFilename);
1394
+ const versionedRelative = `${dir}/${versionedFilename}`;
1395
+ if (await fileExists(versionedPath)) {
1396
+ skipped.push(versionedRelative);
1397
+ } else {
1398
+ await writeFile8(versionedPath, itemJson, "utf-8");
1399
+ written.push(versionedRelative);
1400
+ }
1401
+ }
1402
+ const versions = [];
1403
+ let entries;
1404
+ try {
1405
+ entries = await readdir(typeDir);
1406
+ } catch {
1407
+ entries = [];
1408
+ }
1409
+ for (const filename of entries) {
1410
+ const ver = parseVersionFromFilename(filename, item.name);
1411
+ if (ver) {
1412
+ versions.push(ver);
1413
+ }
1414
+ }
1415
+ versions.sort();
1416
+ indexItems.push({
1417
+ name: item.name,
1418
+ type: item.type,
1419
+ description: item.description,
1420
+ ...item.registryDependencies && item.registryDependencies.length > 0 && {
1421
+ registryDependencies: item.registryDependencies
1422
+ },
1423
+ ...item.categories && item.categories.length > 0 && { categories: item.categories },
1424
+ ...item.version && { version: item.version },
1425
+ ...versions.length > 0 && { versions },
1426
+ ...item.updatedAt && { updatedAt: item.updatedAt }
1427
+ });
1428
+ }
1429
+ const index = {
1430
+ version: "1",
1431
+ items: indexItems
1432
+ };
1433
+ const indexPath = join10(resolvedOutput, "registry.json");
1434
+ await writeFile8(indexPath, JSON.stringify(index, null, 2), "utf-8");
1435
+ written.push("registry.json");
1436
+ return { written, skipped };
1437
+ }
1438
+ var SKIP_DIRS;
1439
+ var init_build_output = __esm({
1440
+ "src/registry/build-output.ts"() {
1441
+ "use strict";
1442
+ init_schema();
1443
+ SKIP_DIRS = /* @__PURE__ */ new Set([
1444
+ "node_modules",
1445
+ "dist",
1446
+ ".git",
1447
+ "r",
1448
+ "test",
1449
+ "tests",
1450
+ ".claude"
1451
+ ]);
1452
+ }
1453
+ });
1454
+
1455
+ // src/registry/builder.ts
1456
+ import { readFile as readFile9, readdir as readdir2 } from "fs/promises";
1457
+ import { join as join11, relative as relative5 } from "path";
1458
+ function isExcludedDevDep(name) {
1459
+ return EXCLUDED_DEV_DEPS.has(name) || name.startsWith("@types/");
1460
+ }
1461
+ function stripScope(name) {
1462
+ const match = name.match(/^@[^/]+\/(.+)$/);
1463
+ return match ? match[1] : name;
1464
+ }
1465
+ async function readTsFiles(dir, baseDir, exclude) {
1466
+ const results = [];
1467
+ const entries = await readdir2(dir, { withFileTypes: true });
1468
+ for (const entry of entries) {
1469
+ const fullPath = join11(dir, entry.name);
1470
+ const relPath = relative5(baseDir, fullPath);
1471
+ if (entry.isDirectory()) {
1472
+ const nested = await readTsFiles(fullPath, baseDir, exclude);
1473
+ results.push(...nested);
1474
+ } else if (entry.isFile() && entry.name.endsWith(".ts")) {
1475
+ if (exclude.includes(relPath)) {
1476
+ continue;
1477
+ }
1478
+ const content = await readFile9(fullPath, "utf-8");
1479
+ results.push({ relativePath: relPath, content });
1480
+ }
1481
+ }
1482
+ return results;
1483
+ }
1484
+ async function buildComponent(componentDir) {
1485
+ let rawConfig;
1486
+ try {
1487
+ rawConfig = await readFile9(join11(componentDir, "registry.json"), "utf-8");
1488
+ } catch {
1489
+ throw new Error(
1490
+ `No registry.json found in ${componentDir}. Every component must have a registry.json file.`
1491
+ );
1492
+ }
1493
+ let config;
1494
+ try {
1495
+ config = componentConfigSchema.parse(JSON.parse(rawConfig));
1496
+ } catch (err) {
1497
+ throw new Error(
1498
+ `Invalid registry.json in ${componentDir}: ${err instanceof Error ? err.message : String(err)}`
1499
+ );
1500
+ }
1501
+ let pkg = null;
1502
+ try {
1503
+ const rawPkg = await readFile9(join11(componentDir, "package.json"), "utf-8");
1504
+ pkg = JSON.parse(rawPkg);
1505
+ } catch {
1506
+ }
1507
+ const name = config.name ?? (pkg?.name ? stripScope(pkg.name) : void 0);
1508
+ const version = config.version ?? pkg?.version;
1509
+ const description = config.description ?? pkg?.description;
1510
+ if (!name) {
1511
+ throw new Error(
1512
+ `Component in ${componentDir} is missing a name. Provide "name" in registry.json or have a package.json with a "name" field.`
1513
+ );
1514
+ }
1515
+ if (!description) {
1516
+ throw new Error(
1517
+ `Component in ${componentDir} is missing a description. Provide "description" in registry.json or have a package.json with a "description" field.`
1518
+ );
1519
+ }
1520
+ let dependencies = config.dependencies;
1521
+ let devDependencies = config.devDependencies;
1522
+ if (pkg && !config.dependencies) {
1523
+ const deps = [];
1524
+ if (pkg.dependencies) {
1525
+ for (const [depName, depVersion] of Object.entries(pkg.dependencies)) {
1526
+ if (depVersion !== "workspace:*") {
1527
+ deps.push(depName);
1528
+ }
1529
+ }
1530
+ }
1531
+ if (pkg.peerDependencies) {
1532
+ for (const [depName, depVersion] of Object.entries(pkg.peerDependencies)) {
1533
+ if (depVersion !== "workspace:*") {
1534
+ deps.push(depName);
1535
+ }
1536
+ }
1537
+ }
1538
+ if (deps.length > 0) {
1539
+ dependencies = deps;
1540
+ }
1541
+ }
1542
+ if (pkg && !config.devDependencies) {
1543
+ const devDeps = [];
1544
+ if (pkg.devDependencies) {
1545
+ for (const depName of Object.keys(pkg.devDependencies)) {
1546
+ if (!isExcludedDevDep(depName)) {
1547
+ devDeps.push(depName);
1548
+ }
1549
+ }
1550
+ }
1551
+ if (devDeps.length > 0) {
1552
+ devDependencies = devDeps;
1553
+ }
1554
+ }
1555
+ const isPackage = config.type === "kitn:package";
1556
+ const dirPrefix = config.installDir ?? typeToDir[config.type];
1557
+ let files;
1558
+ if (isPackage) {
1559
+ const sourceDir = config.sourceDir ?? "src";
1560
+ const sourcePath = join11(componentDir, sourceDir);
1561
+ const exclude = config.exclude ?? [];
1562
+ let tsFiles;
1563
+ try {
1564
+ tsFiles = await readTsFiles(sourcePath, sourcePath, exclude);
1565
+ } catch {
1566
+ throw new Error(
1567
+ `Cannot read source directory "${sourceDir}" in ${componentDir}. Make sure it exists.`
1568
+ );
1569
+ }
1570
+ files = tsFiles.map((f) => ({
1571
+ path: `${dirPrefix}/${f.relativePath}`,
1572
+ content: f.content,
1573
+ type: config.type
1574
+ }));
1575
+ } else {
1576
+ if (!config.files || config.files.length === 0) {
1577
+ throw new Error(
1578
+ `Component "${name}" (type: ${config.type}) has no "files" array in registry.json. Standalone components must list their source files.`
1579
+ );
1580
+ }
1581
+ files = await Promise.all(
1582
+ config.files.map(async (filePath) => {
1583
+ const fullPath = join11(componentDir, filePath);
1584
+ let content;
1585
+ try {
1586
+ content = await readFile9(fullPath, "utf-8");
1587
+ } catch {
1588
+ throw new Error(
1589
+ `Cannot read file "${filePath}" referenced in registry.json for component "${name}". Make sure the file exists at ${fullPath}.`
1590
+ );
1591
+ }
1592
+ return {
1593
+ path: `${dirPrefix}/${filePath}`,
1594
+ content,
1595
+ type: config.type
1596
+ };
1597
+ })
1598
+ );
1599
+ }
1600
+ const item = {
1601
+ name,
1602
+ type: config.type,
1603
+ description,
1604
+ files
1605
+ };
1606
+ if (version) item.version = version;
1607
+ if (dependencies && dependencies.length > 0) item.dependencies = dependencies;
1608
+ if (devDependencies && devDependencies.length > 0) item.devDependencies = devDependencies;
1609
+ if (config.registryDependencies && config.registryDependencies.length > 0) {
1610
+ item.registryDependencies = config.registryDependencies;
1611
+ }
1612
+ if (config.envVars) item.envVars = config.envVars;
1613
+ if (config.tsconfig) item.tsconfig = config.tsconfig;
1614
+ if (config.docs) item.docs = config.docs;
1615
+ if (config.categories && config.categories.length > 0) item.categories = config.categories;
1616
+ if (config.changelog && config.changelog.length > 0) item.changelog = config.changelog;
1617
+ if (isPackage && config.installDir) item.installDir = config.installDir;
1618
+ try {
1619
+ return registryItemSchema.parse(item);
1620
+ } catch (err) {
1621
+ throw new Error(
1622
+ `Built component "${name}" failed validation: ${err instanceof Error ? err.message : String(err)}`
1623
+ );
1624
+ }
1625
+ }
1626
+ var EXCLUDED_DEV_DEPS;
1627
+ var init_builder = __esm({
1628
+ "src/registry/builder.ts"() {
1629
+ "use strict";
1630
+ init_schema();
1631
+ EXCLUDED_DEV_DEPS = /* @__PURE__ */ new Set([
1632
+ "typescript",
1633
+ "@types/bun",
1634
+ "@types/node",
1635
+ "tsup",
1636
+ "vitest",
1637
+ "jest",
1638
+ "@types/jest"
1639
+ ]);
1640
+ }
1641
+ });
1642
+
1643
+ // src/commands/build.ts
1644
+ var build_exports = {};
1645
+ __export(build_exports, {
1646
+ buildCommand: () => buildCommand
1647
+ });
1648
+ import * as p8 from "@clack/prompts";
1649
+ import pc7 from "picocolors";
1650
+ import { resolve as resolve2, relative as relative6 } from "path";
1651
+ async function buildCommand(paths, opts) {
1652
+ p8.intro(pc7.bgCyan(pc7.black(" kitn build ")));
1653
+ const cwd = process.cwd();
1654
+ const outputDir = resolve2(cwd, opts.output ?? "dist/r");
1655
+ const s = p8.spinner();
1656
+ s.start("Scanning for components...");
1657
+ const componentDirs = await scanForComponents(cwd, paths.length > 0 ? paths : void 0);
1658
+ if (componentDirs.length === 0) {
1659
+ s.stop("No components found");
1660
+ p8.log.info(
1661
+ `No directories with ${pc7.bold("registry.json")} found. Run ${pc7.bold("kitn create")} to scaffold a component.`
1662
+ );
1663
+ return;
1664
+ }
1665
+ s.stop(`Found ${componentDirs.length} component(s)`);
1666
+ for (const dir of componentDirs) {
1667
+ p8.log.message(` ${pc7.dim(relative6(cwd, dir))}`);
1668
+ }
1669
+ s.start("Building components...");
1670
+ const items = [];
1671
+ const errors = [];
1672
+ for (const dir of componentDirs) {
1673
+ try {
1674
+ const item = await buildComponent(dir);
1675
+ items.push(item);
1676
+ } catch (err) {
1677
+ errors.push({ dir: relative6(cwd, dir), error: err.message });
1678
+ }
1679
+ }
1680
+ if (errors.length > 0) {
1681
+ s.stop(pc7.red(`Build failed with ${errors.length} error(s)`));
1682
+ for (const { dir, error } of errors) {
1683
+ p8.log.error(`${pc7.bold(dir)}: ${error}`);
1684
+ }
1685
+ process.exit(1);
1686
+ }
1687
+ const { written, skipped } = await writeRegistryOutput(outputDir, items);
1688
+ s.stop(pc7.green(`Built ${items.length} component(s)`));
1689
+ if (written.length > 0) {
1690
+ p8.log.success(`Wrote ${written.length} file(s):`);
1691
+ for (const f of written) {
1692
+ p8.log.message(` ${pc7.green("+")} ${f}`);
1693
+ }
1694
+ }
1695
+ if (skipped.length > 0) {
1696
+ p8.log.info(`Skipped ${skipped.length} file(s) (already exist):`);
1697
+ for (const f of skipped) {
1698
+ p8.log.message(` ${pc7.dim("-")} ${f}`);
1699
+ }
1700
+ }
1701
+ p8.outro(`Output: ${pc7.cyan(relative6(cwd, outputDir) || ".")}`);
1702
+ }
1703
+ var init_build = __esm({
1704
+ "src/commands/build.ts"() {
1705
+ "use strict";
1706
+ init_build_output();
1707
+ init_builder();
1708
+ }
1709
+ });
1710
+
1711
+ // src/commands/create.ts
1712
+ var create_exports = {};
1713
+ __export(create_exports, {
1714
+ createCommand: () => createCommand,
1715
+ createComponent: () => createComponent
1716
+ });
1717
+ import * as p9 from "@clack/prompts";
1718
+ import pc8 from "picocolors";
1719
+ import { join as join12 } from "path";
1720
+ import { mkdir as mkdir5, writeFile as writeFile9 } from "fs/promises";
1721
+ function toCamelCase(str) {
1722
+ return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
1723
+ }
1724
+ function toTitleCase(str) {
1725
+ return str.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
1726
+ }
1727
+ function generateRegistryJson(type, name, sourceFile) {
1728
+ const base = {
1729
+ $schema: "https://kitn.dev/schema/registry.json",
1730
+ name,
1731
+ type: `kitn:${type}`,
1732
+ version: "0.1.0",
1733
+ description: "",
1734
+ files: [sourceFile],
1735
+ categories: []
1736
+ };
1737
+ if (type === "tool") {
1738
+ base.dependencies = ["ai", "zod"];
1739
+ } else if (type === "agent" || type === "storage") {
1740
+ base.dependencies = [];
1741
+ }
1742
+ return base;
1743
+ }
1744
+ function generateAgentSource(name) {
1745
+ const camel = toCamelCase(name);
1746
+ return `import { registerAgent } from "@kitnai/core";
1747
+
1748
+ const SYSTEM_PROMPT = "You are a helpful assistant.";
1749
+
1750
+ registerAgent({
1751
+ name: "${name}",
1752
+ description: "",
1753
+ system: SYSTEM_PROMPT,
1754
+ tools: {},
1755
+ });
1756
+ `;
1757
+ }
1758
+ function generateToolSource(name) {
1759
+ const camel = toCamelCase(name);
1760
+ return `import { registerTool } from "@kitnai/core";
1761
+ import { tool } from "ai";
1762
+ import { z } from "zod";
1763
+
1764
+ export const ${camel} = tool({
1765
+ description: "",
1766
+ parameters: z.object({
1767
+ input: z.string().describe("Input parameter"),
1768
+ }),
1769
+ execute: async ({ input }) => {
1770
+ // TODO: implement
1771
+ return { result: input };
1772
+ },
1773
+ });
1774
+
1775
+ registerTool({
1776
+ name: "${name}",
1777
+ description: "",
1778
+ inputSchema: z.object({ input: z.string() }),
1779
+ tool: ${camel},
1780
+ });
1781
+ `;
1782
+ }
1783
+ function generateSkillSource(name) {
1784
+ const title = toTitleCase(name);
1785
+ return `---
1786
+ name: ${name}
1787
+ description: ""
1788
+ ---
1789
+
1790
+ # ${title}
1791
+
1792
+ Describe what this skill does and how to use it.
1793
+ `;
1794
+ }
1795
+ function generateStorageSource(name) {
1796
+ const camel = toCamelCase("create-" + name);
1797
+ return `import type { StorageProvider } from "@kitnai/core";
1798
+
1799
+ export function ${camel}(config?: Record<string, unknown>): StorageProvider {
1800
+ // TODO: implement storage provider
1801
+ throw new Error("Not implemented");
1802
+ }
1803
+ `;
1804
+ }
1805
+ async function dirExists(path) {
1806
+ try {
1807
+ const { stat: stat2 } = await import("fs/promises");
1808
+ const s = await stat2(path);
1809
+ return s.isDirectory();
1810
+ } catch {
1811
+ return false;
1812
+ }
1813
+ }
1814
+ async function createComponent(type, name, opts) {
1815
+ if (!VALID_TYPES.includes(type)) {
1816
+ throw new Error(
1817
+ `Invalid component type: "${type}". Valid types: ${VALID_TYPES.join(", ")}`
1818
+ );
1819
+ }
1820
+ const cwd = opts?.cwd ?? process.cwd();
1821
+ const dir = join12(cwd, name);
1822
+ if (await dirExists(dir)) {
1823
+ throw new Error(`Directory "${name}" already exists`);
1824
+ }
1825
+ await mkdir5(dir, { recursive: true });
1826
+ const validType = type;
1827
+ const sourceFile = validType === "skill" ? "README.md" : `${name}.ts`;
1828
+ const registryJson = generateRegistryJson(validType, name, sourceFile);
1829
+ await writeFile9(
1830
+ join12(dir, "registry.json"),
1831
+ JSON.stringify(registryJson, null, 2) + "\n"
1832
+ );
1833
+ let source;
1834
+ switch (validType) {
1835
+ case "agent":
1836
+ source = generateAgentSource(name);
1837
+ break;
1838
+ case "tool":
1839
+ source = generateToolSource(name);
1840
+ break;
1841
+ case "skill":
1842
+ source = generateSkillSource(name);
1843
+ break;
1844
+ case "storage":
1845
+ source = generateStorageSource(name);
1846
+ break;
1847
+ }
1848
+ await writeFile9(join12(dir, sourceFile), source);
1849
+ return { dir, files: ["registry.json", sourceFile] };
1850
+ }
1851
+ async function createCommand(type, name) {
1852
+ p9.intro(pc8.bgCyan(pc8.black(" kitn create ")));
1853
+ try {
1854
+ const { dir, files } = await createComponent(type, name);
1855
+ p9.log.success(`Created ${pc8.bold(type)} component ${pc8.cyan(name)}`);
1856
+ for (const file of files) {
1857
+ p9.log.message(` ${pc8.green("+")} ${file}`);
1858
+ }
1859
+ const editFile = files.find((f) => f !== "registry.json") ?? files[0];
1860
+ p9.outro(
1861
+ `Edit ${pc8.cyan(`${name}/${editFile}`)}, then run ${pc8.bold("kitn build")}`
1862
+ );
1863
+ } catch (err) {
1864
+ p9.log.error(err.message);
1865
+ process.exit(1);
1866
+ }
1867
+ }
1868
+ var VALID_TYPES;
1869
+ var init_create = __esm({
1870
+ "src/commands/create.ts"() {
1871
+ "use strict";
1872
+ VALID_TYPES = ["agent", "tool", "skill", "storage"];
1873
+ }
1874
+ });
1875
+
1075
1876
  // src/commands/info.ts
1076
1877
  var info_exports = {};
1077
1878
  __export(info_exports, {
1078
1879
  infoCommand: () => infoCommand
1079
1880
  });
1080
- import * as p7 from "@clack/prompts";
1081
- import pc7 from "picocolors";
1881
+ import * as p10 from "@clack/prompts";
1882
+ import pc9 from "picocolors";
1082
1883
  async function infoCommand(component) {
1083
1884
  const cwd = process.cwd();
1084
1885
  const config = await readConfig(cwd);
1085
1886
  if (!config) {
1086
- p7.log.error("No kitn.json found. Run `kitn init` first.");
1887
+ p10.log.error("No kitn.json found. Run `kitn init` first.");
1087
1888
  process.exit(1);
1088
1889
  }
1089
1890
  const ref = parseComponentRef(component);
1090
1891
  const fetcher = new RegistryFetcher(config.registries);
1091
- const s = p7.spinner();
1892
+ const s = p10.spinner();
1092
1893
  s.start("Fetching component info...");
1093
1894
  let index;
1094
1895
  try {
1095
1896
  index = await fetcher.fetchIndex(ref.namespace);
1096
1897
  } catch (err) {
1097
- s.stop(pc7.red("Failed to fetch registry"));
1098
- p7.log.error(err.message);
1898
+ s.stop(pc9.red("Failed to fetch registry"));
1899
+ p10.log.error(err.message);
1099
1900
  process.exit(1);
1100
1901
  }
1101
1902
  const indexItem = index.items.find((i) => i.name === ref.name);
1102
1903
  if (!indexItem) {
1103
- s.stop(pc7.red("Component not found"));
1104
- p7.log.error(`Component '${ref.name}' not found in registry.`);
1904
+ s.stop(pc9.red("Component not found"));
1905
+ p10.log.error(`Component '${ref.name}' not found in registry.`);
1105
1906
  process.exit(1);
1106
1907
  }
1107
1908
  const dir = typeToDir[indexItem.type];
@@ -1109,8 +1910,8 @@ async function infoCommand(component) {
1109
1910
  try {
1110
1911
  item = await fetcher.fetchItem(ref.name, dir, ref.namespace, ref.version);
1111
1912
  } catch (err) {
1112
- s.stop(pc7.red("Failed to fetch component"));
1113
- p7.log.error(err.message);
1913
+ s.stop(pc9.red("Failed to fetch component"));
1914
+ p10.log.error(err.message);
1114
1915
  process.exit(1);
1115
1916
  }
1116
1917
  s.stop("Component found");
@@ -1118,62 +1919,62 @@ async function infoCommand(component) {
1118
1919
  const typeName = indexItem.type.replace("kitn:", "");
1119
1920
  console.log();
1120
1921
  console.log(
1121
- ` ${pc7.bold(item.name)} ${pc7.cyan(`v${version}`)}${" ".repeat(Math.max(1, 40 - item.name.length - version.length - 2))}${pc7.dim(ref.namespace)}`
1922
+ ` ${pc9.bold(item.name)} ${pc9.cyan(`v${version}`)}${" ".repeat(Math.max(1, 40 - item.name.length - version.length - 2))}${pc9.dim(ref.namespace)}`
1122
1923
  );
1123
- console.log(` ${pc7.dim(item.description)}`);
1924
+ console.log(` ${pc9.dim(item.description)}`);
1124
1925
  console.log();
1125
- console.log(` ${pc7.dim("Type:")} ${typeName}`);
1926
+ console.log(` ${pc9.dim("Type:")} ${typeName}`);
1126
1927
  if (item.dependencies?.length) {
1127
1928
  console.log(
1128
- ` ${pc7.dim("Dependencies:")} ${item.dependencies.join(", ")}`
1929
+ ` ${pc9.dim("Dependencies:")} ${item.dependencies.join(", ")}`
1129
1930
  );
1130
1931
  }
1131
1932
  if (item.registryDependencies?.length) {
1132
1933
  console.log(
1133
- ` ${pc7.dim("Registry deps:")} ${item.registryDependencies.join(", ")}`
1934
+ ` ${pc9.dim("Registry deps:")} ${item.registryDependencies.join(", ")}`
1134
1935
  );
1135
1936
  }
1136
1937
  if (item.categories?.length) {
1137
1938
  console.log(
1138
- ` ${pc7.dim("Categories:")} ${item.categories.join(", ")}`
1939
+ ` ${pc9.dim("Categories:")} ${item.categories.join(", ")}`
1139
1940
  );
1140
1941
  }
1141
1942
  if (item.updatedAt) {
1142
- console.log(` ${pc7.dim("Updated:")} ${item.updatedAt}`);
1943
+ console.log(` ${pc9.dim("Updated:")} ${item.updatedAt}`);
1143
1944
  }
1144
1945
  const versions = indexItem.versions;
1145
1946
  if (versions?.length) {
1146
- console.log(` ${pc7.dim("Versions:")} ${versions.join(", ")}`);
1947
+ console.log(` ${pc9.dim("Versions:")} ${versions.join(", ")}`);
1147
1948
  }
1148
1949
  if (item.changelog?.length) {
1149
1950
  console.log();
1150
- console.log(` ${pc7.bold("Changelog:")}`);
1951
+ console.log(` ${pc9.bold("Changelog:")}`);
1151
1952
  for (const entry of item.changelog) {
1152
- const tag = entry.type === "feature" ? pc7.green(entry.type) : entry.type === "fix" ? pc7.yellow(entry.type) : entry.type === "breaking" ? pc7.red(entry.type) : pc7.dim(entry.type);
1953
+ const tag = entry.type === "feature" ? pc9.green(entry.type) : entry.type === "fix" ? pc9.yellow(entry.type) : entry.type === "breaking" ? pc9.red(entry.type) : pc9.dim(entry.type);
1153
1954
  console.log(
1154
- ` ${pc7.cyan(entry.version)} ${pc7.dim(entry.date)} ${tag} ${entry.note}`
1955
+ ` ${pc9.cyan(entry.version)} ${pc9.dim(entry.date)} ${tag} ${entry.note}`
1155
1956
  );
1156
1957
  }
1157
1958
  }
1158
1959
  console.log();
1159
1960
  const fileCount = item.files.length;
1160
- console.log(` ${pc7.bold(`Files:`)} ${pc7.dim(`(${fileCount})`)}`);
1961
+ console.log(` ${pc9.bold(`Files:`)} ${pc9.dim(`(${fileCount})`)}`);
1161
1962
  const maxShown = 10;
1162
1963
  for (const file of item.files.slice(0, maxShown)) {
1163
- console.log(` ${pc7.dim(file.path)}`);
1964
+ console.log(` ${pc9.dim(file.path)}`);
1164
1965
  }
1165
1966
  if (fileCount > maxShown) {
1166
- console.log(` ${pc7.dim(`... and ${fileCount - maxShown} more`)}`);
1967
+ console.log(` ${pc9.dim(`... and ${fileCount - maxShown} more`)}`);
1167
1968
  }
1168
1969
  const installed = config.installed?.[item.name];
1169
1970
  if (installed) {
1170
1971
  console.log();
1171
1972
  console.log(
1172
- ` ${pc7.green("Installed")} ${pc7.dim(`v${installed.version}`)}`
1973
+ ` ${pc9.green("Installed")} ${pc9.dim(`v${installed.version}`)}`
1173
1974
  );
1174
1975
  if (version !== installed.version) {
1175
1976
  console.log(
1176
- ` ${pc7.yellow("Update available:")} ${pc7.dim(`v${installed.version}`)} \u2192 ${pc7.cyan(`v${version}`)}`
1977
+ ` ${pc9.yellow("Update available:")} ${pc9.dim(`v${installed.version}`)} \u2192 ${pc9.cyan(`v${version}`)}`
1177
1978
  );
1178
1979
  }
1179
1980
  }
@@ -1196,8 +1997,8 @@ __export(registry_exports, {
1196
1997
  registryListCommand: () => registryListCommand,
1197
1998
  registryRemoveCommand: () => registryRemoveCommand
1198
1999
  });
1199
- import * as p8 from "@clack/prompts";
1200
- import pc8 from "picocolors";
2000
+ import * as p11 from "@clack/prompts";
2001
+ import pc10 from "picocolors";
1201
2002
  async function registryAddCommand(namespace, url, opts = {}) {
1202
2003
  const cwd = opts.cwd ?? process.cwd();
1203
2004
  const config = await readConfig(cwd);
@@ -1216,8 +2017,8 @@ async function registryAddCommand(namespace, url, opts = {}) {
1216
2017
  }
1217
2018
  config.registries[namespace] = url;
1218
2019
  await writeConfig(cwd, config);
1219
- p8.log.success(`Added registry ${pc8.bold(namespace)}`);
1220
- p8.log.message(pc8.dim(` ${url}`));
2020
+ p11.log.success(`Added registry ${pc10.bold(namespace)}`);
2021
+ p11.log.message(pc10.dim(` ${url}`));
1221
2022
  }
1222
2023
  async function registryRemoveCommand(namespace, opts = {}) {
1223
2024
  const cwd = opts.cwd ?? process.cwd();
@@ -1239,11 +2040,11 @@ async function registryRemoveCommand(namespace, opts = {}) {
1239
2040
  }
1240
2041
  delete config.registries[namespace];
1241
2042
  await writeConfig(cwd, config);
1242
- p8.log.success(`Removed registry ${pc8.bold(namespace)}`);
2043
+ p11.log.success(`Removed registry ${pc10.bold(namespace)}`);
1243
2044
  if (affectedComponents.length > 0) {
1244
- p8.log.warn(`${affectedComponents.length} installed component(s) referenced this registry:`);
2045
+ p11.log.warn(`${affectedComponents.length} installed component(s) referenced this registry:`);
1245
2046
  for (const name of affectedComponents) {
1246
- p8.log.message(` ${pc8.yellow("!")} ${name}`);
2047
+ p11.log.message(` ${pc10.yellow("!")} ${name}`);
1247
2048
  }
1248
2049
  }
1249
2050
  return { affectedComponents };
@@ -1254,10 +2055,10 @@ async function registryListCommand(opts = {}) {
1254
2055
  if (!config) throw new Error("No kitn.json found. Run `kitn init` first.");
1255
2056
  const entries = Object.entries(config.registries).map(([namespace, url]) => ({ namespace, url }));
1256
2057
  if (entries.length === 0) {
1257
- p8.log.message(pc8.dim(" No registries configured."));
2058
+ p11.log.message(pc10.dim(" No registries configured."));
1258
2059
  } else {
1259
2060
  for (const { namespace, url } of entries) {
1260
- p8.log.message(` ${pc8.bold(namespace.padEnd(16))} ${pc8.dim(url)}`);
2061
+ p11.log.message(` ${pc10.bold(namespace.padEnd(16))} ${pc10.dim(url)}`);
1261
2062
  }
1262
2063
  }
1263
2064
  return entries;
@@ -1347,7 +2148,7 @@ function startUpdateCheck(currentVersion) {
1347
2148
  }
1348
2149
 
1349
2150
  // src/index.ts
1350
- var VERSION = true ? "0.1.7" : "0.0.0-dev";
2151
+ var VERSION = true ? "0.1.9" : "0.0.0-dev";
1351
2152
  var printUpdateNotice = startUpdateCheck(VERSION);
1352
2153
  var program = new Command().name("kitn").description("Install AI agent components from the kitn registry").version(VERSION);
1353
2154
  program.command("init").description("Initialize kitn in your project").action(async () => {
@@ -1374,6 +2175,14 @@ program.command("update").description("Update installed components to latest reg
1374
2175
  const { updateCommand: updateCommand2 } = await Promise.resolve().then(() => (init_update(), update_exports));
1375
2176
  await updateCommand2(components);
1376
2177
  });
2178
+ program.command("build").description("Build registry JSON from components with registry.json files").argument("[paths...]", "directories to build (default: scan from cwd)").option("-o, --output <dir>", "output directory", "dist/r").action(async (paths, opts) => {
2179
+ const { buildCommand: buildCommand2 } = await Promise.resolve().then(() => (init_build(), build_exports));
2180
+ await buildCommand2(paths, opts);
2181
+ });
2182
+ program.command("create").description("Scaffold a new kitn component").argument("<type>", "component type (agent, tool, skill, storage)").argument("<name>", "component name").action(async (type, name) => {
2183
+ const { createCommand: createCommand2 } = await Promise.resolve().then(() => (init_create(), create_exports));
2184
+ await createCommand2(type, name);
2185
+ });
1377
2186
  program.command("info").description("Show details about a component").argument("<component>", "component name (e.g. weather-agent, @acme/tool@1.0.0)").action(async (component) => {
1378
2187
  const { infoCommand: infoCommand2 } = await Promise.resolve().then(() => (init_info(), info_exports));
1379
2188
  await infoCommand2(component);