@kitnai/cli 0.1.32 → 0.1.33

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
@@ -163,8 +163,8 @@ import { z } from "zod";
163
163
  function getRegistryUrl(entry) {
164
164
  return typeof entry === "string" ? entry : entry.url;
165
165
  }
166
- function resolveRoutesAlias(config) {
167
- const fw = config.framework ?? "hono";
166
+ function resolveRoutesAlias(config2) {
167
+ const fw = config2.framework ?? "hono";
168
168
  return FRAMEWORK_TO_ADAPTER[fw] ?? fw;
169
169
  }
170
170
  async function readConfig(projectDir) {
@@ -175,8 +175,8 @@ async function readConfig(projectDir) {
175
175
  return null;
176
176
  }
177
177
  }
178
- async function writeConfig(projectDir, config) {
179
- const data = { $schema: "https://kitn.dev/schema/config.json", ...config };
178
+ async function writeConfig(projectDir, config2) {
179
+ const data = { $schema: "https://kitn.dev/schema/config.json", ...config2 };
180
180
  await writeFile2(join3(projectDir, CONFIG_FILE), JSON.stringify(data, null, 2) + "\n");
181
181
  }
182
182
  async function readLock(projectDir) {
@@ -197,17 +197,17 @@ async function writeLock(projectDir, lock) {
197
197
  }
198
198
  await writeFile2(join3(projectDir, LOCK_FILE), JSON.stringify(lock, null, 2) + "\n");
199
199
  }
200
- function getInstallPath(config, type, fileName, namespace) {
200
+ function getInstallPath(config2, type, fileName, namespace) {
201
201
  const aliasKey = typeToAliasKey[type];
202
- const baseAlias = config.aliases.base ?? "src/ai";
203
- const base = config.aliases[aliasKey] ?? join3(baseAlias, aliasKey);
202
+ const baseAlias = config2.aliases.base ?? "src/ai";
203
+ const base = config2.aliases[aliasKey] ?? join3(baseAlias, aliasKey);
204
204
  if (namespace && namespace !== "@kitn") {
205
205
  const nsDir = namespace.replace("@", "");
206
206
  return join3(base, nsDir, fileName);
207
207
  }
208
208
  return join3(base, fileName);
209
209
  }
210
- var componentType, installedComponentSchema, registryEntrySchema, registryValueSchema, configSchema, FRAMEWORK_TO_ADAPTER, CONFIG_FILE, LOCK_FILE, lockSchema, typeToAliasKey;
210
+ var componentType, installedComponentSchema, registryEntrySchema, registryValueSchema, DEFAULT_REGISTRY_URL, DEFAULT_REGISTRIES, DEFAULT_ALIASES, configSchema, FRAMEWORK_TO_ADAPTER, CONFIG_FILE, LOCK_FILE, lockSchema, typeToAliasKey;
211
211
  var init_config = __esm({
212
212
  "src/utils/config.ts"() {
213
213
  "use strict";
@@ -228,6 +228,22 @@ var init_config = __esm({
228
228
  description: z.string().optional()
229
229
  });
230
230
  registryValueSchema = z.union([z.string(), registryEntrySchema]);
231
+ DEFAULT_REGISTRY_URL = "https://kitn-ai.github.io/kitn/r/{type}/{name}.json";
232
+ DEFAULT_REGISTRIES = {
233
+ "@kitn": {
234
+ url: DEFAULT_REGISTRY_URL,
235
+ homepage: "https://kitn.ai",
236
+ description: "Official kitn AI agent components"
237
+ }
238
+ };
239
+ DEFAULT_ALIASES = {
240
+ base: "src/ai",
241
+ agents: "src/ai/agents",
242
+ tools: "src/ai/tools",
243
+ skills: "src/ai/skills",
244
+ storage: "src/ai/storage",
245
+ crons: "src/ai/crons"
246
+ };
231
247
  configSchema = z.object({
232
248
  $schema: z.string().optional(),
233
249
  runtime: z.enum(["bun", "node", "deno"]),
@@ -241,7 +257,9 @@ var init_config = __esm({
241
257
  crons: z.string().optional()
242
258
  }),
243
259
  registries: z.record(z.string(), registryValueSchema),
244
- aiTools: z.array(z.string()).optional()
260
+ chatService: z.object({
261
+ url: z.string().optional()
262
+ }).optional()
245
263
  });
246
264
  FRAMEWORK_TO_ADAPTER = {
247
265
  hono: "hono",
@@ -268,38 +286,38 @@ function stripJsonc(text3) {
268
286
  return text3.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/,\s*([}\]])/g, "$1");
269
287
  }
270
288
  function patchTsconfig(tsconfigContent, paths, removePrefixes) {
271
- const config = JSON.parse(stripJsonc(tsconfigContent));
272
- if (!config.compilerOptions) {
273
- config.compilerOptions = {};
289
+ const config2 = JSON.parse(stripJsonc(tsconfigContent));
290
+ if (!config2.compilerOptions) {
291
+ config2.compilerOptions = {};
274
292
  }
275
- if (!config.compilerOptions.paths) {
276
- config.compilerOptions.paths = {};
293
+ if (!config2.compilerOptions.paths) {
294
+ config2.compilerOptions.paths = {};
277
295
  }
278
296
  if (removePrefixes) {
279
- for (const key of Object.keys(config.compilerOptions.paths)) {
297
+ for (const key of Object.keys(config2.compilerOptions.paths)) {
280
298
  if (removePrefixes.some((prefix) => key.startsWith(prefix))) {
281
- delete config.compilerOptions.paths[key];
299
+ delete config2.compilerOptions.paths[key];
282
300
  }
283
301
  }
284
302
  }
285
303
  for (const [key, value] of Object.entries(paths)) {
286
- config.compilerOptions.paths[key] = value;
304
+ config2.compilerOptions.paths[key] = value;
287
305
  }
288
306
  const ES_TARGETS = ["es3", "es5", "es6", "es2015", "es2016", "es2017", "es2018", "es2019", "es2020", "es2021"];
289
- const currentTarget = (config.compilerOptions.target ?? "").toLowerCase();
307
+ const currentTarget = (config2.compilerOptions.target ?? "").toLowerCase();
290
308
  if (!currentTarget || ES_TARGETS.includes(currentTarget)) {
291
- config.compilerOptions.target = "ES2022";
309
+ config2.compilerOptions.target = "ES2022";
292
310
  }
293
- if (!config.compilerOptions.moduleResolution) {
294
- config.compilerOptions.moduleResolution = "bundler";
311
+ if (!config2.compilerOptions.moduleResolution) {
312
+ config2.compilerOptions.moduleResolution = "bundler";
295
313
  }
296
- if (!config.compilerOptions.module) {
297
- config.compilerOptions.module = "ESNext";
314
+ if (!config2.compilerOptions.module) {
315
+ config2.compilerOptions.module = "ESNext";
298
316
  }
299
- if (config.compilerOptions.skipLibCheck === void 0) {
300
- config.compilerOptions.skipLibCheck = true;
317
+ if (config2.compilerOptions.skipLibCheck === void 0) {
318
+ config2.compilerOptions.skipLibCheck = true;
301
319
  }
302
- return JSON.stringify(config, null, 2) + "\n";
320
+ return JSON.stringify(config2, null, 2) + "\n";
303
321
  }
304
322
  async function patchProjectTsconfig(projectDir, paths, removePrefixes) {
305
323
  const tsconfigPath = join4(projectDir, "tsconfig.json");
@@ -581,8 +599,8 @@ async function handleEnvVars(cwd, envVars) {
581
599
  const lines = [];
582
600
  if (exampleContent && !exampleContent.endsWith("\n")) lines.push("");
583
601
  for (const key of missingFromExample) {
584
- const config = envVars[key];
585
- lines.push(`# ${config.description}${config.url ? ` (${config.url})` : ""}`);
602
+ const config2 = envVars[key];
603
+ lines.push(`# ${config2.description}${config2.url ? ` (${config2.url})` : ""}`);
586
604
  lines.push(`${key}=`);
587
605
  }
588
606
  await writeFile5(examplePath, exampleContent + lines.join("\n") + "\n");
@@ -594,9 +612,9 @@ async function handleEnvVars(cwd, envVars) {
594
612
  `${missingFromEnv.length} environment variable(s) needed:`
595
613
  );
596
614
  for (const key of missingFromEnv) {
597
- const config = envVars[key];
598
- const req = config.required !== false ? pc2.red("*") : "";
599
- p.log.message(` ${pc2.yellow(key)}${req}: ${config.description}${config.url ? pc2.dim(` -> ${config.url}`) : ""}`);
615
+ const config2 = envVars[key];
616
+ const req = config2.required !== false ? pc2.red("*") : "";
617
+ p.log.message(` ${pc2.yellow(key)}${req}: ${config2.description}${config2.url ? pc2.dim(` -> ${config2.url}`) : ""}`);
600
618
  }
601
619
  const shouldPrompt = await p.confirm({
602
620
  message: "Would you like to enter values now?",
@@ -608,8 +626,8 @@ async function handleEnvVars(cwd, envVars) {
608
626
  }
609
627
  const newEntries = [];
610
628
  for (const key of missingFromEnv) {
611
- const config = envVars[key];
612
- const isSecret = config.secret !== false;
629
+ const config2 = envVars[key];
630
+ const isSecret = config2.secret !== false;
613
631
  let value;
614
632
  if (isSecret) {
615
633
  value = await p.password({
@@ -618,7 +636,7 @@ async function handleEnvVars(cwd, envVars) {
618
636
  } else {
619
637
  value = await p.text({
620
638
  message: `${key}:`,
621
- placeholder: config.description
639
+ placeholder: config2.description
622
640
  });
623
641
  }
624
642
  if (p.isCancel(value)) {
@@ -867,18 +885,18 @@ import { relative as relative2 } from "path";
867
885
  async function addCommand(components, opts) {
868
886
  p2.intro(pc3.bgCyan(pc3.black(" kitn add ")));
869
887
  const cwd = process.cwd();
870
- const config = await readConfig(cwd);
871
- if (!config) {
888
+ const config2 = await readConfig(cwd);
889
+ if (!config2) {
872
890
  p2.log.error("No kitn.json found. Run `kitn init` first.");
873
891
  process.exit(1);
874
892
  }
875
893
  const lock = await readLock(cwd);
876
894
  if (components.length === 0) {
877
- const fetcher2 = new RegistryFetcher(config.registries);
895
+ const fetcher2 = new RegistryFetcher(config2.registries);
878
896
  const s2 = p2.spinner();
879
897
  s2.start("Fetching registry...");
880
898
  const allItems = [];
881
- for (const namespace of Object.keys(config.registries)) {
899
+ for (const namespace of Object.keys(config2.registries)) {
882
900
  try {
883
901
  const index = await fetcher2.fetchIndex(namespace);
884
902
  for (const item of index.items) {
@@ -957,18 +975,18 @@ async function addCommand(components, opts) {
957
975
  }
958
976
  const resolvedComponents = components.map((c) => {
959
977
  if (c === "routes") {
960
- return resolveRoutesAlias(config);
978
+ return resolveRoutesAlias(config2);
961
979
  }
962
980
  return c;
963
981
  });
964
982
  const refs = resolvedComponents.map(parseComponentRef);
965
- const fetcher = new RegistryFetcher(config.registries);
983
+ const fetcher = new RegistryFetcher(config2.registries);
966
984
  const s = p2.spinner();
967
985
  s.start("Resolving dependencies...");
968
986
  const preResolvedTypes = /* @__PURE__ */ new Map();
969
987
  let expandedNames = [...resolvedComponents];
970
988
  try {
971
- const namespacesToFetch = Object.keys(config.registries);
989
+ const namespacesToFetch = Object.keys(config2.registries);
972
990
  const allIndexItems = [];
973
991
  for (const namespace of namespacesToFetch) {
974
992
  try {
@@ -1110,7 +1128,7 @@ async function addCommand(components, opts) {
1110
1128
  }
1111
1129
  }
1112
1130
  if (slotReplacements.size > 0) {
1113
- const baseDir2 = config.aliases.base ?? "src/ai";
1131
+ const baseDir2 = config2.aliases.base ?? "src/ai";
1114
1132
  for (const [oldKey] of slotReplacements) {
1115
1133
  const oldEntry = lock[oldKey];
1116
1134
  if (!oldEntry) continue;
@@ -1122,9 +1140,9 @@ async function addCommand(components, opts) {
1122
1140
  }
1123
1141
  const barrelPath2 = join7(cwd, baseDir2, "index.ts");
1124
1142
  const barrelEligibleDirs = /* @__PURE__ */ new Set([
1125
- config.aliases.agents,
1126
- config.aliases.tools,
1127
- config.aliases.skills
1143
+ config2.aliases.agents,
1144
+ config2.aliases.tools,
1145
+ config2.aliases.skills
1128
1146
  ]);
1129
1147
  if (existsSync(barrelPath2)) {
1130
1148
  let barrelContent = await readFile6(barrelPath2, "utf-8");
@@ -1188,7 +1206,7 @@ async function addCommand(components, opts) {
1188
1206
  }
1189
1207
  }
1190
1208
  if (item.type === "kitn:package") {
1191
- const baseDir2 = config.aliases.base ?? "src/ai";
1209
+ const baseDir2 = config2.aliases.base ?? "src/ai";
1192
1210
  for (const file of item.files) {
1193
1211
  const targetPath = join7(cwd, baseDir2, file.path);
1194
1212
  const relativePath = join7(baseDir2, file.path);
@@ -1258,10 +1276,10 @@ async function addCommand(components, opts) {
1258
1276
  }
1259
1277
  })();
1260
1278
  const fileName = file.path.split("/").pop();
1261
- const installPath = getInstallPath(config, item.type, fileName, ns);
1279
+ const installPath = getInstallPath(config2, item.type, fileName, ns);
1262
1280
  const targetPath = join7(cwd, installPath);
1263
1281
  const relativePath = installPath;
1264
- const content = rewriteKitnImports(file.content, item.type, fileName, config.aliases);
1282
+ const content = rewriteKitnImports(file.content, item.type, fileName, config2.aliases);
1265
1283
  const status = await checkFileStatus(targetPath, content);
1266
1284
  switch (status) {
1267
1285
  case "new" /* New */:
@@ -1298,7 +1316,7 @@ async function addCommand(components, opts) {
1298
1316
  }
1299
1317
  const allContent = item.files.map((f) => {
1300
1318
  const fn = f.path.split("/").pop();
1301
- return rewriteKitnImports(f.content, item.type, fn, config.aliases);
1319
+ return rewriteKitnImports(f.content, item.type, fn, config2.aliases);
1302
1320
  }).join("\n");
1303
1321
  const installedKey = ns === "@kitn" ? item.name : `${ns}/${item.name}`;
1304
1322
  lock[installedKey] = {
@@ -1309,7 +1327,7 @@ async function addCommand(components, opts) {
1309
1327
  installedAt: (/* @__PURE__ */ new Date()).toISOString(),
1310
1328
  files: item.files.map((f) => {
1311
1329
  const fileName = f.path.split("/").pop();
1312
- return getInstallPath(config, item.type, fileName, ns);
1330
+ return getInstallPath(config2, item.type, fileName, ns);
1313
1331
  }),
1314
1332
  hash: contentHash(allContent),
1315
1333
  registryDependencies: item.registryDependencies
@@ -1317,7 +1335,7 @@ async function addCommand(components, opts) {
1317
1335
  }
1318
1336
  }
1319
1337
  const BARREL_ELIGIBLE = /* @__PURE__ */ new Set(["kitn:agent", "kitn:tool", "kitn:skill", "kitn:cron"]);
1320
- const baseDir = config.aliases.base ?? "src/ai";
1338
+ const baseDir = config2.aliases.base ?? "src/ai";
1321
1339
  const barrelPath = join7(cwd, baseDir, "index.ts");
1322
1340
  const barrelDir = join7(cwd, baseDir);
1323
1341
  const barrelImports = [];
@@ -1326,7 +1344,7 @@ async function addCommand(components, opts) {
1326
1344
  const ref = refs.find((r) => r.name === item.name) ?? { namespace: "@kitn", name: item.name, version: void 0 };
1327
1345
  for (const file of item.files) {
1328
1346
  const fileName = file.path.split("/").pop();
1329
- const installPath = getInstallPath(config, item.type, fileName, ref.namespace);
1347
+ const installPath = getInstallPath(config2, item.type, fileName, ref.namespace);
1330
1348
  const filePath = join7(cwd, installPath);
1331
1349
  const importPath = "./" + relative2(barrelDir, filePath).replace(/\\/g, "/");
1332
1350
  barrelImports.push(importPath);
@@ -1357,7 +1375,7 @@ async function addCommand(components, opts) {
1357
1375
  );
1358
1376
  }
1359
1377
  }
1360
- await writeConfig(cwd, config);
1378
+ await writeConfig(cwd, config2);
1361
1379
  await writeLock(cwd, lock);
1362
1380
  if (totalDeps > 0) {
1363
1381
  const pm = await detectPackageManager(cwd);
@@ -1394,7 +1412,7 @@ async function addCommand(components, opts) {
1394
1412
  const resolvedNames = new Set(resolved.map((r) => r.name));
1395
1413
  const projectInstalled = new Set(Object.keys(lock));
1396
1414
  const hints = [];
1397
- const adapterName = resolveRoutesAlias(config);
1415
+ const adapterName = resolveRoutesAlias(config2);
1398
1416
  if (resolvedNames.has("core") && !resolvedNames.has(adapterName) && !projectInstalled.has(adapterName)) {
1399
1417
  hints.push(`Run ${pc3.cyan(`kitn add routes`)} to install the HTTP adapter.`);
1400
1418
  }
@@ -1470,10 +1488,10 @@ ${content}`;
1470
1488
  }
1471
1489
  return content;
1472
1490
  }
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);
1491
+ async function generateRulesFiles(cwd, config2, selectedToolIds) {
1492
+ const rulesConfig = await fetchRulesConfig(config2.registries);
1493
+ const template = await fetchRulesTemplate(config2.registries);
1494
+ const rendered = renderTemplate(template, config2.aliases);
1477
1495
  const toolsToWrite = selectedToolIds ? rulesConfig.tools.filter((t) => selectedToolIds.includes(t.id)) : rulesConfig.tools;
1478
1496
  const written = [];
1479
1497
  for (const tool of toolsToWrite) {
@@ -1718,7 +1736,7 @@ async function initCommand(opts = {}) {
1718
1736
  }
1719
1737
  baseDir = base;
1720
1738
  }
1721
- const config = {
1739
+ const config2 = {
1722
1740
  runtime,
1723
1741
  framework,
1724
1742
  aliases: {
@@ -1730,7 +1748,7 @@ async function initCommand(opts = {}) {
1730
1748
  },
1731
1749
  registries: {
1732
1750
  "@kitn": {
1733
- url: "https://kitn-ai.github.io/kitn/r/{type}/{name}.json",
1751
+ url: DEFAULT_REGISTRY_URL,
1734
1752
  homepage: "https://kitn.ai",
1735
1753
  description: "Official kitn AI agent components"
1736
1754
  }
@@ -1738,7 +1756,7 @@ async function initCommand(opts = {}) {
1738
1756
  };
1739
1757
  const s = p3.spinner();
1740
1758
  s.start("Writing kitn.json");
1741
- await writeConfig(cwd, config);
1759
+ await writeConfig(cwd, config2);
1742
1760
  s.stop("Created kitn.json");
1743
1761
  await patchProjectTsconfig(
1744
1762
  cwd,
@@ -1756,7 +1774,7 @@ async function initCommand(opts = {}) {
1756
1774
  await writeFile8(pluginPath, getPluginTemplate(framework));
1757
1775
  p3.log.success(`Created ${pc4.bold(baseDir + "/plugin.ts")} \u2014 configure your AI provider there`);
1758
1776
  try {
1759
- const rulesConfig = await fetchRulesConfig(config.registries);
1777
+ const rulesConfig = await fetchRulesConfig(config2.registries);
1760
1778
  let selectedToolIds;
1761
1779
  if (opts.yes) {
1762
1780
  selectedToolIds = rulesConfig.tools.map((t) => t.id);
@@ -1777,9 +1795,7 @@ async function initCommand(opts = {}) {
1777
1795
  }
1778
1796
  }
1779
1797
  if (selectedToolIds.length > 0) {
1780
- const updatedConfig = { ...config, aiTools: selectedToolIds };
1781
- await writeConfig(cwd, updatedConfig);
1782
- const written = await generateRulesFiles(cwd, updatedConfig, selectedToolIds);
1798
+ const written = await generateRulesFiles(cwd, config2, selectedToolIds);
1783
1799
  for (const filePath of written) {
1784
1800
  p3.log.success(`Created ${pc4.bold(filePath)}`);
1785
1801
  }
@@ -1827,8 +1843,8 @@ import * as p4 from "@clack/prompts";
1827
1843
  import pc5 from "picocolors";
1828
1844
  async function listCommand(typeFilter, opts) {
1829
1845
  const cwd = process.cwd();
1830
- const config = await readConfig(cwd);
1831
- if (!config) {
1846
+ const config2 = await readConfig(cwd);
1847
+ if (!config2) {
1832
1848
  p4.log.error("No kitn.json found. Run `kitn init` first.");
1833
1849
  process.exit(1);
1834
1850
  }
@@ -1838,9 +1854,9 @@ async function listCommand(typeFilter, opts) {
1838
1854
  p4.log.error(`Unknown type ${pc5.bold(rawType)}. Valid types: agent, tool, skill, storage, package`);
1839
1855
  process.exit(1);
1840
1856
  }
1841
- const fetcher = new RegistryFetcher(config.registries);
1842
- const namespacesToFetch = opts.registry ? [opts.registry] : Object.keys(config.registries);
1843
- if (opts.registry && !config.registries[opts.registry]) {
1857
+ const fetcher = new RegistryFetcher(config2.registries);
1858
+ const namespacesToFetch = opts.registry ? [opts.registry] : Object.keys(config2.registries);
1859
+ if (opts.registry && !config2.registries[opts.registry]) {
1844
1860
  p4.log.error(`Registry ${pc5.bold(opts.registry)} is not configured. Run ${pc5.bold("kitn registry list")} to see configured registries.`);
1845
1861
  process.exit(1);
1846
1862
  }
@@ -1953,12 +1969,12 @@ import * as p5 from "@clack/prompts";
1953
1969
  import { join as join10 } from "path";
1954
1970
  async function diffCommand(componentName) {
1955
1971
  const cwd = process.cwd();
1956
- const config = await readConfig(cwd);
1957
- if (!config) {
1972
+ const config2 = await readConfig(cwd);
1973
+ if (!config2) {
1958
1974
  p5.log.error("No kitn.json found. Run `kitn init` first.");
1959
1975
  process.exit(1);
1960
1976
  }
1961
- const input = componentName === "routes" ? resolveRoutesAlias(config) : componentName;
1977
+ const input = componentName === "routes" ? resolveRoutesAlias(config2) : componentName;
1962
1978
  const ref = parseComponentRef(input);
1963
1979
  const lock = await readLock(cwd);
1964
1980
  const installedKey = ref.namespace === "@kitn" ? ref.name : `${ref.namespace}/${ref.name}`;
@@ -1968,7 +1984,7 @@ async function diffCommand(componentName) {
1968
1984
  process.exit(1);
1969
1985
  }
1970
1986
  const namespace = installed.registry ?? ref.namespace;
1971
- const fetcher = new RegistryFetcher(config.registries);
1987
+ const fetcher = new RegistryFetcher(config2.registries);
1972
1988
  const index = await fetcher.fetchIndex(namespace);
1973
1989
  const indexItem = index.items.find((i) => i.name === ref.name);
1974
1990
  if (!indexItem) {
@@ -1980,7 +1996,7 @@ async function diffCommand(componentName) {
1980
1996
  let hasDiff = false;
1981
1997
  for (const file of registryItem.files) {
1982
1998
  if (indexItem.type === "kitn:package") {
1983
- const baseDir = config.aliases.base ?? "src/ai";
1999
+ const baseDir = config2.aliases.base ?? "src/ai";
1984
2000
  const localPath = join10(cwd, baseDir, file.path);
1985
2001
  const relativePath = join10(baseDir, file.path);
1986
2002
  const localContent = await readExistingFile(localPath);
@@ -2006,7 +2022,7 @@ async function diffCommand(componentName) {
2006
2022
  return "storage";
2007
2023
  }
2008
2024
  })();
2009
- const localPath = join10(cwd, config.aliases[aliasKey], fileName);
2025
+ const localPath = join10(cwd, config2.aliases[aliasKey], fileName);
2010
2026
  const localContent = await readExistingFile(localPath);
2011
2027
  if (localContent === null) {
2012
2028
  p5.log.warn(`${fileName}: file missing locally`);
@@ -2043,7 +2059,7 @@ import pc6 from "picocolors";
2043
2059
  import { join as join11, relative as relative3, dirname as dirname5 } from "path";
2044
2060
  import { unlink as unlink3, readFile as readFile8, writeFile as writeFile9 } from "fs/promises";
2045
2061
  import { existsSync as existsSync2 } from "fs";
2046
- async function removeSingleComponent(installedKey, lock, config, cwd) {
2062
+ async function removeSingleComponent(installedKey, lock, config2, cwd) {
2047
2063
  const entry = lock[installedKey];
2048
2064
  if (!entry) return;
2049
2065
  const deleted = [];
@@ -2055,13 +2071,13 @@ async function removeSingleComponent(installedKey, lock, config, cwd) {
2055
2071
  p6.log.warn(`Could not delete ${filePath} (may have been moved or renamed)`);
2056
2072
  }
2057
2073
  }
2058
- const baseDir = config.aliases.base ?? "src/ai";
2074
+ const baseDir = config2.aliases.base ?? "src/ai";
2059
2075
  const barrelPath = join11(cwd, baseDir, "index.ts");
2060
2076
  const barrelDir = join11(cwd, baseDir);
2061
2077
  const barrelEligibleDirs = /* @__PURE__ */ new Set([
2062
- config.aliases.agents,
2063
- config.aliases.tools,
2064
- config.aliases.skills
2078
+ config2.aliases.agents,
2079
+ config2.aliases.tools,
2080
+ config2.aliases.skills
2065
2081
  ]);
2066
2082
  if (existsSync2(barrelPath) && deleted.length > 0) {
2067
2083
  let barrelContent = await readFile8(barrelPath, "utf-8");
@@ -2087,7 +2103,7 @@ async function removeSingleComponent(installedKey, lock, config, cwd) {
2087
2103
  ` + deleted.map((f) => ` ${pc6.red("-")} ${f}`).join("\n"));
2088
2104
  }
2089
2105
  }
2090
- async function offerOrphanRemoval(removedDeps, lock, config, cwd) {
2106
+ async function offerOrphanRemoval(removedDeps, lock, config2, cwd) {
2091
2107
  if (removedDeps.size === 0) return;
2092
2108
  const remaining = Object.entries(lock);
2093
2109
  const neededDeps = /* @__PURE__ */ new Set();
@@ -2114,13 +2130,13 @@ async function offerOrphanRemoval(removedDeps, lock, config, cwd) {
2114
2130
  });
2115
2131
  if (p6.isCancel(selected)) return;
2116
2132
  for (const key of selected) {
2117
- await removeSingleComponent(key, lock, config, cwd);
2133
+ await removeSingleComponent(key, lock, config2, cwd);
2118
2134
  }
2119
2135
  }
2120
2136
  async function removeCommand(componentName) {
2121
2137
  const cwd = process.cwd();
2122
- const config = await readConfig(cwd);
2123
- if (!config) {
2138
+ const config2 = await readConfig(cwd);
2139
+ if (!config2) {
2124
2140
  p6.log.error("No kitn.json found. Run `kitn init` first.");
2125
2141
  process.exit(1);
2126
2142
  }
@@ -2158,14 +2174,14 @@ async function removeCommand(componentName) {
2158
2174
  }
2159
2175
  }
2160
2176
  for (const key of selectedKeys) {
2161
- await removeSingleComponent(key, lock, config, cwd);
2177
+ await removeSingleComponent(key, lock, config2, cwd);
2162
2178
  }
2163
- await offerOrphanRemoval(allRemovedDeps, lock, config, cwd);
2179
+ await offerOrphanRemoval(allRemovedDeps, lock, config2, cwd);
2164
2180
  await writeLock(cwd, lock);
2165
2181
  p6.outro(pc6.green("Done!"));
2166
2182
  return;
2167
2183
  }
2168
- const input = componentName === "routes" ? resolveRoutesAlias(config) : componentName;
2184
+ const input = componentName === "routes" ? resolveRoutesAlias(config2) : componentName;
2169
2185
  const ref = parseComponentRef(input);
2170
2186
  const installedKey = ref.namespace === "@kitn" ? ref.name : `${ref.namespace}/${ref.name}`;
2171
2187
  const entry = lock[installedKey];
@@ -2182,8 +2198,8 @@ async function removeCommand(componentName) {
2182
2198
  process.exit(0);
2183
2199
  }
2184
2200
  const removedDeps = new Set(entry.registryDependencies ?? []);
2185
- await removeSingleComponent(installedKey, lock, config, cwd);
2186
- await offerOrphanRemoval(removedDeps, lock, config, cwd);
2201
+ await removeSingleComponent(installedKey, lock, config2, cwd);
2202
+ await offerOrphanRemoval(removedDeps, lock, config2, cwd);
2187
2203
  await writeLock(cwd, lock);
2188
2204
  }
2189
2205
  var init_remove = __esm({
@@ -2204,8 +2220,8 @@ import * as p7 from "@clack/prompts";
2204
2220
  async function updateCommand(components) {
2205
2221
  if (components.length === 0) {
2206
2222
  const cwd = process.cwd();
2207
- const config = await readConfig(cwd);
2208
- if (!config) {
2223
+ const config2 = await readConfig(cwd);
2224
+ if (!config2) {
2209
2225
  p7.log.error("No kitn.json found. Run `kitn init` first.");
2210
2226
  process.exit(1);
2211
2227
  }
@@ -2347,8 +2363,8 @@ async function createComponentInProject(type, name, opts) {
2347
2363
  );
2348
2364
  }
2349
2365
  const cwd = opts?.cwd ?? process.cwd();
2350
- const config = await readConfig(cwd);
2351
- if (!config) {
2366
+ const config2 = await readConfig(cwd);
2367
+ if (!config2) {
2352
2368
  throw new Error(
2353
2369
  `No kitn.json found in ${cwd}. Run ${pc7.bold("kitn init")} first.`
2354
2370
  );
@@ -2356,7 +2372,7 @@ async function createComponentInProject(type, name, opts) {
2356
2372
  const validType = type;
2357
2373
  const kitnType = typeToKitnType[validType];
2358
2374
  const fileName = validType === "skill" ? `${name}.md` : `${name}.ts`;
2359
- const filePath = join12(cwd, getInstallPath(config, kitnType, fileName));
2375
+ const filePath = join12(cwd, getInstallPath(config2, kitnType, fileName));
2360
2376
  const dummyContent = "";
2361
2377
  const status = await checkFileStatus(filePath, dummyContent);
2362
2378
  if (status !== "new" /* New */) {
@@ -2383,7 +2399,7 @@ async function createComponentInProject(type, name, opts) {
2383
2399
  await writeComponentFile(filePath, source);
2384
2400
  let barrelUpdated = false;
2385
2401
  if (BARREL_TYPES.includes(validType)) {
2386
- const baseDir = config.aliases.base ?? "src/ai";
2402
+ const baseDir = config2.aliases.base ?? "src/ai";
2387
2403
  const barrelPath = join12(cwd, baseDir, "index.ts");
2388
2404
  let barrelContent;
2389
2405
  if (existsSync3(barrelPath)) {
@@ -2447,14 +2463,14 @@ function stripSuffix(name, suffix) {
2447
2463
  }
2448
2464
  return name;
2449
2465
  }
2450
- function toolsDir(config, cwd) {
2451
- const baseAlias = config.aliases.base ?? "src/ai";
2452
- const tools = config.aliases.tools ?? join13(baseAlias, "tools");
2466
+ function toolsDir(config2, cwd) {
2467
+ const baseAlias = config2.aliases.base ?? "src/ai";
2468
+ const tools = config2.aliases.tools ?? join13(baseAlias, "tools");
2453
2469
  return join13(cwd, tools);
2454
2470
  }
2455
- function agentsDir(config, cwd) {
2456
- const baseAlias = config.aliases.base ?? "src/ai";
2457
- const agents = config.aliases.agents ?? join13(baseAlias, "agents");
2471
+ function agentsDir(config2, cwd) {
2472
+ const baseAlias = config2.aliases.base ?? "src/ai";
2473
+ const agents = config2.aliases.agents ?? join13(baseAlias, "agents");
2458
2474
  return join13(cwd, agents);
2459
2475
  }
2460
2476
  async function findFile(dir, candidates) {
@@ -2483,14 +2499,14 @@ function computeImportPath(fromDir, toFile) {
2483
2499
  }
2484
2500
  return rel.replace(/\.ts$/, ".js");
2485
2501
  }
2486
- async function resolveToolByName(name, config, cwd) {
2487
- const tDir = toolsDir(config, cwd);
2488
- const aDir = agentsDir(config, cwd);
2502
+ async function resolveToolByName(name, config2, cwd) {
2503
+ const tDir = toolsDir(config2, cwd);
2504
+ const aDir = agentsDir(config2, cwd);
2489
2505
  const lock = await readLock(cwd);
2490
2506
  for (const [componentName, entry] of Object.entries(lock)) {
2491
2507
  if (componentName === name || componentName === `${name}-tool`) {
2492
2508
  const toolFile = entry.files.find((f) => {
2493
- const toolsAlias = config.aliases.tools ?? join13(config.aliases.base ?? "src/ai", "tools");
2509
+ const toolsAlias = config2.aliases.tools ?? join13(config2.aliases.base ?? "src/ai", "tools");
2494
2510
  return f.startsWith(toolsAlias);
2495
2511
  });
2496
2512
  if (toolFile) {
@@ -2523,13 +2539,13 @@ async function resolveToolByName(name, config, cwd) {
2523
2539
  importPath: computeImportPath(aDir, filePath)
2524
2540
  };
2525
2541
  }
2526
- async function resolveAgentByName(name, config, cwd) {
2527
- const aDir = agentsDir(config, cwd);
2542
+ async function resolveAgentByName(name, config2, cwd) {
2543
+ const aDir = agentsDir(config2, cwd);
2528
2544
  const lock = await readLock(cwd);
2529
2545
  for (const [componentName, entry] of Object.entries(lock)) {
2530
2546
  if (componentName === name || componentName === `${name}-agent`) {
2531
2547
  const agentFile = entry.files.find((f) => {
2532
- const agentsAlias = config.aliases.agents ?? join13(config.aliases.base ?? "src/ai", "agents");
2548
+ const agentsAlias = config2.aliases.agents ?? join13(config2.aliases.base ?? "src/ai", "agents");
2533
2549
  return f.startsWith(agentsAlias);
2534
2550
  });
2535
2551
  if (agentFile) {
@@ -2558,12 +2574,12 @@ async function resolveAgentByName(name, config, cwd) {
2558
2574
  name: agentName ?? fallbackName
2559
2575
  };
2560
2576
  }
2561
- async function listTools(config, cwd) {
2562
- const dir = toolsDir(config, cwd);
2577
+ async function listTools(config2, cwd) {
2578
+ const dir = toolsDir(config2, cwd);
2563
2579
  return listComponentsInDir(dir);
2564
2580
  }
2565
- async function listAgents(config, cwd) {
2566
- const dir = agentsDir(config, cwd);
2581
+ async function listAgents(config2, cwd) {
2582
+ const dir = agentsDir(config2, cwd);
2567
2583
  return listComponentsInDir(dir);
2568
2584
  }
2569
2585
  async function listComponentsInDir(dir) {
@@ -2843,8 +2859,8 @@ import { basename } from "path";
2843
2859
  async function linkCommand(type, name, opts) {
2844
2860
  p9.intro(pc8.bgCyan(pc8.black(" kitn link ")));
2845
2861
  const cwd = process.cwd();
2846
- const config = await readConfig(cwd);
2847
- if (!config) {
2862
+ const config2 = await readConfig(cwd);
2863
+ if (!config2) {
2848
2864
  p9.log.error("No kitn.json found. Run `kitn init` first.");
2849
2865
  process.exit(1);
2850
2866
  }
@@ -2856,7 +2872,7 @@ async function linkCommand(type, name, opts) {
2856
2872
  }
2857
2873
  let toolName = name;
2858
2874
  if (!toolName) {
2859
- const tools = await listTools(config, cwd);
2875
+ const tools = await listTools(config2, cwd);
2860
2876
  if (tools.length === 0) {
2861
2877
  p9.log.error("No tools found in your project.");
2862
2878
  process.exit(1);
@@ -2874,7 +2890,7 @@ async function linkCommand(type, name, opts) {
2874
2890
  }
2875
2891
  toolName = selected;
2876
2892
  }
2877
- const tool = await resolveToolByName(toolName, config, cwd);
2893
+ const tool = await resolveToolByName(toolName, config2, cwd);
2878
2894
  if (!tool) {
2879
2895
  p9.log.error(
2880
2896
  `Tool "${toolName}" not found. Check that the file exists in your tools directory.`
@@ -2883,7 +2899,7 @@ async function linkCommand(type, name, opts) {
2883
2899
  }
2884
2900
  let agentName = opts?.to;
2885
2901
  if (!agentName) {
2886
- const agents = await listAgents(config, cwd);
2902
+ const agents = await listAgents(config2, cwd);
2887
2903
  if (agents.length === 0) {
2888
2904
  p9.log.error("No agents found in your project.");
2889
2905
  process.exit(1);
@@ -2901,7 +2917,7 @@ async function linkCommand(type, name, opts) {
2901
2917
  }
2902
2918
  agentName = selected;
2903
2919
  }
2904
- const agent = await resolveAgentByName(agentName, config, cwd);
2920
+ const agent = await resolveAgentByName(agentName, config2, cwd);
2905
2921
  if (!agent) {
2906
2922
  p9.log.error(
2907
2923
  `Agent "${agentName}" not found. Check that the file exists in your agents directory.`
@@ -2959,8 +2975,8 @@ import { basename as basename2 } from "path";
2959
2975
  async function unlinkCommand(type, name, opts) {
2960
2976
  p10.intro(pc9.bgCyan(pc9.black(" kitn unlink ")));
2961
2977
  const cwd = process.cwd();
2962
- const config = await readConfig(cwd);
2963
- if (!config) {
2978
+ const config2 = await readConfig(cwd);
2979
+ if (!config2) {
2964
2980
  p10.log.error("No kitn.json found. Run `kitn init` first.");
2965
2981
  process.exit(1);
2966
2982
  }
@@ -2972,7 +2988,7 @@ async function unlinkCommand(type, name, opts) {
2972
2988
  }
2973
2989
  let toolName = name;
2974
2990
  if (!toolName) {
2975
- const tools = await listTools(config, cwd);
2991
+ const tools = await listTools(config2, cwd);
2976
2992
  if (tools.length === 0) {
2977
2993
  p10.log.error("No tools found in your project.");
2978
2994
  process.exit(1);
@@ -2990,7 +3006,7 @@ async function unlinkCommand(type, name, opts) {
2990
3006
  }
2991
3007
  toolName = selected;
2992
3008
  }
2993
- const tool = await resolveToolByName(toolName, config, cwd);
3009
+ const tool = await resolveToolByName(toolName, config2, cwd);
2994
3010
  if (!tool) {
2995
3011
  p10.log.error(
2996
3012
  `Tool "${toolName}" not found. Check that the file exists in your tools directory.`
@@ -2999,7 +3015,7 @@ async function unlinkCommand(type, name, opts) {
2999
3015
  }
3000
3016
  let agentName = opts?.from;
3001
3017
  if (!agentName) {
3002
- const agents = await listAgents(config, cwd);
3018
+ const agents = await listAgents(config2, cwd);
3003
3019
  if (agents.length === 0) {
3004
3020
  p10.log.error("No agents found in your project.");
3005
3021
  process.exit(1);
@@ -3017,7 +3033,7 @@ async function unlinkCommand(type, name, opts) {
3017
3033
  }
3018
3034
  agentName = selected;
3019
3035
  }
3020
- const agent = await resolveAgentByName(agentName, config, cwd);
3036
+ const agent = await resolveAgentByName(agentName, config2, cwd);
3021
3037
  if (!agent) {
3022
3038
  p10.log.error(
3023
3039
  `Agent "${agentName}" not found. Check that the file exists in your agents directory.`
@@ -3072,13 +3088,13 @@ import * as p11 from "@clack/prompts";
3072
3088
  import pc10 from "picocolors";
3073
3089
  async function infoCommand(component) {
3074
3090
  const cwd = process.cwd();
3075
- const config = await readConfig(cwd);
3076
- if (!config) {
3091
+ const config2 = await readConfig(cwd);
3092
+ if (!config2) {
3077
3093
  p11.log.error("No kitn.json found. Run `kitn init` first.");
3078
3094
  process.exit(1);
3079
3095
  }
3080
3096
  const ref = parseComponentRef(component);
3081
- const fetcher = new RegistryFetcher(config.registries);
3097
+ const fetcher = new RegistryFetcher(config2.registries);
3082
3098
  const s = p11.spinner();
3083
3099
  s.start("Fetching component info...");
3084
3100
  let index;
@@ -3241,40 +3257,33 @@ import pc12 from "picocolors";
3241
3257
  async function rulesCommand() {
3242
3258
  p13.intro(pc12.bgCyan(pc12.black(" kitn rules ")));
3243
3259
  const cwd = process.cwd();
3244
- const config = await readConfig(cwd);
3245
- if (!config) {
3246
- p13.log.error(`No kitn.json found. Run ${pc12.bold("kitn init")} first.`);
3247
- process.exit(1);
3260
+ const config2 = await readConfig(cwd);
3261
+ const registries = config2?.registries ?? DEFAULT_REGISTRIES;
3262
+ const aliases = config2?.aliases ?? DEFAULT_ALIASES;
3263
+ const rulesConfig = await fetchRulesConfig(registries);
3264
+ const selected = await p13.multiselect({
3265
+ message: "Which AI coding tools do you use?",
3266
+ options: rulesConfig.tools.map((t) => ({
3267
+ value: t.id,
3268
+ label: t.name,
3269
+ hint: t.description
3270
+ })),
3271
+ required: false
3272
+ });
3273
+ if (p13.isCancel(selected)) {
3274
+ p13.cancel("Cancelled.");
3275
+ process.exit(0);
3248
3276
  }
3249
- let selectedIds = config.aiTools;
3250
- if (!selectedIds || selectedIds.length === 0) {
3251
- const rulesConfig = await fetchRulesConfig(config.registries);
3252
- const selected = await p13.multiselect({
3253
- message: "Which AI coding tools do you use?",
3254
- options: rulesConfig.tools.map((t) => ({
3255
- value: t.id,
3256
- label: t.name,
3257
- hint: t.description
3258
- })),
3259
- required: false
3260
- });
3261
- if (p13.isCancel(selected)) {
3262
- p13.cancel("Cancelled.");
3263
- process.exit(0);
3264
- }
3265
- selectedIds = selected;
3266
- if (selectedIds.length === 0) {
3267
- p13.log.warn("No tools selected. Nothing to generate.");
3268
- p13.outro("Done.");
3269
- return;
3270
- }
3271
- const updatedConfig = { ...config, aiTools: selectedIds };
3272
- await writeConfig(cwd, updatedConfig);
3273
- p13.log.info(`Saved tool selections to ${pc12.bold("kitn.json")}`);
3277
+ const selectedIds = selected;
3278
+ if (selectedIds.length === 0) {
3279
+ p13.log.warn("No tools selected. Nothing to generate.");
3280
+ p13.outro("Done.");
3281
+ return;
3274
3282
  }
3275
3283
  const s = p13.spinner();
3276
3284
  s.start("Generating rules files");
3277
- const written = await generateRulesFiles(cwd, config, selectedIds);
3285
+ const effectiveConfig = { registries, aliases };
3286
+ const written = await generateRulesFiles(cwd, effectiveConfig, selectedIds);
3278
3287
  s.stop("Rules files generated");
3279
3288
  for (const filePath of written) {
3280
3289
  p13.log.success(`${pc12.green("+")} ${filePath}`);
@@ -3289,6 +3298,284 @@ var init_rules = __esm({
3289
3298
  }
3290
3299
  });
3291
3300
 
3301
+ // src/commands/config.ts
3302
+ var config_exports = {};
3303
+ __export(config_exports, {
3304
+ configGetCommand: () => configGetCommand,
3305
+ configListCommand: () => configListCommand,
3306
+ configSetCommand: () => configSetCommand,
3307
+ readUserConfig: () => readUserConfig
3308
+ });
3309
+ import * as p14 from "@clack/prompts";
3310
+ import pc13 from "picocolors";
3311
+ import { readFile as readFile13, writeFile as writeFile13, mkdir as mkdir7 } from "fs/promises";
3312
+ import { join as join14 } from "path";
3313
+ import { homedir as homedir2 } from "os";
3314
+ import { existsSync as existsSync4 } from "fs";
3315
+ async function readUserConfig() {
3316
+ try {
3317
+ const raw = await readFile13(CONFIG_FILE2, "utf-8");
3318
+ return JSON.parse(raw);
3319
+ } catch {
3320
+ return {};
3321
+ }
3322
+ }
3323
+ async function writeUserConfig(config2) {
3324
+ if (!existsSync4(CONFIG_DIR)) {
3325
+ await mkdir7(CONFIG_DIR, { recursive: true });
3326
+ }
3327
+ await writeFile13(CONFIG_FILE2, JSON.stringify(config2, null, 2) + "\n");
3328
+ }
3329
+ async function configSetCommand(key, value) {
3330
+ if (!VALID_KEYS.includes(key)) {
3331
+ p14.log.error(`Unknown config key: ${pc13.red(key)}`);
3332
+ p14.log.info(`Valid keys: ${VALID_KEYS.join(", ")}`);
3333
+ process.exit(1);
3334
+ }
3335
+ const config2 = await readUserConfig();
3336
+ config2[key] = value;
3337
+ await writeUserConfig(config2);
3338
+ const displayValue = key.includes("key") ? value.slice(0, 8) + "..." : value;
3339
+ p14.log.success(`Set ${pc13.cyan(key)} = ${pc13.green(displayValue)}`);
3340
+ }
3341
+ async function configGetCommand(key) {
3342
+ const config2 = await readUserConfig();
3343
+ const value = config2[key];
3344
+ if (value === void 0) {
3345
+ p14.log.warn(`${pc13.cyan(key)} is not set`);
3346
+ } else {
3347
+ const displayValue = key.includes("key") ? value.slice(0, 8) + "..." : value;
3348
+ p14.log.info(`${pc13.cyan(key)} = ${pc13.green(displayValue)}`);
3349
+ }
3350
+ }
3351
+ async function configListCommand() {
3352
+ const config2 = await readUserConfig();
3353
+ const entries = Object.entries(config2);
3354
+ if (entries.length === 0) {
3355
+ p14.log.info("No user configuration set.");
3356
+ p14.log.info(`Run ${pc13.cyan("kitn config set <key> <value>")} to configure.`);
3357
+ return;
3358
+ }
3359
+ for (const [key, value] of entries) {
3360
+ const displayValue = key.includes("key") ? value.slice(0, 8) + "..." : value;
3361
+ p14.log.info(`${pc13.cyan(key)} = ${pc13.green(displayValue)}`);
3362
+ }
3363
+ }
3364
+ var CONFIG_DIR, CONFIG_FILE2, VALID_KEYS;
3365
+ var init_config2 = __esm({
3366
+ "src/commands/config.ts"() {
3367
+ "use strict";
3368
+ CONFIG_DIR = join14(homedir2(), ".kitn");
3369
+ CONFIG_FILE2 = join14(CONFIG_DIR, "config.json");
3370
+ VALID_KEYS = ["chat-url", "api-key"];
3371
+ }
3372
+ });
3373
+
3374
+ // src/commands/chat.ts
3375
+ var chat_exports = {};
3376
+ __export(chat_exports, {
3377
+ buildRequestPayload: () => buildRequestPayload,
3378
+ chatCommand: () => chatCommand,
3379
+ formatPlan: () => formatPlan,
3380
+ resolveServiceUrl: () => resolveServiceUrl
3381
+ });
3382
+ import * as p15 from "@clack/prompts";
3383
+ import pc14 from "picocolors";
3384
+ async function resolveServiceUrl(urlOverride, chatServiceConfig) {
3385
+ if (urlOverride) return urlOverride;
3386
+ if (process.env.KITN_CHAT_URL) return process.env.KITN_CHAT_URL;
3387
+ const userConfig = await readUserConfig();
3388
+ if (userConfig["chat-url"]) return userConfig["chat-url"];
3389
+ if (chatServiceConfig?.url) return chatServiceConfig.url;
3390
+ return DEFAULT_SERVICE_URL;
3391
+ }
3392
+ function buildRequestPayload(message, metadata) {
3393
+ return { message, metadata };
3394
+ }
3395
+ function formatPlan(plan) {
3396
+ const lines = [plan.summary, ""];
3397
+ for (let i = 0; i < plan.steps.length; i++) {
3398
+ const step = plan.steps[i];
3399
+ const num = `${i + 1}.`;
3400
+ const label = formatStepLabel(step);
3401
+ lines.push(`${num} ${label} - ${step.reason}`);
3402
+ }
3403
+ return lines.join("\n");
3404
+ }
3405
+ function formatStepLabel(step) {
3406
+ switch (step.action) {
3407
+ case "add":
3408
+ return `Add ${pc14.cyan(step.component)}`;
3409
+ case "remove":
3410
+ return `Remove ${pc14.red(step.component)}`;
3411
+ case "create":
3412
+ return `Create ${pc14.green(`${step.type}/${step.name}`)}`;
3413
+ case "link":
3414
+ return `Link ${pc14.cyan(step.toolName)} \u2192 ${pc14.cyan(step.agentName)}`;
3415
+ case "unlink":
3416
+ return `Unlink ${pc14.red(step.toolName)} from ${pc14.cyan(step.agentName)}`;
3417
+ }
3418
+ }
3419
+ async function chatCommand(message, opts) {
3420
+ const cwd = process.cwd();
3421
+ const config2 = await readConfig(cwd);
3422
+ if (!config2) {
3423
+ p15.log.error("No kitn.json found. Run `kitn init` first.");
3424
+ process.exit(1);
3425
+ }
3426
+ if (!message) {
3427
+ p15.log.error('Please provide a message. Usage: kitn chat "add a weather tool"');
3428
+ process.exit(1);
3429
+ }
3430
+ p15.intro(pc14.bold("kitn assistant"));
3431
+ const s = p15.spinner();
3432
+ s.start("Gathering project context...");
3433
+ let registryIndex;
3434
+ let installed;
3435
+ try {
3436
+ 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);
3447
+ installed = Object.keys(lock);
3448
+ } 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.");
3451
+ process.exit(1);
3452
+ }
3453
+ s.stop("Context gathered");
3454
+ s.start("Thinking...");
3455
+ const serviceUrl = await resolveServiceUrl(opts?.url, config2.chatService);
3456
+ const payload = buildRequestPayload(message, { registryIndex, installed });
3457
+ let response;
3458
+ try {
3459
+ const headers = {
3460
+ "Content-Type": "application/json"
3461
+ };
3462
+ if (process.env.KITN_API_KEY) {
3463
+ headers["Authorization"] = `Bearer ${process.env.KITN_API_KEY}`;
3464
+ }
3465
+ response = await fetch(`${serviceUrl}/api/chat`, {
3466
+ method: "POST",
3467
+ headers,
3468
+ body: JSON.stringify(payload)
3469
+ });
3470
+ } catch (err) {
3471
+ s.stop(pc14.red("Connection failed"));
3472
+ p15.log.error(`Could not reach chat service at ${serviceUrl}. ${err.message ?? ""}`);
3473
+ process.exit(1);
3474
+ }
3475
+ if (!response.ok) {
3476
+ s.stop(pc14.red("Request failed"));
3477
+ p15.log.error(`Chat service returned ${response.status}: ${response.statusText}`);
3478
+ process.exit(1);
3479
+ }
3480
+ let data;
3481
+ try {
3482
+ data = await response.json();
3483
+ } catch {
3484
+ s.stop(pc14.red("Invalid response"));
3485
+ p15.log.error("Chat service returned an invalid response.");
3486
+ process.exit(1);
3487
+ }
3488
+ s.stop("Done");
3489
+ if (data.rejected) {
3490
+ p15.log.warn(data.text ?? "Request was rejected by the assistant.");
3491
+ p15.outro("Try rephrasing your request.");
3492
+ return;
3493
+ }
3494
+ if (!data.plan) {
3495
+ p15.log.info(data.text ?? "No actionable plan returned.");
3496
+ p15.outro("Nothing to do.");
3497
+ return;
3498
+ }
3499
+ p15.log.message(formatPlan(data.plan));
3500
+ 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
+ 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
+ }))
3521
+ });
3522
+ if (p15.isCancel(choices)) {
3523
+ p15.cancel("Cancelled.");
3524
+ return;
3525
+ }
3526
+ selectedSteps = choices.map((i) => steps[i]);
3527
+ } else {
3528
+ selectedSteps = steps;
3529
+ }
3530
+ for (const step of selectedSteps) {
3531
+ s.start(`Running: ${formatStepLabel(step)}...`);
3532
+ try {
3533
+ switch (step.action) {
3534
+ case "add": {
3535
+ const { addCommand: addCommand2 } = await Promise.resolve().then(() => (init_add(), add_exports));
3536
+ await addCommand2([step.component], { yes: true });
3537
+ break;
3538
+ }
3539
+ case "create": {
3540
+ const { createCommand: createCommand2 } = await Promise.resolve().then(() => (init_create(), create_exports));
3541
+ await createCommand2(step.type, step.name);
3542
+ break;
3543
+ }
3544
+ case "link": {
3545
+ const { linkCommand: linkCommand2 } = await Promise.resolve().then(() => (init_link(), link_exports));
3546
+ await linkCommand2("tool", step.toolName, { to: step.agentName });
3547
+ break;
3548
+ }
3549
+ case "remove": {
3550
+ const { removeCommand: removeCommand2 } = await Promise.resolve().then(() => (init_remove(), remove_exports));
3551
+ await removeCommand2(step.component);
3552
+ break;
3553
+ }
3554
+ case "unlink": {
3555
+ const { unlinkCommand: unlinkCommand2 } = await Promise.resolve().then(() => (init_unlink(), unlink_exports));
3556
+ await unlinkCommand2("tool", step.toolName, { from: step.agentName });
3557
+ break;
3558
+ }
3559
+ }
3560
+ s.stop(pc14.green(`Done: ${formatStepLabel(step)}`));
3561
+ } catch (err) {
3562
+ s.stop(pc14.red(`Failed: ${formatStepLabel(step)}`));
3563
+ p15.log.error(err.message ?? "Unknown error");
3564
+ }
3565
+ }
3566
+ p15.outro(pc14.green("All done! Run your dev server to test the new components."));
3567
+ }
3568
+ var DEFAULT_SERVICE_URL;
3569
+ var init_chat = __esm({
3570
+ "src/commands/chat.ts"() {
3571
+ "use strict";
3572
+ init_config();
3573
+ init_fetcher();
3574
+ init_config2();
3575
+ DEFAULT_SERVICE_URL = "https://chat.kitn.dev";
3576
+ }
3577
+ });
3578
+
3292
3579
  // src/commands/registry.ts
3293
3580
  var registry_exports = {};
3294
3581
  __export(registry_exports, {
@@ -3296,12 +3583,12 @@ __export(registry_exports, {
3296
3583
  registryListCommand: () => registryListCommand,
3297
3584
  registryRemoveCommand: () => registryRemoveCommand
3298
3585
  });
3299
- import * as p14 from "@clack/prompts";
3300
- import pc13 from "picocolors";
3586
+ import * as p16 from "@clack/prompts";
3587
+ import pc15 from "picocolors";
3301
3588
  async function registryAddCommand(namespace, url, opts = {}) {
3302
3589
  const cwd = opts.cwd ?? process.cwd();
3303
- const config = await readConfig(cwd);
3304
- if (!config) throw new Error("No kitn.json found. Run `kitn init` first.");
3590
+ const config2 = await readConfig(cwd);
3591
+ if (!config2) throw new Error("No kitn.json found. Run `kitn init` first.");
3305
3592
  if (!namespace.startsWith("@")) {
3306
3593
  throw new Error("Namespace must start with @ (e.g. @myteam)");
3307
3594
  }
@@ -3311,28 +3598,28 @@ async function registryAddCommand(namespace, url, opts = {}) {
3311
3598
  if (!url.includes("{name}")) {
3312
3599
  throw new Error("URL template must include {name} placeholder");
3313
3600
  }
3314
- if (config.registries[namespace] && !opts.overwrite) {
3601
+ if (config2.registries[namespace] && !opts.overwrite) {
3315
3602
  throw new Error(`Registry '${namespace}' is already configured. Use --overwrite to replace.`);
3316
3603
  }
3317
3604
  if (opts.homepage || opts.description) {
3318
3605
  const entry = { url };
3319
3606
  if (opts.homepage) entry.homepage = opts.homepage;
3320
3607
  if (opts.description) entry.description = opts.description;
3321
- config.registries[namespace] = entry;
3608
+ config2.registries[namespace] = entry;
3322
3609
  } else {
3323
- config.registries[namespace] = url;
3610
+ config2.registries[namespace] = url;
3324
3611
  }
3325
- await writeConfig(cwd, config);
3326
- p14.log.success(`Added registry ${pc13.bold(namespace)}`);
3327
- p14.log.message(pc13.dim(` ${url}`));
3328
- if (opts.homepage) p14.log.message(pc13.dim(` Homepage: ${opts.homepage}`));
3329
- if (opts.description) p14.log.message(pc13.dim(` ${opts.description}`));
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}`));
3330
3617
  }
3331
3618
  async function registryRemoveCommand(namespace, opts = {}) {
3332
3619
  const cwd = opts.cwd ?? process.cwd();
3333
- const config = await readConfig(cwd);
3334
- if (!config) throw new Error("No kitn.json found. Run `kitn init` first.");
3335
- if (!config.registries[namespace]) {
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]) {
3336
3623
  throw new Error(`Registry '${namespace}' is not configured.`);
3337
3624
  }
3338
3625
  if (namespace === "@kitn" && !opts.force) {
@@ -3345,35 +3632,35 @@ async function registryRemoveCommand(namespace, opts = {}) {
3345
3632
  affectedComponents.push(name);
3346
3633
  }
3347
3634
  }
3348
- delete config.registries[namespace];
3349
- await writeConfig(cwd, config);
3350
- p14.log.success(`Removed registry ${pc13.bold(namespace)}`);
3635
+ delete config2.registries[namespace];
3636
+ await writeConfig(cwd, config2);
3637
+ p16.log.success(`Removed registry ${pc15.bold(namespace)}`);
3351
3638
  if (affectedComponents.length > 0) {
3352
- p14.log.warn(`${affectedComponents.length} installed component(s) referenced this registry:
3353
- ` + affectedComponents.map((name) => ` ${pc13.yellow("!")} ${name}`).join("\n"));
3639
+ p16.log.warn(`${affectedComponents.length} installed component(s) referenced this registry:
3640
+ ` + affectedComponents.map((name) => ` ${pc15.yellow("!")} ${name}`).join("\n"));
3354
3641
  }
3355
3642
  return { affectedComponents };
3356
3643
  }
3357
3644
  async function registryListCommand(opts = {}) {
3358
3645
  const cwd = opts.cwd ?? process.cwd();
3359
- const config = await readConfig(cwd);
3360
- if (!config) throw new Error("No kitn.json found. Run `kitn init` first.");
3361
- const entries = Object.entries(config.registries).map(([namespace, value]) => {
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]) => {
3362
3649
  const url = getRegistryUrl(value);
3363
3650
  const homepage = typeof value === "object" ? value.homepage : void 0;
3364
3651
  const description = typeof value === "object" ? value.description : void 0;
3365
3652
  return { namespace, url, homepage, description };
3366
3653
  });
3367
3654
  if (entries.length === 0) {
3368
- p14.log.message(pc13.dim(" No registries configured."));
3655
+ p16.log.message(pc15.dim(" No registries configured."));
3369
3656
  } else {
3370
3657
  const lines = [];
3371
3658
  for (const { namespace, url, homepage, description } of entries) {
3372
- lines.push(` ${pc13.bold(namespace.padEnd(16))} ${pc13.dim(url)}`);
3659
+ lines.push(` ${pc15.bold(namespace.padEnd(16))} ${pc15.dim(url)}`);
3373
3660
  if (description) lines.push(` ${" ".repeat(16)} ${description}`);
3374
- if (homepage) lines.push(` ${" ".repeat(16)} ${pc13.dim(homepage)}`);
3661
+ if (homepage) lines.push(` ${" ".repeat(16)} ${pc15.dim(homepage)}`);
3375
3662
  }
3376
- p14.log.message(lines.join("\n"));
3663
+ p16.log.message(lines.join("\n"));
3377
3664
  }
3378
3665
  return entries;
3379
3666
  }
@@ -3387,7 +3674,7 @@ var init_registry = __esm({
3387
3674
  // src/index.ts
3388
3675
  init_update_check();
3389
3676
  import { Command } from "commander";
3390
- var VERSION = true ? "0.1.32" : "0.0.0-dev";
3677
+ var VERSION = true ? "0.1.33" : "0.0.0-dev";
3391
3678
  var printUpdateNotice = startUpdateCheck(VERSION);
3392
3679
  var program = new Command().name("kitn").description("Install AI agent components from the kitn registry").version(VERSION);
3393
3680
  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) => {
@@ -3438,6 +3725,10 @@ program.command("rules").description("Regenerate AI coding tool rules files").ac
3438
3725
  const { rulesCommand: rulesCommand2 } = await Promise.resolve().then(() => (init_rules(), rules_exports));
3439
3726
  await rulesCommand2();
3440
3727
  });
3728
+ program.command("chat").description("AI-powered scaffolding assistant \u2014 describe what you need in plain English").argument("<message>", 'what you want to build (e.g. "I want a weather agent")').option("-u, --url <url>", "chat service URL (overrides config and default)").action(async (message, opts) => {
3729
+ const { chatCommand: chatCommand2 } = await Promise.resolve().then(() => (init_chat(), chat_exports));
3730
+ await chatCommand2(message, opts);
3731
+ });
3441
3732
  var registry = program.command("registry").description("Manage component registries");
3442
3733
  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) => {
3443
3734
  const { registryAddCommand: registryAddCommand2 } = await Promise.resolve().then(() => (init_registry(), registry_exports));
@@ -3451,6 +3742,19 @@ registry.command("list").description("List all configured registries").action(as
3451
3742
  const { registryListCommand: registryListCommand2 } = await Promise.resolve().then(() => (init_registry(), registry_exports));
3452
3743
  await registryListCommand2();
3453
3744
  });
3745
+ var config = program.command("config").description("Manage user-level configuration");
3746
+ config.command("set").description("Set a config value").argument("<key>", "config key (chat-url, api-key)").argument("<value>", "config value").action(async (key, value) => {
3747
+ const { configSetCommand: configSetCommand2 } = await Promise.resolve().then(() => (init_config2(), config_exports));
3748
+ await configSetCommand2(key, value);
3749
+ });
3750
+ config.command("get").description("Get a config value").argument("<key>", "config key").action(async (key) => {
3751
+ const { configGetCommand: configGetCommand2 } = await Promise.resolve().then(() => (init_config2(), config_exports));
3752
+ await configGetCommand2(key);
3753
+ });
3754
+ config.command("list").description("List all config values").action(async () => {
3755
+ const { configListCommand: configListCommand2 } = await Promise.resolve().then(() => (init_config2(), config_exports));
3756
+ await configListCommand2();
3757
+ });
3454
3758
  await program.parseAsync();
3455
3759
  var ranCommand = program.args[0];
3456
3760
  if (ranCommand !== "check") {