@kitnai/cli 0.1.16 → 0.1.17

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/README.md CHANGED
@@ -27,14 +27,27 @@ pnpm dlx @kitnai/cli init
27
27
  Initialize kitn in your project. Creates `kitn.json`, installs the core engine and Hono routes, and sets up tsconfig path aliases.
28
28
 
29
29
  ```bash
30
+ # Interactive (prompts for runtime and base directory)
30
31
  kitn init
32
+
33
+ # Non-interactive with flags
34
+ kitn init --runtime bun --base src/ai
35
+
36
+ # Accept all defaults (runtime=bun, base=src/ai)
37
+ kitn init -y
31
38
  ```
32
39
 
33
- Prompts for:
34
- - **Runtime**: bun, node, or deno
35
- - **Install path**: Base directory for kitn components (defaults to `src/ai`)
40
+ **Flags:**
41
+
42
+ | Flag | Description |
43
+ |------|-------------|
44
+ | `-r, --runtime <runtime>` | Runtime to use (`bun`, `node`, `deno`) — skips runtime prompt |
45
+ | `-b, --base <path>` | Base directory for components (default: `src/ai`) — skips path prompt |
46
+ | `-y, --yes` | Accept all defaults without prompting |
47
+
48
+ When flags are provided, the corresponding prompts are skipped. This enables scripting and CI usage.
36
49
 
37
- After answering, the CLI automatically installs the core engine and HTTP routes into your project.
50
+ After setup, the CLI automatically installs the core engine and HTTP routes into your project.
38
51
 
39
52
  ### `kitn add [components...]`
40
53
 
@@ -180,6 +193,16 @@ kitn build
180
193
  kitn build src/components --output dist/r
181
194
  ```
182
195
 
196
+ ### `kitn check`
197
+
198
+ Check for CLI updates.
199
+
200
+ ```bash
201
+ kitn check
202
+ ```
203
+
204
+ Shows the current version and whether a newer version is available on npm.
205
+
183
206
  ### `kitn registry`
184
207
 
185
208
  Manage component registries.
package/dist/index.js CHANGED
@@ -9,6 +9,86 @@ var __export = (target, all) => {
9
9
  __defProp(target, name, { get: all[name], enumerable: true });
10
10
  };
11
11
 
12
+ // src/utils/update-check.ts
13
+ import { readFile, writeFile, mkdir } from "fs/promises";
14
+ import { join } from "path";
15
+ import { homedir } from "os";
16
+ import pc from "picocolors";
17
+ async function readCache() {
18
+ try {
19
+ const raw = await readFile(CACHE_FILE, "utf-8");
20
+ return JSON.parse(raw);
21
+ } catch {
22
+ return null;
23
+ }
24
+ }
25
+ async function writeCache(entry) {
26
+ try {
27
+ await mkdir(CACHE_DIR, { recursive: true });
28
+ await writeFile(CACHE_FILE, JSON.stringify(entry));
29
+ } catch {
30
+ }
31
+ }
32
+ async function fetchLatestVersion() {
33
+ try {
34
+ const controller = new AbortController();
35
+ const timeout = setTimeout(() => controller.abort(), 3e3);
36
+ const res = await fetch("https://registry.npmjs.org/@kitnai/cli/latest", {
37
+ signal: controller.signal
38
+ });
39
+ clearTimeout(timeout);
40
+ if (!res.ok) return null;
41
+ const data = await res.json();
42
+ return data.version ?? null;
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+ function isNewer(latest, current) {
48
+ const [lMaj, lMin, lPat] = latest.split(".").map(Number);
49
+ const [cMaj, cMin, cPat] = current.split(".").map(Number);
50
+ if (lMaj !== cMaj) return lMaj > cMaj;
51
+ if (lMin !== cMin) return lMin > cMin;
52
+ return lPat > cPat;
53
+ }
54
+ function startUpdateCheck(currentVersion) {
55
+ let message = "";
56
+ const check = (async () => {
57
+ const cache = await readCache();
58
+ let latest = null;
59
+ if (cache && Date.now() - cache.checkedAt < CHECK_INTERVAL) {
60
+ latest = cache.latest;
61
+ } else {
62
+ latest = await fetchLatestVersion();
63
+ if (latest) {
64
+ await writeCache({ latest, checkedAt: Date.now() });
65
+ }
66
+ }
67
+ if (latest && isNewer(latest, currentVersion)) {
68
+ message = [
69
+ "",
70
+ pc.yellow(` Update available: ${pc.dim(currentVersion)} \u2192 ${pc.green(latest)}`),
71
+ pc.dim(` Run ${pc.cyan("npx @kitnai/cli@latest")} or ${pc.cyan("npm i -g @kitnai/cli")}`),
72
+ ""
73
+ ].join("\n");
74
+ }
75
+ })();
76
+ check.catch(() => {
77
+ });
78
+ return () => {
79
+ if (message) process.stderr.write(message);
80
+ };
81
+ }
82
+ var CACHE_DIR, CACHE_FILE, CHECK_INTERVAL;
83
+ var init_update_check = __esm({
84
+ "src/utils/update-check.ts"() {
85
+ "use strict";
86
+ CACHE_DIR = join(homedir(), ".kitn");
87
+ CACHE_FILE = join(CACHE_DIR, "update-check.json");
88
+ CHECK_INTERVAL = 60 * 60 * 1e3;
89
+ }
90
+ });
91
+
12
92
  // src/utils/config.ts
13
93
  import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
14
94
  import { join as join2 } from "path";
@@ -714,6 +794,13 @@ async function addCommand(components, opts) {
714
794
  for (const item of resolved) {
715
795
  if (item.dependencies) allDeps.push(...item.dependencies);
716
796
  if (item.devDependencies) allDevDeps.push(...item.devDependencies);
797
+ const existingInstall = (config.installed ?? {})[item.name];
798
+ if (existingInstall && item.type === "kitn:package") {
799
+ const allContent = item.files.map((f) => f.content).join("\n");
800
+ if (contentHash(allContent) === existingInstall.hash) {
801
+ continue;
802
+ }
803
+ }
717
804
  if (item.type === "kitn:package") {
718
805
  const baseDir2 = config.aliases.base ?? "src/ai";
719
806
  for (const file of item.files) {
@@ -899,7 +986,7 @@ async function addCommand(components, opts) {
899
986
  }
900
987
  }
901
988
  if (created.length > 0) {
902
- p2.log.success(`Created ${created.length} file(s):
989
+ p2.log.success(`Added ${created.length} file(s):
903
990
  ` + created.map((f) => ` ${pc3.green("+")} ${f}`).join("\n"));
904
991
  }
905
992
  if (updated.length > 0) {
@@ -917,13 +1004,14 @@ async function addCommand(components, opts) {
917
1004
  p2.log.info(`${pc3.bold(item.name)}: ${item.docs}`);
918
1005
  }
919
1006
  }
920
- const installedNames = new Set(resolved.map((r) => r.name));
1007
+ const resolvedNames = new Set(resolved.map((r) => r.name));
1008
+ const projectInstalled = new Set(Object.keys(config.installed ?? {}));
921
1009
  const hints = [];
922
- if (installedNames.has("core") && !installedNames.has(config.framework ?? "hono")) {
1010
+ const fw = config.framework ?? "hono";
1011
+ if (resolvedNames.has("core") && !resolvedNames.has(fw) && !projectInstalled.has(fw)) {
923
1012
  hints.push(`Run ${pc3.cyan(`kitn add routes`)} to install the HTTP adapter.`);
924
1013
  }
925
- const fw = config.framework ?? "hono";
926
- if (installedNames.has(fw) || installedNames.has("core") && installedNames.has(fw)) {
1014
+ if (resolvedNames.has(fw)) {
927
1015
  hints.push(`Configure your AI provider in ${pc3.bold(baseDir + "/plugin.ts")}, then add to your server:`);
928
1016
  hints.push("");
929
1017
  hints.push(pc3.dim(` import { ai } from "./${baseDir.replace(/^src\//, "")}/plugin";`));
@@ -964,43 +1052,66 @@ import * as p3 from "@clack/prompts";
964
1052
  import pc4 from "picocolors";
965
1053
  import { mkdir as mkdir4, writeFile as writeFile7 } from "fs/promises";
966
1054
  import { join as join8 } from "path";
967
- async function initCommand() {
1055
+ async function initCommand(opts = {}) {
968
1056
  p3.intro(pc4.bgCyan(pc4.black(" kitn init ")));
969
1057
  const cwd = process.cwd();
970
1058
  const existing = await readConfig(cwd);
971
1059
  if (existing) {
972
- p3.log.warn("kitn.json already exists in this directory.");
973
- const shouldContinue = await p3.confirm({
974
- message: "Overwrite existing configuration?",
975
- initialValue: false
1060
+ if (opts.yes) {
1061
+ p3.log.warn("kitn.json already exists \u2014 overwriting (--yes).");
1062
+ } else {
1063
+ p3.log.warn("kitn.json already exists in this directory.");
1064
+ const shouldContinue = await p3.confirm({
1065
+ message: "Overwrite existing configuration?",
1066
+ initialValue: false
1067
+ });
1068
+ if (p3.isCancel(shouldContinue) || !shouldContinue) {
1069
+ p3.cancel("Init cancelled.");
1070
+ process.exit(0);
1071
+ }
1072
+ }
1073
+ }
1074
+ let runtime;
1075
+ if (opts.runtime) {
1076
+ if (!["bun", "node", "deno"].includes(opts.runtime)) {
1077
+ p3.log.error(`Invalid runtime: ${opts.runtime}. Must be bun, node, or deno.`);
1078
+ process.exit(1);
1079
+ }
1080
+ runtime = opts.runtime;
1081
+ } else if (opts.yes) {
1082
+ runtime = "bun";
1083
+ } else {
1084
+ const selected = await p3.select({
1085
+ message: "Which runtime do you use?",
1086
+ options: [
1087
+ { value: "bun", label: "Bun", hint: "recommended" },
1088
+ { value: "node", label: "Node.js" },
1089
+ { value: "deno", label: "Deno" }
1090
+ ]
976
1091
  });
977
- if (p3.isCancel(shouldContinue) || !shouldContinue) {
1092
+ if (p3.isCancel(selected)) {
978
1093
  p3.cancel("Init cancelled.");
979
1094
  process.exit(0);
980
1095
  }
1096
+ runtime = selected;
981
1097
  }
982
- const runtime = await p3.select({
983
- message: "Which runtime do you use?",
984
- options: [
985
- { value: "bun", label: "Bun", hint: "recommended" },
986
- { value: "node", label: "Node.js" },
987
- { value: "deno", label: "Deno" }
988
- ]
989
- });
990
- if (p3.isCancel(runtime)) {
991
- p3.cancel("Init cancelled.");
992
- process.exit(0);
993
- }
994
- const base = await p3.text({
995
- message: "Where should kitn components be installed?",
996
- initialValue: "src/ai",
997
- placeholder: "src/ai"
998
- });
999
- if (p3.isCancel(base)) {
1000
- p3.cancel("Init cancelled.");
1001
- process.exit(0);
1098
+ let baseDir;
1099
+ if (opts.base) {
1100
+ baseDir = opts.base;
1101
+ } else if (opts.yes) {
1102
+ baseDir = "src/ai";
1103
+ } else {
1104
+ const base = await p3.text({
1105
+ message: "Where should kitn components be installed?",
1106
+ initialValue: "src/ai",
1107
+ placeholder: "src/ai"
1108
+ });
1109
+ if (p3.isCancel(base)) {
1110
+ p3.cancel("Init cancelled.");
1111
+ process.exit(0);
1112
+ }
1113
+ baseDir = base;
1002
1114
  }
1003
- const baseDir = base;
1004
1115
  const config = {
1005
1116
  runtime,
1006
1117
  framework: "hono",
@@ -1047,6 +1158,15 @@ async function initCommand() {
1047
1158
  ].join("\n"),
1048
1159
  "Add this to your server entry point:"
1049
1160
  );
1161
+ p3.log.message(
1162
+ [
1163
+ pc4.bold("Add your first agent:"),
1164
+ ` ${pc4.cyan("kitn add weather-agent")}`,
1165
+ "",
1166
+ pc4.bold("Browse all components:"),
1167
+ ` ${pc4.cyan("kitn list")}`
1168
+ ].join("\n")
1169
+ );
1050
1170
  p3.outro("Done!");
1051
1171
  }
1052
1172
  var PLUGIN_TEMPLATE;
@@ -1458,8 +1578,8 @@ async function scanForComponents(cwd, paths) {
1458
1578
  const resolvedCwd = resolve(cwd);
1459
1579
  if (paths && paths.length > 0) {
1460
1580
  const results = [];
1461
- for (const p12 of paths) {
1462
- const absPath = resolve(resolvedCwd, p12);
1581
+ for (const p13 of paths) {
1582
+ const absPath = resolve(resolvedCwd, p13);
1463
1583
  if (await fileExists(join11(absPath, "registry.json"))) {
1464
1584
  results.push(absPath);
1465
1585
  continue;
@@ -2099,6 +2219,39 @@ var init_info = __esm({
2099
2219
  }
2100
2220
  });
2101
2221
 
2222
+ // src/commands/check.ts
2223
+ var check_exports = {};
2224
+ __export(check_exports, {
2225
+ checkCommand: () => checkCommand
2226
+ });
2227
+ import * as p11 from "@clack/prompts";
2228
+ import pc10 from "picocolors";
2229
+ async function checkCommand(currentVersion) {
2230
+ p11.intro(pc10.bgCyan(pc10.black(" kitn check ")));
2231
+ p11.log.info(`kitn v${currentVersion}`);
2232
+ const s = p11.spinner();
2233
+ s.start("Checking for updates...");
2234
+ const latest = await fetchLatestVersion();
2235
+ if (!latest) {
2236
+ s.stop(pc10.yellow("Could not reach the npm registry"));
2237
+ p11.outro("Try again later.");
2238
+ return;
2239
+ }
2240
+ if (isNewer(latest, currentVersion)) {
2241
+ s.stop(pc10.yellow(`Update available: ${currentVersion} \u2192 ${latest}`));
2242
+ p11.log.message(` Run: ${pc10.cyan("npm i -g @kitnai/cli")}`);
2243
+ } else {
2244
+ s.stop(pc10.green("You're on the latest version"));
2245
+ }
2246
+ p11.outro("");
2247
+ }
2248
+ var init_check = __esm({
2249
+ "src/commands/check.ts"() {
2250
+ "use strict";
2251
+ init_update_check();
2252
+ }
2253
+ });
2254
+
2102
2255
  // src/commands/registry.ts
2103
2256
  var registry_exports = {};
2104
2257
  __export(registry_exports, {
@@ -2106,8 +2259,8 @@ __export(registry_exports, {
2106
2259
  registryListCommand: () => registryListCommand,
2107
2260
  registryRemoveCommand: () => registryRemoveCommand
2108
2261
  });
2109
- import * as p11 from "@clack/prompts";
2110
- import pc10 from "picocolors";
2262
+ import * as p12 from "@clack/prompts";
2263
+ import pc11 from "picocolors";
2111
2264
  async function registryAddCommand(namespace, url, opts = {}) {
2112
2265
  const cwd = opts.cwd ?? process.cwd();
2113
2266
  const config = await readConfig(cwd);
@@ -2133,10 +2286,10 @@ async function registryAddCommand(namespace, url, opts = {}) {
2133
2286
  config.registries[namespace] = url;
2134
2287
  }
2135
2288
  await writeConfig(cwd, config);
2136
- p11.log.success(`Added registry ${pc10.bold(namespace)}`);
2137
- p11.log.message(pc10.dim(` ${url}`));
2138
- if (opts.homepage) p11.log.message(pc10.dim(` Homepage: ${opts.homepage}`));
2139
- if (opts.description) p11.log.message(pc10.dim(` ${opts.description}`));
2289
+ p12.log.success(`Added registry ${pc11.bold(namespace)}`);
2290
+ p12.log.message(pc11.dim(` ${url}`));
2291
+ if (opts.homepage) p12.log.message(pc11.dim(` Homepage: ${opts.homepage}`));
2292
+ if (opts.description) p12.log.message(pc11.dim(` ${opts.description}`));
2140
2293
  }
2141
2294
  async function registryRemoveCommand(namespace, opts = {}) {
2142
2295
  const cwd = opts.cwd ?? process.cwd();
@@ -2158,10 +2311,10 @@ async function registryRemoveCommand(namespace, opts = {}) {
2158
2311
  }
2159
2312
  delete config.registries[namespace];
2160
2313
  await writeConfig(cwd, config);
2161
- p11.log.success(`Removed registry ${pc10.bold(namespace)}`);
2314
+ p12.log.success(`Removed registry ${pc11.bold(namespace)}`);
2162
2315
  if (affectedComponents.length > 0) {
2163
- p11.log.warn(`${affectedComponents.length} installed component(s) referenced this registry:
2164
- ` + affectedComponents.map((name) => ` ${pc10.yellow("!")} ${name}`).join("\n"));
2316
+ p12.log.warn(`${affectedComponents.length} installed component(s) referenced this registry:
2317
+ ` + affectedComponents.map((name) => ` ${pc11.yellow("!")} ${name}`).join("\n"));
2165
2318
  }
2166
2319
  return { affectedComponents };
2167
2320
  }
@@ -2176,15 +2329,15 @@ async function registryListCommand(opts = {}) {
2176
2329
  return { namespace, url, homepage, description };
2177
2330
  });
2178
2331
  if (entries.length === 0) {
2179
- p11.log.message(pc10.dim(" No registries configured."));
2332
+ p12.log.message(pc11.dim(" No registries configured."));
2180
2333
  } else {
2181
2334
  const lines = [];
2182
2335
  for (const { namespace, url, homepage, description } of entries) {
2183
- lines.push(` ${pc10.bold(namespace.padEnd(16))} ${pc10.dim(url)}`);
2336
+ lines.push(` ${pc11.bold(namespace.padEnd(16))} ${pc11.dim(url)}`);
2184
2337
  if (description) lines.push(` ${" ".repeat(16)} ${description}`);
2185
- if (homepage) lines.push(` ${" ".repeat(16)} ${pc10.dim(homepage)}`);
2338
+ if (homepage) lines.push(` ${" ".repeat(16)} ${pc11.dim(homepage)}`);
2186
2339
  }
2187
- p11.log.message(lines.join("\n"));
2340
+ p12.log.message(lines.join("\n"));
2188
2341
  }
2189
2342
  return entries;
2190
2343
  }
@@ -2196,89 +2349,14 @@ var init_registry = __esm({
2196
2349
  });
2197
2350
 
2198
2351
  // src/index.ts
2352
+ init_update_check();
2199
2353
  import { Command } from "commander";
2200
-
2201
- // src/utils/update-check.ts
2202
- import { readFile, writeFile, mkdir } from "fs/promises";
2203
- import { join } from "path";
2204
- import { homedir } from "os";
2205
- import pc from "picocolors";
2206
- var CACHE_DIR = join(homedir(), ".kitn");
2207
- var CACHE_FILE = join(CACHE_DIR, "update-check.json");
2208
- var CHECK_INTERVAL = 60 * 60 * 1e3;
2209
- async function readCache() {
2210
- try {
2211
- const raw = await readFile(CACHE_FILE, "utf-8");
2212
- return JSON.parse(raw);
2213
- } catch {
2214
- return null;
2215
- }
2216
- }
2217
- async function writeCache(entry) {
2218
- try {
2219
- await mkdir(CACHE_DIR, { recursive: true });
2220
- await writeFile(CACHE_FILE, JSON.stringify(entry));
2221
- } catch {
2222
- }
2223
- }
2224
- async function fetchLatestVersion() {
2225
- try {
2226
- const controller = new AbortController();
2227
- const timeout = setTimeout(() => controller.abort(), 3e3);
2228
- const res = await fetch("https://registry.npmjs.org/@kitnai/cli/latest", {
2229
- signal: controller.signal
2230
- });
2231
- clearTimeout(timeout);
2232
- if (!res.ok) return null;
2233
- const data = await res.json();
2234
- return data.version ?? null;
2235
- } catch {
2236
- return null;
2237
- }
2238
- }
2239
- function isNewer(latest, current) {
2240
- const [lMaj, lMin, lPat] = latest.split(".").map(Number);
2241
- const [cMaj, cMin, cPat] = current.split(".").map(Number);
2242
- if (lMaj !== cMaj) return lMaj > cMaj;
2243
- if (lMin !== cMin) return lMin > cMin;
2244
- return lPat > cPat;
2245
- }
2246
- function startUpdateCheck(currentVersion) {
2247
- let message = "";
2248
- const check = (async () => {
2249
- const cache = await readCache();
2250
- let latest = null;
2251
- if (cache && Date.now() - cache.checkedAt < CHECK_INTERVAL) {
2252
- latest = cache.latest;
2253
- } else {
2254
- latest = await fetchLatestVersion();
2255
- if (latest) {
2256
- await writeCache({ latest, checkedAt: Date.now() });
2257
- }
2258
- }
2259
- if (latest && isNewer(latest, currentVersion)) {
2260
- message = [
2261
- "",
2262
- pc.yellow(` Update available: ${pc.dim(currentVersion)} \u2192 ${pc.green(latest)}`),
2263
- pc.dim(` Run ${pc.cyan("npx @kitnai/cli@latest")} or ${pc.cyan("npm i -g @kitnai/cli")}`),
2264
- ""
2265
- ].join("\n");
2266
- }
2267
- })();
2268
- check.catch(() => {
2269
- });
2270
- return () => {
2271
- if (message) process.stderr.write(message);
2272
- };
2273
- }
2274
-
2275
- // src/index.ts
2276
- var VERSION = true ? "0.1.16" : "0.0.0-dev";
2354
+ var VERSION = true ? "0.1.17" : "0.0.0-dev";
2277
2355
  var printUpdateNotice = startUpdateCheck(VERSION);
2278
2356
  var program = new Command().name("kitn").description("Install AI agent components from the kitn registry").version(VERSION);
2279
- program.command("init").description("Initialize kitn in your project").action(async () => {
2357
+ program.command("init").description("Initialize kitn in your project").option("-r, --runtime <runtime>", "runtime to use (bun, node, deno)").option("-b, --base <path>", "base directory for components (default: src/ai)").option("-y, --yes", "accept all defaults without prompting").action(async (opts) => {
2280
2358
  const { initCommand: initCommand2 } = await Promise.resolve().then(() => (init_init(), init_exports));
2281
- await initCommand2();
2359
+ await initCommand2(opts);
2282
2360
  });
2283
2361
  program.command("add").description("Add components from the kitn registry").argument("[components...]", "component names to install").option("-o, --overwrite", "overwrite existing files without prompting").option("-t, --type <type>", "filter by component type").action(async (components, opts) => {
2284
2362
  const { addCommand: addCommand2 } = await Promise.resolve().then(() => (init_add(), add_exports));
@@ -2312,6 +2390,10 @@ program.command("info").description("Show details about a component").argument("
2312
2390
  const { infoCommand: infoCommand2 } = await Promise.resolve().then(() => (init_info(), info_exports));
2313
2391
  await infoCommand2(component);
2314
2392
  });
2393
+ program.command("check").description("Check for CLI updates").action(async () => {
2394
+ const { checkCommand: checkCommand2 } = await Promise.resolve().then(() => (init_check(), check_exports));
2395
+ await checkCommand2(VERSION);
2396
+ });
2315
2397
  var registry = program.command("registry").description("Manage component registries");
2316
2398
  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) => {
2317
2399
  const { registryAddCommand: registryAddCommand2 } = await Promise.resolve().then(() => (init_registry(), registry_exports));