@kitnai/cli 0.1.33 → 0.1.35

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
@@ -1188,8 +1188,8 @@ async function addCommand(components, opts) {
1188
1188
  if (!opts.yes && process.stdin.isTTY) {
1189
1189
  const totalComponents = resolved.length;
1190
1190
  const summary = totalDeps > 0 ? `Install ${totalComponents} component(s) and ${totalDeps} npm package(s)?` : `Install ${totalComponents} component(s)?`;
1191
- const confirm6 = await p2.confirm({ message: summary });
1192
- if (p2.isCancel(confirm6) || !confirm6) {
1191
+ const confirm7 = await p2.confirm({ message: summary });
1192
+ if (p2.isCancel(confirm7) || !confirm7) {
1193
1193
  p2.cancel("Cancelled.");
1194
1194
  process.exit(0);
1195
1195
  }
@@ -1441,8 +1441,14 @@ var init_add = __esm({
1441
1441
  });
1442
1442
 
1443
1443
  // src/installers/rules-generator.ts
1444
- import { writeFile as writeFile7, mkdir as mkdir4 } from "fs/promises";
1444
+ import { writeFile as writeFile7, mkdir as mkdir4, readFile as readFile7 } from "fs/promises";
1445
1445
  import { join as join8, dirname as dirname4 } from "path";
1446
+ async function loadFallbackTemplate() {
1447
+ if (!_fallbackTemplate) {
1448
+ _fallbackTemplate = await readFile7(TEMPLATE_PATH, "utf-8");
1449
+ }
1450
+ return _fallbackTemplate;
1451
+ }
1446
1452
  function deriveRulesBaseUrl(registries) {
1447
1453
  const kitnEntry = registries["@kitn"];
1448
1454
  if (!kitnEntry) {
@@ -1468,7 +1474,7 @@ async function fetchRulesTemplate(registries) {
1468
1474
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
1469
1475
  return await res.text();
1470
1476
  } catch {
1471
- return FALLBACK_TEMPLATE;
1477
+ return loadFallbackTemplate();
1472
1478
  }
1473
1479
  }
1474
1480
  function renderTemplate(template, aliases) {
@@ -1503,11 +1509,12 @@ async function generateRulesFiles(cwd, config2, selectedToolIds) {
1503
1509
  }
1504
1510
  return written;
1505
1511
  }
1506
- var FALLBACK_CONFIG, FALLBACK_TEMPLATE;
1512
+ var TEMPLATE_PATH, FALLBACK_CONFIG, _fallbackTemplate;
1507
1513
  var init_rules_generator = __esm({
1508
1514
  "src/installers/rules-generator.ts"() {
1509
1515
  "use strict";
1510
1516
  init_config();
1517
+ TEMPLATE_PATH = join8(import.meta.dirname, "rules-template.md");
1511
1518
  FALLBACK_CONFIG = {
1512
1519
  version: "1.0.0",
1513
1520
  tools: [
@@ -1525,7 +1532,7 @@ var init_rules_generator = __esm({
1525
1532
  format: "mdc",
1526
1533
  frontmatter: {
1527
1534
  description: "kitn AI agent framework conventions and patterns",
1528
- globs: "src/ai/**/*.ts, kitn.json"
1535
+ globs: "src/ai/**/*.ts, src/ai/**/*.md, kitn.json, kitn.lock"
1529
1536
  }
1530
1537
  },
1531
1538
  {
@@ -1548,34 +1555,6 @@ var init_rules_generator = __esm({
1548
1555
  }
1549
1556
  ]
1550
1557
  };
1551
- FALLBACK_TEMPLATE = `# kitn AI Agent Framework
1552
-
1553
- This project uses **kitn** to build multi-agent AI systems.
1554
-
1555
- ## Project Structure
1556
-
1557
- AI components live under \`{base}\`:
1558
-
1559
- - \`{agents}/\` \u2014 Agent definitions
1560
- - \`{tools}/\` \u2014 Tool definitions
1561
- - \`{skills}/\` \u2014 Skill files (markdown)
1562
- - \`{storage}/\` \u2014 Storage providers
1563
- - \`{crons}/\` \u2014 Cron job definitions
1564
-
1565
- ## Patterns
1566
-
1567
- - Agents: \`registerAgent({ name, system, tools })\` from \`@kitn/core\`
1568
- - Tools: \`tool()\` from \`ai\` + \`registerTool()\` from \`@kitn/core\`
1569
- - Always use \`.js\` extension in relative imports
1570
- - Use \`@kitn/core\` for core imports, \`ai\` for Vercel AI SDK
1571
-
1572
- ## CLI
1573
-
1574
- - \`kitn add <name>\` \u2014 install from registry
1575
- - \`kitn create <type> <name>\` \u2014 scaffold locally
1576
- - \`kitn link tool <name> --to <agent>\` \u2014 wire a tool to an agent
1577
- - \`kitn list\` \u2014 browse components
1578
- `;
1579
1558
  }
1580
1559
  });
1581
1560
 
@@ -1586,11 +1565,11 @@ __export(init_exports, {
1586
1565
  });
1587
1566
  import * as p3 from "@clack/prompts";
1588
1567
  import pc4 from "picocolors";
1589
- import { mkdir as mkdir5, readFile as readFile7, writeFile as writeFile8 } from "fs/promises";
1568
+ import { mkdir as mkdir5, readFile as readFile8, writeFile as writeFile8 } from "fs/promises";
1590
1569
  import { join as join9 } from "path";
1591
1570
  async function detectFramework(cwd) {
1592
1571
  try {
1593
- const pkg = JSON.parse(await readFile7(join9(cwd, "package.json"), "utf-8"));
1572
+ const pkg = JSON.parse(await readFile8(join9(cwd, "package.json"), "utf-8"));
1594
1573
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
1595
1574
  if (deps["elysia"]) return "elysia";
1596
1575
  if (deps["@hono/zod-openapi"]) return "hono-openapi";
@@ -1678,15 +1657,15 @@ async function initCommand(opts = {}) {
1678
1657
  framework = detected;
1679
1658
  p3.log.info(`Detected ${pc4.bold(detected)} from package.json`);
1680
1659
  if (!opts.yes) {
1681
- const confirm6 = await p3.confirm({
1660
+ const confirm7 = await p3.confirm({
1682
1661
  message: `Use ${detected}?`,
1683
1662
  initialValue: true
1684
1663
  });
1685
- if (p3.isCancel(confirm6)) {
1664
+ if (p3.isCancel(confirm7)) {
1686
1665
  p3.cancel("Init cancelled.");
1687
1666
  process.exit(0);
1688
1667
  }
1689
- if (!confirm6) {
1668
+ if (!confirm7) {
1690
1669
  const selected = await p3.select({
1691
1670
  message: "Which HTTP framework do you use?",
1692
1671
  options: [
@@ -2057,7 +2036,7 @@ __export(remove_exports, {
2057
2036
  import * as p6 from "@clack/prompts";
2058
2037
  import pc6 from "picocolors";
2059
2038
  import { join as join11, relative as relative3, dirname as dirname5 } from "path";
2060
- import { unlink as unlink3, readFile as readFile8, writeFile as writeFile9 } from "fs/promises";
2039
+ import { unlink as unlink3, readFile as readFile9, writeFile as writeFile9 } from "fs/promises";
2061
2040
  import { existsSync as existsSync2 } from "fs";
2062
2041
  async function removeSingleComponent(installedKey, lock, config2, cwd) {
2063
2042
  const entry = lock[installedKey];
@@ -2080,7 +2059,7 @@ async function removeSingleComponent(installedKey, lock, config2, cwd) {
2080
2059
  config2.aliases.skills
2081
2060
  ]);
2082
2061
  if (existsSync2(barrelPath) && deleted.length > 0) {
2083
- let barrelContent = await readFile8(barrelPath, "utf-8");
2062
+ let barrelContent = await readFile9(barrelPath, "utf-8");
2084
2063
  let barrelChanged = false;
2085
2064
  for (const filePath of deleted) {
2086
2065
  const fileDir = dirname5(filePath);
@@ -2265,7 +2244,7 @@ import * as p8 from "@clack/prompts";
2265
2244
  import pc7 from "picocolors";
2266
2245
  import { join as join12, relative as relative4 } from "path";
2267
2246
  import { existsSync as existsSync3 } from "fs";
2268
- import { readFile as readFile9, writeFile as writeFile10, mkdir as mkdir6 } from "fs/promises";
2247
+ import { readFile as readFile10, writeFile as writeFile10, mkdir as mkdir6 } from "fs/promises";
2269
2248
  function generateAgentSource(name) {
2270
2249
  const camel = toCamelCase(name);
2271
2250
  return `import { registerAgent } from "@kitn/core";
@@ -2403,7 +2382,7 @@ async function createComponentInProject(type, name, opts) {
2403
2382
  const barrelPath = join12(cwd, baseDir, "index.ts");
2404
2383
  let barrelContent;
2405
2384
  if (existsSync3(barrelPath)) {
2406
- barrelContent = await readFile9(barrelPath, "utf-8");
2385
+ barrelContent = await readFile10(barrelPath, "utf-8");
2407
2386
  } else {
2408
2387
  barrelContent = createBarrelFile();
2409
2388
  await mkdir6(join12(cwd, baseDir), { recursive: true });
@@ -2455,7 +2434,7 @@ var init_create = __esm({
2455
2434
  });
2456
2435
 
2457
2436
  // src/utils/component-resolver.ts
2458
- import { readdir, readFile as readFile10 } from "fs/promises";
2437
+ import { readdir, readFile as readFile11 } from "fs/promises";
2459
2438
  import { join as join13, relative as relative5 } from "path";
2460
2439
  function stripSuffix(name, suffix) {
2461
2440
  if (name.endsWith(`-${suffix}`)) {
@@ -2477,7 +2456,7 @@ async function findFile(dir, candidates) {
2477
2456
  for (const name of candidates) {
2478
2457
  const filePath = join13(dir, `${name}.ts`);
2479
2458
  try {
2480
- await readFile10(filePath);
2459
+ await readFile11(filePath);
2481
2460
  return filePath;
2482
2461
  } catch {
2483
2462
  }
@@ -2512,7 +2491,7 @@ async function resolveToolByName(name, config2, cwd) {
2512
2491
  if (toolFile) {
2513
2492
  const filePath2 = join13(cwd, toolFile);
2514
2493
  try {
2515
- const source2 = await readFile10(filePath2, "utf-8");
2494
+ const source2 = await readFile11(filePath2, "utf-8");
2516
2495
  const exportName2 = parseExportName(source2);
2517
2496
  if (exportName2) {
2518
2497
  return {
@@ -2530,7 +2509,7 @@ async function resolveToolByName(name, config2, cwd) {
2530
2509
  const uniqueCandidates = [...new Set(candidates)];
2531
2510
  const filePath = await findFile(tDir, uniqueCandidates);
2532
2511
  if (!filePath) return null;
2533
- const source = await readFile10(filePath, "utf-8");
2512
+ const source = await readFile11(filePath, "utf-8");
2534
2513
  const exportName = parseExportName(source);
2535
2514
  if (!exportName) return null;
2536
2515
  return {
@@ -2551,7 +2530,7 @@ async function resolveAgentByName(name, config2, cwd) {
2551
2530
  if (agentFile) {
2552
2531
  const filePath2 = join13(cwd, agentFile);
2553
2532
  try {
2554
- const source2 = await readFile10(filePath2, "utf-8");
2533
+ const source2 = await readFile11(filePath2, "utf-8");
2555
2534
  const agentName2 = parseAgentName(source2);
2556
2535
  return {
2557
2536
  filePath: filePath2,
@@ -2566,7 +2545,7 @@ async function resolveAgentByName(name, config2, cwd) {
2566
2545
  const uniqueCandidates = [...new Set(candidates)];
2567
2546
  const filePath = await findFile(aDir, uniqueCandidates);
2568
2547
  if (!filePath) return null;
2569
- const source = await readFile10(filePath, "utf-8");
2548
+ const source = await readFile11(filePath, "utf-8");
2570
2549
  const agentName = parseAgentName(source);
2571
2550
  const fallbackName = filePath.split("/").pop().replace(/\.ts$/, "");
2572
2551
  return {
@@ -2854,7 +2833,7 @@ __export(link_exports, {
2854
2833
  });
2855
2834
  import * as p9 from "@clack/prompts";
2856
2835
  import pc8 from "picocolors";
2857
- import { readFile as readFile11, writeFile as writeFile11 } from "fs/promises";
2836
+ import { readFile as readFile12, writeFile as writeFile11 } from "fs/promises";
2858
2837
  import { basename } from "path";
2859
2838
  async function linkCommand(type, name, opts) {
2860
2839
  p9.intro(pc8.bgCyan(pc8.black(" kitn link ")));
@@ -2924,7 +2903,7 @@ async function linkCommand(type, name, opts) {
2924
2903
  );
2925
2904
  process.exit(1);
2926
2905
  }
2927
- const agentContent = await readFile11(agent.filePath, "utf-8");
2906
+ const agentContent = await readFile12(agent.filePath, "utf-8");
2928
2907
  const toolRef = {
2929
2908
  exportName: tool.exportName,
2930
2909
  importPath: tool.importPath
@@ -2970,7 +2949,7 @@ __export(unlink_exports, {
2970
2949
  });
2971
2950
  import * as p10 from "@clack/prompts";
2972
2951
  import pc9 from "picocolors";
2973
- import { readFile as readFile12, writeFile as writeFile12 } from "fs/promises";
2952
+ import { readFile as readFile13, writeFile as writeFile12 } from "fs/promises";
2974
2953
  import { basename as basename2 } from "path";
2975
2954
  async function unlinkCommand(type, name, opts) {
2976
2955
  p10.intro(pc9.bgCyan(pc9.black(" kitn unlink ")));
@@ -3040,7 +3019,7 @@ async function unlinkCommand(type, name, opts) {
3040
3019
  );
3041
3020
  process.exit(1);
3042
3021
  }
3043
- const agentContent = await readFile12(agent.filePath, "utf-8");
3022
+ const agentContent = await readFile13(agent.filePath, "utf-8");
3044
3023
  const toolRef = {
3045
3024
  exportName: tool.exportName,
3046
3025
  importPath: tool.importPath
@@ -3308,13 +3287,13 @@ __export(config_exports, {
3308
3287
  });
3309
3288
  import * as p14 from "@clack/prompts";
3310
3289
  import pc13 from "picocolors";
3311
- import { readFile as readFile13, writeFile as writeFile13, mkdir as mkdir7 } from "fs/promises";
3290
+ import { readFile as readFile14, writeFile as writeFile13, mkdir as mkdir7 } from "fs/promises";
3312
3291
  import { join as join14 } from "path";
3313
3292
  import { homedir as homedir2 } from "os";
3314
3293
  import { existsSync as existsSync4 } from "fs";
3315
3294
  async function readUserConfig() {
3316
3295
  try {
3317
- const raw = await readFile13(CONFIG_FILE2, "utf-8");
3296
+ const raw = await readFile14(CONFIG_FILE2, "utf-8");
3318
3297
  return JSON.parse(raw);
3319
3298
  } catch {
3320
3299
  return {};
@@ -3371,16 +3350,144 @@ var init_config2 = __esm({
3371
3350
  }
3372
3351
  });
3373
3352
 
3353
+ // src/commands/registry.ts
3354
+ var registry_exports = {};
3355
+ __export(registry_exports, {
3356
+ registryAddCommand: () => registryAddCommand,
3357
+ registryListCommand: () => registryListCommand,
3358
+ registryRemoveCommand: () => registryRemoveCommand
3359
+ });
3360
+ import * as p15 from "@clack/prompts";
3361
+ import pc14 from "picocolors";
3362
+ async function registryAddCommand(namespace, url, opts = {}) {
3363
+ const cwd = opts.cwd ?? process.cwd();
3364
+ const config2 = await readConfig(cwd);
3365
+ if (!config2) throw new Error("No kitn.json found. Run `kitn init` first.");
3366
+ if (!namespace.startsWith("@")) {
3367
+ throw new Error("Namespace must start with @ (e.g. @myteam)");
3368
+ }
3369
+ if (!url.includes("{type}")) {
3370
+ throw new Error("URL template must include {type} placeholder");
3371
+ }
3372
+ if (!url.includes("{name}")) {
3373
+ throw new Error("URL template must include {name} placeholder");
3374
+ }
3375
+ if (config2.registries[namespace] && !opts.overwrite) {
3376
+ throw new Error(`Registry '${namespace}' is already configured. Use --overwrite to replace.`);
3377
+ }
3378
+ if (opts.homepage || opts.description) {
3379
+ const entry = { url };
3380
+ if (opts.homepage) entry.homepage = opts.homepage;
3381
+ if (opts.description) entry.description = opts.description;
3382
+ config2.registries[namespace] = entry;
3383
+ } else {
3384
+ config2.registries[namespace] = url;
3385
+ }
3386
+ await writeConfig(cwd, config2);
3387
+ p15.log.success(`Added registry ${pc14.bold(namespace)}`);
3388
+ p15.log.message(pc14.dim(` ${url}`));
3389
+ if (opts.homepage) p15.log.message(pc14.dim(` Homepage: ${opts.homepage}`));
3390
+ if (opts.description) p15.log.message(pc14.dim(` ${opts.description}`));
3391
+ }
3392
+ async function registryRemoveCommand(namespace, opts = {}) {
3393
+ const cwd = opts.cwd ?? process.cwd();
3394
+ const config2 = await readConfig(cwd);
3395
+ if (!config2) throw new Error("No kitn.json found. Run `kitn init` first.");
3396
+ if (!config2.registries[namespace]) {
3397
+ throw new Error(`Registry '${namespace}' is not configured.`);
3398
+ }
3399
+ if (namespace === "@kitn" && !opts.force) {
3400
+ throw new Error("Cannot remove the default @kitn registry. Use --force to override.");
3401
+ }
3402
+ const lock = await readLock(cwd);
3403
+ const affectedComponents = [];
3404
+ for (const [name, entry] of Object.entries(lock)) {
3405
+ if (entry.registry === namespace) {
3406
+ affectedComponents.push(name);
3407
+ }
3408
+ }
3409
+ delete config2.registries[namespace];
3410
+ await writeConfig(cwd, config2);
3411
+ p15.log.success(`Removed registry ${pc14.bold(namespace)}`);
3412
+ if (affectedComponents.length > 0) {
3413
+ p15.log.warn(`${affectedComponents.length} installed component(s) referenced this registry:
3414
+ ` + affectedComponents.map((name) => ` ${pc14.yellow("!")} ${name}`).join("\n"));
3415
+ }
3416
+ return { affectedComponents };
3417
+ }
3418
+ async function registryListCommand(opts = {}) {
3419
+ const cwd = opts.cwd ?? process.cwd();
3420
+ const config2 = await readConfig(cwd);
3421
+ if (!config2) throw new Error("No kitn.json found. Run `kitn init` first.");
3422
+ const entries = Object.entries(config2.registries).map(([namespace, value]) => {
3423
+ const url = getRegistryUrl(value);
3424
+ const homepage = typeof value === "object" ? value.homepage : void 0;
3425
+ const description = typeof value === "object" ? value.description : void 0;
3426
+ return { namespace, url, homepage, description };
3427
+ });
3428
+ if (entries.length === 0) {
3429
+ p15.log.message(pc14.dim(" No registries configured."));
3430
+ } else {
3431
+ const lines = [];
3432
+ for (const { namespace, url, homepage, description } of entries) {
3433
+ lines.push(` ${pc14.bold(namespace.padEnd(16))} ${pc14.dim(url)}`);
3434
+ if (description) lines.push(` ${" ".repeat(16)} ${description}`);
3435
+ if (homepage) lines.push(` ${" ".repeat(16)} ${pc14.dim(homepage)}`);
3436
+ }
3437
+ p15.log.message(lines.join("\n"));
3438
+ }
3439
+ return entries;
3440
+ }
3441
+ var init_registry = __esm({
3442
+ "src/commands/registry.ts"() {
3443
+ "use strict";
3444
+ init_config();
3445
+ }
3446
+ });
3447
+
3374
3448
  // src/commands/chat.ts
3375
3449
  var chat_exports = {};
3376
3450
  __export(chat_exports, {
3377
3451
  buildRequestPayload: () => buildRequestPayload,
3378
3452
  chatCommand: () => chatCommand,
3453
+ fetchGlobalRegistries: () => fetchGlobalRegistries,
3379
3454
  formatPlan: () => formatPlan,
3380
3455
  resolveServiceUrl: () => resolveServiceUrl
3381
3456
  });
3382
- import * as p15 from "@clack/prompts";
3383
- import pc14 from "picocolors";
3457
+ import * as p16 from "@clack/prompts";
3458
+ import pc15 from "picocolors";
3459
+ async function fetchGlobalRegistries(configuredNamespaces) {
3460
+ let directory;
3461
+ try {
3462
+ const res = await fetch(GLOBAL_REGISTRY_URL);
3463
+ if (!res.ok) return [];
3464
+ directory = await res.json();
3465
+ } catch {
3466
+ return [];
3467
+ }
3468
+ const unconfigured = directory.filter(
3469
+ (entry) => !configuredNamespaces.includes(entry.name)
3470
+ );
3471
+ if (unconfigured.length === 0) return [];
3472
+ const results = [];
3473
+ for (const entry of unconfigured) {
3474
+ try {
3475
+ const indexUrl = entry.url.replace("{type}/{name}.json", "registry.json");
3476
+ const res = await fetch(indexUrl);
3477
+ if (!res.ok) continue;
3478
+ const index = await res.json();
3479
+ const items = (index.items ?? []).map((item) => ({
3480
+ name: item.name,
3481
+ type: item.type,
3482
+ description: item.description,
3483
+ registryDependencies: item.registryDependencies
3484
+ }));
3485
+ results.push({ namespace: entry.name, url: entry.url, items });
3486
+ } catch {
3487
+ }
3488
+ }
3489
+ return results;
3490
+ }
3384
3491
  async function resolveServiceUrl(urlOverride, chatServiceConfig) {
3385
3492
  if (urlOverride) return urlOverride;
3386
3493
  if (process.env.KITN_CHAT_URL) return process.env.KITN_CHAT_URL;
@@ -3405,55 +3512,71 @@ function formatPlan(plan) {
3405
3512
  function formatStepLabel(step) {
3406
3513
  switch (step.action) {
3407
3514
  case "add":
3408
- return `Add ${pc14.cyan(step.component)}`;
3515
+ return `Add ${pc15.cyan(step.component)}`;
3409
3516
  case "remove":
3410
- return `Remove ${pc14.red(step.component)}`;
3517
+ return `Remove ${pc15.red(step.component)}`;
3411
3518
  case "create":
3412
- return `Create ${pc14.green(`${step.type}/${step.name}`)}`;
3519
+ return `Create ${pc15.green(step.name)} ${pc15.dim(`(${step.type})`)}`;
3413
3520
  case "link":
3414
- return `Link ${pc14.cyan(step.toolName)} \u2192 ${pc14.cyan(step.agentName)}`;
3521
+ return `Link ${pc15.cyan(step.toolName)} \u2192 ${pc15.cyan(step.agentName)}`;
3415
3522
  case "unlink":
3416
- return `Unlink ${pc14.red(step.toolName)} from ${pc14.cyan(step.agentName)}`;
3523
+ return `Unlink ${pc15.red(step.toolName)} from ${pc15.cyan(step.agentName)}`;
3524
+ case "registry-add":
3525
+ return `Add registry ${pc15.magenta(step.namespace)}`;
3417
3526
  }
3418
3527
  }
3419
3528
  async function chatCommand(message, opts) {
3420
3529
  const cwd = process.cwd();
3421
3530
  const config2 = await readConfig(cwd);
3422
3531
  if (!config2) {
3423
- p15.log.error("No kitn.json found. Run `kitn init` first.");
3532
+ p16.log.error("No kitn.json found. Run `kitn init` first.");
3424
3533
  process.exit(1);
3425
3534
  }
3426
3535
  if (!message) {
3427
- p15.log.error('Please provide a message. Usage: kitn chat "add a weather tool"');
3536
+ p16.log.error('Please provide a message. Usage: kitn chat "add a weather tool"');
3428
3537
  process.exit(1);
3429
3538
  }
3430
- p15.intro(pc14.bold("kitn assistant"));
3431
- const s = p15.spinner();
3539
+ p16.intro(pc15.bold("kitn assistant"));
3540
+ const s = p16.spinner();
3432
3541
  s.start("Gathering project context...");
3433
3542
  let registryIndex;
3434
3543
  let installed;
3544
+ let globalRegistryIndex;
3435
3545
  try {
3546
+ const configuredNamespaces = Object.keys(config2.registries);
3436
3547
  const fetcher = new RegistryFetcher(config2.registries);
3437
- const indices = [];
3438
- for (const namespace of Object.keys(config2.registries)) {
3439
- try {
3440
- const index = await fetcher.fetchIndex(namespace);
3441
- indices.push(index);
3442
- } catch {
3443
- }
3444
- }
3445
- registryIndex = indices;
3446
- const lock = await readLock(cwd);
3548
+ const [indices, globalEntries, lock] = await Promise.all([
3549
+ Promise.all(
3550
+ configuredNamespaces.map(async (ns) => {
3551
+ try {
3552
+ return await fetcher.fetchIndex(ns);
3553
+ } catch {
3554
+ return null;
3555
+ }
3556
+ })
3557
+ ),
3558
+ fetchGlobalRegistries(configuredNamespaces),
3559
+ readLock(cwd)
3560
+ ]);
3561
+ registryIndex = indices.filter(Boolean).flatMap((index) => (index.items ?? []).map((item) => ({
3562
+ name: item.name,
3563
+ type: item.type,
3564
+ description: item.description,
3565
+ registryDependencies: item.registryDependencies
3566
+ })));
3447
3567
  installed = Object.keys(lock);
3568
+ globalRegistryIndex = globalEntries.length > 0 ? globalEntries : void 0;
3448
3569
  } catch {
3449
- s.stop(pc14.red("Failed to gather context"));
3450
- p15.log.error("Could not read project context. Check your kitn.json and network connection.");
3570
+ s.stop(pc15.red("Failed to gather context"));
3571
+ p16.log.error("Could not read project context. Check your kitn.json and network connection.");
3451
3572
  process.exit(1);
3452
3573
  }
3453
3574
  s.stop("Context gathered");
3454
3575
  s.start("Thinking...");
3455
3576
  const serviceUrl = await resolveServiceUrl(opts?.url, config2.chatService);
3456
- const payload = buildRequestPayload(message, { registryIndex, installed });
3577
+ const metadata = { registryIndex, installed };
3578
+ if (globalRegistryIndex) metadata.globalRegistryIndex = globalRegistryIndex;
3579
+ const payload = buildRequestPayload(message, metadata);
3457
3580
  let response;
3458
3581
  try {
3459
3582
  const headers = {
@@ -3468,69 +3591,85 @@ async function chatCommand(message, opts) {
3468
3591
  body: JSON.stringify(payload)
3469
3592
  });
3470
3593
  } catch (err) {
3471
- s.stop(pc14.red("Connection failed"));
3472
- p15.log.error(`Could not reach chat service at ${serviceUrl}. ${err.message ?? ""}`);
3594
+ s.stop(pc15.red("Connection failed"));
3595
+ p16.log.error(`Could not reach chat service at ${serviceUrl}. ${err.message ?? ""}`);
3473
3596
  process.exit(1);
3474
3597
  }
3475
3598
  if (!response.ok) {
3476
- s.stop(pc14.red("Request failed"));
3477
- p15.log.error(`Chat service returned ${response.status}: ${response.statusText}`);
3599
+ s.stop(pc15.red("Request failed"));
3600
+ p16.log.error(`Chat service returned ${response.status}: ${response.statusText}`);
3478
3601
  process.exit(1);
3479
3602
  }
3480
3603
  let data;
3481
3604
  try {
3482
3605
  data = await response.json();
3483
3606
  } catch {
3484
- s.stop(pc14.red("Invalid response"));
3485
- p15.log.error("Chat service returned an invalid response.");
3607
+ s.stop(pc15.red("Invalid response"));
3608
+ p16.log.error("Chat service returned an invalid response.");
3486
3609
  process.exit(1);
3487
3610
  }
3488
3611
  s.stop("Done");
3489
3612
  if (data.rejected) {
3490
- p15.log.warn(data.text ?? "Request was rejected by the assistant.");
3491
- p15.outro("Try rephrasing your request.");
3613
+ p16.log.warn(data.text ?? "Request was rejected by the assistant.");
3614
+ p16.outro("Try rephrasing your request.");
3492
3615
  return;
3493
3616
  }
3494
3617
  if (!data.plan) {
3495
- p15.log.info(data.text ?? "No actionable plan returned.");
3496
- p15.outro("Nothing to do.");
3618
+ p16.log.info(data.text ?? "No actionable plan returned.");
3619
+ p16.outro("Nothing to do.");
3497
3620
  return;
3498
3621
  }
3499
- p15.log.message(formatPlan(data.plan));
3622
+ p16.log.message(formatPlan(data.plan));
3500
3623
  const steps = data.plan.steps;
3501
- const action = await p15.select({
3502
- message: "How would you like to proceed?",
3503
- options: [
3504
- { value: "all", label: "Yes, run all steps" },
3505
- { value: "select", label: "Select which steps to run" },
3506
- { value: "cancel", label: "Cancel" }
3507
- ]
3508
- });
3509
- if (p15.isCancel(action) || action === "cancel") {
3510
- p15.cancel("Cancelled.");
3511
- return;
3512
- }
3513
3624
  let selectedSteps;
3514
- if (action === "select") {
3515
- const choices = await p15.multiselect({
3516
- message: "Select steps to run:",
3517
- options: steps.map((step, i) => ({
3518
- value: i,
3519
- label: `${formatStepLabel(step)} - ${step.reason}`
3520
- }))
3625
+ if (steps.length === 1) {
3626
+ const confirm7 = await p16.confirm({
3627
+ message: `Run: ${formatStepLabel(steps[0])}?`
3521
3628
  });
3522
- if (p15.isCancel(choices)) {
3523
- p15.cancel("Cancelled.");
3629
+ if (p16.isCancel(confirm7) || !confirm7) {
3630
+ p16.cancel("Cancelled.");
3524
3631
  return;
3525
3632
  }
3526
- selectedSteps = choices.map((i) => steps[i]);
3527
- } else {
3528
3633
  selectedSteps = steps;
3634
+ } else {
3635
+ const action = await p16.select({
3636
+ message: "How would you like to proceed?",
3637
+ options: [
3638
+ { value: "all", label: "Yes, run all steps" },
3639
+ { value: "select", label: "Select which steps to run" },
3640
+ { value: "cancel", label: "Cancel" }
3641
+ ]
3642
+ });
3643
+ if (p16.isCancel(action) || action === "cancel") {
3644
+ p16.cancel("Cancelled.");
3645
+ return;
3646
+ }
3647
+ if (action === "select") {
3648
+ const choices = await p16.multiselect({
3649
+ message: "Select steps to run:",
3650
+ options: steps.map((step, i) => ({
3651
+ value: i,
3652
+ label: `${formatStepLabel(step)} - ${step.reason}`
3653
+ }))
3654
+ });
3655
+ if (p16.isCancel(choices)) {
3656
+ p16.cancel("Cancelled.");
3657
+ return;
3658
+ }
3659
+ selectedSteps = choices.map((i) => steps[i]);
3660
+ } else {
3661
+ selectedSteps = steps;
3662
+ }
3529
3663
  }
3530
3664
  for (const step of selectedSteps) {
3531
3665
  s.start(`Running: ${formatStepLabel(step)}...`);
3532
3666
  try {
3533
3667
  switch (step.action) {
3668
+ case "registry-add": {
3669
+ const { registryAddCommand: registryAddCommand2 } = await Promise.resolve().then(() => (init_registry(), registry_exports));
3670
+ await registryAddCommand2(step.namespace, step.url, { overwrite: true });
3671
+ break;
3672
+ }
3534
3673
  case "add": {
3535
3674
  const { addCommand: addCommand2 } = await Promise.resolve().then(() => (init_add(), add_exports));
3536
3675
  await addCommand2([step.component], { yes: true });
@@ -3557,15 +3696,15 @@ async function chatCommand(message, opts) {
3557
3696
  break;
3558
3697
  }
3559
3698
  }
3560
- s.stop(pc14.green(`Done: ${formatStepLabel(step)}`));
3699
+ s.stop(pc15.green(`Done: ${formatStepLabel(step)}`));
3561
3700
  } catch (err) {
3562
- s.stop(pc14.red(`Failed: ${formatStepLabel(step)}`));
3563
- p15.log.error(err.message ?? "Unknown error");
3701
+ s.stop(pc15.red(`Failed: ${formatStepLabel(step)}`));
3702
+ p16.log.error(err.message ?? "Unknown error");
3564
3703
  }
3565
3704
  }
3566
- p15.outro(pc14.green("All done! Run your dev server to test the new components."));
3705
+ p16.outro(pc15.green("All done! Run your dev server to test the new components."));
3567
3706
  }
3568
- var DEFAULT_SERVICE_URL;
3707
+ var DEFAULT_SERVICE_URL, GLOBAL_REGISTRY_URL;
3569
3708
  var init_chat = __esm({
3570
3709
  "src/commands/chat.ts"() {
3571
3710
  "use strict";
@@ -3573,108 +3712,14 @@ var init_chat = __esm({
3573
3712
  init_fetcher();
3574
3713
  init_config2();
3575
3714
  DEFAULT_SERVICE_URL = "https://chat.kitn.dev";
3576
- }
3577
- });
3578
-
3579
- // src/commands/registry.ts
3580
- var registry_exports = {};
3581
- __export(registry_exports, {
3582
- registryAddCommand: () => registryAddCommand,
3583
- registryListCommand: () => registryListCommand,
3584
- registryRemoveCommand: () => registryRemoveCommand
3585
- });
3586
- import * as p16 from "@clack/prompts";
3587
- import pc15 from "picocolors";
3588
- async function registryAddCommand(namespace, url, opts = {}) {
3589
- const cwd = opts.cwd ?? process.cwd();
3590
- const config2 = await readConfig(cwd);
3591
- if (!config2) throw new Error("No kitn.json found. Run `kitn init` first.");
3592
- if (!namespace.startsWith("@")) {
3593
- throw new Error("Namespace must start with @ (e.g. @myteam)");
3594
- }
3595
- if (!url.includes("{type}")) {
3596
- throw new Error("URL template must include {type} placeholder");
3597
- }
3598
- if (!url.includes("{name}")) {
3599
- throw new Error("URL template must include {name} placeholder");
3600
- }
3601
- if (config2.registries[namespace] && !opts.overwrite) {
3602
- throw new Error(`Registry '${namespace}' is already configured. Use --overwrite to replace.`);
3603
- }
3604
- if (opts.homepage || opts.description) {
3605
- const entry = { url };
3606
- if (opts.homepage) entry.homepage = opts.homepage;
3607
- if (opts.description) entry.description = opts.description;
3608
- config2.registries[namespace] = entry;
3609
- } else {
3610
- config2.registries[namespace] = url;
3611
- }
3612
- await writeConfig(cwd, config2);
3613
- p16.log.success(`Added registry ${pc15.bold(namespace)}`);
3614
- p16.log.message(pc15.dim(` ${url}`));
3615
- if (opts.homepage) p16.log.message(pc15.dim(` Homepage: ${opts.homepage}`));
3616
- if (opts.description) p16.log.message(pc15.dim(` ${opts.description}`));
3617
- }
3618
- async function registryRemoveCommand(namespace, opts = {}) {
3619
- const cwd = opts.cwd ?? process.cwd();
3620
- const config2 = await readConfig(cwd);
3621
- if (!config2) throw new Error("No kitn.json found. Run `kitn init` first.");
3622
- if (!config2.registries[namespace]) {
3623
- throw new Error(`Registry '${namespace}' is not configured.`);
3624
- }
3625
- if (namespace === "@kitn" && !opts.force) {
3626
- throw new Error("Cannot remove the default @kitn registry. Use --force to override.");
3627
- }
3628
- const lock = await readLock(cwd);
3629
- const affectedComponents = [];
3630
- for (const [name, entry] of Object.entries(lock)) {
3631
- if (entry.registry === namespace) {
3632
- affectedComponents.push(name);
3633
- }
3634
- }
3635
- delete config2.registries[namespace];
3636
- await writeConfig(cwd, config2);
3637
- p16.log.success(`Removed registry ${pc15.bold(namespace)}`);
3638
- if (affectedComponents.length > 0) {
3639
- p16.log.warn(`${affectedComponents.length} installed component(s) referenced this registry:
3640
- ` + affectedComponents.map((name) => ` ${pc15.yellow("!")} ${name}`).join("\n"));
3641
- }
3642
- return { affectedComponents };
3643
- }
3644
- async function registryListCommand(opts = {}) {
3645
- const cwd = opts.cwd ?? process.cwd();
3646
- const config2 = await readConfig(cwd);
3647
- if (!config2) throw new Error("No kitn.json found. Run `kitn init` first.");
3648
- const entries = Object.entries(config2.registries).map(([namespace, value]) => {
3649
- const url = getRegistryUrl(value);
3650
- const homepage = typeof value === "object" ? value.homepage : void 0;
3651
- const description = typeof value === "object" ? value.description : void 0;
3652
- return { namespace, url, homepage, description };
3653
- });
3654
- if (entries.length === 0) {
3655
- p16.log.message(pc15.dim(" No registries configured."));
3656
- } else {
3657
- const lines = [];
3658
- for (const { namespace, url, homepage, description } of entries) {
3659
- lines.push(` ${pc15.bold(namespace.padEnd(16))} ${pc15.dim(url)}`);
3660
- if (description) lines.push(` ${" ".repeat(16)} ${description}`);
3661
- if (homepage) lines.push(` ${" ".repeat(16)} ${pc15.dim(homepage)}`);
3662
- }
3663
- p16.log.message(lines.join("\n"));
3664
- }
3665
- return entries;
3666
- }
3667
- var init_registry = __esm({
3668
- "src/commands/registry.ts"() {
3669
- "use strict";
3670
- init_config();
3715
+ GLOBAL_REGISTRY_URL = "https://kitn-ai.github.io/registry/registries.json";
3671
3716
  }
3672
3717
  });
3673
3718
 
3674
3719
  // src/index.ts
3675
3720
  init_update_check();
3676
3721
  import { Command } from "commander";
3677
- var VERSION = true ? "0.1.33" : "0.0.0-dev";
3722
+ var VERSION = true ? "0.1.35" : "0.0.0-dev";
3678
3723
  var printUpdateNotice = startUpdateCheck(VERSION);
3679
3724
  var program = new Command().name("kitn").description("Install AI agent components from the kitn registry").version(VERSION);
3680
3725
  program.command("init").description("Initialize kitn in your project").option("-r, --runtime <runtime>", "runtime to use (bun, node, deno)").option("-f, --framework <framework>", "HTTP framework (hono, hono-openapi, elysia)").option("-b, --base <path>", "base directory for components (default: src/ai)").option("-y, --yes", "accept all defaults without prompting").action(async (opts) => {