@kitnai/cli 0.1.29 → 0.1.31

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
@@ -240,7 +240,8 @@ var init_config = __esm({
240
240
  storage: z.string(),
241
241
  crons: z.string().optional()
242
242
  }),
243
- registries: z.record(z.string(), registryValueSchema)
243
+ registries: z.record(z.string(), registryValueSchema),
244
+ aiTools: z.array(z.string()).optional()
244
245
  });
245
246
  FRAMEWORK_TO_ADAPTER = {
246
247
  hono: "hono",
@@ -1152,14 +1153,33 @@ async function addCommand(components, opts) {
1152
1153
  const label = isExplicit ? item.name : `${item.name} ${pc3.dim("(dependency)")}`;
1153
1154
  return ` ${pc3.cyan(label)}`;
1154
1155
  }).join("\n"));
1155
- const created = [];
1156
- const updated = [];
1157
- const skipped = [];
1158
1156
  const allDeps = [];
1159
1157
  const allDevDeps = [];
1160
1158
  for (const item of resolved) {
1161
1159
  if (item.dependencies) allDeps.push(...item.dependencies);
1162
1160
  if (item.devDependencies) allDevDeps.push(...item.devDependencies);
1161
+ }
1162
+ const uniqueDeps = [...new Set(allDeps)];
1163
+ const uniqueDevDeps = [...new Set(allDevDeps)].filter((d) => !uniqueDeps.includes(d));
1164
+ const totalDeps = uniqueDeps.length + uniqueDevDeps.length;
1165
+ if (totalDeps > 0) {
1166
+ const depLines = uniqueDeps.map((d) => ` ${pc3.cyan(d)}`);
1167
+ const devDepLines = uniqueDevDeps.map((d) => ` ${pc3.dim(d)}`);
1168
+ p2.log.info("Dependencies:\n" + [...depLines, ...devDepLines].join("\n"));
1169
+ }
1170
+ if (!opts.yes && process.stdin.isTTY) {
1171
+ const totalComponents = resolved.length;
1172
+ const summary = totalDeps > 0 ? `Install ${totalComponents} component(s) and ${totalDeps} npm package(s)?` : `Install ${totalComponents} component(s)?`;
1173
+ const confirm6 = await p2.confirm({ message: summary });
1174
+ if (p2.isCancel(confirm6) || !confirm6) {
1175
+ p2.cancel("Cancelled.");
1176
+ process.exit(0);
1177
+ }
1178
+ }
1179
+ const created = [];
1180
+ const updated = [];
1181
+ const skipped = [];
1182
+ for (const item of resolved) {
1163
1183
  const existingInstall = lock[item.name];
1164
1184
  if (existingInstall && item.type === "kitn:package") {
1165
1185
  const allContent = item.files.map((f) => f.content).join("\n");
@@ -1339,9 +1359,6 @@ async function addCommand(components, opts) {
1339
1359
  }
1340
1360
  await writeConfig(cwd, config);
1341
1361
  await writeLock(cwd, lock);
1342
- const uniqueDeps = [...new Set(allDeps)];
1343
- const uniqueDevDeps = [...new Set(allDevDeps)].filter((d) => !uniqueDeps.includes(d));
1344
- const totalDeps = uniqueDeps.length + uniqueDevDeps.length;
1345
1362
  if (totalDeps > 0) {
1346
1363
  const pm = await detectPackageManager(cwd);
1347
1364
  if (pm) {
@@ -1405,6 +1422,145 @@ var init_add = __esm({
1405
1422
  }
1406
1423
  });
1407
1424
 
1425
+ // src/installers/rules-generator.ts
1426
+ import { writeFile as writeFile7, mkdir as mkdir4 } from "fs/promises";
1427
+ import { join as join8, dirname as dirname4 } from "path";
1428
+ function deriveRulesBaseUrl(registries) {
1429
+ const kitnEntry = registries["@kitn"];
1430
+ if (!kitnEntry) {
1431
+ throw new Error("No @kitn registry configured");
1432
+ }
1433
+ const url = getRegistryUrl(kitnEntry);
1434
+ return url.replace("{type}/{name}.json", "rules/");
1435
+ }
1436
+ async function fetchRulesConfig(registries) {
1437
+ try {
1438
+ const baseUrl = deriveRulesBaseUrl(registries);
1439
+ const res = await fetch(baseUrl + "config.json");
1440
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
1441
+ return await res.json();
1442
+ } catch {
1443
+ return FALLBACK_CONFIG;
1444
+ }
1445
+ }
1446
+ async function fetchRulesTemplate(registries) {
1447
+ try {
1448
+ const baseUrl = deriveRulesBaseUrl(registries);
1449
+ const res = await fetch(baseUrl + "template.md");
1450
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
1451
+ return await res.text();
1452
+ } catch {
1453
+ return FALLBACK_TEMPLATE;
1454
+ }
1455
+ }
1456
+ function renderTemplate(template, aliases) {
1457
+ const base = aliases.base ?? "src/ai";
1458
+ return template.replace(/\{base\}/g, base).replace(/\{agents\}/g, aliases.agents).replace(/\{tools\}/g, aliases.tools).replace(/\{skills\}/g, aliases.skills).replace(/\{storage\}/g, aliases.storage).replace(/\{crons\}/g, aliases.crons ?? `${base}/crons`);
1459
+ }
1460
+ function wrapContent(content, tool) {
1461
+ if (tool.format === "mdc" && tool.frontmatter) {
1462
+ const lines = Object.entries(tool.frontmatter).map(
1463
+ ([key, value]) => `${key}: ${value}`
1464
+ );
1465
+ return `---
1466
+ ${lines.join("\n")}
1467
+ ---
1468
+
1469
+ ${content}`;
1470
+ }
1471
+ return content;
1472
+ }
1473
+ async function generateRulesFiles(cwd, config, selectedToolIds) {
1474
+ const rulesConfig = await fetchRulesConfig(config.registries);
1475
+ const template = await fetchRulesTemplate(config.registries);
1476
+ const rendered = renderTemplate(template, config.aliases);
1477
+ const toolsToWrite = selectedToolIds ? rulesConfig.tools.filter((t) => selectedToolIds.includes(t.id)) : rulesConfig.tools;
1478
+ const written = [];
1479
+ for (const tool of toolsToWrite) {
1480
+ const content = wrapContent(rendered, tool);
1481
+ const filePath = join8(cwd, tool.filePath);
1482
+ await mkdir4(dirname4(filePath), { recursive: true });
1483
+ await writeFile7(filePath, content);
1484
+ written.push(tool.filePath);
1485
+ }
1486
+ return written;
1487
+ }
1488
+ var FALLBACK_CONFIG, FALLBACK_TEMPLATE;
1489
+ var init_rules_generator = __esm({
1490
+ "src/installers/rules-generator.ts"() {
1491
+ "use strict";
1492
+ init_config();
1493
+ FALLBACK_CONFIG = {
1494
+ version: "1.0.0",
1495
+ tools: [
1496
+ {
1497
+ id: "claude-code",
1498
+ name: "Claude Code",
1499
+ filePath: "AGENTS.md",
1500
+ format: "plain",
1501
+ description: "Also works with any tool that reads AGENTS.md"
1502
+ },
1503
+ {
1504
+ id: "cursor",
1505
+ name: "Cursor",
1506
+ filePath: ".cursor/rules/kitn.mdc",
1507
+ format: "mdc",
1508
+ frontmatter: {
1509
+ description: "kitn AI agent framework conventions and patterns",
1510
+ globs: "src/ai/**/*.ts, kitn.json"
1511
+ }
1512
+ },
1513
+ {
1514
+ id: "github-copilot",
1515
+ name: "GitHub Copilot",
1516
+ filePath: ".github/copilot-instructions.md",
1517
+ format: "plain"
1518
+ },
1519
+ {
1520
+ id: "cline",
1521
+ name: "Cline",
1522
+ filePath: ".clinerules",
1523
+ format: "plain"
1524
+ },
1525
+ {
1526
+ id: "windsurf",
1527
+ name: "Windsurf",
1528
+ filePath: ".windsurfrules",
1529
+ format: "plain"
1530
+ }
1531
+ ]
1532
+ };
1533
+ FALLBACK_TEMPLATE = `# kitn AI Agent Framework
1534
+
1535
+ This project uses **kitn** to build multi-agent AI systems.
1536
+
1537
+ ## Project Structure
1538
+
1539
+ AI components live under \`{base}\`:
1540
+
1541
+ - \`{agents}/\` \u2014 Agent definitions
1542
+ - \`{tools}/\` \u2014 Tool definitions
1543
+ - \`{skills}/\` \u2014 Skill files (markdown)
1544
+ - \`{storage}/\` \u2014 Storage providers
1545
+ - \`{crons}/\` \u2014 Cron job definitions
1546
+
1547
+ ## Patterns
1548
+
1549
+ - Agents: \`registerAgent({ name, system, tools })\` from \`@kitn/core\`
1550
+ - Tools: \`tool()\` from \`ai\` + \`registerTool()\` from \`@kitn/core\`
1551
+ - Always use \`.js\` extension in relative imports
1552
+ - Use \`@kitn/core\` for core imports, \`ai\` for Vercel AI SDK
1553
+
1554
+ ## CLI
1555
+
1556
+ - \`kitn add <name>\` \u2014 install from registry
1557
+ - \`kitn create <type> <name>\` \u2014 scaffold locally
1558
+ - \`kitn link tool <name> --to <agent>\` \u2014 wire a tool to an agent
1559
+ - \`kitn list\` \u2014 browse components
1560
+ `;
1561
+ }
1562
+ });
1563
+
1408
1564
  // src/commands/init.ts
1409
1565
  var init_exports = {};
1410
1566
  __export(init_exports, {
@@ -1412,8 +1568,8 @@ __export(init_exports, {
1412
1568
  });
1413
1569
  import * as p3 from "@clack/prompts";
1414
1570
  import pc4 from "picocolors";
1415
- import { mkdir as mkdir4, writeFile as writeFile7 } from "fs/promises";
1416
- import { join as join8 } from "path";
1571
+ import { mkdir as mkdir5, writeFile as writeFile8 } from "fs/promises";
1572
+ import { join as join9 } from "path";
1417
1573
  function getPluginTemplate(framework) {
1418
1574
  const adapterName = framework === "hono-openapi" ? "hono-openapi" : framework;
1419
1575
  return `import { createAIPlugin } from "@kitn/adapters/${adapterName}";
@@ -1551,13 +1707,45 @@ async function initCommand(opts = {}) {
1551
1707
  p3.log.info(`Patched tsconfig.json with path: ${pc4.bold("@kitn/*")}`);
1552
1708
  p3.log.info("Installing core engine and adapter...");
1553
1709
  await addCommand(["core", "routes"], { overwrite: true });
1554
- const aiDir = join8(cwd, baseDir);
1555
- await mkdir4(aiDir, { recursive: true });
1556
- const barrelPath = join8(aiDir, "index.ts");
1557
- await writeFile7(barrelPath, createBarrelFile());
1558
- const pluginPath = join8(aiDir, "plugin.ts");
1559
- await writeFile7(pluginPath, getPluginTemplate(framework));
1710
+ const aiDir = join9(cwd, baseDir);
1711
+ await mkdir5(aiDir, { recursive: true });
1712
+ const barrelPath = join9(aiDir, "index.ts");
1713
+ await writeFile8(barrelPath, createBarrelFile());
1714
+ const pluginPath = join9(aiDir, "plugin.ts");
1715
+ await writeFile8(pluginPath, getPluginTemplate(framework));
1560
1716
  p3.log.success(`Created ${pc4.bold(baseDir + "/plugin.ts")} \u2014 configure your AI provider there`);
1717
+ try {
1718
+ const rulesConfig = await fetchRulesConfig(config.registries);
1719
+ let selectedToolIds;
1720
+ if (opts.yes) {
1721
+ selectedToolIds = rulesConfig.tools.map((t) => t.id);
1722
+ } else {
1723
+ const selected = await p3.multiselect({
1724
+ message: "Which AI coding tools do you use?",
1725
+ options: rulesConfig.tools.map((t) => ({
1726
+ value: t.id,
1727
+ label: t.name,
1728
+ hint: t.description
1729
+ })),
1730
+ required: false
1731
+ });
1732
+ if (p3.isCancel(selected)) {
1733
+ selectedToolIds = [];
1734
+ } else {
1735
+ selectedToolIds = selected;
1736
+ }
1737
+ }
1738
+ if (selectedToolIds.length > 0) {
1739
+ const updatedConfig = { ...config, aiTools: selectedToolIds };
1740
+ await writeConfig(cwd, updatedConfig);
1741
+ const written = await generateRulesFiles(cwd, updatedConfig, selectedToolIds);
1742
+ for (const filePath of written) {
1743
+ p3.log.success(`Created ${pc4.bold(filePath)}`);
1744
+ }
1745
+ }
1746
+ } catch {
1747
+ p3.log.warn("Could not generate AI coding tool rules (non-fatal).");
1748
+ }
1561
1749
  const mountCode = framework === "elysia" ? `app.use(new Elysia({ prefix: "/api" }).use(ai.router));` : `app.route("/api", ai.router);`;
1562
1750
  p3.note(
1563
1751
  [
@@ -1585,6 +1773,7 @@ var init_init = __esm({
1585
1773
  init_tsconfig_patcher();
1586
1774
  init_barrel_manager();
1587
1775
  init_add();
1776
+ init_rules_generator();
1588
1777
  }
1589
1778
  });
1590
1779
 
@@ -1720,7 +1909,7 @@ __export(diff_exports, {
1720
1909
  diffCommand: () => diffCommand
1721
1910
  });
1722
1911
  import * as p5 from "@clack/prompts";
1723
- import { join as join9 } from "path";
1912
+ import { join as join10 } from "path";
1724
1913
  async function diffCommand(componentName) {
1725
1914
  const cwd = process.cwd();
1726
1915
  const config = await readConfig(cwd);
@@ -1751,8 +1940,8 @@ async function diffCommand(componentName) {
1751
1940
  for (const file of registryItem.files) {
1752
1941
  if (indexItem.type === "kitn:package") {
1753
1942
  const baseDir = config.aliases.base ?? "src/ai";
1754
- const localPath = join9(cwd, baseDir, file.path);
1755
- const relativePath = join9(baseDir, file.path);
1943
+ const localPath = join10(cwd, baseDir, file.path);
1944
+ const relativePath = join10(baseDir, file.path);
1756
1945
  const localContent = await readExistingFile(localPath);
1757
1946
  if (localContent === null) {
1758
1947
  p5.log.warn(`${relativePath}: file missing locally`);
@@ -1776,7 +1965,7 @@ async function diffCommand(componentName) {
1776
1965
  return "storage";
1777
1966
  }
1778
1967
  })();
1779
- const localPath = join9(cwd, config.aliases[aliasKey], fileName);
1968
+ const localPath = join10(cwd, config.aliases[aliasKey], fileName);
1780
1969
  const localContent = await readExistingFile(localPath);
1781
1970
  if (localContent === null) {
1782
1971
  p5.log.warn(`${fileName}: file missing locally`);
@@ -1810,8 +1999,8 @@ __export(remove_exports, {
1810
1999
  });
1811
2000
  import * as p6 from "@clack/prompts";
1812
2001
  import pc6 from "picocolors";
1813
- import { join as join10, relative as relative3, dirname as dirname4 } from "path";
1814
- import { unlink as unlink3, readFile as readFile7, writeFile as writeFile8 } from "fs/promises";
2002
+ import { join as join11, relative as relative3, dirname as dirname5 } from "path";
2003
+ import { unlink as unlink3, readFile as readFile7, writeFile as writeFile9 } from "fs/promises";
1815
2004
  import { existsSync as existsSync2 } from "fs";
1816
2005
  async function removeSingleComponent(installedKey, lock, config, cwd) {
1817
2006
  const entry = lock[installedKey];
@@ -1819,15 +2008,15 @@ async function removeSingleComponent(installedKey, lock, config, cwd) {
1819
2008
  const deleted = [];
1820
2009
  for (const filePath of entry.files) {
1821
2010
  try {
1822
- await unlink3(join10(cwd, filePath));
2011
+ await unlink3(join11(cwd, filePath));
1823
2012
  deleted.push(filePath);
1824
2013
  } catch {
1825
2014
  p6.log.warn(`Could not delete ${filePath} (may have been moved or renamed)`);
1826
2015
  }
1827
2016
  }
1828
2017
  const baseDir = config.aliases.base ?? "src/ai";
1829
- const barrelPath = join10(cwd, baseDir, "index.ts");
1830
- const barrelDir = join10(cwd, baseDir);
2018
+ const barrelPath = join11(cwd, baseDir, "index.ts");
2019
+ const barrelDir = join11(cwd, baseDir);
1831
2020
  const barrelEligibleDirs = /* @__PURE__ */ new Set([
1832
2021
  config.aliases.agents,
1833
2022
  config.aliases.tools,
@@ -1837,9 +2026,9 @@ async function removeSingleComponent(installedKey, lock, config, cwd) {
1837
2026
  let barrelContent = await readFile7(barrelPath, "utf-8");
1838
2027
  let barrelChanged = false;
1839
2028
  for (const filePath of deleted) {
1840
- const fileDir = dirname4(filePath);
2029
+ const fileDir = dirname5(filePath);
1841
2030
  if (!barrelEligibleDirs.has(fileDir)) continue;
1842
- const importPath = "./" + relative3(barrelDir, join10(cwd, filePath)).replace(/\\/g, "/");
2031
+ const importPath = "./" + relative3(barrelDir, join11(cwd, filePath)).replace(/\\/g, "/");
1843
2032
  const updated = removeImportFromBarrel(barrelContent, importPath);
1844
2033
  if (updated !== barrelContent) {
1845
2034
  barrelContent = updated;
@@ -1847,8 +2036,8 @@ async function removeSingleComponent(installedKey, lock, config, cwd) {
1847
2036
  }
1848
2037
  }
1849
2038
  if (barrelChanged) {
1850
- await writeFile8(barrelPath, barrelContent);
1851
- p6.log.info(`Updated barrel file: ${join10(baseDir, "index.ts")}`);
2039
+ await writeFile9(barrelPath, barrelContent);
2040
+ p6.log.info(`Updated barrel file: ${join11(baseDir, "index.ts")}`);
1852
2041
  }
1853
2042
  }
1854
2043
  delete lock[installedKey];
@@ -1996,6 +2185,19 @@ var init_update = __esm({
1996
2185
  }
1997
2186
  });
1998
2187
 
2188
+ // src/utils/naming.ts
2189
+ function toCamelCase(str) {
2190
+ return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
2191
+ }
2192
+ function toTitleCase(str) {
2193
+ return str.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
2194
+ }
2195
+ var init_naming = __esm({
2196
+ "src/utils/naming.ts"() {
2197
+ "use strict";
2198
+ }
2199
+ });
2200
+
1999
2201
  // src/commands/create.ts
2000
2202
  var create_exports = {};
2001
2203
  __export(create_exports, {
@@ -2004,15 +2206,9 @@ __export(create_exports, {
2004
2206
  });
2005
2207
  import * as p8 from "@clack/prompts";
2006
2208
  import pc7 from "picocolors";
2007
- import { join as join11, relative as relative4 } from "path";
2209
+ import { join as join12, relative as relative4 } from "path";
2008
2210
  import { existsSync as existsSync3 } from "fs";
2009
- import { readFile as readFile8, writeFile as writeFile9, mkdir as mkdir5 } from "fs/promises";
2010
- function toCamelCase(str) {
2011
- return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
2012
- }
2013
- function toTitleCase(str) {
2014
- return str.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
2015
- }
2211
+ import { readFile as readFile8, writeFile as writeFile10, mkdir as mkdir6 } from "fs/promises";
2016
2212
  function generateAgentSource(name) {
2017
2213
  const camel = toCamelCase(name);
2018
2214
  return `import { registerAgent } from "@kitn/core";
@@ -2119,7 +2315,7 @@ async function createComponentInProject(type, name, opts) {
2119
2315
  const validType = type;
2120
2316
  const kitnType = typeToKitnType[validType];
2121
2317
  const fileName = validType === "skill" ? `${name}.md` : `${name}.ts`;
2122
- const filePath = join11(cwd, getInstallPath(config, kitnType, fileName));
2318
+ const filePath = join12(cwd, getInstallPath(config, kitnType, fileName));
2123
2319
  const dummyContent = "";
2124
2320
  const status = await checkFileStatus(filePath, dummyContent);
2125
2321
  if (status !== "new" /* New */) {
@@ -2147,18 +2343,18 @@ async function createComponentInProject(type, name, opts) {
2147
2343
  let barrelUpdated = false;
2148
2344
  if (BARREL_TYPES.includes(validType)) {
2149
2345
  const baseDir = config.aliases.base ?? "src/ai";
2150
- const barrelPath = join11(cwd, baseDir, "index.ts");
2346
+ const barrelPath = join12(cwd, baseDir, "index.ts");
2151
2347
  let barrelContent;
2152
2348
  if (existsSync3(barrelPath)) {
2153
2349
  barrelContent = await readFile8(barrelPath, "utf-8");
2154
2350
  } else {
2155
2351
  barrelContent = createBarrelFile();
2156
- await mkdir5(join11(cwd, baseDir), { recursive: true });
2352
+ await mkdir6(join12(cwd, baseDir), { recursive: true });
2157
2353
  }
2158
- const importPath = "./" + relative4(join11(cwd, baseDir), filePath).replace(/\.ts$/, ".js");
2354
+ const importPath = "./" + relative4(join12(cwd, baseDir), filePath).replace(/\.ts$/, ".js");
2159
2355
  const updatedBarrel = addImportToBarrel(barrelContent, importPath);
2160
2356
  if (updatedBarrel !== barrelContent) {
2161
- await writeFile9(barrelPath, updatedBarrel);
2357
+ await writeFile10(barrelPath, updatedBarrel);
2162
2358
  barrelUpdated = true;
2163
2359
  }
2164
2360
  }
@@ -2186,6 +2382,7 @@ var init_create = __esm({
2186
2382
  "src/commands/create.ts"() {
2187
2383
  "use strict";
2188
2384
  init_config();
2385
+ init_naming();
2189
2386
  init_file_writer();
2190
2387
  init_barrel_manager();
2191
2388
  VALID_TYPES = ["agent", "tool", "skill", "storage", "cron"];
@@ -2200,36 +2397,661 @@ var init_create = __esm({
2200
2397
  }
2201
2398
  });
2202
2399
 
2400
+ // src/utils/component-resolver.ts
2401
+ import { readdir, readFile as readFile9 } from "fs/promises";
2402
+ import { join as join13, relative as relative5 } from "path";
2403
+ function stripSuffix(name, suffix) {
2404
+ if (name.endsWith(`-${suffix}`)) {
2405
+ return name.slice(0, -(suffix.length + 1));
2406
+ }
2407
+ return name;
2408
+ }
2409
+ function toolsDir(config, cwd) {
2410
+ const baseAlias = config.aliases.base ?? "src/ai";
2411
+ const tools = config.aliases.tools ?? join13(baseAlias, "tools");
2412
+ return join13(cwd, tools);
2413
+ }
2414
+ function agentsDir(config, cwd) {
2415
+ const baseAlias = config.aliases.base ?? "src/ai";
2416
+ const agents = config.aliases.agents ?? join13(baseAlias, "agents");
2417
+ return join13(cwd, agents);
2418
+ }
2419
+ async function findFile(dir, candidates) {
2420
+ for (const name of candidates) {
2421
+ const filePath = join13(dir, `${name}.ts`);
2422
+ try {
2423
+ await readFile9(filePath);
2424
+ return filePath;
2425
+ } catch {
2426
+ }
2427
+ }
2428
+ return null;
2429
+ }
2430
+ function parseExportName(source) {
2431
+ const match = source.match(/export\s+const\s+(\w+)/);
2432
+ return match ? match[1] : null;
2433
+ }
2434
+ function parseAgentName(source) {
2435
+ const match = source.match(/registerAgent\s*\(\s*\{[^}]*name:\s*"([^"]+)"/s);
2436
+ return match ? match[1] : null;
2437
+ }
2438
+ function computeImportPath(fromDir, toFile) {
2439
+ let rel = relative5(fromDir, toFile);
2440
+ if (!rel.startsWith(".")) {
2441
+ rel = `./${rel}`;
2442
+ }
2443
+ return rel.replace(/\.ts$/, ".js");
2444
+ }
2445
+ async function resolveToolByName(name, config, cwd) {
2446
+ const tDir = toolsDir(config, cwd);
2447
+ const aDir = agentsDir(config, cwd);
2448
+ const lock = await readLock(cwd);
2449
+ for (const [componentName, entry] of Object.entries(lock)) {
2450
+ if (componentName === name || componentName === `${name}-tool`) {
2451
+ const toolFile = entry.files.find((f) => {
2452
+ const toolsAlias = config.aliases.tools ?? join13(config.aliases.base ?? "src/ai", "tools");
2453
+ return f.startsWith(toolsAlias);
2454
+ });
2455
+ if (toolFile) {
2456
+ const filePath2 = join13(cwd, toolFile);
2457
+ try {
2458
+ const source2 = await readFile9(filePath2, "utf-8");
2459
+ const exportName2 = parseExportName(source2);
2460
+ if (exportName2) {
2461
+ return {
2462
+ filePath: filePath2,
2463
+ exportName: exportName2,
2464
+ importPath: computeImportPath(aDir, filePath2)
2465
+ };
2466
+ }
2467
+ } catch {
2468
+ }
2469
+ }
2470
+ }
2471
+ }
2472
+ const candidates = [name, stripSuffix(name, "tool")];
2473
+ const uniqueCandidates = [...new Set(candidates)];
2474
+ const filePath = await findFile(tDir, uniqueCandidates);
2475
+ if (!filePath) return null;
2476
+ const source = await readFile9(filePath, "utf-8");
2477
+ const exportName = parseExportName(source);
2478
+ if (!exportName) return null;
2479
+ return {
2480
+ filePath,
2481
+ exportName,
2482
+ importPath: computeImportPath(aDir, filePath)
2483
+ };
2484
+ }
2485
+ async function resolveAgentByName(name, config, cwd) {
2486
+ const aDir = agentsDir(config, cwd);
2487
+ const lock = await readLock(cwd);
2488
+ for (const [componentName, entry] of Object.entries(lock)) {
2489
+ if (componentName === name || componentName === `${name}-agent`) {
2490
+ const agentFile = entry.files.find((f) => {
2491
+ const agentsAlias = config.aliases.agents ?? join13(config.aliases.base ?? "src/ai", "agents");
2492
+ return f.startsWith(agentsAlias);
2493
+ });
2494
+ if (agentFile) {
2495
+ const filePath2 = join13(cwd, agentFile);
2496
+ try {
2497
+ const source2 = await readFile9(filePath2, "utf-8");
2498
+ const agentName2 = parseAgentName(source2);
2499
+ return {
2500
+ filePath: filePath2,
2501
+ name: agentName2 ?? componentName
2502
+ };
2503
+ } catch {
2504
+ }
2505
+ }
2506
+ }
2507
+ }
2508
+ const candidates = [name, stripSuffix(name, "agent")];
2509
+ const uniqueCandidates = [...new Set(candidates)];
2510
+ const filePath = await findFile(aDir, uniqueCandidates);
2511
+ if (!filePath) return null;
2512
+ const source = await readFile9(filePath, "utf-8");
2513
+ const agentName = parseAgentName(source);
2514
+ const fallbackName = filePath.split("/").pop().replace(/\.ts$/, "");
2515
+ return {
2516
+ filePath,
2517
+ name: agentName ?? fallbackName
2518
+ };
2519
+ }
2520
+ async function listTools(config, cwd) {
2521
+ const dir = toolsDir(config, cwd);
2522
+ return listComponentsInDir(dir);
2523
+ }
2524
+ async function listAgents(config, cwd) {
2525
+ const dir = agentsDir(config, cwd);
2526
+ return listComponentsInDir(dir);
2527
+ }
2528
+ async function listComponentsInDir(dir) {
2529
+ try {
2530
+ const entries = await readdir(dir);
2531
+ return entries.filter((f) => f.endsWith(".ts") && !f.endsWith(".test.ts") && !f.endsWith(".d.ts")).sort().map((f) => ({
2532
+ name: f.replace(/\.ts$/, ""),
2533
+ filePath: join13(dir, f)
2534
+ }));
2535
+ } catch {
2536
+ return [];
2537
+ }
2538
+ }
2539
+ var init_component_resolver = __esm({
2540
+ "src/utils/component-resolver.ts"() {
2541
+ "use strict";
2542
+ init_config();
2543
+ }
2544
+ });
2545
+
2546
+ // src/installers/agent-linker.ts
2547
+ function linkToolToAgent(content, tool, toolKey) {
2548
+ const key = toolKey ?? tool.exportName;
2549
+ const { exportName, importPath } = tool;
2550
+ const toolEntry = key === exportName ? exportName : `${key}: ${exportName}`;
2551
+ if (hasToolEntry(content, key)) {
2552
+ return { content, changed: false };
2553
+ }
2554
+ const importLine = `import { ${exportName} } from "${importPath}";`;
2555
+ let result = content;
2556
+ if (!result.includes(importLine)) {
2557
+ const lines = result.split("\n");
2558
+ let lastImportIndex = -1;
2559
+ for (let i = 0; i < lines.length; i++) {
2560
+ if (/^import\s+/.test(lines[i])) lastImportIndex = i;
2561
+ }
2562
+ if (lastImportIndex === -1) {
2563
+ result = `${importLine}
2564
+ ${result}`;
2565
+ } else {
2566
+ lines.splice(lastImportIndex + 1, 0, importLine);
2567
+ result = lines.join("\n");
2568
+ }
2569
+ }
2570
+ const insertResult = insertToolEntry(result, key, exportName);
2571
+ if (insertResult.error) {
2572
+ return {
2573
+ content,
2574
+ changed: false,
2575
+ error: `Could not auto-modify the agent file. Add manually:
2576
+ 1. Import: ${importLine}
2577
+ 2. Add to tools: { ${toolEntry} }`
2578
+ };
2579
+ }
2580
+ return { content: insertResult.content, changed: true };
2581
+ }
2582
+ function unlinkToolFromAgent(content, tool, toolKey) {
2583
+ const key = toolKey ?? tool.exportName;
2584
+ const { exportName, importPath } = tool;
2585
+ if (!hasToolEntry(content, key)) {
2586
+ return { content, changed: false };
2587
+ }
2588
+ const removeResult = removeToolEntry(content, key, exportName);
2589
+ if (removeResult.error) {
2590
+ return {
2591
+ content,
2592
+ changed: false,
2593
+ error: `Could not auto-modify the agent file. Remove manually:
2594
+ 1. Remove from tools: ${key === exportName ? key : `${key}: ${exportName}`}
2595
+ 2. Remove import if unused: import { ${exportName} } from "${importPath}";`
2596
+ };
2597
+ }
2598
+ let result = removeResult.content;
2599
+ if (!isExportNameReferenced(result, exportName)) {
2600
+ result = removeImportLine(result, exportName, importPath);
2601
+ }
2602
+ return { content: result, changed: true };
2603
+ }
2604
+ function hasToolEntry(content, key) {
2605
+ const toolsMatch = extractToolsBlock(content);
2606
+ if (!toolsMatch) return false;
2607
+ const toolsContent = toolsMatch.inner;
2608
+ const keyPattern = new RegExp(
2609
+ `(?:^|[,{\\s])${escapeRegex(key)}(?:\\s*[:,}\\s]|$)`
2610
+ );
2611
+ return keyPattern.test(toolsContent);
2612
+ }
2613
+ function extractToolsBlock(content) {
2614
+ const singleLine = /^([ \t]*)tools\s*:\s*\{([^}]*)\}/m;
2615
+ const singleMatch = singleLine.exec(content);
2616
+ if (singleMatch) {
2617
+ return {
2618
+ full: singleMatch[0],
2619
+ inner: singleMatch[2],
2620
+ startIndex: singleMatch.index,
2621
+ indent: singleMatch[1]
2622
+ };
2623
+ }
2624
+ const multiStart = /^([ \t]*)tools\s*:\s*\{/m;
2625
+ const multiMatch = multiStart.exec(content);
2626
+ if (!multiMatch) return null;
2627
+ const braceStart = multiMatch.index + multiMatch[0].length;
2628
+ let depth = 1;
2629
+ let i = braceStart;
2630
+ while (i < content.length && depth > 0) {
2631
+ if (content[i] === "{") depth++;
2632
+ else if (content[i] === "}") depth--;
2633
+ i++;
2634
+ }
2635
+ if (depth !== 0) return null;
2636
+ const full = content.slice(multiMatch.index, i);
2637
+ const inner = content.slice(braceStart, i - 1);
2638
+ return {
2639
+ full,
2640
+ inner,
2641
+ startIndex: multiMatch.index,
2642
+ indent: multiMatch[1]
2643
+ };
2644
+ }
2645
+ function insertToolEntry(content, key, exportName) {
2646
+ const block = extractToolsBlock(content);
2647
+ if (!block) return { content, error: "no tools block found" };
2648
+ const entry = key === exportName ? key : `${key}: ${exportName}`;
2649
+ const trimmedInner = block.inner.trim();
2650
+ let newToolsContent;
2651
+ if (trimmedInner === "") {
2652
+ newToolsContent = `tools: { ${entry} }`;
2653
+ } else if (!block.inner.includes("\n")) {
2654
+ const cleaned = trimmedInner.replace(/,?\s*$/, "");
2655
+ newToolsContent = `tools: { ${cleaned}, ${entry} }`;
2656
+ } else {
2657
+ const entryIndent = block.indent + " ";
2658
+ const existingTrimmed = block.inner.trimEnd();
2659
+ const withComma = existingTrimmed.endsWith(",") ? existingTrimmed : existingTrimmed + ",";
2660
+ newToolsContent = `tools: {
2661
+ ${withComma}
2662
+ ${entryIndent}${entry},
2663
+ ${block.indent}}`;
2664
+ }
2665
+ const newContent = content.replace(block.full, newToolsContent);
2666
+ return { content: newContent };
2667
+ }
2668
+ function removeToolEntry(content, key, exportName) {
2669
+ const block = extractToolsBlock(content);
2670
+ if (!block) return { content, error: "no tools block found" };
2671
+ const trimmedInner = block.inner.trim();
2672
+ if (!block.inner.includes("\n")) {
2673
+ const entries = trimmedInner.split(",").map((e) => e.trim()).filter((e) => e !== "");
2674
+ const filtered = entries.filter((e) => {
2675
+ const colonIdx = e.indexOf(":");
2676
+ const entryKey = colonIdx >= 0 ? e.slice(0, colonIdx).trim() : e.trim();
2677
+ return entryKey !== key;
2678
+ });
2679
+ const newInner = filtered.length === 0 ? "" : ` ${filtered.join(", ")} `;
2680
+ const newBlock = `tools: {${newInner}}`;
2681
+ return { content: content.replace(block.full, newBlock) };
2682
+ } else {
2683
+ const lines = block.inner.split("\n");
2684
+ const keyPattern = new RegExp(
2685
+ `^\\s*${escapeRegex(key)}\\s*(?::|,|$)`
2686
+ );
2687
+ const filtered = lines.filter((line) => !keyPattern.test(line));
2688
+ const hasEntries = filtered.some((l) => l.trim() !== "");
2689
+ if (!hasEntries) {
2690
+ const newBlock2 = `tools: {}`;
2691
+ return { content: content.replace(block.full, newBlock2) };
2692
+ }
2693
+ const cleanedLines = filtered.slice();
2694
+ for (let i = cleanedLines.length - 1; i >= 0; i--) {
2695
+ if (cleanedLines[i].trim() !== "") {
2696
+ if (!cleanedLines[i].trimEnd().endsWith(",")) {
2697
+ cleanedLines[i] = cleanedLines[i].trimEnd() + ",";
2698
+ }
2699
+ break;
2700
+ }
2701
+ }
2702
+ const newBlock = `tools: {
2703
+ ${cleanedLines.join("\n")}
2704
+ ${block.indent}}`;
2705
+ return { content: content.replace(block.full, newBlock) };
2706
+ }
2707
+ }
2708
+ function isExportNameReferenced(content, exportName) {
2709
+ const lines = content.split("\n");
2710
+ for (const line of lines) {
2711
+ if (/^import\s+/.test(line)) continue;
2712
+ const wordPattern = new RegExp(`\\b${escapeRegex(exportName)}\\b`);
2713
+ if (wordPattern.test(line)) return true;
2714
+ }
2715
+ return false;
2716
+ }
2717
+ function removeImportLine(content, exportName, importPath) {
2718
+ const lines = content.split("\n");
2719
+ const result = [];
2720
+ for (let i = 0; i < lines.length; i++) {
2721
+ const line = lines[i];
2722
+ const singleImportMatch = line.match(
2723
+ /^import\s*\{([^}]+)\}\s*from\s*["'](.+?)["']\s*;?\s*$/
2724
+ );
2725
+ if (singleImportMatch && singleImportMatch[2] === importPath) {
2726
+ const imports = singleImportMatch[1].split(",").map((s) => s.trim()).filter((s) => s !== "");
2727
+ if (imports.length === 1 && imports[0] === exportName) {
2728
+ if (i + 1 < lines.length && lines[i + 1].trim() === "") {
2729
+ i++;
2730
+ }
2731
+ continue;
2732
+ }
2733
+ const remaining = imports.filter((s) => s !== exportName);
2734
+ result.push(
2735
+ `import { ${remaining.join(", ")} } from "${importPath}";`
2736
+ );
2737
+ continue;
2738
+ }
2739
+ if (/^import\s*\{/.test(line) && !line.includes("}") && content.includes(importPath)) {
2740
+ const importLines = [line];
2741
+ let j = i + 1;
2742
+ while (j < lines.length && !lines[j].includes("}")) {
2743
+ importLines.push(lines[j]);
2744
+ j++;
2745
+ }
2746
+ if (j < lines.length) {
2747
+ importLines.push(lines[j]);
2748
+ }
2749
+ const fullImport = importLines.join("\n");
2750
+ if (fullImport.includes(importPath)) {
2751
+ const namesMatch = fullImport.match(/\{([^}]+)\}/);
2752
+ if (namesMatch) {
2753
+ const imports = namesMatch[1].split(",").map((s) => s.trim()).filter((s) => s !== "");
2754
+ if (imports.length === 1 && imports[0] === exportName) {
2755
+ i = j;
2756
+ if (i + 1 < lines.length && lines[i + 1].trim() === "") {
2757
+ i++;
2758
+ }
2759
+ continue;
2760
+ }
2761
+ const remaining = imports.filter((s) => s !== exportName);
2762
+ if (remaining.length <= 2) {
2763
+ result.push(
2764
+ `import { ${remaining.join(", ")} } from "${importPath}";`
2765
+ );
2766
+ } else {
2767
+ result.push(`import {`);
2768
+ remaining.forEach((name, idx) => {
2769
+ result.push(
2770
+ ` ${name}${idx < remaining.length - 1 ? "," : ""}`
2771
+ );
2772
+ });
2773
+ result.push(`} from "${importPath}";`);
2774
+ }
2775
+ i = j;
2776
+ continue;
2777
+ }
2778
+ }
2779
+ }
2780
+ result.push(line);
2781
+ }
2782
+ return result.join("\n");
2783
+ }
2784
+ function escapeRegex(s) {
2785
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2786
+ }
2787
+ var init_agent_linker = __esm({
2788
+ "src/installers/agent-linker.ts"() {
2789
+ "use strict";
2790
+ }
2791
+ });
2792
+
2793
+ // src/commands/link.ts
2794
+ var link_exports = {};
2795
+ __export(link_exports, {
2796
+ linkCommand: () => linkCommand
2797
+ });
2798
+ import * as p9 from "@clack/prompts";
2799
+ import pc8 from "picocolors";
2800
+ import { readFile as readFile10, writeFile as writeFile11 } from "fs/promises";
2801
+ import { basename } from "path";
2802
+ async function linkCommand(type, name, opts) {
2803
+ p9.intro(pc8.bgCyan(pc8.black(" kitn link ")));
2804
+ const cwd = process.cwd();
2805
+ const config = await readConfig(cwd);
2806
+ if (!config) {
2807
+ p9.log.error("No kitn.json found. Run `kitn init` first.");
2808
+ process.exit(1);
2809
+ }
2810
+ if (type && type !== "tool") {
2811
+ p9.log.error(
2812
+ `Unsupported type "${type}". Only ${pc8.bold("tool")} is supported.`
2813
+ );
2814
+ process.exit(1);
2815
+ }
2816
+ let toolName = name;
2817
+ if (!toolName) {
2818
+ const tools = await listTools(config, cwd);
2819
+ if (tools.length === 0) {
2820
+ p9.log.error("No tools found in your project.");
2821
+ process.exit(1);
2822
+ }
2823
+ const selected = await p9.select({
2824
+ message: "Select a tool to link:",
2825
+ options: tools.map((t) => ({
2826
+ value: t.name,
2827
+ label: t.name
2828
+ }))
2829
+ });
2830
+ if (p9.isCancel(selected)) {
2831
+ p9.cancel("Cancelled.");
2832
+ process.exit(0);
2833
+ }
2834
+ toolName = selected;
2835
+ }
2836
+ const tool = await resolveToolByName(toolName, config, cwd);
2837
+ if (!tool) {
2838
+ p9.log.error(
2839
+ `Tool "${toolName}" not found. Check that the file exists in your tools directory.`
2840
+ );
2841
+ process.exit(1);
2842
+ }
2843
+ let agentName = opts?.to;
2844
+ if (!agentName) {
2845
+ const agents = await listAgents(config, cwd);
2846
+ if (agents.length === 0) {
2847
+ p9.log.error("No agents found in your project.");
2848
+ process.exit(1);
2849
+ }
2850
+ const selected = await p9.select({
2851
+ message: "Select an agent to link the tool to:",
2852
+ options: agents.map((a) => ({
2853
+ value: a.name,
2854
+ label: a.name
2855
+ }))
2856
+ });
2857
+ if (p9.isCancel(selected)) {
2858
+ p9.cancel("Cancelled.");
2859
+ process.exit(0);
2860
+ }
2861
+ agentName = selected;
2862
+ }
2863
+ const agent = await resolveAgentByName(agentName, config, cwd);
2864
+ if (!agent) {
2865
+ p9.log.error(
2866
+ `Agent "${agentName}" not found. Check that the file exists in your agents directory.`
2867
+ );
2868
+ process.exit(1);
2869
+ }
2870
+ const agentContent = await readFile10(agent.filePath, "utf-8");
2871
+ const toolRef = {
2872
+ exportName: tool.exportName,
2873
+ importPath: tool.importPath
2874
+ };
2875
+ const result = linkToolToAgent(agentContent, toolRef, opts?.as);
2876
+ if (result.error) {
2877
+ p9.log.warn(result.error);
2878
+ p9.outro("Could not auto-link. Follow the manual instructions above.");
2879
+ process.exit(1);
2880
+ }
2881
+ if (!result.changed) {
2882
+ p9.log.info(
2883
+ `${pc8.cyan(tool.exportName)} is already linked to ${pc8.cyan(basename(agent.filePath))}.`
2884
+ );
2885
+ p9.outro("Nothing to do.");
2886
+ return;
2887
+ }
2888
+ await writeFile11(agent.filePath, result.content);
2889
+ p9.log.success(
2890
+ `Linked ${pc8.cyan(tool.exportName)} to ${pc8.cyan(basename(agent.filePath))}`
2891
+ );
2892
+ p9.log.message(
2893
+ ` ${pc8.green("+")} import { ${tool.exportName} } from "${tool.importPath}"`
2894
+ );
2895
+ p9.log.message(
2896
+ ` ${pc8.green("+")} tools: { ${opts?.as ? `${opts.as}: ${tool.exportName}` : tool.exportName} }`
2897
+ );
2898
+ p9.outro("Done!");
2899
+ }
2900
+ var init_link = __esm({
2901
+ "src/commands/link.ts"() {
2902
+ "use strict";
2903
+ init_config();
2904
+ init_component_resolver();
2905
+ init_agent_linker();
2906
+ }
2907
+ });
2908
+
2909
+ // src/commands/unlink.ts
2910
+ var unlink_exports = {};
2911
+ __export(unlink_exports, {
2912
+ unlinkCommand: () => unlinkCommand
2913
+ });
2914
+ import * as p10 from "@clack/prompts";
2915
+ import pc9 from "picocolors";
2916
+ import { readFile as readFile11, writeFile as writeFile12 } from "fs/promises";
2917
+ import { basename as basename2 } from "path";
2918
+ async function unlinkCommand(type, name, opts) {
2919
+ p10.intro(pc9.bgCyan(pc9.black(" kitn unlink ")));
2920
+ const cwd = process.cwd();
2921
+ const config = await readConfig(cwd);
2922
+ if (!config) {
2923
+ p10.log.error("No kitn.json found. Run `kitn init` first.");
2924
+ process.exit(1);
2925
+ }
2926
+ if (type && type !== "tool") {
2927
+ p10.log.error(
2928
+ `Unsupported type "${type}". Only ${pc9.bold("tool")} is supported.`
2929
+ );
2930
+ process.exit(1);
2931
+ }
2932
+ let toolName = name;
2933
+ if (!toolName) {
2934
+ const tools = await listTools(config, cwd);
2935
+ if (tools.length === 0) {
2936
+ p10.log.error("No tools found in your project.");
2937
+ process.exit(1);
2938
+ }
2939
+ const selected = await p10.select({
2940
+ message: "Select a tool to unlink:",
2941
+ options: tools.map((t) => ({
2942
+ value: t.name,
2943
+ label: t.name
2944
+ }))
2945
+ });
2946
+ if (p10.isCancel(selected)) {
2947
+ p10.cancel("Cancelled.");
2948
+ process.exit(0);
2949
+ }
2950
+ toolName = selected;
2951
+ }
2952
+ const tool = await resolveToolByName(toolName, config, cwd);
2953
+ if (!tool) {
2954
+ p10.log.error(
2955
+ `Tool "${toolName}" not found. Check that the file exists in your tools directory.`
2956
+ );
2957
+ process.exit(1);
2958
+ }
2959
+ let agentName = opts?.from;
2960
+ if (!agentName) {
2961
+ const agents = await listAgents(config, cwd);
2962
+ if (agents.length === 0) {
2963
+ p10.log.error("No agents found in your project.");
2964
+ process.exit(1);
2965
+ }
2966
+ const selected = await p10.select({
2967
+ message: "Select an agent to unlink the tool from:",
2968
+ options: agents.map((a) => ({
2969
+ value: a.name,
2970
+ label: a.name
2971
+ }))
2972
+ });
2973
+ if (p10.isCancel(selected)) {
2974
+ p10.cancel("Cancelled.");
2975
+ process.exit(0);
2976
+ }
2977
+ agentName = selected;
2978
+ }
2979
+ const agent = await resolveAgentByName(agentName, config, cwd);
2980
+ if (!agent) {
2981
+ p10.log.error(
2982
+ `Agent "${agentName}" not found. Check that the file exists in your agents directory.`
2983
+ );
2984
+ process.exit(1);
2985
+ }
2986
+ const agentContent = await readFile11(agent.filePath, "utf-8");
2987
+ const toolRef = {
2988
+ exportName: tool.exportName,
2989
+ importPath: tool.importPath
2990
+ };
2991
+ const result = unlinkToolFromAgent(agentContent, toolRef);
2992
+ if (result.error) {
2993
+ p10.log.warn(result.error);
2994
+ p10.outro("Could not auto-unlink. Follow the manual instructions above.");
2995
+ process.exit(1);
2996
+ }
2997
+ if (!result.changed) {
2998
+ p10.log.info(
2999
+ `${pc9.cyan(tool.exportName)} is not linked to ${pc9.cyan(basename2(agent.filePath))}.`
3000
+ );
3001
+ p10.outro("Nothing to do.");
3002
+ return;
3003
+ }
3004
+ await writeFile12(agent.filePath, result.content);
3005
+ p10.log.success(
3006
+ `Unlinked ${pc9.cyan(tool.exportName)} from ${pc9.cyan(basename2(agent.filePath))}`
3007
+ );
3008
+ p10.log.message(
3009
+ ` ${pc9.red("-")} import { ${tool.exportName} } from "${tool.importPath}"`
3010
+ );
3011
+ p10.log.message(
3012
+ ` ${pc9.red("-")} tools: { ${tool.exportName} }`
3013
+ );
3014
+ p10.outro("Done!");
3015
+ }
3016
+ var init_unlink = __esm({
3017
+ "src/commands/unlink.ts"() {
3018
+ "use strict";
3019
+ init_config();
3020
+ init_component_resolver();
3021
+ init_agent_linker();
3022
+ }
3023
+ });
3024
+
2203
3025
  // src/commands/info.ts
2204
3026
  var info_exports = {};
2205
3027
  __export(info_exports, {
2206
3028
  infoCommand: () => infoCommand
2207
3029
  });
2208
- import * as p9 from "@clack/prompts";
2209
- import pc8 from "picocolors";
3030
+ import * as p11 from "@clack/prompts";
3031
+ import pc10 from "picocolors";
2210
3032
  async function infoCommand(component) {
2211
3033
  const cwd = process.cwd();
2212
3034
  const config = await readConfig(cwd);
2213
3035
  if (!config) {
2214
- p9.log.error("No kitn.json found. Run `kitn init` first.");
3036
+ p11.log.error("No kitn.json found. Run `kitn init` first.");
2215
3037
  process.exit(1);
2216
3038
  }
2217
3039
  const ref = parseComponentRef(component);
2218
3040
  const fetcher = new RegistryFetcher(config.registries);
2219
- const s = p9.spinner();
3041
+ const s = p11.spinner();
2220
3042
  s.start("Fetching component info...");
2221
3043
  let index;
2222
3044
  try {
2223
3045
  index = await fetcher.fetchIndex(ref.namespace);
2224
3046
  } catch (err) {
2225
- s.stop(pc8.red("Failed to fetch registry"));
2226
- p9.log.error(err.message);
3047
+ s.stop(pc10.red("Failed to fetch registry"));
3048
+ p11.log.error(err.message);
2227
3049
  process.exit(1);
2228
3050
  }
2229
3051
  const indexItem = index.items.find((i) => i.name === ref.name);
2230
3052
  if (!indexItem) {
2231
- s.stop(pc8.red("Component not found"));
2232
- p9.log.error(`Component '${ref.name}' not found in registry.`);
3053
+ s.stop(pc10.red("Component not found"));
3054
+ p11.log.error(`Component '${ref.name}' not found in registry.`);
2233
3055
  process.exit(1);
2234
3056
  }
2235
3057
  const dir = typeToDir[indexItem.type];
@@ -2237,8 +3059,8 @@ async function infoCommand(component) {
2237
3059
  try {
2238
3060
  item = await fetcher.fetchItem(ref.name, dir, ref.namespace, ref.version);
2239
3061
  } catch (err) {
2240
- s.stop(pc8.red("Failed to fetch component"));
2241
- p9.log.error(err.message);
3062
+ s.stop(pc10.red("Failed to fetch component"));
3063
+ p11.log.error(err.message);
2242
3064
  process.exit(1);
2243
3065
  }
2244
3066
  s.stop("Component found");
@@ -2246,63 +3068,63 @@ async function infoCommand(component) {
2246
3068
  const typeName = indexItem.type.replace("kitn:", "");
2247
3069
  console.log();
2248
3070
  console.log(
2249
- ` ${pc8.bold(item.name)} ${pc8.cyan(`v${version}`)}${" ".repeat(Math.max(1, 40 - item.name.length - version.length - 2))}${pc8.dim(ref.namespace)}`
3071
+ ` ${pc10.bold(item.name)} ${pc10.cyan(`v${version}`)}${" ".repeat(Math.max(1, 40 - item.name.length - version.length - 2))}${pc10.dim(ref.namespace)}`
2250
3072
  );
2251
- console.log(` ${pc8.dim(item.description)}`);
3073
+ console.log(` ${pc10.dim(item.description)}`);
2252
3074
  console.log();
2253
- console.log(` ${pc8.dim("Type:")} ${typeName}`);
3075
+ console.log(` ${pc10.dim("Type:")} ${typeName}`);
2254
3076
  if (item.dependencies?.length) {
2255
3077
  console.log(
2256
- ` ${pc8.dim("Dependencies:")} ${item.dependencies.join(", ")}`
3078
+ ` ${pc10.dim("Dependencies:")} ${item.dependencies.join(", ")}`
2257
3079
  );
2258
3080
  }
2259
3081
  if (item.registryDependencies?.length) {
2260
3082
  console.log(
2261
- ` ${pc8.dim("Registry deps:")} ${item.registryDependencies.join(", ")}`
3083
+ ` ${pc10.dim("Registry deps:")} ${item.registryDependencies.join(", ")}`
2262
3084
  );
2263
3085
  }
2264
3086
  if (item.categories?.length) {
2265
3087
  console.log(
2266
- ` ${pc8.dim("Categories:")} ${item.categories.join(", ")}`
3088
+ ` ${pc10.dim("Categories:")} ${item.categories.join(", ")}`
2267
3089
  );
2268
3090
  }
2269
3091
  if (item.updatedAt) {
2270
- console.log(` ${pc8.dim("Updated:")} ${item.updatedAt}`);
3092
+ console.log(` ${pc10.dim("Updated:")} ${item.updatedAt}`);
2271
3093
  }
2272
3094
  const versions = indexItem.versions;
2273
3095
  if (versions?.length) {
2274
- console.log(` ${pc8.dim("Versions:")} ${versions.join(", ")}`);
3096
+ console.log(` ${pc10.dim("Versions:")} ${versions.join(", ")}`);
2275
3097
  }
2276
3098
  if (item.changelog?.length) {
2277
3099
  console.log();
2278
- console.log(` ${pc8.bold("Changelog:")}`);
3100
+ console.log(` ${pc10.bold("Changelog:")}`);
2279
3101
  for (const entry of item.changelog) {
2280
- const tag = entry.type === "feature" ? pc8.green(entry.type) : entry.type === "fix" ? pc8.yellow(entry.type) : entry.type === "breaking" ? pc8.red(entry.type) : pc8.dim(entry.type);
3102
+ const tag = entry.type === "feature" ? pc10.green(entry.type) : entry.type === "fix" ? pc10.yellow(entry.type) : entry.type === "breaking" ? pc10.red(entry.type) : pc10.dim(entry.type);
2281
3103
  console.log(
2282
- ` ${pc8.cyan(entry.version)} ${pc8.dim(entry.date)} ${tag} ${entry.note}`
3104
+ ` ${pc10.cyan(entry.version)} ${pc10.dim(entry.date)} ${tag} ${entry.note}`
2283
3105
  );
2284
3106
  }
2285
3107
  }
2286
3108
  console.log();
2287
3109
  const fileCount = item.files.length;
2288
- console.log(` ${pc8.bold(`Files:`)} ${pc8.dim(`(${fileCount})`)}`);
3110
+ console.log(` ${pc10.bold(`Files:`)} ${pc10.dim(`(${fileCount})`)}`);
2289
3111
  const maxShown = 10;
2290
3112
  for (const file of item.files.slice(0, maxShown)) {
2291
- console.log(` ${pc8.dim(file.path)}`);
3113
+ console.log(` ${pc10.dim(file.path)}`);
2292
3114
  }
2293
3115
  if (fileCount > maxShown) {
2294
- console.log(` ${pc8.dim(`... and ${fileCount - maxShown} more`)}`);
3116
+ console.log(` ${pc10.dim(`... and ${fileCount - maxShown} more`)}`);
2295
3117
  }
2296
3118
  const lock = await readLock(cwd);
2297
3119
  const installed = lock[item.name];
2298
3120
  if (installed) {
2299
3121
  console.log();
2300
3122
  console.log(
2301
- ` ${pc8.green("Installed")} ${pc8.dim(`v${installed.version}`)}`
3123
+ ` ${pc10.green("Installed")} ${pc10.dim(`v${installed.version}`)}`
2302
3124
  );
2303
3125
  if (version !== installed.version) {
2304
3126
  console.log(
2305
- ` ${pc8.yellow("Update available:")} ${pc8.dim(`v${installed.version}`)} \u2192 ${pc8.cyan(`v${version}`)}`
3127
+ ` ${pc10.yellow("Update available:")} ${pc10.dim(`v${installed.version}`)} \u2192 ${pc10.cyan(`v${version}`)}`
2306
3128
  );
2307
3129
  }
2308
3130
  }
@@ -2324,41 +3146,41 @@ __export(check_exports, {
2324
3146
  checkCommand: () => checkCommand
2325
3147
  });
2326
3148
  import { execSync as execSync2 } from "child_process";
2327
- import * as p10 from "@clack/prompts";
2328
- import pc9 from "picocolors";
3149
+ import * as p12 from "@clack/prompts";
3150
+ import pc11 from "picocolors";
2329
3151
  async function checkCommand(currentVersion) {
2330
- p10.intro(pc9.bgCyan(pc9.black(" kitn check ")));
2331
- p10.log.info(`kitn v${currentVersion}`);
2332
- const s = p10.spinner();
3152
+ p12.intro(pc11.bgCyan(pc11.black(" kitn check ")));
3153
+ p12.log.info(`kitn v${currentVersion}`);
3154
+ const s = p12.spinner();
2333
3155
  s.start("Checking for updates...");
2334
3156
  const latest = await fetchLatestVersion();
2335
3157
  if (!latest) {
2336
- s.stop(pc9.yellow("Could not reach the npm registry"));
2337
- p10.outro("Try again later.");
3158
+ s.stop(pc11.yellow("Could not reach the npm registry"));
3159
+ p12.outro("Try again later.");
2338
3160
  return;
2339
3161
  }
2340
3162
  if (isNewer(latest, currentVersion)) {
2341
- s.stop(pc9.yellow(`Update available: ${currentVersion} \u2192 ${latest}`));
3163
+ s.stop(pc11.yellow(`Update available: ${currentVersion} \u2192 ${latest}`));
2342
3164
  const pm = detectCliInstaller();
2343
3165
  const installCmd = getGlobalInstallCommand(pm, "@kitnai/cli");
2344
- const shouldUpdate = await p10.confirm({ message: "Update now?" });
2345
- if (p10.isCancel(shouldUpdate) || !shouldUpdate) {
2346
- p10.log.message(` Run: ${pc9.cyan(installCmd)}`);
3166
+ const shouldUpdate = await p12.confirm({ message: "Update now?" });
3167
+ if (p12.isCancel(shouldUpdate) || !shouldUpdate) {
3168
+ p12.log.message(` Run: ${pc11.cyan(installCmd)}`);
2347
3169
  } else {
2348
- const us = p10.spinner();
3170
+ const us = p12.spinner();
2349
3171
  us.start("Updating...");
2350
3172
  try {
2351
3173
  execSync2(installCmd, { stdio: "pipe" });
2352
- us.stop(pc9.green(`Updated to v${latest}`));
3174
+ us.stop(pc11.green(`Updated to v${latest}`));
2353
3175
  } catch {
2354
- us.stop(pc9.red("Update failed"));
2355
- p10.log.message(` Run manually: ${pc9.cyan(installCmd)}`);
3176
+ us.stop(pc11.red("Update failed"));
3177
+ p12.log.message(` Run manually: ${pc11.cyan(installCmd)}`);
2356
3178
  }
2357
3179
  }
2358
3180
  } else {
2359
- s.stop(pc9.green("You're on the latest version"));
3181
+ s.stop(pc11.green("You're on the latest version"));
2360
3182
  }
2361
- p10.outro("");
3183
+ p12.outro("");
2362
3184
  }
2363
3185
  var init_check = __esm({
2364
3186
  "src/commands/check.ts"() {
@@ -2368,6 +3190,64 @@ var init_check = __esm({
2368
3190
  }
2369
3191
  });
2370
3192
 
3193
+ // src/commands/rules.ts
3194
+ var rules_exports = {};
3195
+ __export(rules_exports, {
3196
+ rulesCommand: () => rulesCommand
3197
+ });
3198
+ import * as p13 from "@clack/prompts";
3199
+ import pc12 from "picocolors";
3200
+ async function rulesCommand() {
3201
+ p13.intro(pc12.bgCyan(pc12.black(" kitn rules ")));
3202
+ const cwd = process.cwd();
3203
+ const config = await readConfig(cwd);
3204
+ if (!config) {
3205
+ p13.log.error(`No kitn.json found. Run ${pc12.bold("kitn init")} first.`);
3206
+ process.exit(1);
3207
+ }
3208
+ let selectedIds = config.aiTools;
3209
+ if (!selectedIds || selectedIds.length === 0) {
3210
+ const rulesConfig = await fetchRulesConfig(config.registries);
3211
+ const selected = await p13.multiselect({
3212
+ message: "Which AI coding tools do you use?",
3213
+ options: rulesConfig.tools.map((t) => ({
3214
+ value: t.id,
3215
+ label: t.name,
3216
+ hint: t.description
3217
+ })),
3218
+ required: false
3219
+ });
3220
+ if (p13.isCancel(selected)) {
3221
+ p13.cancel("Cancelled.");
3222
+ process.exit(0);
3223
+ }
3224
+ selectedIds = selected;
3225
+ if (selectedIds.length === 0) {
3226
+ p13.log.warn("No tools selected. Nothing to generate.");
3227
+ p13.outro("Done.");
3228
+ return;
3229
+ }
3230
+ const updatedConfig = { ...config, aiTools: selectedIds };
3231
+ await writeConfig(cwd, updatedConfig);
3232
+ p13.log.info(`Saved tool selections to ${pc12.bold("kitn.json")}`);
3233
+ }
3234
+ const s = p13.spinner();
3235
+ s.start("Generating rules files");
3236
+ const written = await generateRulesFiles(cwd, config, selectedIds);
3237
+ s.stop("Rules files generated");
3238
+ for (const filePath of written) {
3239
+ p13.log.success(`${pc12.green("+")} ${filePath}`);
3240
+ }
3241
+ p13.outro(`Generated ${written.length} rules file${written.length === 1 ? "" : "s"}.`);
3242
+ }
3243
+ var init_rules = __esm({
3244
+ "src/commands/rules.ts"() {
3245
+ "use strict";
3246
+ init_config();
3247
+ init_rules_generator();
3248
+ }
3249
+ });
3250
+
2371
3251
  // src/commands/registry.ts
2372
3252
  var registry_exports = {};
2373
3253
  __export(registry_exports, {
@@ -2375,8 +3255,8 @@ __export(registry_exports, {
2375
3255
  registryListCommand: () => registryListCommand,
2376
3256
  registryRemoveCommand: () => registryRemoveCommand
2377
3257
  });
2378
- import * as p11 from "@clack/prompts";
2379
- import pc10 from "picocolors";
3258
+ import * as p14 from "@clack/prompts";
3259
+ import pc13 from "picocolors";
2380
3260
  async function registryAddCommand(namespace, url, opts = {}) {
2381
3261
  const cwd = opts.cwd ?? process.cwd();
2382
3262
  const config = await readConfig(cwd);
@@ -2402,10 +3282,10 @@ async function registryAddCommand(namespace, url, opts = {}) {
2402
3282
  config.registries[namespace] = url;
2403
3283
  }
2404
3284
  await writeConfig(cwd, config);
2405
- p11.log.success(`Added registry ${pc10.bold(namespace)}`);
2406
- p11.log.message(pc10.dim(` ${url}`));
2407
- if (opts.homepage) p11.log.message(pc10.dim(` Homepage: ${opts.homepage}`));
2408
- if (opts.description) p11.log.message(pc10.dim(` ${opts.description}`));
3285
+ p14.log.success(`Added registry ${pc13.bold(namespace)}`);
3286
+ p14.log.message(pc13.dim(` ${url}`));
3287
+ if (opts.homepage) p14.log.message(pc13.dim(` Homepage: ${opts.homepage}`));
3288
+ if (opts.description) p14.log.message(pc13.dim(` ${opts.description}`));
2409
3289
  }
2410
3290
  async function registryRemoveCommand(namespace, opts = {}) {
2411
3291
  const cwd = opts.cwd ?? process.cwd();
@@ -2426,10 +3306,10 @@ async function registryRemoveCommand(namespace, opts = {}) {
2426
3306
  }
2427
3307
  delete config.registries[namespace];
2428
3308
  await writeConfig(cwd, config);
2429
- p11.log.success(`Removed registry ${pc10.bold(namespace)}`);
3309
+ p14.log.success(`Removed registry ${pc13.bold(namespace)}`);
2430
3310
  if (affectedComponents.length > 0) {
2431
- p11.log.warn(`${affectedComponents.length} installed component(s) referenced this registry:
2432
- ` + affectedComponents.map((name) => ` ${pc10.yellow("!")} ${name}`).join("\n"));
3311
+ p14.log.warn(`${affectedComponents.length} installed component(s) referenced this registry:
3312
+ ` + affectedComponents.map((name) => ` ${pc13.yellow("!")} ${name}`).join("\n"));
2433
3313
  }
2434
3314
  return { affectedComponents };
2435
3315
  }
@@ -2444,15 +3324,15 @@ async function registryListCommand(opts = {}) {
2444
3324
  return { namespace, url, homepage, description };
2445
3325
  });
2446
3326
  if (entries.length === 0) {
2447
- p11.log.message(pc10.dim(" No registries configured."));
3327
+ p14.log.message(pc13.dim(" No registries configured."));
2448
3328
  } else {
2449
3329
  const lines = [];
2450
3330
  for (const { namespace, url, homepage, description } of entries) {
2451
- lines.push(` ${pc10.bold(namespace.padEnd(16))} ${pc10.dim(url)}`);
3331
+ lines.push(` ${pc13.bold(namespace.padEnd(16))} ${pc13.dim(url)}`);
2452
3332
  if (description) lines.push(` ${" ".repeat(16)} ${description}`);
2453
- if (homepage) lines.push(` ${" ".repeat(16)} ${pc10.dim(homepage)}`);
3333
+ if (homepage) lines.push(` ${" ".repeat(16)} ${pc13.dim(homepage)}`);
2454
3334
  }
2455
- p11.log.message(lines.join("\n"));
3335
+ p14.log.message(lines.join("\n"));
2456
3336
  }
2457
3337
  return entries;
2458
3338
  }
@@ -2466,14 +3346,14 @@ var init_registry = __esm({
2466
3346
  // src/index.ts
2467
3347
  init_update_check();
2468
3348
  import { Command } from "commander";
2469
- var VERSION = true ? "0.1.29" : "0.0.0-dev";
3349
+ var VERSION = true ? "0.1.31" : "0.0.0-dev";
2470
3350
  var printUpdateNotice = startUpdateCheck(VERSION);
2471
3351
  var program = new Command().name("kitn").description("Install AI agent components from the kitn registry").version(VERSION);
2472
3352
  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) => {
2473
3353
  const { initCommand: initCommand2 } = await Promise.resolve().then(() => (init_init(), init_exports));
2474
3354
  await initCommand2(opts);
2475
3355
  });
2476
- program.command("add").alias("install").description("Add components from the registry (supports type-first: kitn add agent <name>)").argument("[components...]", "component names or type followed by names").option("-o, --overwrite", "overwrite existing files without prompting").option("-t, --type <type>", "filter by component type during resolution").action(async (components, opts) => {
3356
+ program.command("add").alias("install").description("Add components from the registry (supports type-first: kitn add agent <name>)").argument("[components...]", "component names or type followed by names").option("-o, --overwrite", "overwrite existing files without prompting").option("-t, --type <type>", "filter by component type during resolution").option("-y, --yes", "skip confirmation prompt").action(async (components, opts) => {
2477
3357
  const { addCommand: addCommand2 } = await Promise.resolve().then(() => (init_add(), add_exports));
2478
3358
  await addCommand2(components, opts);
2479
3359
  });
@@ -2497,6 +3377,14 @@ program.command("create").description("Scaffold a new kitn component").argument(
2497
3377
  const { createCommand: createCommand2 } = await Promise.resolve().then(() => (init_create(), create_exports));
2498
3378
  await createCommand2(type, name);
2499
3379
  });
3380
+ program.command("link").description("Wire a tool to an agent").argument("[type]", "component type (tool)").argument("[name]", "component name").option("--to <agent>", "target agent name").option("--as <key>", "key name in the tools object").action(async (type, name, opts) => {
3381
+ const { linkCommand: linkCommand2 } = await Promise.resolve().then(() => (init_link(), link_exports));
3382
+ await linkCommand2(type, name, opts);
3383
+ });
3384
+ program.command("unlink").description("Unwire a tool from an agent").argument("[type]", "component type (tool)").argument("[name]", "component name").option("--from <agent>", "target agent name").action(async (type, name, opts) => {
3385
+ const { unlinkCommand: unlinkCommand2 } = await Promise.resolve().then(() => (init_unlink(), unlink_exports));
3386
+ await unlinkCommand2(type, name, opts);
3387
+ });
2500
3388
  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) => {
2501
3389
  const { infoCommand: infoCommand2 } = await Promise.resolve().then(() => (init_info(), info_exports));
2502
3390
  await infoCommand2(component);
@@ -2505,6 +3393,10 @@ program.command("check").description("Check for CLI updates").action(async () =>
2505
3393
  const { checkCommand: checkCommand2 } = await Promise.resolve().then(() => (init_check(), check_exports));
2506
3394
  await checkCommand2(VERSION);
2507
3395
  });
3396
+ program.command("rules").description("Regenerate AI coding tool rules files").action(async () => {
3397
+ const { rulesCommand: rulesCommand2 } = await Promise.resolve().then(() => (init_rules(), rules_exports));
3398
+ await rulesCommand2();
3399
+ });
2508
3400
  var registry = program.command("registry").description("Manage component registries");
2509
3401
  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) => {
2510
3402
  const { registryAddCommand: registryAddCommand2 } = await Promise.resolve().then(() => (init_registry(), registry_exports));