@kitnai/cli 0.1.10 → 0.1.12

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
@@ -13,6 +13,9 @@ var __export = (target, all) => {
13
13
  import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
14
14
  import { join as join2 } from "path";
15
15
  import { z } from "zod";
16
+ function getRegistryUrl(entry) {
17
+ return typeof entry === "string" ? entry : entry.url;
18
+ }
16
19
  async function readConfig(projectDir) {
17
20
  try {
18
21
  const raw = await readFile2(join2(projectDir, CONFIG_FILE), "utf-8");
@@ -25,7 +28,16 @@ async function writeConfig(projectDir, config) {
25
28
  const data = { $schema: "https://kitn.dev/schema/config.json", ...config };
26
29
  await writeFile2(join2(projectDir, CONFIG_FILE), JSON.stringify(data, null, 2) + "\n");
27
30
  }
28
- var componentType, installedComponentSchema, configSchema, CONFIG_FILE;
31
+ function getInstallPath(config, type, fileName, namespace) {
32
+ const aliasKey = typeToAliasKey[type];
33
+ const base = config.aliases[aliasKey];
34
+ if (namespace && namespace !== "@kitn") {
35
+ const nsDir = namespace.replace("@", "");
36
+ return join2(base, nsDir, fileName);
37
+ }
38
+ return join2(base, fileName);
39
+ }
40
+ var componentType, installedComponentSchema, registryEntrySchema, registryValueSchema, configSchema, CONFIG_FILE, typeToAliasKey;
29
41
  var init_config = __esm({
30
42
  "src/utils/config.ts"() {
31
43
  "use strict";
@@ -37,6 +49,12 @@ var init_config = __esm({
37
49
  files: z.array(z.string()),
38
50
  hash: z.string()
39
51
  });
52
+ registryEntrySchema = z.object({
53
+ url: z.string(),
54
+ homepage: z.string().optional(),
55
+ description: z.string().optional()
56
+ });
57
+ registryValueSchema = z.union([z.string(), registryEntrySchema]);
40
58
  configSchema = z.object({
41
59
  $schema: z.string().optional(),
42
60
  runtime: z.enum(["bun", "node", "deno"]),
@@ -48,105 +66,103 @@ var init_config = __esm({
48
66
  skills: z.string(),
49
67
  storage: z.string()
50
68
  }),
51
- registries: z.record(z.string(), z.string()),
69
+ registries: z.record(z.string(), registryValueSchema),
52
70
  installed: z.record(z.string(), installedComponentSchema).optional()
53
71
  });
54
72
  CONFIG_FILE = "kitn.json";
73
+ typeToAliasKey = {
74
+ "kitn:agent": "agents",
75
+ "kitn:tool": "tools",
76
+ "kitn:skill": "skills",
77
+ "kitn:storage": "storage"
78
+ };
55
79
  }
56
80
  });
57
81
 
58
- // src/commands/init.ts
59
- var init_exports = {};
60
- __export(init_exports, {
61
- initCommand: () => initCommand
62
- });
63
- import * as p from "@clack/prompts";
64
- import pc2 from "picocolors";
65
- async function initCommand() {
66
- p.intro(pc2.bgCyan(pc2.black(" kitn ")));
67
- const cwd = process.cwd();
68
- const existing = await readConfig(cwd);
69
- if (existing) {
70
- p.log.warn("kitn.json already exists in this directory.");
71
- const shouldContinue = await p.confirm({
72
- message: "Overwrite existing configuration?",
73
- initialValue: false
74
- });
75
- if (p.isCancel(shouldContinue) || !shouldContinue) {
76
- p.cancel("Init cancelled.");
77
- process.exit(0);
82
+ // src/installers/tsconfig-patcher.ts
83
+ import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
84
+ import { join as join3 } from "path";
85
+ function stripJsonc(text3) {
86
+ return text3.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/,\s*([}\]])/g, "$1");
87
+ }
88
+ function patchTsconfig(tsconfigContent, paths, removePrefixes) {
89
+ const config = JSON.parse(stripJsonc(tsconfigContent));
90
+ if (!config.compilerOptions) {
91
+ config.compilerOptions = {};
92
+ }
93
+ if (!config.compilerOptions.paths) {
94
+ config.compilerOptions.paths = {};
95
+ }
96
+ if (removePrefixes) {
97
+ for (const key of Object.keys(config.compilerOptions.paths)) {
98
+ if (removePrefixes.some((prefix) => key.startsWith(prefix))) {
99
+ delete config.compilerOptions.paths[key];
100
+ }
78
101
  }
79
102
  }
80
- const runtime = await p.select({
81
- message: "Which runtime do you use?",
82
- options: [
83
- { value: "bun", label: "Bun", hint: "recommended" },
84
- { value: "node", label: "Node.js" },
85
- { value: "deno", label: "Deno" }
86
- ]
87
- });
88
- if (p.isCancel(runtime)) {
89
- p.cancel("Init cancelled.");
90
- process.exit(0);
103
+ for (const [key, value] of Object.entries(paths)) {
104
+ config.compilerOptions.paths[key] = value;
91
105
  }
92
- const framework = await p.select({
93
- message: "Which framework are you using?",
94
- options: [
95
- { value: "hono", label: "Hono", hint: "recommended" },
96
- { value: "cloudflare", label: "Cloudflare Workers", hint: "coming soon" },
97
- { value: "elysia", label: "Elysia", hint: "coming soon" },
98
- { value: "fastify", label: "Fastify", hint: "coming soon" },
99
- { value: "express", label: "Express", hint: "coming soon" }
100
- ]
101
- });
102
- if (p.isCancel(framework)) {
103
- p.cancel("Init cancelled.");
104
- process.exit(0);
106
+ return JSON.stringify(config, null, 2) + "\n";
107
+ }
108
+ async function patchProjectTsconfig(projectDir, paths, removePrefixes) {
109
+ const tsconfigPath = join3(projectDir, "tsconfig.json");
110
+ let content;
111
+ try {
112
+ content = await readFile3(tsconfigPath, "utf-8");
113
+ } catch {
114
+ content = "{}";
105
115
  }
106
- const base = await p.text({
107
- message: "Where should kitn packages be installed?",
108
- initialValue: "src/ai",
109
- placeholder: "src/ai"
110
- });
111
- if (p.isCancel(base)) {
112
- p.cancel("Init cancelled.");
113
- process.exit(0);
116
+ const patched = patchTsconfig(content, paths, removePrefixes);
117
+ await writeFile3(tsconfigPath, patched);
118
+ }
119
+ var init_tsconfig_patcher = __esm({
120
+ "src/installers/tsconfig-patcher.ts"() {
121
+ "use strict";
114
122
  }
115
- const baseDir = base;
116
- const config = {
117
- runtime,
118
- framework,
119
- aliases: {
120
- base: baseDir,
121
- agents: `${baseDir}/agents`,
122
- tools: `${baseDir}/tools`,
123
- skills: `${baseDir}/skills`,
124
- storage: `${baseDir}/storage`
125
- },
126
- registries: {
127
- "@kitn": "https://kitn-ai.github.io/registry/r/{type}/{name}.json"
128
- }
129
- };
130
- const s = p.spinner();
131
- s.start("Writing kitn.json");
132
- await writeConfig(cwd, config);
133
- s.stop("Created kitn.json");
134
- p.outro(pc2.green("Done! Run `kitn add core` to install the engine, then `kitn add routes` for HTTP routes."));
123
+ });
124
+
125
+ // src/installers/barrel-manager.ts
126
+ function createBarrelFile() {
127
+ return `${BARREL_COMMENT}
128
+ ${EXPORT_LINE}
129
+ `;
135
130
  }
136
- var init_init = __esm({
137
- "src/commands/init.ts"() {
131
+ function addImportToBarrel(content, importPath) {
132
+ const importLine = `import "${importPath}";`;
133
+ if (content.includes(importLine)) return content;
134
+ const exportIndex = content.indexOf(EXPORT_LINE);
135
+ if (exportIndex === -1) {
136
+ return `${content.trimEnd()}
137
+ ${importLine}
138
+ ${EXPORT_LINE}
139
+ `;
140
+ }
141
+ const before = content.slice(0, exportIndex);
142
+ const after = content.slice(exportIndex);
143
+ return `${before}${importLine}
144
+ ${after}`;
145
+ }
146
+ function removeImportFromBarrel(content, importPath) {
147
+ const importLine = `import "${importPath}";`;
148
+ return content.split("\n").filter((line) => line.trim() !== importLine).join("\n");
149
+ }
150
+ var EXPORT_LINE, BARREL_COMMENT;
151
+ var init_barrel_manager = __esm({
152
+ "src/installers/barrel-manager.ts"() {
138
153
  "use strict";
139
- init_config();
154
+ EXPORT_LINE = 'export { registerWithPlugin } from "@kitn/core";';
155
+ BARREL_COMMENT = "// Managed by kitn CLI \u2014 components auto-imported below";
140
156
  }
141
157
  });
142
158
 
143
159
  // src/utils/detect.ts
144
160
  import { access } from "fs/promises";
145
- import { join as join3 } from "path";
161
+ import { join as join4 } from "path";
146
162
  async function detectPackageManager(dir) {
147
163
  for (const [lockfile, pm] of LOCKFILE_MAP) {
148
164
  try {
149
- await access(join3(dir, lockfile));
165
+ await access(join4(dir, lockfile));
150
166
  return pm;
151
167
  } catch {
152
168
  }
@@ -168,6 +184,9 @@ var init_detect = __esm({
168
184
  });
169
185
 
170
186
  // src/registry/fetcher.ts
187
+ function urlOf(entry) {
188
+ return typeof entry === "string" ? entry : entry.url;
189
+ }
171
190
  var RegistryFetcher;
172
191
  var init_fetcher = __esm({
173
192
  "src/registry/fetcher.ts"() {
@@ -181,8 +200,9 @@ var init_fetcher = __esm({
181
200
  this.fetchFn = fetchFn ?? this.defaultFetch;
182
201
  }
183
202
  resolveUrl(name, typeDir, namespace = "@kitn", version) {
184
- const template = this.registries[namespace];
185
- if (!template) throw new Error(`No registry configured for ${namespace}`);
203
+ const entry = this.registries[namespace];
204
+ if (!entry) throw new Error(`No registry configured for ${namespace}`);
205
+ const template = urlOf(entry);
186
206
  const fileName = version ? `${name}@${version}` : name;
187
207
  return template.replace("{name}", fileName).replace("{type}", typeDir);
188
208
  }
@@ -194,8 +214,9 @@ var init_fetcher = __esm({
194
214
  return this.cache.get(url);
195
215
  }
196
216
  async fetchIndex(namespace = "@kitn") {
197
- const template = this.registries[namespace];
198
- if (!template) throw new Error(`No registry configured for ${namespace}`);
217
+ const entry = this.registries[namespace];
218
+ if (!entry) throw new Error(`No registry configured for ${namespace}`);
219
+ const template = urlOf(entry);
199
220
  const baseUrl = template.replace("{type}/{name}.json", "registry.json");
200
221
  const res = await fetch(baseUrl);
201
222
  if (!res.ok) throw new Error(`Failed to fetch registry index: ${res.statusText}`);
@@ -269,7 +290,7 @@ var init_resolver = __esm({
269
290
  });
270
291
 
271
292
  // src/installers/file-writer.ts
272
- import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir2, access as access2 } from "fs/promises";
293
+ import { readFile as readFile4, writeFile as writeFile4, mkdir as mkdir2, access as access2 } from "fs/promises";
273
294
  import { dirname } from "path";
274
295
  import { createPatch } from "diff";
275
296
  async function checkFileStatus(filePath, newContent) {
@@ -278,7 +299,7 @@ async function checkFileStatus(filePath, newContent) {
278
299
  } catch {
279
300
  return "new" /* New */;
280
301
  }
281
- const existing = await readFile3(filePath, "utf-8");
302
+ const existing = await readFile4(filePath, "utf-8");
282
303
  return existing === newContent ? "identical" /* Identical */ : "different" /* Different */;
283
304
  }
284
305
  function generateDiff(filePath, oldContent, newContent) {
@@ -286,14 +307,14 @@ function generateDiff(filePath, oldContent, newContent) {
286
307
  }
287
308
  async function readExistingFile(filePath) {
288
309
  try {
289
- return await readFile3(filePath, "utf-8");
310
+ return await readFile4(filePath, "utf-8");
290
311
  } catch {
291
312
  return null;
292
313
  }
293
314
  }
294
315
  async function writeComponentFile(filePath, content) {
295
316
  await mkdir2(dirname(filePath), { recursive: true });
296
- await writeFile3(filePath, content);
317
+ await writeFile4(filePath, content);
297
318
  }
298
319
  var init_file_writer = __esm({
299
320
  "src/installers/file-writer.ts"() {
@@ -327,10 +348,10 @@ var init_dep_installer = __esm({
327
348
  });
328
349
 
329
350
  // src/installers/env-writer.ts
330
- import * as p2 from "@clack/prompts";
331
- import pc3 from "picocolors";
332
- import { readFile as readFile4, writeFile as writeFile4, access as access3 } from "fs/promises";
333
- import { join as join4 } from "path";
351
+ import * as p from "@clack/prompts";
352
+ import pc2 from "picocolors";
353
+ import { readFile as readFile5, writeFile as writeFile5, access as access3 } from "fs/promises";
354
+ import { join as join5 } from "path";
334
355
  function parseEnvKeys(content) {
335
356
  const keys = /* @__PURE__ */ new Set();
336
357
  for (const line of content.split("\n")) {
@@ -345,7 +366,7 @@ function parseEnvKeys(content) {
345
366
  }
346
367
  async function readEnvFile(path) {
347
368
  try {
348
- return await readFile4(path, "utf-8");
369
+ return await readFile5(path, "utf-8");
349
370
  } catch {
350
371
  return "";
351
372
  }
@@ -362,8 +383,8 @@ function collectEnvVars(items) {
362
383
  async function handleEnvVars(cwd, envVars) {
363
384
  const keys = Object.keys(envVars);
364
385
  if (keys.length === 0) return;
365
- const envPath = join4(cwd, ".env");
366
- const examplePath = join4(cwd, ".env.example");
386
+ const envPath = join5(cwd, ".env");
387
+ const examplePath = join5(cwd, ".env.example");
367
388
  const envContent = await readEnvFile(envPath);
368
389
  const exampleContent = await readEnvFile(examplePath);
369
390
  const envKeys = parseEnvKeys(envContent);
@@ -378,25 +399,25 @@ async function handleEnvVars(cwd, envVars) {
378
399
  lines.push(`# ${config.description}${config.url ? ` (${config.url})` : ""}`);
379
400
  lines.push(`${key}=`);
380
401
  }
381
- await writeFile4(examplePath, exampleContent + lines.join("\n") + "\n");
382
- p2.log.info(`Updated ${pc3.cyan(".env.example")} with ${missingFromExample.length} variable(s)`);
402
+ await writeFile5(examplePath, exampleContent + lines.join("\n") + "\n");
403
+ p.log.info(`Updated ${pc2.cyan(".env.example")} with ${missingFromExample.length} variable(s)`);
383
404
  }
384
405
  if (missingFromEnv.length === 0) return;
385
- p2.log.message("");
386
- p2.log.warn(
406
+ p.log.message("");
407
+ p.log.warn(
387
408
  `${missingFromEnv.length} environment variable(s) needed:`
388
409
  );
389
410
  for (const key of missingFromEnv) {
390
411
  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}`) : ""}`);
412
+ const req = config.required !== false ? pc2.red("*") : "";
413
+ p.log.message(` ${pc2.yellow(key)}${req}: ${config.description}${config.url ? pc2.dim(` -> ${config.url}`) : ""}`);
393
414
  }
394
- const shouldPrompt = await p2.confirm({
415
+ const shouldPrompt = await p.confirm({
395
416
  message: "Would you like to enter values now?",
396
417
  initialValue: true
397
418
  });
398
- if (p2.isCancel(shouldPrompt) || !shouldPrompt) {
399
- p2.log.info(`Add them to ${pc3.cyan(".env")} when ready.`);
419
+ if (p.isCancel(shouldPrompt) || !shouldPrompt) {
420
+ p.log.info(`Add them to ${pc2.cyan(".env")} when ready.`);
400
421
  return;
401
422
  }
402
423
  const newEntries = [];
@@ -405,17 +426,17 @@ async function handleEnvVars(cwd, envVars) {
405
426
  const isSecret = config.secret !== false;
406
427
  let value;
407
428
  if (isSecret) {
408
- value = await p2.password({
429
+ value = await p.password({
409
430
  message: `${key}:`
410
431
  });
411
432
  } else {
412
- value = await p2.text({
433
+ value = await p.text({
413
434
  message: `${key}:`,
414
435
  placeholder: config.description
415
436
  });
416
437
  }
417
- if (p2.isCancel(value)) {
418
- p2.log.info(`Skipped remaining variables. Add them to ${pc3.cyan(".env")} when ready.`);
438
+ if (p.isCancel(value)) {
439
+ p.log.info(`Skipped remaining variables. Add them to ${pc2.cyan(".env")} when ready.`);
419
440
  break;
420
441
  }
421
442
  if (value) {
@@ -427,8 +448,8 @@ async function handleEnvVars(cwd, envVars) {
427
448
  const lines = [];
428
449
  if (existingEnv && !existingEnv.endsWith("\n")) lines.push("");
429
450
  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")}`);
451
+ await writeFile5(envPath, existingEnv + lines.join("\n") + "\n");
452
+ p.log.success(`Wrote ${newEntries.length} variable(s) to ${pc2.cyan(".env")}`);
432
453
  }
433
454
  }
434
455
  var init_env_writer = __esm({
@@ -438,7 +459,7 @@ var init_env_writer = __esm({
438
459
  });
439
460
 
440
461
  // src/installers/import-rewriter.ts
441
- import { relative, join as join5 } from "path";
462
+ import { relative, join as join6 } from "path";
442
463
  function rewriteKitnImports(content, fileType, fileName, aliases) {
443
464
  const sourceAliasKey = TYPE_TO_ALIAS_KEY[fileType];
444
465
  if (!sourceAliasKey) return content;
@@ -450,7 +471,7 @@ function rewriteKitnImports(content, fileType, fileName, aliases) {
450
471
  return `${prefix}@kitn/${type}/${targetPath}${quote}`;
451
472
  }
452
473
  const targetDir = aliases[type];
453
- const targetFile = join5(targetDir, targetPath);
474
+ const targetFile = join6(targetDir, targetPath);
454
475
  let rel = relative(sourceDir, targetFile);
455
476
  rel = rel.split("\\").join("/");
456
477
  if (!rel.startsWith(".")) {
@@ -474,76 +495,6 @@ var init_import_rewriter = __esm({
474
495
  }
475
496
  });
476
497
 
477
- // src/installers/tsconfig-patcher.ts
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");
482
- }
483
- function patchTsconfig(tsconfigContent, paths) {
484
- const config = JSON.parse(stripJsonc(tsconfigContent));
485
- if (!config.compilerOptions) {
486
- config.compilerOptions = {};
487
- }
488
- if (!config.compilerOptions.paths) {
489
- config.compilerOptions.paths = {};
490
- }
491
- for (const [key, value] of Object.entries(paths)) {
492
- config.compilerOptions.paths[key] = value;
493
- }
494
- return JSON.stringify(config, null, 2) + "\n";
495
- }
496
- async function patchProjectTsconfig(projectDir, paths) {
497
- const tsconfigPath = join6(projectDir, "tsconfig.json");
498
- let content;
499
- try {
500
- content = await readFile5(tsconfigPath, "utf-8");
501
- } catch {
502
- content = "{}";
503
- }
504
- const patched = patchTsconfig(content, paths);
505
- await writeFile5(tsconfigPath, patched);
506
- }
507
- var init_tsconfig_patcher = __esm({
508
- "src/installers/tsconfig-patcher.ts"() {
509
- "use strict";
510
- }
511
- });
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
-
547
498
  // src/utils/hash.ts
548
499
  import { createHash } from "crypto";
549
500
  function contentHash(content) {
@@ -674,22 +625,22 @@ var add_exports = {};
674
625
  __export(add_exports, {
675
626
  addCommand: () => addCommand
676
627
  });
677
- import * as p3 from "@clack/prompts";
678
- import pc4 from "picocolors";
628
+ import * as p2 from "@clack/prompts";
629
+ import pc3 from "picocolors";
679
630
  import { join as join7 } from "path";
680
631
  import { existsSync } from "fs";
681
632
  import { readFile as readFile6, writeFile as writeFile6, mkdir as mkdir3 } from "fs/promises";
682
633
  import { relative as relative2 } from "path";
683
634
  async function addCommand(components, opts) {
684
- p3.intro(pc4.bgCyan(pc4.black(" kitn add ")));
635
+ p2.intro(pc3.bgCyan(pc3.black(" kitn add ")));
685
636
  const cwd = process.cwd();
686
637
  const config = await readConfig(cwd);
687
638
  if (!config) {
688
- p3.log.error("No kitn.json found. Run `kitn init` first.");
639
+ p2.log.error("No kitn.json found. Run `kitn init` first.");
689
640
  process.exit(1);
690
641
  }
691
642
  if (components.length === 0) {
692
- p3.log.error("Please specify at least one component to add.");
643
+ p2.log.error("Please specify at least one component to add.");
693
644
  process.exit(1);
694
645
  }
695
646
  const resolvedComponents = components.map((c) => {
@@ -701,7 +652,7 @@ async function addCommand(components, opts) {
701
652
  });
702
653
  const refs = resolvedComponents.map(parseComponentRef);
703
654
  const fetcher = new RegistryFetcher(config.registries);
704
- const s = p3.spinner();
655
+ const s = p2.spinner();
705
656
  s.start("Resolving dependencies...");
706
657
  let resolved;
707
658
  try {
@@ -714,16 +665,16 @@ async function addCommand(components, opts) {
714
665
  return fetcher.fetchItem(name, dir, ref.namespace, ref.version);
715
666
  });
716
667
  } catch (err) {
717
- s.stop(pc4.red("Failed to resolve dependencies"));
718
- p3.log.error(err.message);
668
+ s.stop(pc3.red("Failed to resolve dependencies"));
669
+ p2.log.error(err.message);
719
670
  process.exit(1);
720
671
  }
721
672
  s.stop(`Resolved ${resolved.length} component(s)`);
722
- p3.log.info("Components to install:");
673
+ p2.log.info("Components to install:");
723
674
  for (const item of resolved) {
724
675
  const isExplicit = resolvedComponents.includes(item.name) || components.includes(item.name);
725
- const label = isExplicit ? item.name : `${item.name} ${pc4.dim("(dependency)")}`;
726
- p3.log.message(` ${pc4.cyan(label)}`);
676
+ const label = isExplicit ? item.name : `${item.name} ${pc3.dim("(dependency)")}`;
677
+ p2.log.message(` ${pc3.cyan(label)}`);
727
678
  }
728
679
  const created = [];
729
680
  const updated = [];
@@ -752,15 +703,15 @@ async function addCommand(components, opts) {
752
703
  } else {
753
704
  const existing = await readExistingFile(targetPath);
754
705
  const diff = generateDiff(relativePath, existing ?? "", file.content);
755
- p3.log.message(pc4.dim(diff));
756
- const action = await p3.select({
706
+ p2.log.message(pc3.dim(diff));
707
+ const action = await p2.select({
757
708
  message: `${relativePath} already exists and differs. What to do?`,
758
709
  options: [
759
710
  { value: "skip", label: "Keep local version" },
760
711
  { value: "overwrite", label: "Overwrite with registry version" }
761
712
  ]
762
713
  });
763
- if (!p3.isCancel(action) && action === "overwrite") {
714
+ if (!p2.isCancel(action) && action === "overwrite") {
764
715
  await writeComponentFile(targetPath, file.content);
765
716
  updated.push(relativePath);
766
717
  } else {
@@ -770,15 +721,6 @@ async function addCommand(components, opts) {
770
721
  break;
771
722
  }
772
723
  }
773
- if (item.tsconfig) {
774
- const resolvedPaths = {};
775
- const installDir = item.installDir ?? item.name;
776
- for (const [key, values] of Object.entries(item.tsconfig)) {
777
- resolvedPaths[key] = values.map((v) => `./${join7(baseDir2, installDir, v)}`);
778
- }
779
- await patchProjectTsconfig(cwd, resolvedPaths);
780
- p3.log.info(`Patched tsconfig.json with paths: ${Object.keys(resolvedPaths).join(", ")}`);
781
- }
782
724
  const installed = config.installed ?? {};
783
725
  const allContent = item.files.map((f) => f.content).join("\n");
784
726
  const ref = refs.find((r) => r.name === item.name) ?? { namespace: "@kitn", name: item.name, version: void 0 };
@@ -792,6 +734,8 @@ async function addCommand(components, opts) {
792
734
  };
793
735
  config.installed = installed;
794
736
  } else {
737
+ const ref = refs.find((r) => r.name === item.name) ?? { namespace: "@kitn", name: item.name, version: void 0 };
738
+ const ns = ref.namespace;
795
739
  for (const file of item.files) {
796
740
  const aliasKey = (() => {
797
741
  switch (item.type) {
@@ -806,8 +750,9 @@ async function addCommand(components, opts) {
806
750
  }
807
751
  })();
808
752
  const fileName = file.path.split("/").pop();
809
- const targetPath = join7(cwd, config.aliases[aliasKey], fileName);
810
- const relativePath = join7(config.aliases[aliasKey], fileName);
753
+ const installPath = getInstallPath(config, item.type, fileName, ns);
754
+ const targetPath = join7(cwd, installPath);
755
+ const relativePath = installPath;
811
756
  const content = rewriteKitnImports(file.content, item.type, fileName, config.aliases);
812
757
  const status = await checkFileStatus(targetPath, content);
813
758
  switch (status) {
@@ -825,15 +770,15 @@ async function addCommand(components, opts) {
825
770
  } else {
826
771
  const existing = await readExistingFile(targetPath);
827
772
  const diff = generateDiff(relativePath, existing ?? "", content);
828
- p3.log.message(pc4.dim(diff));
829
- const action = await p3.select({
773
+ p2.log.message(pc3.dim(diff));
774
+ const action = await p2.select({
830
775
  message: `${relativePath} already exists and differs. What to do?`,
831
776
  options: [
832
777
  { value: "skip", label: "Keep local version" },
833
778
  { value: "overwrite", label: "Overwrite with registry version" }
834
779
  ]
835
780
  });
836
- if (!p3.isCancel(action) && action === "overwrite") {
781
+ if (!p2.isCancel(action) && action === "overwrite") {
837
782
  await writeComponentFile(targetPath, content);
838
783
  updated.push(relativePath);
839
784
  } else {
@@ -848,27 +793,14 @@ async function addCommand(components, opts) {
848
793
  const fn = f.path.split("/").pop();
849
794
  return rewriteKitnImports(f.content, item.type, fn, config.aliases);
850
795
  }).join("\n");
851
- const ref = refs.find((r) => r.name === item.name) ?? { namespace: "@kitn", name: item.name, version: void 0 };
852
- const installedKey = ref.namespace === "@kitn" ? item.name : `${ref.namespace}/${item.name}`;
796
+ const installedKey = ns === "@kitn" ? item.name : `${ns}/${item.name}`;
853
797
  installed[installedKey] = {
854
- registry: ref.namespace,
798
+ registry: ns,
855
799
  version: item.version ?? "1.0.0",
856
800
  installedAt: (/* @__PURE__ */ new Date()).toISOString(),
857
801
  files: item.files.map((f) => {
858
- const aliasKey = (() => {
859
- switch (item.type) {
860
- case "kitn:agent":
861
- return "agents";
862
- case "kitn:tool":
863
- return "tools";
864
- case "kitn:skill":
865
- return "skills";
866
- case "kitn:storage":
867
- return "storage";
868
- }
869
- })();
870
802
  const fileName = f.path.split("/").pop();
871
- return join7(config.aliases[aliasKey], fileName);
803
+ return getInstallPath(config, item.type, fileName, ns);
872
804
  }),
873
805
  hash: contentHash(allContent)
874
806
  };
@@ -882,19 +814,11 @@ async function addCommand(components, opts) {
882
814
  const barrelImports = [];
883
815
  for (const item of resolved) {
884
816
  if (!BARREL_ELIGIBLE.has(item.type)) continue;
817
+ const ref = refs.find((r) => r.name === item.name) ?? { namespace: "@kitn", name: item.name, version: void 0 };
885
818
  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
819
  const fileName = file.path.split("/").pop();
897
- const filePath = join7(cwd, config.aliases[aliasKey], fileName);
820
+ const installPath = getInstallPath(config, item.type, fileName, ref.namespace);
821
+ const filePath = join7(cwd, installPath);
898
822
  const importPath = "./" + relative2(barrelDir, filePath).replace(/\\/g, "/");
899
823
  barrelImports.push(importPath);
900
824
  }
@@ -912,21 +836,16 @@ async function addCommand(components, opts) {
912
836
  barrelContent = addImportToBarrel(barrelContent, importPath);
913
837
  }
914
838
  await writeFile6(barrelPath, barrelContent);
915
- p3.log.info(`Updated barrel file: ${join7(baseDir, "index.ts")}`);
839
+ p2.log.info(`Updated barrel file: ${join7(baseDir, "index.ts")}`);
916
840
  if (!barrelExisted) {
917
- p3.note(
841
+ p2.note(
918
842
  [
919
- `import { createAIPlugin } from "@kitnai/hono";`,
920
- `import { registerWithPlugin } from "./ai";`,
921
- ``,
922
- `const plugin = createAIPlugin({`,
923
- ` model: (model) => yourProvider(model ?? "default-model"),`,
924
- `});`,
843
+ `import { ai } from "./${baseDir}/plugin.js";`,
925
844
  ``,
926
- `registerWithPlugin(plugin);`,
927
- `app.route("/api", plugin.app);`
845
+ `app.route("/api", ai.router);`,
846
+ `await ai.initialize();`
928
847
  ].join("\n"),
929
- "Add this to your app setup"
848
+ "Add this to your server entry point"
930
849
  );
931
850
  }
932
851
  }
@@ -940,58 +859,49 @@ async function addCommand(components, opts) {
940
859
  installDependencies(pm, uniqueDeps, cwd);
941
860
  s.stop("Dependencies installed");
942
861
  } catch {
943
- s.stop(pc4.yellow("Some dependencies failed to install"));
862
+ s.stop(pc3.yellow("Some dependencies failed to install"));
944
863
  }
945
864
  }
946
865
  }
947
866
  if (created.length > 0) {
948
- p3.log.success(`Created ${created.length} file(s):`);
949
- for (const f of created) p3.log.message(` ${pc4.green("+")} ${f}`);
867
+ p2.log.success(`Created ${created.length} file(s):`);
868
+ for (const f of created) p2.log.message(` ${pc3.green("+")} ${f}`);
950
869
  }
951
870
  if (updated.length > 0) {
952
- p3.log.success(`Updated ${updated.length} file(s):`);
953
- for (const f of updated) p3.log.message(` ${pc4.yellow("~")} ${f}`);
871
+ p2.log.success(`Updated ${updated.length} file(s):`);
872
+ for (const f of updated) p2.log.message(` ${pc3.yellow("~")} ${f}`);
954
873
  }
955
874
  if (skipped.length > 0) {
956
- p3.log.info(`Skipped ${skipped.length} file(s):`);
957
- for (const f of skipped) p3.log.message(` ${pc4.dim("-")} ${f}`);
875
+ p2.log.info(`Skipped ${skipped.length} file(s):`);
876
+ for (const f of skipped) p2.log.message(` ${pc3.dim("-")} ${f}`);
958
877
  }
959
878
  const allEnvVars = collectEnvVars(resolved);
960
879
  await handleEnvVars(cwd, allEnvVars);
961
880
  for (const item of resolved) {
962
881
  if (item.docs) {
963
- p3.log.info(`${pc4.bold(item.name)}: ${item.docs}`);
882
+ p2.log.info(`${pc3.bold(item.name)}: ${item.docs}`);
964
883
  }
965
884
  }
966
885
  const installedNames = new Set(resolved.map((r) => r.name));
967
886
  const hints = [];
968
887
  if (installedNames.has("core") && !installedNames.has(config.framework ?? "hono")) {
969
- hints.push(`Run ${pc4.cyan(`kitn add routes`)} to install the HTTP adapter.`);
888
+ hints.push(`Run ${pc3.cyan(`kitn add routes`)} to install the HTTP adapter.`);
970
889
  }
971
890
  const fw = config.framework ?? "hono";
972
891
  if (installedNames.has(fw) || installedNames.has("core") && installedNames.has(fw)) {
973
- hints.push(`Add this to your server entry point:`);
974
- if (fw === "hono") {
975
- hints.push("");
976
- hints.push(pc4.dim(` import { Hono } from "hono";`));
977
- hints.push(pc4.dim(` import { createAIPlugin } from "@kitnai/hono";`));
978
- hints.push(pc4.dim(` import { yourProvider } from "your-ai-provider";`));
979
- hints.push(pc4.dim(``));
980
- hints.push(pc4.dim(` const plugin = createAIPlugin({`));
981
- hints.push(pc4.dim(` model: (model) => yourProvider(model ?? "default-model"),`));
982
- hints.push(pc4.dim(` });`));
983
- hints.push(pc4.dim(``));
984
- hints.push(pc4.dim(` const app = new Hono();`));
985
- hints.push(pc4.dim(` app.route("/api", plugin.app);`));
986
- hints.push(pc4.dim(` await plugin.initialize();`));
987
- hints.push("");
988
- }
892
+ hints.push(`Configure your AI provider in ${pc3.bold(baseDir + "/plugin.ts")}, then add to your server:`);
893
+ hints.push("");
894
+ hints.push(pc3.dim(` import { ai } from "./${baseDir}/plugin.js";`));
895
+ hints.push(pc3.dim(``));
896
+ hints.push(pc3.dim(` app.route("/api", ai.router);`));
897
+ hints.push(pc3.dim(` await ai.initialize();`));
898
+ hints.push("");
989
899
  }
990
900
  if (hints.length > 0) {
991
- p3.log.message(pc4.bold("\nNext steps:"));
992
- for (const hint of hints) p3.log.message(hint);
901
+ p2.log.message(pc3.bold("\nNext steps:"));
902
+ for (const hint of hints) p2.log.message(hint);
993
903
  }
994
- p3.outro(pc4.green("Done!"));
904
+ p2.outro(pc3.green("Done!"));
995
905
  }
996
906
  var init_add = __esm({
997
907
  "src/commands/add.ts"() {
@@ -1004,7 +914,6 @@ var init_add = __esm({
1004
914
  init_dep_installer();
1005
915
  init_env_writer();
1006
916
  init_import_rewriter();
1007
- init_tsconfig_patcher();
1008
917
  init_barrel_manager();
1009
918
  init_hash();
1010
919
  init_parse_ref();
@@ -1012,6 +921,130 @@ var init_add = __esm({
1012
921
  }
1013
922
  });
1014
923
 
924
+ // src/commands/init.ts
925
+ var init_exports = {};
926
+ __export(init_exports, {
927
+ initCommand: () => initCommand
928
+ });
929
+ import * as p3 from "@clack/prompts";
930
+ import pc4 from "picocolors";
931
+ import { mkdir as mkdir4, writeFile as writeFile7 } from "fs/promises";
932
+ import { join as join8 } from "path";
933
+ async function initCommand() {
934
+ p3.intro(pc4.bgCyan(pc4.black(" kitn init ")));
935
+ const cwd = process.cwd();
936
+ const existing = await readConfig(cwd);
937
+ if (existing) {
938
+ p3.log.warn("kitn.json already exists in this directory.");
939
+ const shouldContinue = await p3.confirm({
940
+ message: "Overwrite existing configuration?",
941
+ initialValue: false
942
+ });
943
+ if (p3.isCancel(shouldContinue) || !shouldContinue) {
944
+ p3.cancel("Init cancelled.");
945
+ process.exit(0);
946
+ }
947
+ }
948
+ const runtime = await p3.select({
949
+ message: "Which runtime do you use?",
950
+ options: [
951
+ { value: "bun", label: "Bun", hint: "recommended" },
952
+ { value: "node", label: "Node.js" },
953
+ { value: "deno", label: "Deno" }
954
+ ]
955
+ });
956
+ if (p3.isCancel(runtime)) {
957
+ p3.cancel("Init cancelled.");
958
+ process.exit(0);
959
+ }
960
+ const base = await p3.text({
961
+ message: "Where should kitn components be installed?",
962
+ initialValue: "src/ai",
963
+ placeholder: "src/ai"
964
+ });
965
+ if (p3.isCancel(base)) {
966
+ p3.cancel("Init cancelled.");
967
+ process.exit(0);
968
+ }
969
+ const baseDir = base;
970
+ const config = {
971
+ runtime,
972
+ framework: "hono",
973
+ aliases: {
974
+ base: baseDir,
975
+ agents: `${baseDir}/agents`,
976
+ tools: `${baseDir}/tools`,
977
+ skills: `${baseDir}/skills`,
978
+ storage: `${baseDir}/storage`
979
+ },
980
+ registries: {
981
+ "@kitn": {
982
+ url: "https://kitn-ai.github.io/registry/r/{type}/{name}.json",
983
+ homepage: "https://kitn.ai",
984
+ description: "Official kitn AI agent components"
985
+ }
986
+ }
987
+ };
988
+ const s = p3.spinner();
989
+ s.start("Writing kitn.json");
990
+ await writeConfig(cwd, config);
991
+ s.stop("Created kitn.json");
992
+ await patchProjectTsconfig(
993
+ cwd,
994
+ { "@kitn/*": [`./${baseDir}/*`] },
995
+ ["@kitn", "@kitnai"]
996
+ );
997
+ p3.log.info(`Patched tsconfig.json with path: ${pc4.bold("@kitn/*")}`);
998
+ p3.log.info("Installing core engine and routes...");
999
+ await addCommand(["core", "routes"], { overwrite: true });
1000
+ const aiDir = join8(cwd, baseDir);
1001
+ await mkdir4(aiDir, { recursive: true });
1002
+ const barrelPath = join8(aiDir, "index.ts");
1003
+ await writeFile7(barrelPath, createBarrelFile());
1004
+ const pluginPath = join8(aiDir, "plugin.ts");
1005
+ await writeFile7(pluginPath, PLUGIN_TEMPLATE);
1006
+ p3.log.success(`Created ${pc4.bold(baseDir + "/plugin.ts")} \u2014 configure your AI provider there`);
1007
+ p3.note(
1008
+ [
1009
+ `import { ai } from "./${baseDir}/plugin.js";`,
1010
+ ``,
1011
+ `app.route("/api", ai.router);`,
1012
+ `await ai.initialize();`
1013
+ ].join("\n"),
1014
+ "Add this to your server entry point:"
1015
+ );
1016
+ p3.outro("Done!");
1017
+ }
1018
+ var PLUGIN_TEMPLATE;
1019
+ var init_init = __esm({
1020
+ "src/commands/init.ts"() {
1021
+ "use strict";
1022
+ init_config();
1023
+ init_tsconfig_patcher();
1024
+ init_barrel_manager();
1025
+ init_add();
1026
+ PLUGIN_TEMPLATE = `import { createAIPlugin } from "@kitn/hono-routes";
1027
+ import { registerWithPlugin } from "./index.js";
1028
+
1029
+ export const ai = createAIPlugin({
1030
+ // To enable agent chat, add an AI provider:
1031
+ // https://sdk.vercel.ai/providers/ai-sdk-providers
1032
+ //
1033
+ // Example with OpenRouter (access to many models):
1034
+ // import { openrouter } from "@openrouter/ai-sdk-provider";
1035
+ // model: (id) => openrouter(id ?? "openai/gpt-4o-mini"),
1036
+ //
1037
+ // Example with OpenAI directly:
1038
+ // import { openai } from "@ai-sdk/openai";
1039
+ // model: (id) => openai(id ?? "gpt-4o-mini"),
1040
+ });
1041
+
1042
+ // Flush all auto-registered components into the plugin
1043
+ registerWithPlugin(ai);
1044
+ `;
1045
+ }
1046
+ });
1047
+
1015
1048
  // src/commands/list.ts
1016
1049
  var list_exports = {};
1017
1050
  __export(list_exports, {
@@ -1066,6 +1099,7 @@ async function listCommand(typeFilter, opts) {
1066
1099
  for (const item of allItems) {
1067
1100
  const group = item.type.replace("kitn:", "");
1068
1101
  if (resolvedType && group !== resolvedType) continue;
1102
+ if (!resolvedType && group === "package") continue;
1069
1103
  if (!typeGroups.has(group)) typeGroups.set(group, []);
1070
1104
  typeGroups.get(group).push(item);
1071
1105
  }
@@ -1121,7 +1155,8 @@ async function listCommand(typeFilter, opts) {
1121
1155
  console.log(pc5.dim(`
1122
1156
  No ${resolvedType} components found.`));
1123
1157
  }
1124
- const parts = [`${installedCount} installed`, `${allItems.length - installedCount} available`];
1158
+ const totalShown = [...typeGroups.values()].reduce((sum, items) => sum + items.length, 0);
1159
+ const parts = [`${installedCount} installed`, `${totalShown - installedCount} available`];
1125
1160
  if (updateCount > 0) parts.push(pc5.yellow(`${updateCount} update${updateCount === 1 ? "" : "s"}`));
1126
1161
  console.log(`
1127
1162
  ${pc5.dim(parts.join(" \xB7 "))}
@@ -1154,7 +1189,7 @@ __export(diff_exports, {
1154
1189
  diffCommand: () => diffCommand
1155
1190
  });
1156
1191
  import * as p5 from "@clack/prompts";
1157
- import { join as join8 } from "path";
1192
+ import { join as join9 } from "path";
1158
1193
  async function diffCommand(componentName) {
1159
1194
  const cwd = process.cwd();
1160
1195
  const config = await readConfig(cwd);
@@ -1184,8 +1219,8 @@ async function diffCommand(componentName) {
1184
1219
  for (const file of registryItem.files) {
1185
1220
  if (indexItem.type === "kitn:package") {
1186
1221
  const baseDir = config.aliases.base ?? "src/ai";
1187
- const localPath = join8(cwd, baseDir, file.path);
1188
- const relativePath = join8(baseDir, file.path);
1222
+ const localPath = join9(cwd, baseDir, file.path);
1223
+ const relativePath = join9(baseDir, file.path);
1189
1224
  const localContent = await readExistingFile(localPath);
1190
1225
  if (localContent === null) {
1191
1226
  p5.log.warn(`${relativePath}: file missing locally`);
@@ -1209,7 +1244,7 @@ async function diffCommand(componentName) {
1209
1244
  return "storage";
1210
1245
  }
1211
1246
  })();
1212
- const localPath = join8(cwd, config.aliases[aliasKey], fileName);
1247
+ const localPath = join9(cwd, config.aliases[aliasKey], fileName);
1213
1248
  const localContent = await readExistingFile(localPath);
1214
1249
  if (localContent === null) {
1215
1250
  p5.log.warn(`${fileName}: file missing locally`);
@@ -1243,8 +1278,8 @@ __export(remove_exports, {
1243
1278
  });
1244
1279
  import * as p6 from "@clack/prompts";
1245
1280
  import pc6 from "picocolors";
1246
- import { join as join9, relative as relative3, dirname as dirname3 } from "path";
1247
- import { unlink, readFile as readFile7, writeFile as writeFile7 } from "fs/promises";
1281
+ import { join as join10, relative as relative3, dirname as dirname3 } from "path";
1282
+ import { unlink, readFile as readFile7, writeFile as writeFile8 } from "fs/promises";
1248
1283
  import { existsSync as existsSync2 } from "fs";
1249
1284
  async function removeCommand(componentName) {
1250
1285
  const cwd = process.cwd();
@@ -1272,15 +1307,15 @@ async function removeCommand(componentName) {
1272
1307
  const deleted = [];
1273
1308
  for (const filePath of installed.files) {
1274
1309
  try {
1275
- await unlink(join9(cwd, filePath));
1310
+ await unlink(join10(cwd, filePath));
1276
1311
  deleted.push(filePath);
1277
1312
  } catch {
1278
1313
  p6.log.warn(`Could not delete ${filePath} (may have been moved or renamed)`);
1279
1314
  }
1280
1315
  }
1281
1316
  const baseDir = config.aliases.base ?? "src/ai";
1282
- const barrelPath = join9(cwd, baseDir, "index.ts");
1283
- const barrelDir = join9(cwd, baseDir);
1317
+ const barrelPath = join10(cwd, baseDir, "index.ts");
1318
+ const barrelDir = join10(cwd, baseDir);
1284
1319
  const barrelEligibleDirs = /* @__PURE__ */ new Set([
1285
1320
  config.aliases.agents,
1286
1321
  config.aliases.tools,
@@ -1292,7 +1327,7 @@ async function removeCommand(componentName) {
1292
1327
  for (const filePath of deleted) {
1293
1328
  const fileDir = dirname3(filePath);
1294
1329
  if (!barrelEligibleDirs.has(fileDir)) continue;
1295
- const importPath = "./" + relative3(barrelDir, join9(cwd, filePath)).replace(/\\/g, "/");
1330
+ const importPath = "./" + relative3(barrelDir, join10(cwd, filePath)).replace(/\\/g, "/");
1296
1331
  const updated = removeImportFromBarrel(barrelContent, importPath);
1297
1332
  if (updated !== barrelContent) {
1298
1333
  barrelContent = updated;
@@ -1300,8 +1335,8 @@ async function removeCommand(componentName) {
1300
1335
  }
1301
1336
  }
1302
1337
  if (barrelChanged) {
1303
- await writeFile7(barrelPath, barrelContent);
1304
- p6.log.info(`Updated barrel file: ${join9(baseDir, "index.ts")}`);
1338
+ await writeFile8(barrelPath, barrelContent);
1339
+ p6.log.info(`Updated barrel file: ${join10(baseDir, "index.ts")}`);
1305
1340
  }
1306
1341
  }
1307
1342
  delete config.installed[installedKey];
@@ -1355,8 +1390,8 @@ var init_update = __esm({
1355
1390
  });
1356
1391
 
1357
1392
  // src/registry/build-output.ts
1358
- import { readdir, writeFile as writeFile8, mkdir as mkdir4, access as access4 } from "fs/promises";
1359
- import { join as join10, resolve } from "path";
1393
+ import { readdir, writeFile as writeFile9, mkdir as mkdir5, access as access4 } from "fs/promises";
1394
+ import { join as join11, resolve } from "path";
1360
1395
  async function fileExists(path) {
1361
1396
  try {
1362
1397
  await access4(path);
@@ -1373,13 +1408,13 @@ async function walkForRegistryJson(dir) {
1373
1408
  } catch {
1374
1409
  return results;
1375
1410
  }
1376
- if (await fileExists(join10(dir, "registry.json"))) {
1411
+ if (await fileExists(join11(dir, "registry.json"))) {
1377
1412
  results.push(dir);
1378
1413
  return results;
1379
1414
  }
1380
1415
  for (const entry of entries) {
1381
1416
  if (entry.isDirectory() && !SKIP_DIRS.has(entry.name)) {
1382
- const subResults = await walkForRegistryJson(join10(dir, entry.name));
1417
+ const subResults = await walkForRegistryJson(join11(dir, entry.name));
1383
1418
  results.push(...subResults);
1384
1419
  }
1385
1420
  }
@@ -1391,7 +1426,7 @@ async function scanForComponents(cwd, paths) {
1391
1426
  const results = [];
1392
1427
  for (const p12 of paths) {
1393
1428
  const absPath = resolve(resolvedCwd, p12);
1394
- if (await fileExists(join10(absPath, "registry.json"))) {
1429
+ if (await fileExists(join11(absPath, "registry.json"))) {
1395
1430
  results.push(absPath);
1396
1431
  continue;
1397
1432
  }
@@ -1403,8 +1438,8 @@ async function scanForComponents(cwd, paths) {
1403
1438
  }
1404
1439
  for (const entry of entries) {
1405
1440
  if (entry.isDirectory()) {
1406
- const subDir = join10(absPath, entry.name);
1407
- if (await fileExists(join10(subDir, "registry.json"))) {
1441
+ const subDir = join11(absPath, entry.name);
1442
+ if (await fileExists(join11(subDir, "registry.json"))) {
1408
1443
  results.push(subDir);
1409
1444
  }
1410
1445
  }
@@ -1429,21 +1464,21 @@ async function writeRegistryOutput(outputDir, items) {
1429
1464
  const indexItems = [];
1430
1465
  for (const item of items) {
1431
1466
  const dir = typeToDir[item.type];
1432
- const typeDir = join10(resolvedOutput, dir);
1433
- await mkdir4(typeDir, { recursive: true });
1467
+ const typeDir = join11(resolvedOutput, dir);
1468
+ await mkdir5(typeDir, { recursive: true });
1434
1469
  const itemJson = JSON.stringify(item, null, 2);
1435
- const latestPath = join10(typeDir, `${item.name}.json`);
1470
+ const latestPath = join11(typeDir, `${item.name}.json`);
1436
1471
  const latestRelative = `${dir}/${item.name}.json`;
1437
- await writeFile8(latestPath, itemJson, "utf-8");
1472
+ await writeFile9(latestPath, itemJson, "utf-8");
1438
1473
  written.push(latestRelative);
1439
1474
  if (item.version) {
1440
1475
  const versionedFilename = `${item.name}@${item.version}.json`;
1441
- const versionedPath = join10(typeDir, versionedFilename);
1476
+ const versionedPath = join11(typeDir, versionedFilename);
1442
1477
  const versionedRelative = `${dir}/${versionedFilename}`;
1443
1478
  if (await fileExists(versionedPath)) {
1444
1479
  skipped.push(versionedRelative);
1445
1480
  } else {
1446
- await writeFile8(versionedPath, itemJson, "utf-8");
1481
+ await writeFile9(versionedPath, itemJson, "utf-8");
1447
1482
  written.push(versionedRelative);
1448
1483
  }
1449
1484
  }
@@ -1478,8 +1513,8 @@ async function writeRegistryOutput(outputDir, items) {
1478
1513
  version: "1",
1479
1514
  items: indexItems
1480
1515
  };
1481
- const indexPath = join10(resolvedOutput, "registry.json");
1482
- await writeFile8(indexPath, JSON.stringify(index, null, 2), "utf-8");
1516
+ const indexPath = join11(resolvedOutput, "registry.json");
1517
+ await writeFile9(indexPath, JSON.stringify(index, null, 2), "utf-8");
1483
1518
  written.push("registry.json");
1484
1519
  return { written, skipped };
1485
1520
  }
@@ -1502,7 +1537,7 @@ var init_build_output = __esm({
1502
1537
 
1503
1538
  // src/registry/builder.ts
1504
1539
  import { readFile as readFile9, readdir as readdir2 } from "fs/promises";
1505
- import { join as join11, relative as relative5 } from "path";
1540
+ import { join as join12, relative as relative5 } from "path";
1506
1541
  function isExcludedDevDep(name) {
1507
1542
  return EXCLUDED_DEV_DEPS.has(name) || name.startsWith("@types/");
1508
1543
  }
@@ -1514,7 +1549,7 @@ async function readTsFiles(dir, baseDir, exclude) {
1514
1549
  const results = [];
1515
1550
  const entries = await readdir2(dir, { withFileTypes: true });
1516
1551
  for (const entry of entries) {
1517
- const fullPath = join11(dir, entry.name);
1552
+ const fullPath = join12(dir, entry.name);
1518
1553
  const relPath = relative5(baseDir, fullPath);
1519
1554
  if (entry.isDirectory()) {
1520
1555
  const nested = await readTsFiles(fullPath, baseDir, exclude);
@@ -1532,7 +1567,7 @@ async function readTsFiles(dir, baseDir, exclude) {
1532
1567
  async function buildComponent(componentDir) {
1533
1568
  let rawConfig;
1534
1569
  try {
1535
- rawConfig = await readFile9(join11(componentDir, "registry.json"), "utf-8");
1570
+ rawConfig = await readFile9(join12(componentDir, "registry.json"), "utf-8");
1536
1571
  } catch {
1537
1572
  throw new Error(
1538
1573
  `No registry.json found in ${componentDir}. Every component must have a registry.json file.`
@@ -1548,7 +1583,7 @@ async function buildComponent(componentDir) {
1548
1583
  }
1549
1584
  let pkg = null;
1550
1585
  try {
1551
- const rawPkg = await readFile9(join11(componentDir, "package.json"), "utf-8");
1586
+ const rawPkg = await readFile9(join12(componentDir, "package.json"), "utf-8");
1552
1587
  pkg = JSON.parse(rawPkg);
1553
1588
  } catch {
1554
1589
  }
@@ -1605,7 +1640,7 @@ async function buildComponent(componentDir) {
1605
1640
  let files;
1606
1641
  if (isPackage) {
1607
1642
  const sourceDir = config.sourceDir ?? "src";
1608
- const sourcePath = join11(componentDir, sourceDir);
1643
+ const sourcePath = join12(componentDir, sourceDir);
1609
1644
  const exclude = config.exclude ?? [];
1610
1645
  let tsFiles;
1611
1646
  try {
@@ -1628,7 +1663,7 @@ async function buildComponent(componentDir) {
1628
1663
  }
1629
1664
  files = await Promise.all(
1630
1665
  config.files.map(async (filePath) => {
1631
- const fullPath = join11(componentDir, filePath);
1666
+ const fullPath = join12(componentDir, filePath);
1632
1667
  let content;
1633
1668
  try {
1634
1669
  content = await readFile9(fullPath, "utf-8");
@@ -1764,8 +1799,8 @@ __export(create_exports, {
1764
1799
  });
1765
1800
  import * as p9 from "@clack/prompts";
1766
1801
  import pc8 from "picocolors";
1767
- import { join as join12 } from "path";
1768
- import { mkdir as mkdir5, writeFile as writeFile9 } from "fs/promises";
1802
+ import { join as join13 } from "path";
1803
+ import { mkdir as mkdir6, writeFile as writeFile10 } from "fs/promises";
1769
1804
  function toCamelCase(str) {
1770
1805
  return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
1771
1806
  }
@@ -1791,7 +1826,7 @@ function generateRegistryJson(type, name, sourceFile) {
1791
1826
  }
1792
1827
  function generateAgentSource(name) {
1793
1828
  const camel = toCamelCase(name);
1794
- return `import { registerAgent } from "@kitnai/core";
1829
+ return `import { registerAgent } from "@kitn/core";
1795
1830
 
1796
1831
  const SYSTEM_PROMPT = "You are a helpful assistant.";
1797
1832
 
@@ -1805,7 +1840,7 @@ registerAgent({
1805
1840
  }
1806
1841
  function generateToolSource(name) {
1807
1842
  const camel = toCamelCase(name);
1808
- return `import { registerTool } from "@kitnai/core";
1843
+ return `import { registerTool } from "@kitn/core";
1809
1844
  import { tool } from "ai";
1810
1845
  import { z } from "zod";
1811
1846
 
@@ -1842,7 +1877,7 @@ Describe what this skill does and how to use it.
1842
1877
  }
1843
1878
  function generateStorageSource(name) {
1844
1879
  const camel = toCamelCase("create-" + name);
1845
- return `import type { StorageProvider } from "@kitnai/core";
1880
+ return `import type { StorageProvider } from "@kitn/core";
1846
1881
 
1847
1882
  export function ${camel}(config?: Record<string, unknown>): StorageProvider {
1848
1883
  // TODO: implement storage provider
@@ -1866,16 +1901,16 @@ async function createComponent(type, name, opts) {
1866
1901
  );
1867
1902
  }
1868
1903
  const cwd = opts?.cwd ?? process.cwd();
1869
- const dir = join12(cwd, name);
1904
+ const dir = join13(cwd, name);
1870
1905
  if (await dirExists(dir)) {
1871
1906
  throw new Error(`Directory "${name}" already exists`);
1872
1907
  }
1873
- await mkdir5(dir, { recursive: true });
1908
+ await mkdir6(dir, { recursive: true });
1874
1909
  const validType = type;
1875
1910
  const sourceFile = validType === "skill" ? "README.md" : `${name}.ts`;
1876
1911
  const registryJson = generateRegistryJson(validType, name, sourceFile);
1877
- await writeFile9(
1878
- join12(dir, "registry.json"),
1912
+ await writeFile10(
1913
+ join13(dir, "registry.json"),
1879
1914
  JSON.stringify(registryJson, null, 2) + "\n"
1880
1915
  );
1881
1916
  let source;
@@ -1893,7 +1928,7 @@ async function createComponent(type, name, opts) {
1893
1928
  source = generateStorageSource(name);
1894
1929
  break;
1895
1930
  }
1896
- await writeFile9(join12(dir, sourceFile), source);
1931
+ await writeFile10(join13(dir, sourceFile), source);
1897
1932
  return { dir, files: ["registry.json", sourceFile] };
1898
1933
  }
1899
1934
  async function createCommand(type, name) {
@@ -2063,10 +2098,19 @@ async function registryAddCommand(namespace, url, opts = {}) {
2063
2098
  if (config.registries[namespace] && !opts.overwrite) {
2064
2099
  throw new Error(`Registry '${namespace}' is already configured. Use --overwrite to replace.`);
2065
2100
  }
2066
- config.registries[namespace] = url;
2101
+ if (opts.homepage || opts.description) {
2102
+ const entry = { url };
2103
+ if (opts.homepage) entry.homepage = opts.homepage;
2104
+ if (opts.description) entry.description = opts.description;
2105
+ config.registries[namespace] = entry;
2106
+ } else {
2107
+ config.registries[namespace] = url;
2108
+ }
2067
2109
  await writeConfig(cwd, config);
2068
2110
  p11.log.success(`Added registry ${pc10.bold(namespace)}`);
2069
2111
  p11.log.message(pc10.dim(` ${url}`));
2112
+ if (opts.homepage) p11.log.message(pc10.dim(` Homepage: ${opts.homepage}`));
2113
+ if (opts.description) p11.log.message(pc10.dim(` ${opts.description}`));
2070
2114
  }
2071
2115
  async function registryRemoveCommand(namespace, opts = {}) {
2072
2116
  const cwd = opts.cwd ?? process.cwd();
@@ -2101,12 +2145,19 @@ async function registryListCommand(opts = {}) {
2101
2145
  const cwd = opts.cwd ?? process.cwd();
2102
2146
  const config = await readConfig(cwd);
2103
2147
  if (!config) throw new Error("No kitn.json found. Run `kitn init` first.");
2104
- const entries = Object.entries(config.registries).map(([namespace, url]) => ({ namespace, url }));
2148
+ const entries = Object.entries(config.registries).map(([namespace, value]) => {
2149
+ const url = getRegistryUrl(value);
2150
+ const homepage = typeof value === "object" ? value.homepage : void 0;
2151
+ const description = typeof value === "object" ? value.description : void 0;
2152
+ return { namespace, url, homepage, description };
2153
+ });
2105
2154
  if (entries.length === 0) {
2106
2155
  p11.log.message(pc10.dim(" No registries configured."));
2107
2156
  } else {
2108
- for (const { namespace, url } of entries) {
2157
+ for (const { namespace, url, homepage, description } of entries) {
2109
2158
  p11.log.message(` ${pc10.bold(namespace.padEnd(16))} ${pc10.dim(url)}`);
2159
+ if (description) p11.log.message(` ${" ".repeat(16)} ${description}`);
2160
+ if (homepage) p11.log.message(` ${" ".repeat(16)} ${pc10.dim(homepage)}`);
2110
2161
  }
2111
2162
  }
2112
2163
  return entries;
@@ -2196,7 +2247,7 @@ function startUpdateCheck(currentVersion) {
2196
2247
  }
2197
2248
 
2198
2249
  // src/index.ts
2199
- var VERSION = true ? "0.1.10" : "0.0.0-dev";
2250
+ var VERSION = true ? "0.1.12" : "0.0.0-dev";
2200
2251
  var printUpdateNotice = startUpdateCheck(VERSION);
2201
2252
  var program = new Command().name("kitn").description("Install AI agent components from the kitn registry").version(VERSION);
2202
2253
  program.command("init").description("Initialize kitn in your project").action(async () => {
@@ -2236,7 +2287,7 @@ program.command("info").description("Show details about a component").argument("
2236
2287
  await infoCommand2(component);
2237
2288
  });
2238
2289
  var registry = program.command("registry").description("Manage component registries");
2239
- registry.command("add").description("Add a component registry").argument("<namespace>", "registry namespace (e.g. @myteam)").argument("<url>", "URL template with {type} and {name} placeholders").option("-o, --overwrite", "overwrite if namespace already exists").action(async (namespace, url, opts) => {
2290
+ registry.command("add").description("Add a component registry").argument("<namespace>", "registry namespace (e.g. @myteam)").argument("<url>", "URL template with {type} and {name} placeholders").option("-o, --overwrite", "overwrite if namespace already exists").option("--homepage <url>", "registry homepage URL").option("--description <text>", "short description of the registry").action(async (namespace, url, opts) => {
2240
2291
  const { registryAddCommand: registryAddCommand2 } = await Promise.resolve().then(() => (init_registry(), registry_exports));
2241
2292
  await registryAddCommand2(namespace, url, opts);
2242
2293
  });