@kitnai/cli 0.1.0 → 0.1.3

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
@@ -29,8 +29,9 @@ var componentType, installedComponentSchema, configSchema, CONFIG_FILE;
29
29
  var init_config = __esm({
30
30
  "src/utils/config.ts"() {
31
31
  "use strict";
32
- componentType = z.enum(["kitn:agent", "kitn:tool", "kitn:skill", "kitn:storage"]);
32
+ componentType = z.enum(["kitn:agent", "kitn:tool", "kitn:skill", "kitn:storage", "kitn:package"]);
33
33
  installedComponentSchema = z.object({
34
+ registry: z.string().optional(),
34
35
  version: z.string(),
35
36
  installedAt: z.string(),
36
37
  files: z.array(z.string()),
@@ -39,71 +40,21 @@ var init_config = __esm({
39
40
  configSchema = z.object({
40
41
  $schema: z.string().optional(),
41
42
  runtime: z.enum(["bun", "node", "deno"]),
43
+ framework: z.enum(["hono", "cloudflare", "elysia", "fastify", "express"]).optional(),
42
44
  aliases: z.object({
45
+ base: z.string().optional(),
43
46
  agents: z.string(),
44
47
  tools: z.string(),
45
48
  skills: z.string(),
46
49
  storage: z.string()
47
50
  }),
48
51
  registries: z.record(z.string(), z.string()),
49
- _installed: z.record(z.string(), installedComponentSchema).optional()
52
+ installed: z.record(z.string(), installedComponentSchema).optional()
50
53
  });
51
54
  CONFIG_FILE = "kitn.json";
52
55
  }
53
56
  });
54
57
 
55
- // src/utils/detect.ts
56
- import { access } from "fs/promises";
57
- import { join as join2 } from "path";
58
- async function detectPackageManager(dir) {
59
- for (const [lockfile, pm] of LOCKFILE_MAP) {
60
- try {
61
- await access(join2(dir, lockfile));
62
- return pm;
63
- } catch {
64
- }
65
- }
66
- return null;
67
- }
68
- var LOCKFILE_MAP;
69
- var init_detect = __esm({
70
- "src/utils/detect.ts"() {
71
- "use strict";
72
- LOCKFILE_MAP = [
73
- ["bun.lock", "bun"],
74
- ["bun.lockb", "bun"],
75
- ["pnpm-lock.yaml", "pnpm"],
76
- ["yarn.lock", "yarn"],
77
- ["package-lock.json", "npm"]
78
- ];
79
- }
80
- });
81
-
82
- // src/installers/dep-installer.ts
83
- import { execSync } from "child_process";
84
- function installDependencies(pm, deps, projectDir) {
85
- if (deps.length === 0) return;
86
- const pkgs = deps.join(" ");
87
- const cmd = (() => {
88
- switch (pm) {
89
- case "bun":
90
- return `bun add ${pkgs}`;
91
- case "pnpm":
92
- return `pnpm add ${pkgs}`;
93
- case "yarn":
94
- return `yarn add ${pkgs}`;
95
- case "npm":
96
- return `npm install ${pkgs}`;
97
- }
98
- })();
99
- execSync(cmd, { cwd: projectDir, stdio: "pipe" });
100
- }
101
- var init_dep_installer = __esm({
102
- "src/installers/dep-installer.ts"() {
103
- "use strict";
104
- }
105
- });
106
-
107
58
  // src/commands/init.ts
108
59
  var init_exports = {};
109
60
  __export(init_exports, {
@@ -138,74 +89,81 @@ async function initCommand() {
138
89
  p.cancel("Init cancelled.");
139
90
  process.exit(0);
140
91
  }
141
- const aliases = await p.group({
142
- agents: () => p.text({
143
- message: "Where should agents be installed?",
144
- initialValue: "src/agents",
145
- placeholder: "src/agents"
146
- }),
147
- tools: () => p.text({
148
- message: "Where should tools be installed?",
149
- initialValue: "src/tools",
150
- placeholder: "src/tools"
151
- }),
152
- skills: () => p.text({
153
- message: "Where should skills be installed?",
154
- initialValue: "src/skills",
155
- placeholder: "src/skills"
156
- }),
157
- storage: () => p.text({
158
- message: "Where should storage adapters be installed?",
159
- initialValue: "src/storage",
160
- placeholder: "src/storage"
161
- })
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);
105
+ }
106
+ const base = await p.text({
107
+ message: "Where should kitn packages be installed?",
108
+ initialValue: "src/ai",
109
+ placeholder: "src/ai"
162
110
  });
163
- if (p.isCancel(aliases)) {
111
+ if (p.isCancel(base)) {
164
112
  p.cancel("Init cancelled.");
165
113
  process.exit(0);
166
114
  }
115
+ const baseDir = base;
167
116
  const config = {
168
117
  runtime,
118
+ framework,
169
119
  aliases: {
170
- agents: aliases.agents,
171
- tools: aliases.tools,
172
- skills: aliases.skills,
173
- storage: aliases.storage
120
+ base: baseDir,
121
+ agents: `${baseDir}/agents`,
122
+ tools: `${baseDir}/tools`,
123
+ skills: `${baseDir}/skills`,
124
+ storage: `${baseDir}/storage`
174
125
  },
175
126
  registries: {
176
- "@kitn": "https://kitn-ai.github.io/kitn/r/{type}/{name}.json"
127
+ "@kitn": "https://kitn-ai.github.io/registry/r/{type}/{name}.json"
177
128
  }
178
129
  };
179
130
  const s = p.spinner();
180
131
  s.start("Writing kitn.json");
181
132
  await writeConfig(cwd, config);
182
133
  s.stop("Created kitn.json");
183
- const pm = await detectPackageManager(cwd);
184
- if (pm) {
185
- const shouldInstall = await p.confirm({
186
- message: `Install @kitnai/server using ${pm}?`,
187
- initialValue: true
188
- });
189
- if (!p.isCancel(shouldInstall) && shouldInstall) {
190
- s.start("Installing @kitnai/server...");
191
- try {
192
- installDependencies(pm, ["@kitnai/server"], cwd);
193
- s.stop("Installed @kitnai/server");
194
- } catch {
195
- s.stop(pc.yellow("Failed to install @kitnai/server \u2014 you can install it manually"));
196
- }
197
- }
198
- } else {
199
- p.log.info("No package manager detected. Install @kitnai/server manually.");
200
- }
201
- p.outro(pc.green("Done! Run `kitn add <component>` to add your first component."));
134
+ p.outro(pc.green("Done! Run `kitn add core` to install the engine, then `kitn add routes` for HTTP routes."));
202
135
  }
203
136
  var init_init = __esm({
204
137
  "src/commands/init.ts"() {
205
138
  "use strict";
206
139
  init_config();
207
- init_detect();
208
- init_dep_installer();
140
+ }
141
+ });
142
+
143
+ // src/utils/detect.ts
144
+ import { access } from "fs/promises";
145
+ import { join as join2 } from "path";
146
+ async function detectPackageManager(dir) {
147
+ for (const [lockfile, pm] of LOCKFILE_MAP) {
148
+ try {
149
+ await access(join2(dir, lockfile));
150
+ return pm;
151
+ } catch {
152
+ }
153
+ }
154
+ return null;
155
+ }
156
+ var LOCKFILE_MAP;
157
+ var init_detect = __esm({
158
+ "src/utils/detect.ts"() {
159
+ "use strict";
160
+ LOCKFILE_MAP = [
161
+ ["bun.lock", "bun"],
162
+ ["bun.lockb", "bun"],
163
+ ["pnpm-lock.yaml", "pnpm"],
164
+ ["yarn.lock", "yarn"],
165
+ ["package-lock.json", "npm"]
166
+ ];
209
167
  }
210
168
  });
211
169
 
@@ -222,21 +180,22 @@ var init_fetcher = __esm({
222
180
  this.registries = registries;
223
181
  this.fetchFn = fetchFn ?? this.defaultFetch;
224
182
  }
225
- resolveUrl(name, typeDir) {
226
- const template = this.registries["@kitn"];
227
- if (!template) throw new Error("No @kitn registry configured");
228
- return template.replace("{name}", name).replace("{type}", typeDir);
183
+ resolveUrl(name, typeDir, namespace = "@kitn", version) {
184
+ const template = this.registries[namespace];
185
+ if (!template) throw new Error(`No registry configured for ${namespace}`);
186
+ const fileName = version ? `${name}@${version}` : name;
187
+ return template.replace("{name}", fileName).replace("{type}", typeDir);
229
188
  }
230
- async fetchItem(name, typeDir) {
231
- const url = this.resolveUrl(name, typeDir);
189
+ async fetchItem(name, typeDir, namespace = "@kitn", version) {
190
+ const url = this.resolveUrl(name, typeDir, namespace, version);
232
191
  if (!this.cache.has(url)) {
233
192
  this.cache.set(url, this.fetchFn(url));
234
193
  }
235
194
  return this.cache.get(url);
236
195
  }
237
- async fetchIndex() {
238
- const template = this.registries["@kitn"];
239
- if (!template) throw new Error("No @kitn registry configured");
196
+ async fetchIndex(namespace = "@kitn") {
197
+ const template = this.registries[namespace];
198
+ if (!template) throw new Error(`No registry configured for ${namespace}`);
240
199
  const baseUrl = template.replace("{type}/{name}.json", "registry.json");
241
200
  const res = await fetch(baseUrl);
242
201
  if (!res.ok) throw new Error(`Failed to fetch registry index: ${res.statusText}`);
@@ -342,6 +301,31 @@ var init_file_writer = __esm({
342
301
  }
343
302
  });
344
303
 
304
+ // src/installers/dep-installer.ts
305
+ import { execSync } from "child_process";
306
+ function installDependencies(pm, deps, projectDir) {
307
+ if (deps.length === 0) return;
308
+ const pkgs = deps.join(" ");
309
+ const cmd = (() => {
310
+ switch (pm) {
311
+ case "bun":
312
+ return `bun add ${pkgs}`;
313
+ case "pnpm":
314
+ return `pnpm add ${pkgs}`;
315
+ case "yarn":
316
+ return `yarn add ${pkgs}`;
317
+ case "npm":
318
+ return `npm install ${pkgs}`;
319
+ }
320
+ })();
321
+ execSync(cmd, { cwd: projectDir, stdio: "pipe" });
322
+ }
323
+ var init_dep_installer = __esm({
324
+ "src/installers/dep-installer.ts"() {
325
+ "use strict";
326
+ }
327
+ });
328
+
345
329
  // src/installers/env-checker.ts
346
330
  import pc2 from "picocolors";
347
331
  function checkEnvVars(envVars) {
@@ -359,6 +343,79 @@ var init_env_checker = __esm({
359
343
  }
360
344
  });
361
345
 
346
+ // src/installers/import-rewriter.ts
347
+ import { relative, join as join3 } from "path";
348
+ function rewriteKitnImports(content, fileType, fileName, aliases) {
349
+ const sourceAliasKey = TYPE_TO_ALIAS_KEY[fileType];
350
+ if (!sourceAliasKey) return content;
351
+ const sourceDir = aliases[sourceAliasKey];
352
+ return content.replace(
353
+ /((?:import|export)\s+.*?\s+from\s+["'])@kitn\/([\w-]+)\/([^"']+)(["'])/g,
354
+ (_match, prefix, type, targetPath, quote) => {
355
+ if (!KNOWN_TYPES.includes(type)) {
356
+ return `${prefix}@kitn/${type}/${targetPath}${quote}`;
357
+ }
358
+ const targetDir = aliases[type];
359
+ const targetFile = join3(targetDir, targetPath);
360
+ let rel = relative(sourceDir, targetFile);
361
+ rel = rel.split("\\").join("/");
362
+ if (!rel.startsWith(".")) {
363
+ rel = `./${rel}`;
364
+ }
365
+ return `${prefix}${rel}${quote}`;
366
+ }
367
+ );
368
+ }
369
+ var KNOWN_TYPES, TYPE_TO_ALIAS_KEY;
370
+ var init_import_rewriter = __esm({
371
+ "src/installers/import-rewriter.ts"() {
372
+ "use strict";
373
+ KNOWN_TYPES = ["agents", "tools", "skills", "storage"];
374
+ TYPE_TO_ALIAS_KEY = {
375
+ "kitn:agent": "agents",
376
+ "kitn:tool": "tools",
377
+ "kitn:skill": "skills",
378
+ "kitn:storage": "storage"
379
+ };
380
+ }
381
+ });
382
+
383
+ // src/installers/tsconfig-patcher.ts
384
+ import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
385
+ import { join as join4 } from "path";
386
+ function stripJsonc(text2) {
387
+ return text2.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/,\s*([}\]])/g, "$1");
388
+ }
389
+ function patchTsconfig(tsconfigContent, paths) {
390
+ const config = JSON.parse(stripJsonc(tsconfigContent));
391
+ if (!config.compilerOptions) {
392
+ config.compilerOptions = {};
393
+ }
394
+ if (!config.compilerOptions.paths) {
395
+ config.compilerOptions.paths = {};
396
+ }
397
+ for (const [key, value] of Object.entries(paths)) {
398
+ config.compilerOptions.paths[key] = value;
399
+ }
400
+ return JSON.stringify(config, null, 2) + "\n";
401
+ }
402
+ async function patchProjectTsconfig(projectDir, paths) {
403
+ const tsconfigPath = join4(projectDir, "tsconfig.json");
404
+ let content;
405
+ try {
406
+ content = await readFile3(tsconfigPath, "utf-8");
407
+ } catch {
408
+ content = "{}";
409
+ }
410
+ const patched = patchTsconfig(content, paths);
411
+ await writeFile3(tsconfigPath, patched);
412
+ }
413
+ var init_tsconfig_patcher = __esm({
414
+ "src/installers/tsconfig-patcher.ts"() {
415
+ "use strict";
416
+ }
417
+ });
418
+
362
419
  // src/utils/hash.ts
363
420
  import { createHash } from "crypto";
364
421
  function contentHash(content) {
@@ -370,18 +427,52 @@ var init_hash = __esm({
370
427
  }
371
428
  });
372
429
 
430
+ // src/utils/parse-ref.ts
431
+ function parseComponentRef(input) {
432
+ let namespace = "@kitn";
433
+ let rest = input;
434
+ if (rest.startsWith("@")) {
435
+ const slashIdx = rest.indexOf("/");
436
+ if (slashIdx === -1) {
437
+ throw new Error(`Invalid component reference: ${input}. Expected @namespace/name`);
438
+ }
439
+ namespace = rest.slice(0, slashIdx);
440
+ rest = rest.slice(slashIdx + 1);
441
+ }
442
+ const atIdx = rest.indexOf("@");
443
+ if (atIdx === -1) {
444
+ return { namespace, name: rest, version: void 0 };
445
+ }
446
+ return {
447
+ namespace,
448
+ name: rest.slice(0, atIdx),
449
+ version: rest.slice(atIdx + 1)
450
+ };
451
+ }
452
+ var init_parse_ref = __esm({
453
+ "src/utils/parse-ref.ts"() {
454
+ "use strict";
455
+ }
456
+ });
457
+
373
458
  // src/registry/schema.ts
374
459
  import { z as z2 } from "zod";
375
- var componentType2, registryFileSchema, registryItemSchema, registryIndexItemSchema, registryIndexSchema, typeToDir;
460
+ var componentType2, registryFileSchema, changelogEntrySchema, registryItemSchema, registryIndexItemSchema, registryIndexSchema, typeToDir;
376
461
  var init_schema = __esm({
377
462
  "src/registry/schema.ts"() {
378
463
  "use strict";
379
- componentType2 = z2.enum(["kitn:agent", "kitn:tool", "kitn:skill", "kitn:storage"]);
464
+ componentType2 = z2.enum(["kitn:agent", "kitn:tool", "kitn:skill", "kitn:storage", "kitn:package"]);
380
465
  registryFileSchema = z2.object({
381
466
  path: z2.string(),
382
467
  content: z2.string(),
383
468
  type: componentType2
384
469
  });
470
+ changelogEntrySchema = z2.object({
471
+ version: z2.string(),
472
+ date: z2.string(),
473
+ type: z2.enum(["feature", "fix", "breaking", "initial"]),
474
+ note: z2.string()
475
+ });
385
476
  registryItemSchema = z2.object({
386
477
  $schema: z2.string().optional(),
387
478
  name: z2.string(),
@@ -392,9 +483,13 @@ var init_schema = __esm({
392
483
  registryDependencies: z2.array(z2.string()).optional(),
393
484
  envVars: z2.record(z2.string(), z2.string()).optional(),
394
485
  files: z2.array(registryFileSchema),
486
+ installDir: z2.string().optional(),
487
+ tsconfig: z2.record(z2.string(), z2.array(z2.string())).optional(),
395
488
  docs: z2.string().optional(),
396
489
  categories: z2.array(z2.string()).optional(),
397
- version: z2.string().optional()
490
+ version: z2.string().optional(),
491
+ updatedAt: z2.string().optional(),
492
+ changelog: z2.array(changelogEntrySchema).optional()
398
493
  });
399
494
  registryIndexItemSchema = z2.object({
400
495
  name: z2.string(),
@@ -402,7 +497,9 @@ var init_schema = __esm({
402
497
  description: z2.string(),
403
498
  registryDependencies: z2.array(z2.string()).optional(),
404
499
  categories: z2.array(z2.string()).optional(),
405
- version: z2.string().optional()
500
+ version: z2.string().optional(),
501
+ versions: z2.array(z2.string()).optional(),
502
+ updatedAt: z2.string().optional()
406
503
  });
407
504
  registryIndexSchema = z2.object({
408
505
  $schema: z2.string().optional(),
@@ -413,7 +510,8 @@ var init_schema = __esm({
413
510
  "kitn:agent": "agents",
414
511
  "kitn:tool": "tools",
415
512
  "kitn:skill": "skills",
416
- "kitn:storage": "storage"
513
+ "kitn:storage": "storage",
514
+ "kitn:package": "package"
417
515
  };
418
516
  }
419
517
  });
@@ -425,7 +523,7 @@ __export(add_exports, {
425
523
  });
426
524
  import * as p2 from "@clack/prompts";
427
525
  import pc3 from "picocolors";
428
- import { join as join3 } from "path";
526
+ import { join as join5 } from "path";
429
527
  async function addCommand(components, opts) {
430
528
  p2.intro(pc3.bgCyan(pc3.black(" kitn add ")));
431
529
  const cwd = process.cwd();
@@ -438,17 +536,26 @@ async function addCommand(components, opts) {
438
536
  p2.log.error("Please specify at least one component to add.");
439
537
  process.exit(1);
440
538
  }
539
+ const resolvedComponents = components.map((c) => {
540
+ if (c === "routes") {
541
+ const fw = config.framework ?? "hono";
542
+ return fw;
543
+ }
544
+ return c;
545
+ });
546
+ const refs = resolvedComponents.map(parseComponentRef);
441
547
  const fetcher = new RegistryFetcher(config.registries);
442
548
  const s = p2.spinner();
443
549
  s.start("Resolving dependencies...");
444
550
  let resolved;
445
551
  try {
446
- resolved = await resolveDependencies(components, async (name) => {
447
- const index = await fetcher.fetchIndex();
552
+ resolved = await resolveDependencies(resolvedComponents, async (name) => {
553
+ const ref = refs.find((r) => r.name === name) ?? { namespace: "@kitn", name, version: void 0 };
554
+ const index = await fetcher.fetchIndex(ref.namespace);
448
555
  const indexItem = index.items.find((i) => i.name === name);
449
- if (!indexItem) throw new Error(`Component '${name}' not found in registry`);
556
+ if (!indexItem) throw new Error(`Component '${name}' not found in ${ref.namespace} registry`);
450
557
  const dir = typeToDir[indexItem.type];
451
- return fetcher.fetchItem(name, dir);
558
+ return fetcher.fetchItem(name, dir, ref.namespace, ref.version);
452
559
  });
453
560
  } catch (err) {
454
561
  s.stop(pc3.red("Failed to resolve dependencies"));
@@ -458,7 +565,7 @@ async function addCommand(components, opts) {
458
565
  s.stop(`Resolved ${resolved.length} component(s)`);
459
566
  p2.log.info("Components to install:");
460
567
  for (const item of resolved) {
461
- const isExplicit = components.includes(item.name);
568
+ const isExplicit = resolvedComponents.includes(item.name) || components.includes(item.name);
462
569
  const label = isExplicit ? item.name : `${item.name} ${pc3.dim("(dependency)")}`;
463
570
  p2.log.message(` ${pc3.cyan(label)}`);
464
571
  }
@@ -472,62 +579,68 @@ async function addCommand(components, opts) {
472
579
  if (item.envVars) {
473
580
  allEnvWarnings.push(...checkEnvVars(item.envVars));
474
581
  }
475
- for (const file of item.files) {
476
- const aliasKey = (() => {
477
- switch (item.type) {
478
- case "kitn:agent":
479
- return "agents";
480
- case "kitn:tool":
481
- return "tools";
482
- case "kitn:skill":
483
- return "skills";
484
- case "kitn:storage":
485
- return "storage";
486
- }
487
- })();
488
- const fileName = file.path.split("/").pop();
489
- const targetPath = join3(cwd, config.aliases[aliasKey], fileName);
490
- const relativePath = join3(config.aliases[aliasKey], fileName);
491
- const status = await checkFileStatus(targetPath, file.content);
492
- switch (status) {
493
- case "new" /* New */:
494
- await writeComponentFile(targetPath, file.content);
495
- created.push(relativePath);
496
- break;
497
- case "identical" /* Identical */:
498
- skipped.push(relativePath);
499
- break;
500
- case "different" /* Different */:
501
- if (opts.overwrite) {
582
+ if (item.type === "kitn:package") {
583
+ const baseDir = config.aliases.base ?? "src/ai";
584
+ for (const file of item.files) {
585
+ const targetPath = join5(cwd, baseDir, file.path);
586
+ const relativePath = join5(baseDir, file.path);
587
+ const status = await checkFileStatus(targetPath, file.content);
588
+ switch (status) {
589
+ case "new" /* New */:
502
590
  await writeComponentFile(targetPath, file.content);
503
- updated.push(relativePath);
504
- } else {
505
- const existing = await readExistingFile(targetPath);
506
- const diff = generateDiff(relativePath, existing ?? "", file.content);
507
- p2.log.message(pc3.dim(diff));
508
- const action = await p2.select({
509
- message: `${relativePath} already exists and differs. What to do?`,
510
- options: [
511
- { value: "skip", label: "Keep local version" },
512
- { value: "overwrite", label: "Overwrite with registry version" }
513
- ]
514
- });
515
- if (!p2.isCancel(action) && action === "overwrite") {
591
+ created.push(relativePath);
592
+ break;
593
+ case "identical" /* Identical */:
594
+ skipped.push(relativePath);
595
+ break;
596
+ case "different" /* Different */:
597
+ if (opts.overwrite) {
516
598
  await writeComponentFile(targetPath, file.content);
517
599
  updated.push(relativePath);
518
600
  } else {
519
- skipped.push(relativePath);
601
+ const existing = await readExistingFile(targetPath);
602
+ const diff = generateDiff(relativePath, existing ?? "", file.content);
603
+ p2.log.message(pc3.dim(diff));
604
+ const action = await p2.select({
605
+ message: `${relativePath} already exists and differs. What to do?`,
606
+ options: [
607
+ { value: "skip", label: "Keep local version" },
608
+ { value: "overwrite", label: "Overwrite with registry version" }
609
+ ]
610
+ });
611
+ if (!p2.isCancel(action) && action === "overwrite") {
612
+ await writeComponentFile(targetPath, file.content);
613
+ updated.push(relativePath);
614
+ } else {
615
+ skipped.push(relativePath);
616
+ }
520
617
  }
521
- }
522
- break;
618
+ break;
619
+ }
523
620
  }
524
- }
525
- const installed = config._installed ?? {};
526
- const allContent = item.files.map((f) => f.content).join("\n");
527
- installed[item.name] = {
528
- version: item.version ?? "1.0.0",
529
- installedAt: (/* @__PURE__ */ new Date()).toISOString(),
530
- files: item.files.map((f) => {
621
+ if (item.tsconfig) {
622
+ const resolvedPaths = {};
623
+ const installDir = item.installDir ?? item.name;
624
+ for (const [key, values] of Object.entries(item.tsconfig)) {
625
+ resolvedPaths[key] = values.map((v) => `./${join5(baseDir, installDir, v)}`);
626
+ }
627
+ await patchProjectTsconfig(cwd, resolvedPaths);
628
+ p2.log.info(`Patched tsconfig.json with paths: ${Object.keys(resolvedPaths).join(", ")}`);
629
+ }
630
+ const installed = config.installed ?? {};
631
+ const allContent = item.files.map((f) => f.content).join("\n");
632
+ const ref = refs.find((r) => r.name === item.name) ?? { namespace: "@kitn", name: item.name, version: void 0 };
633
+ const installedKey = ref.namespace === "@kitn" ? item.name : `${ref.namespace}/${item.name}`;
634
+ installed[installedKey] = {
635
+ registry: ref.namespace,
636
+ version: item.version ?? "1.0.0",
637
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
638
+ files: item.files.map((f) => join5(baseDir, f.path)),
639
+ hash: contentHash(allContent)
640
+ };
641
+ config.installed = installed;
642
+ } else {
643
+ for (const file of item.files) {
531
644
  const aliasKey = (() => {
532
645
  switch (item.type) {
533
646
  case "kitn:agent":
@@ -540,12 +653,75 @@ async function addCommand(components, opts) {
540
653
  return "storage";
541
654
  }
542
655
  })();
543
- const fileName = f.path.split("/").pop();
544
- return join3(config.aliases[aliasKey], fileName);
545
- }),
546
- hash: contentHash(allContent)
547
- };
548
- config._installed = installed;
656
+ const fileName = file.path.split("/").pop();
657
+ const targetPath = join5(cwd, config.aliases[aliasKey], fileName);
658
+ const relativePath = join5(config.aliases[aliasKey], fileName);
659
+ const content = rewriteKitnImports(file.content, item.type, fileName, config.aliases);
660
+ const status = await checkFileStatus(targetPath, content);
661
+ switch (status) {
662
+ case "new" /* New */:
663
+ await writeComponentFile(targetPath, content);
664
+ created.push(relativePath);
665
+ break;
666
+ case "identical" /* Identical */:
667
+ skipped.push(relativePath);
668
+ break;
669
+ case "different" /* Different */:
670
+ if (opts.overwrite) {
671
+ await writeComponentFile(targetPath, content);
672
+ updated.push(relativePath);
673
+ } else {
674
+ const existing = await readExistingFile(targetPath);
675
+ const diff = generateDiff(relativePath, existing ?? "", content);
676
+ p2.log.message(pc3.dim(diff));
677
+ const action = await p2.select({
678
+ message: `${relativePath} already exists and differs. What to do?`,
679
+ options: [
680
+ { value: "skip", label: "Keep local version" },
681
+ { value: "overwrite", label: "Overwrite with registry version" }
682
+ ]
683
+ });
684
+ if (!p2.isCancel(action) && action === "overwrite") {
685
+ await writeComponentFile(targetPath, content);
686
+ updated.push(relativePath);
687
+ } else {
688
+ skipped.push(relativePath);
689
+ }
690
+ }
691
+ break;
692
+ }
693
+ }
694
+ const installed = config.installed ?? {};
695
+ const allContent = item.files.map((f) => {
696
+ const fn = f.path.split("/").pop();
697
+ return rewriteKitnImports(f.content, item.type, fn, config.aliases);
698
+ }).join("\n");
699
+ const ref = refs.find((r) => r.name === item.name) ?? { namespace: "@kitn", name: item.name, version: void 0 };
700
+ const installedKey = ref.namespace === "@kitn" ? item.name : `${ref.namespace}/${item.name}`;
701
+ installed[installedKey] = {
702
+ registry: ref.namespace,
703
+ version: item.version ?? "1.0.0",
704
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
705
+ files: item.files.map((f) => {
706
+ const aliasKey = (() => {
707
+ switch (item.type) {
708
+ case "kitn:agent":
709
+ return "agents";
710
+ case "kitn:tool":
711
+ return "tools";
712
+ case "kitn:skill":
713
+ return "skills";
714
+ case "kitn:storage":
715
+ return "storage";
716
+ }
717
+ })();
718
+ const fileName = f.path.split("/").pop();
719
+ return join5(config.aliases[aliasKey], fileName);
720
+ }),
721
+ hash: contentHash(allContent)
722
+ };
723
+ config.installed = installed;
724
+ }
549
725
  }
550
726
  await writeConfig(cwd, config);
551
727
  const uniqueDeps = [...new Set(allDeps)];
@@ -594,7 +770,10 @@ var init_add = __esm({
594
770
  init_file_writer();
595
771
  init_dep_installer();
596
772
  init_env_checker();
773
+ init_import_rewriter();
774
+ init_tsconfig_patcher();
597
775
  init_hash();
776
+ init_parse_ref();
598
777
  init_schema();
599
778
  }
600
779
  });
@@ -614,40 +793,70 @@ async function listCommand(opts) {
614
793
  process.exit(1);
615
794
  }
616
795
  const fetcher = new RegistryFetcher(config.registries);
796
+ const namespacesToFetch = opts.registry ? [opts.registry] : Object.keys(config.registries);
797
+ if (opts.registry && !config.registries[opts.registry]) {
798
+ p3.log.error(`Registry ${pc4.bold(opts.registry)} is not configured. Run ${pc4.bold("kitn registry list")} to see configured registries.`);
799
+ process.exit(1);
800
+ }
617
801
  const s = p3.spinner();
618
802
  s.start("Fetching registry index...");
619
- let index;
620
- try {
621
- index = await fetcher.fetchIndex();
622
- } catch (err) {
623
- s.stop(pc4.red("Failed to fetch registry"));
624
- p3.log.error(err.message);
803
+ const allItems = [];
804
+ const errors = [];
805
+ for (const namespace of namespacesToFetch) {
806
+ try {
807
+ const index = await fetcher.fetchIndex(namespace);
808
+ for (const item of index.items) {
809
+ allItems.push({ ...item, namespace });
810
+ }
811
+ } catch (err) {
812
+ errors.push(`${namespace}: ${err.message}`);
813
+ }
814
+ }
815
+ if (allItems.length === 0 && errors.length > 0) {
816
+ s.stop(pc4.red("Failed to fetch registries"));
817
+ for (const e of errors) p3.log.error(e);
625
818
  process.exit(1);
626
819
  }
627
- s.stop(`Found ${index.items.length} components`);
628
- const installed = config._installed ?? {};
820
+ s.stop(`Found ${allItems.length} components across ${namespacesToFetch.length - errors.length} ${namespacesToFetch.length - errors.length === 1 ? "registry" : "registries"}`);
821
+ for (const e of errors) {
822
+ p3.log.warn(`${pc4.yellow("\u26A0")} Failed to fetch ${e}`);
823
+ }
824
+ const installed = config.installed ?? {};
629
825
  const typeGroups = /* @__PURE__ */ new Map();
630
- for (const item of index.items) {
826
+ for (const item of allItems) {
631
827
  if (opts.type && !item.type.endsWith(opts.type)) continue;
632
- const group2 = item.type.replace("kitn:", "");
633
- if (!typeGroups.has(group2)) typeGroups.set(group2, []);
634
- typeGroups.get(group2).push(item);
828
+ const group = item.type.replace("kitn:", "");
829
+ if (!typeGroups.has(group)) typeGroups.set(group, []);
830
+ typeGroups.get(group).push(item);
635
831
  }
636
- for (const [group2, items] of typeGroups) {
832
+ let installedCount = 0;
833
+ let updateCount = 0;
834
+ for (const [group, items] of typeGroups) {
637
835
  p3.log.message(pc4.bold(`
638
- ${group2.charAt(0).toUpperCase() + group2.slice(1)}s:`));
836
+ ${group.charAt(0).toUpperCase() + group.slice(1)}s:`));
639
837
  for (const item of items) {
640
- const inst = installed[item.name];
838
+ const displayName = item.namespace === "@kitn" ? item.name : `${item.namespace}/${item.name}`;
839
+ const inst = installed[item.name] ?? installed[displayName];
641
840
  if (opts.installed && !inst) continue;
841
+ const version = pc4.dim(`v${item.version ?? "1.0.0"}`);
642
842
  if (inst) {
843
+ installedCount++;
643
844
  const status = pc4.green("\u2713");
644
- p3.log.message(` ${status} ${item.name} ${pc4.dim(item.description)}`);
845
+ const hasUpdate = item.version && inst.version !== item.version;
846
+ const updateTag = hasUpdate ? pc4.yellow(` \u2B06 v${item.version} available`) : "";
847
+ if (hasUpdate) updateCount++;
848
+ p3.log.message(` ${status} ${displayName.padEnd(20)} ${version} ${pc4.dim(item.description)}${updateTag}`);
645
849
  } else {
646
850
  const status = pc4.dim("\u25CB");
647
- p3.log.message(` ${status} ${item.name} ${pc4.dim(item.description)}`);
851
+ p3.log.message(` ${status} ${displayName.padEnd(20)} ${version} ${pc4.dim(item.description)}`);
648
852
  }
649
853
  }
650
854
  }
855
+ const availableCount = allItems.length - installedCount;
856
+ const parts = [`${installedCount} installed`, `${availableCount} available`];
857
+ if (updateCount > 0) parts.push(`${updateCount} update${updateCount === 1 ? "" : "s"} available`);
858
+ p3.log.message("");
859
+ p3.log.message(pc4.dim(` ${parts.join(", ")}`));
651
860
  }
652
861
  var init_list = __esm({
653
862
  "src/commands/list.ts"() {
@@ -663,7 +872,7 @@ __export(diff_exports, {
663
872
  diffCommand: () => diffCommand
664
873
  });
665
874
  import * as p4 from "@clack/prompts";
666
- import { join as join4 } from "path";
875
+ import { join as join6 } from "path";
667
876
  async function diffCommand(componentName) {
668
877
  const cwd = process.cwd();
669
878
  const config = await readConfig(cwd);
@@ -671,48 +880,67 @@ async function diffCommand(componentName) {
671
880
  p4.log.error("No kitn.json found. Run `kitn init` first.");
672
881
  process.exit(1);
673
882
  }
674
- const installed = config._installed?.[componentName];
883
+ const input = componentName === "routes" ? config.framework ?? "hono" : componentName;
884
+ const ref = parseComponentRef(input);
885
+ const installedKey = ref.namespace === "@kitn" ? ref.name : `${ref.namespace}/${ref.name}`;
886
+ const installed = config.installed?.[installedKey];
675
887
  if (!installed) {
676
- p4.log.error(`Component '${componentName}' is not installed.`);
888
+ p4.log.error(`Component '${ref.name}' is not installed.`);
677
889
  process.exit(1);
678
890
  }
891
+ const namespace = installed.registry ?? ref.namespace;
679
892
  const fetcher = new RegistryFetcher(config.registries);
680
- const index = await fetcher.fetchIndex();
681
- const indexItem = index.items.find((i) => i.name === componentName);
893
+ const index = await fetcher.fetchIndex(namespace);
894
+ const indexItem = index.items.find((i) => i.name === ref.name);
682
895
  if (!indexItem) {
683
- p4.log.error(`Component '${componentName}' not found in registry.`);
896
+ p4.log.error(`Component '${ref.name}' not found in ${namespace} registry.`);
684
897
  process.exit(1);
685
898
  }
686
899
  const dir = typeToDir[indexItem.type];
687
- const registryItem = await fetcher.fetchItem(componentName, dir);
900
+ const registryItem = await fetcher.fetchItem(ref.name, dir, namespace, ref.version);
688
901
  let hasDiff = false;
689
902
  for (const file of registryItem.files) {
690
- const fileName = file.path.split("/").pop();
691
- const aliasKey = (() => {
692
- switch (indexItem.type) {
693
- case "kitn:agent":
694
- return "agents";
695
- case "kitn:tool":
696
- return "tools";
697
- case "kitn:skill":
698
- return "skills";
699
- case "kitn:storage":
700
- return "storage";
903
+ if (indexItem.type === "kitn:package") {
904
+ const baseDir = config.aliases.base ?? "src/ai";
905
+ const localPath = join6(cwd, baseDir, file.path);
906
+ const relativePath = join6(baseDir, file.path);
907
+ const localContent = await readExistingFile(localPath);
908
+ if (localContent === null) {
909
+ p4.log.warn(`${relativePath}: file missing locally`);
910
+ hasDiff = true;
911
+ } else if (localContent !== file.content) {
912
+ const diff = generateDiff(relativePath, localContent, file.content);
913
+ console.log(diff);
914
+ hasDiff = true;
915
+ }
916
+ } else {
917
+ const fileName = file.path.split("/").pop();
918
+ const aliasKey = (() => {
919
+ switch (indexItem.type) {
920
+ case "kitn:agent":
921
+ return "agents";
922
+ case "kitn:tool":
923
+ return "tools";
924
+ case "kitn:skill":
925
+ return "skills";
926
+ case "kitn:storage":
927
+ return "storage";
928
+ }
929
+ })();
930
+ const localPath = join6(cwd, config.aliases[aliasKey], fileName);
931
+ const localContent = await readExistingFile(localPath);
932
+ if (localContent === null) {
933
+ p4.log.warn(`${fileName}: file missing locally`);
934
+ hasDiff = true;
935
+ } else if (localContent !== file.content) {
936
+ const diff = generateDiff(fileName, localContent, file.content);
937
+ console.log(diff);
938
+ hasDiff = true;
701
939
  }
702
- })();
703
- const localPath = join4(cwd, config.aliases[aliasKey], fileName);
704
- const localContent = await readExistingFile(localPath);
705
- if (localContent === null) {
706
- p4.log.warn(`${fileName}: file missing locally`);
707
- hasDiff = true;
708
- } else if (localContent !== file.content) {
709
- const diff = generateDiff(fileName, localContent, file.content);
710
- console.log(diff);
711
- hasDiff = true;
712
940
  }
713
941
  }
714
942
  if (!hasDiff) {
715
- p4.log.success(`${componentName}: up to date, no differences.`);
943
+ p4.log.success(`${ref.name}: up to date, no differences.`);
716
944
  }
717
945
  }
718
946
  var init_diff = __esm({
@@ -721,6 +949,7 @@ var init_diff = __esm({
721
949
  init_config();
722
950
  init_fetcher();
723
951
  init_file_writer();
952
+ init_parse_ref();
724
953
  init_schema();
725
954
  }
726
955
  });
@@ -732,7 +961,7 @@ __export(remove_exports, {
732
961
  });
733
962
  import * as p5 from "@clack/prompts";
734
963
  import pc5 from "picocolors";
735
- import { join as join5 } from "path";
964
+ import { join as join7 } from "path";
736
965
  import { unlink } from "fs/promises";
737
966
  async function removeCommand(componentName) {
738
967
  const cwd = process.cwd();
@@ -741,13 +970,16 @@ async function removeCommand(componentName) {
741
970
  p5.log.error("No kitn.json found. Run `kitn init` first.");
742
971
  process.exit(1);
743
972
  }
744
- const installed = config._installed?.[componentName];
973
+ const input = componentName === "routes" ? config.framework ?? "hono" : componentName;
974
+ const ref = parseComponentRef(input);
975
+ const installedKey = ref.namespace === "@kitn" ? ref.name : `${ref.namespace}/${ref.name}`;
976
+ const installed = config.installed?.[installedKey];
745
977
  if (!installed) {
746
- p5.log.error(`Component '${componentName}' is not installed.`);
978
+ p5.log.error(`Component '${ref.name}' is not installed.`);
747
979
  process.exit(1);
748
980
  }
749
981
  const shouldRemove = await p5.confirm({
750
- message: `Remove ${componentName}? This will delete ${installed.files.length} file(s).`,
982
+ message: `Remove ${ref.name}? This will delete ${installed.files.length} file(s).`,
751
983
  initialValue: false
752
984
  });
753
985
  if (p5.isCancel(shouldRemove) || !shouldRemove) {
@@ -757,19 +989,19 @@ async function removeCommand(componentName) {
757
989
  const deleted = [];
758
990
  for (const filePath of installed.files) {
759
991
  try {
760
- await unlink(join5(cwd, filePath));
992
+ await unlink(join7(cwd, filePath));
761
993
  deleted.push(filePath);
762
994
  } catch {
763
995
  p5.log.warn(`Could not delete ${filePath} (may have been moved or renamed)`);
764
996
  }
765
997
  }
766
- delete config._installed[componentName];
767
- if (Object.keys(config._installed).length === 0) {
768
- delete config._installed;
998
+ delete config.installed[installedKey];
999
+ if (Object.keys(config.installed).length === 0) {
1000
+ delete config.installed;
769
1001
  }
770
1002
  await writeConfig(cwd, config);
771
1003
  if (deleted.length > 0) {
772
- p5.log.success(`Removed ${componentName}:`);
1004
+ p5.log.success(`Removed ${ref.name}:`);
773
1005
  for (const f of deleted) p5.log.message(` ${pc5.red("-")} ${f}`);
774
1006
  }
775
1007
  }
@@ -777,6 +1009,7 @@ var init_remove = __esm({
777
1009
  "src/commands/remove.ts"() {
778
1010
  "use strict";
779
1011
  init_config();
1012
+ init_parse_ref();
780
1013
  }
781
1014
  });
782
1015
 
@@ -785,13 +1018,226 @@ var update_exports = {};
785
1018
  __export(update_exports, {
786
1019
  updateCommand: () => updateCommand
787
1020
  });
1021
+ import * as p6 from "@clack/prompts";
788
1022
  async function updateCommand(components) {
1023
+ if (components.length === 0) {
1024
+ const cwd = process.cwd();
1025
+ const config = await readConfig(cwd);
1026
+ if (!config) {
1027
+ p6.log.error("No kitn.json found. Run `kitn init` first.");
1028
+ process.exit(1);
1029
+ }
1030
+ const installed = config.installed;
1031
+ if (!installed || Object.keys(installed).length === 0) {
1032
+ p6.log.info("No installed components to update.");
1033
+ return;
1034
+ }
1035
+ components = Object.keys(installed);
1036
+ }
789
1037
  await addCommand(components, { overwrite: true });
790
1038
  }
791
1039
  var init_update = __esm({
792
1040
  "src/commands/update.ts"() {
793
1041
  "use strict";
794
1042
  init_add();
1043
+ init_config();
1044
+ }
1045
+ });
1046
+
1047
+ // src/commands/info.ts
1048
+ var info_exports = {};
1049
+ __export(info_exports, {
1050
+ infoCommand: () => infoCommand
1051
+ });
1052
+ import * as p7 from "@clack/prompts";
1053
+ import pc6 from "picocolors";
1054
+ async function infoCommand(component) {
1055
+ const cwd = process.cwd();
1056
+ const config = await readConfig(cwd);
1057
+ if (!config) {
1058
+ p7.log.error("No kitn.json found. Run `kitn init` first.");
1059
+ process.exit(1);
1060
+ }
1061
+ const ref = parseComponentRef(component);
1062
+ const fetcher = new RegistryFetcher(config.registries);
1063
+ const s = p7.spinner();
1064
+ s.start("Fetching component info...");
1065
+ let index;
1066
+ try {
1067
+ index = await fetcher.fetchIndex(ref.namespace);
1068
+ } catch (err) {
1069
+ s.stop(pc6.red("Failed to fetch registry"));
1070
+ p7.log.error(err.message);
1071
+ process.exit(1);
1072
+ }
1073
+ const indexItem = index.items.find((i) => i.name === ref.name);
1074
+ if (!indexItem) {
1075
+ s.stop(pc6.red("Component not found"));
1076
+ p7.log.error(`Component '${ref.name}' not found in registry.`);
1077
+ process.exit(1);
1078
+ }
1079
+ const dir = typeToDir[indexItem.type];
1080
+ let item;
1081
+ try {
1082
+ item = await fetcher.fetchItem(ref.name, dir, ref.namespace, ref.version);
1083
+ } catch (err) {
1084
+ s.stop(pc6.red("Failed to fetch component"));
1085
+ p7.log.error(err.message);
1086
+ process.exit(1);
1087
+ }
1088
+ s.stop("Component found");
1089
+ const version = item.version ?? indexItem.version ?? "unknown";
1090
+ const typeName = indexItem.type.replace("kitn:", "");
1091
+ console.log();
1092
+ console.log(
1093
+ ` ${pc6.bold(item.name)} ${pc6.cyan(`v${version}`)}${" ".repeat(Math.max(1, 40 - item.name.length - version.length - 2))}${pc6.dim(ref.namespace)}`
1094
+ );
1095
+ console.log(` ${pc6.dim(item.description)}`);
1096
+ console.log();
1097
+ console.log(` ${pc6.dim("Type:")} ${typeName}`);
1098
+ if (item.dependencies?.length) {
1099
+ console.log(
1100
+ ` ${pc6.dim("Dependencies:")} ${item.dependencies.join(", ")}`
1101
+ );
1102
+ }
1103
+ if (item.registryDependencies?.length) {
1104
+ console.log(
1105
+ ` ${pc6.dim("Registry deps:")} ${item.registryDependencies.join(", ")}`
1106
+ );
1107
+ }
1108
+ if (item.categories?.length) {
1109
+ console.log(
1110
+ ` ${pc6.dim("Categories:")} ${item.categories.join(", ")}`
1111
+ );
1112
+ }
1113
+ if (item.updatedAt) {
1114
+ console.log(` ${pc6.dim("Updated:")} ${item.updatedAt}`);
1115
+ }
1116
+ const versions = indexItem.versions;
1117
+ if (versions?.length) {
1118
+ console.log(` ${pc6.dim("Versions:")} ${versions.join(", ")}`);
1119
+ }
1120
+ if (item.changelog?.length) {
1121
+ console.log();
1122
+ console.log(` ${pc6.bold("Changelog:")}`);
1123
+ for (const entry of item.changelog) {
1124
+ const tag = entry.type === "feature" ? pc6.green(entry.type) : entry.type === "fix" ? pc6.yellow(entry.type) : entry.type === "breaking" ? pc6.red(entry.type) : pc6.dim(entry.type);
1125
+ console.log(
1126
+ ` ${pc6.cyan(entry.version)} ${pc6.dim(entry.date)} ${tag} ${entry.note}`
1127
+ );
1128
+ }
1129
+ }
1130
+ console.log();
1131
+ const fileCount = item.files.length;
1132
+ console.log(` ${pc6.bold(`Files:`)} ${pc6.dim(`(${fileCount})`)}`);
1133
+ const maxShown = 10;
1134
+ for (const file of item.files.slice(0, maxShown)) {
1135
+ console.log(` ${pc6.dim(file.path)}`);
1136
+ }
1137
+ if (fileCount > maxShown) {
1138
+ console.log(` ${pc6.dim(`... and ${fileCount - maxShown} more`)}`);
1139
+ }
1140
+ const installed = config.installed?.[item.name];
1141
+ if (installed) {
1142
+ console.log();
1143
+ console.log(
1144
+ ` ${pc6.green("Installed")} ${pc6.dim(`v${installed.version}`)}`
1145
+ );
1146
+ if (version !== installed.version) {
1147
+ console.log(
1148
+ ` ${pc6.yellow("Update available:")} ${pc6.dim(`v${installed.version}`)} \u2192 ${pc6.cyan(`v${version}`)}`
1149
+ );
1150
+ }
1151
+ }
1152
+ console.log();
1153
+ }
1154
+ var init_info = __esm({
1155
+ "src/commands/info.ts"() {
1156
+ "use strict";
1157
+ init_config();
1158
+ init_parse_ref();
1159
+ init_fetcher();
1160
+ init_schema();
1161
+ }
1162
+ });
1163
+
1164
+ // src/commands/registry.ts
1165
+ var registry_exports = {};
1166
+ __export(registry_exports, {
1167
+ registryAddCommand: () => registryAddCommand,
1168
+ registryListCommand: () => registryListCommand,
1169
+ registryRemoveCommand: () => registryRemoveCommand
1170
+ });
1171
+ import * as p8 from "@clack/prompts";
1172
+ import pc7 from "picocolors";
1173
+ async function registryAddCommand(namespace, url, opts = {}) {
1174
+ const cwd = opts.cwd ?? process.cwd();
1175
+ const config = await readConfig(cwd);
1176
+ if (!config) throw new Error("No kitn.json found. Run `kitn init` first.");
1177
+ if (!namespace.startsWith("@")) {
1178
+ throw new Error("Namespace must start with @ (e.g. @myteam)");
1179
+ }
1180
+ if (!url.includes("{type}")) {
1181
+ throw new Error("URL template must include {type} placeholder");
1182
+ }
1183
+ if (!url.includes("{name}")) {
1184
+ throw new Error("URL template must include {name} placeholder");
1185
+ }
1186
+ if (config.registries[namespace] && !opts.overwrite) {
1187
+ throw new Error(`Registry '${namespace}' is already configured. Use --overwrite to replace.`);
1188
+ }
1189
+ config.registries[namespace] = url;
1190
+ await writeConfig(cwd, config);
1191
+ p8.log.success(`Added registry ${pc7.bold(namespace)}`);
1192
+ p8.log.message(pc7.dim(` ${url}`));
1193
+ }
1194
+ async function registryRemoveCommand(namespace, opts = {}) {
1195
+ const cwd = opts.cwd ?? process.cwd();
1196
+ const config = await readConfig(cwd);
1197
+ if (!config) throw new Error("No kitn.json found. Run `kitn init` first.");
1198
+ if (!config.registries[namespace]) {
1199
+ throw new Error(`Registry '${namespace}' is not configured.`);
1200
+ }
1201
+ if (namespace === "@kitn" && !opts.force) {
1202
+ throw new Error("Cannot remove the default @kitn registry. Use --force to override.");
1203
+ }
1204
+ const affectedComponents = [];
1205
+ if (config.installed) {
1206
+ for (const [name, entry] of Object.entries(config.installed)) {
1207
+ if (entry.registry === namespace) {
1208
+ affectedComponents.push(name);
1209
+ }
1210
+ }
1211
+ }
1212
+ delete config.registries[namespace];
1213
+ await writeConfig(cwd, config);
1214
+ p8.log.success(`Removed registry ${pc7.bold(namespace)}`);
1215
+ if (affectedComponents.length > 0) {
1216
+ p8.log.warn(`${affectedComponents.length} installed component(s) referenced this registry:`);
1217
+ for (const name of affectedComponents) {
1218
+ p8.log.message(` ${pc7.yellow("!")} ${name}`);
1219
+ }
1220
+ }
1221
+ return { affectedComponents };
1222
+ }
1223
+ async function registryListCommand(opts = {}) {
1224
+ const cwd = opts.cwd ?? process.cwd();
1225
+ const config = await readConfig(cwd);
1226
+ if (!config) throw new Error("No kitn.json found. Run `kitn init` first.");
1227
+ const entries = Object.entries(config.registries).map(([namespace, url]) => ({ namespace, url }));
1228
+ if (entries.length === 0) {
1229
+ p8.log.message(pc7.dim(" No registries configured."));
1230
+ } else {
1231
+ for (const { namespace, url } of entries) {
1232
+ p8.log.message(` ${pc7.bold(namespace.padEnd(16))} ${pc7.dim(url)}`);
1233
+ }
1234
+ }
1235
+ return entries;
1236
+ }
1237
+ var init_registry = __esm({
1238
+ "src/commands/registry.ts"() {
1239
+ "use strict";
1240
+ init_config();
795
1241
  }
796
1242
  });
797
1243
 
@@ -806,7 +1252,7 @@ program.command("add").description("Add components from the kitn registry").argu
806
1252
  const { addCommand: addCommand2 } = await Promise.resolve().then(() => (init_add(), add_exports));
807
1253
  await addCommand2(components, opts);
808
1254
  });
809
- program.command("list").description("List available and installed components").option("-i, --installed", "only show installed components").option("-t, --type <type>", "filter by type (agent, tool, skill, storage)").action(async (opts) => {
1255
+ program.command("list").description("List available and installed components").option("-i, --installed", "only show installed components").option("-t, --type <type>", "filter by type (agent, tool, skill, storage, package)").option("-r, --registry <namespace>", "only show components from this registry").action(async (opts) => {
810
1256
  const { listCommand: listCommand2 } = await Promise.resolve().then(() => (init_list(), list_exports));
811
1257
  await listCommand2(opts);
812
1258
  });
@@ -822,5 +1268,22 @@ program.command("update").description("Update installed components to latest reg
822
1268
  const { updateCommand: updateCommand2 } = await Promise.resolve().then(() => (init_update(), update_exports));
823
1269
  await updateCommand2(components);
824
1270
  });
1271
+ 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) => {
1272
+ const { infoCommand: infoCommand2 } = await Promise.resolve().then(() => (init_info(), info_exports));
1273
+ await infoCommand2(component);
1274
+ });
1275
+ var registry = program.command("registry").description("Manage component registries");
1276
+ 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) => {
1277
+ const { registryAddCommand: registryAddCommand2 } = await Promise.resolve().then(() => (init_registry(), registry_exports));
1278
+ await registryAddCommand2(namespace, url, opts);
1279
+ });
1280
+ registry.command("remove").description("Remove a component registry").argument("<namespace>", "registry namespace to remove (e.g. @myteam)").option("-f, --force", "allow removing the default @kitn registry").action(async (namespace, opts) => {
1281
+ const { registryRemoveCommand: registryRemoveCommand2 } = await Promise.resolve().then(() => (init_registry(), registry_exports));
1282
+ await registryRemoveCommand2(namespace, opts);
1283
+ });
1284
+ registry.command("list").description("List all configured registries").action(async () => {
1285
+ const { registryListCommand: registryListCommand2 } = await Promise.resolve().then(() => (init_registry(), registry_exports));
1286
+ await registryListCommand2();
1287
+ });
825
1288
  program.parse();
826
1289
  //# sourceMappingURL=index.js.map