@caik.dev/cli 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +690 -85
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2,10 +2,10 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
- import { readFileSync as readFileSync8 } from "fs";
5
+ import { readFileSync as readFileSync9 } from "fs";
6
6
  import { fileURLToPath } from "url";
7
- import { dirname as dirname5, join as join8 } from "path";
8
- import chalk2 from "chalk";
7
+ import { dirname as dirname5, join as join9 } from "path";
8
+ import chalk3 from "chalk";
9
9
 
10
10
  // src/errors.ts
11
11
  var CaikError = class extends Error {
@@ -165,6 +165,11 @@ function writeConfig(config) {
165
165
  writeFileSync(path, JSON.stringify(config, null, 2) + "\n", "utf-8");
166
166
  chmodSync(path, 384);
167
167
  }
168
+ function setApiKey(key) {
169
+ const config = readConfig();
170
+ config.apiKey = key;
171
+ writeConfig(config);
172
+ }
168
173
  function resolveConfig(opts) {
169
174
  const config = readConfig();
170
175
  return {
@@ -294,7 +299,7 @@ ${result.total} result${result.total !== 1 ? "s" : ""} found.`);
294
299
  }
295
300
 
296
301
  // src/commands/install.ts
297
- import { existsSync as existsSync7 } from "fs";
302
+ import { existsSync as existsSync7, unlinkSync as unlinkSync2 } from "fs";
298
303
  import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync6 } from "fs";
299
304
  import { dirname as dirname3, resolve } from "path";
300
305
  import { execSync as execSync3 } from "child_process";
@@ -335,7 +340,8 @@ function detectOpenClaw() {
335
340
  const home = homedir2();
336
341
  const openclawDir = join2(home, ".openclaw");
337
342
  const cwdConfig = join2(process.cwd(), "openclaw.json");
338
- if (!existsSync2(openclawDir) && !existsSync2(cwdConfig)) return null;
343
+ const inPath = commandExists("openclaw");
344
+ if (!existsSync2(openclawDir) && !existsSync2(cwdConfig) && !inPath) return null;
339
345
  const configPaths = [];
340
346
  if (existsSync2(cwdConfig)) configPaths.push(cwdConfig);
341
347
  if (existsSync2(join2(openclawDir, "openclaw.json"))) {
@@ -453,26 +459,26 @@ function upsertRegistryEntry(entry) {
453
459
  }
454
460
  writeRegistry(registry);
455
461
  }
456
- function removeRegistryEntry(slug, platform) {
462
+ function removeRegistryEntry(slug, platform2) {
457
463
  const registry = readRegistry();
458
464
  const idx = registry.entries.findIndex(
459
- (e) => e.slug === slug && e.platform === platform
465
+ (e) => e.slug === slug && e.platform === platform2
460
466
  );
461
467
  if (idx < 0) return null;
462
468
  const [removed] = registry.entries.splice(idx, 1);
463
469
  writeRegistry(registry);
464
470
  return removed;
465
471
  }
466
- function findRegistryEntry(slug, platform) {
472
+ function findRegistryEntry(slug, platform2) {
467
473
  const registry = readRegistry();
468
474
  return registry.entries.find(
469
- (e) => e.slug === slug && (!platform || e.platform === platform)
475
+ (e) => e.slug === slug && (!platform2 || e.platform === platform2)
470
476
  ) ?? null;
471
477
  }
472
- function listRegistryEntries(platform) {
478
+ function listRegistryEntries(platform2) {
473
479
  const registry = readRegistry();
474
- if (!platform) return registry.entries;
475
- return registry.entries.filter((e) => e.platform === platform);
480
+ if (!platform2) return registry.entries;
481
+ return registry.entries.filter((e) => e.platform === platform2);
476
482
  }
477
483
  function cleanupFiles(entry) {
478
484
  const failed = [];
@@ -771,6 +777,12 @@ var OpenClawAdapter = class {
771
777
  get openclawDir() {
772
778
  return join5(homedir4(), ".openclaw");
773
779
  }
780
+ get managedHooksDir() {
781
+ return join5(this.openclawDir, "hooks");
782
+ }
783
+ get caikHookDir() {
784
+ return join5(this.managedHooksDir, "caik-contributions");
785
+ }
774
786
  /** OpenClaw's own config — NOT where MCP servers go. */
775
787
  get openclawConfigPath() {
776
788
  return join5(this.openclawDir, "openclaw.json");
@@ -849,8 +861,12 @@ var OpenClawAdapter = class {
849
861
  async detect() {
850
862
  const hasDir = existsSync5(this.openclawDir);
851
863
  const inPath = this.isInPath();
852
- if (!hasDir && !inPath) return null;
864
+ const hasCwdConfig = existsSync5(join5(process.cwd(), "openclaw.json"));
865
+ if (!hasDir && !inPath && !hasCwdConfig) return null;
853
866
  const configPaths = [];
867
+ if (hasCwdConfig) {
868
+ configPaths.push(join5(process.cwd(), "openclaw.json"));
869
+ }
854
870
  if (existsSync5(this.openclawConfigPath)) {
855
871
  configPaths.push(this.openclawConfigPath);
856
872
  }
@@ -886,14 +902,27 @@ var OpenClawAdapter = class {
886
902
  this.writeJson(this.mcpConfigPath, config);
887
903
  }
888
904
  async registerHooks(_hookConfig) {
889
- console.error(
890
- "[caik] To enable CAIK telemetry hooks in OpenClaw, install the CAIK hook pack:\n openclaw hooks install @caik.dev/openclaw-hooks\n (Hook pack not yet available \u2014 hooks are optional, CAIK works without them.)"
905
+ if (!existsSync5(this.caikHookDir)) {
906
+ mkdirSync4(this.caikHookDir, { recursive: true });
907
+ }
908
+ writeFileSync4(
909
+ join5(this.caikHookDir, "HOOK.md"),
910
+ HOOK_MD_CONTENT,
911
+ "utf-8"
912
+ );
913
+ writeFileSync4(
914
+ join5(this.caikHookDir, "handler.js"),
915
+ HANDLER_JS_CONTENT,
916
+ "utf-8"
891
917
  );
892
918
  }
893
919
  async unregisterHooks() {
894
- console.error(
895
- "[caik] To remove CAIK hooks from OpenClaw, run:\n openclaw hooks uninstall @caik.dev/openclaw-hooks"
896
- );
920
+ if (existsSync5(this.caikHookDir)) {
921
+ try {
922
+ rmSync2(this.caikHookDir, { recursive: true, force: true });
923
+ } catch {
924
+ }
925
+ }
897
926
  }
898
927
  async installSkill(_slug, content, files) {
899
928
  if (!existsSync5(this.skillDir)) {
@@ -950,6 +979,135 @@ var OpenClawAdapter = class {
950
979
  return !!servers && MCP_KEY2 in servers;
951
980
  }
952
981
  };
982
+ var HOOK_MD_CONTENT = `---
983
+ name: caik-contributions
984
+ description: "Track artifact usage to build your CAIK contribution level and community karma"
985
+ metadata:
986
+ {
987
+ "openclaw":
988
+ {
989
+ "emoji": "\u{1F4E6}",
990
+ "events": ["command:new", "command:reset", "command:stop", "command"],
991
+ "install": [{ "id": "local", "kind": "local", "label": "CAIK CLI hook pack" }],
992
+ },
993
+ }
994
+ ---
995
+
996
+ # CAIK Contribution Tracking
997
+
998
+ Reports session lifecycle events to the CAIK API to build your contribution level and community karma.
999
+
1000
+ ## What It Does
1001
+
1002
+ - **Session start** (\`command:new\`): Records that a new agent session began
1003
+ - **Session end** (\`command:stop\`, \`command:reset\`): Flushes buffered tool-use events and records session end
1004
+ - **Tool use** (\`command\`): Buffers tool execution events for batch reporting
1005
+
1006
+ ## Privacy
1007
+
1008
+ - Only sends: event type, platform name, tool name, timestamp
1009
+ - No code, file contents, or conversation data is transmitted
1010
+ - Contribution tracking can be disabled: \`caik config set contributions false\`
1011
+
1012
+ ## Configuration
1013
+
1014
+ Set \`CAIK_API_URL\` and \`CAIK_API_KEY\` environment variables, or configure via \`caik init --auth\`.
1015
+ `;
1016
+ var HANDLER_JS_CONTENT = `/**
1017
+ * CAIK contribution tracking hook for OpenClaw.
1018
+ * Fire-and-forget \u2014 never blocks the agent, never throws.
1019
+ */
1020
+ import fs from "node:fs/promises";
1021
+ import os from "node:os";
1022
+ import path from "node:path";
1023
+
1024
+ const CAIK_DIR = path.join(os.homedir(), ".caik");
1025
+ const PENDING_PATH = path.join(CAIK_DIR, "pending-events.json");
1026
+ const CONFIG_PATH = path.join(CAIK_DIR, "config.json");
1027
+ const TIMEOUT_MS = 2000;
1028
+
1029
+ async function loadConfig() {
1030
+ try {
1031
+ const raw = await fs.readFile(CONFIG_PATH, "utf-8");
1032
+ return JSON.parse(raw);
1033
+ } catch { return {}; }
1034
+ }
1035
+
1036
+ async function getApiUrl() {
1037
+ const cfg = await loadConfig();
1038
+ return process.env.CAIK_API_URL || cfg.apiUrl || "https://caik.dev";
1039
+ }
1040
+
1041
+ async function getApiKey() {
1042
+ const cfg = await loadConfig();
1043
+ return process.env.CAIK_API_KEY || cfg.apiKey || null;
1044
+ }
1045
+
1046
+ async function readPending() {
1047
+ try {
1048
+ const raw = await fs.readFile(PENDING_PATH, "utf-8");
1049
+ const parsed = JSON.parse(raw);
1050
+ return Array.isArray(parsed) ? parsed : [];
1051
+ } catch { return []; }
1052
+ }
1053
+
1054
+ async function writePending(events) {
1055
+ await fs.mkdir(CAIK_DIR, { recursive: true });
1056
+ await fs.writeFile(PENDING_PATH, JSON.stringify(events) + "\\n", "utf-8");
1057
+ }
1058
+
1059
+ async function appendEvent(event) {
1060
+ const events = await readPending();
1061
+ events.push(event);
1062
+ await writePending(events);
1063
+ return events;
1064
+ }
1065
+
1066
+ async function clearPending() { await writePending([]); }
1067
+
1068
+ async function postEvents(events) {
1069
+ if (events.length === 0) return;
1070
+ const apiUrl = await getApiUrl();
1071
+ const apiKey = await getApiKey();
1072
+ const headers = { "Content-Type": "application/json" };
1073
+ if (apiKey) headers["Authorization"] = "Bearer " + apiKey;
1074
+ const controller = new AbortController();
1075
+ const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS);
1076
+ try {
1077
+ await fetch(apiUrl + "/api/v1/events", {
1078
+ method: "POST", headers,
1079
+ body: JSON.stringify({ events }),
1080
+ signal: controller.signal,
1081
+ });
1082
+ await clearPending();
1083
+ } catch { /* fire-and-forget */ }
1084
+ finally { clearTimeout(timeout); }
1085
+ }
1086
+
1087
+ const handler = async (event) => {
1088
+ try {
1089
+ const cfg = await loadConfig();
1090
+ if (cfg.contributions === false || cfg.telemetry === false) return;
1091
+ const timestamp = (event.timestamp ?? new Date()).toISOString();
1092
+ if (event.type === "command") {
1093
+ const action = event.action;
1094
+ if (action === "new") {
1095
+ await postEvents([{ type: "session_start", platform: "openclaw", timestamp }]);
1096
+ } else if (action === "stop" || action === "reset") {
1097
+ const pending = await readPending();
1098
+ pending.push({ type: "session_end", platform: "openclaw", timestamp });
1099
+ await postEvents(pending);
1100
+ } else {
1101
+ await appendEvent({ type: "tool_use", platform: "openclaw", tool: action, timestamp });
1102
+ const pending = await readPending();
1103
+ if (pending.length >= 50) await postEvents(pending);
1104
+ }
1105
+ }
1106
+ } catch { /* never fail */ }
1107
+ };
1108
+
1109
+ export default handler;
1110
+ `;
953
1111
 
954
1112
  // src/platform/cursor.ts
955
1113
  import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5, rmSync as rmSync3 } from "fs";
@@ -1210,14 +1368,29 @@ Examples:
1210
1368
  detectedPlatform = legacyPlatformMap[raw] ?? raw;
1211
1369
  } else {
1212
1370
  const detected = detectPlatforms();
1213
- if (detected.length > 0) {
1371
+ if (detected.length > 1 && !opts.yes && !globalOpts.json) {
1372
+ console.log(info("Multiple platforms detected:"));
1373
+ for (let i = 0; i < detected.length; i++) {
1374
+ const d = detected[i];
1375
+ console.log(info(` ${i + 1}) ${d.name} (${d.tier})`));
1376
+ }
1377
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
1378
+ const answer = await rl.question(`Select platform [1-${detected.length}]: `);
1379
+ rl.close();
1380
+ const idx = parseInt(answer, 10);
1381
+ if (isNaN(idx) || idx < 1 || idx > detected.length) {
1382
+ console.log(error("Invalid selection. Aborting."));
1383
+ return;
1384
+ }
1385
+ detectedPlatform = detected[idx - 1].name;
1386
+ } else if (detected.length > 0) {
1214
1387
  detectedPlatform = detected[0].name;
1215
1388
  }
1216
1389
  }
1217
- const platform = detectedPlatform;
1390
+ const platform2 = detectedPlatform;
1218
1391
  const spinner = createSpinner(`Fetching ${slug}...`);
1219
1392
  if (!globalOpts.json) spinner.start();
1220
- const params = { platform };
1393
+ const params = { platform: platform2 };
1221
1394
  const installInfo = await client.get(`/install/${encodeURIComponent(slug)}`, params);
1222
1395
  if (globalOpts.json) {
1223
1396
  spinner.stop();
@@ -1268,41 +1441,77 @@ Examples:
1268
1441
  }
1269
1442
  spinner.start();
1270
1443
  }
1271
- for (const file of safeFiles) {
1272
- const dir = dirname3(file.resolvedPath);
1273
- if (!existsSync7(dir)) {
1274
- mkdirSync6(dir, { recursive: true });
1444
+ const writtenFiles = [];
1445
+ const resolvedPlatform = detectedPlatform ?? "generic";
1446
+ let registryWritten = false;
1447
+ let mcpRegistered = false;
1448
+ let skillInstalled = false;
1449
+ const rollback = async () => {
1450
+ for (const filePath of writtenFiles) {
1451
+ try {
1452
+ if (existsSync7(filePath)) unlinkSync2(filePath);
1453
+ } catch {
1454
+ }
1275
1455
  }
1276
- writeFileSync6(file.resolvedPath, file.content, "utf-8");
1277
- }
1278
- if (installInfo.installCommand) {
1279
- try {
1456
+ if (registryWritten) {
1457
+ try {
1458
+ removeRegistryEntry(slug, resolvedPlatform);
1459
+ } catch {
1460
+ }
1461
+ }
1462
+ const adapter2 = getPlatformAdapter(resolvedPlatform);
1463
+ if (mcpRegistered) {
1464
+ try {
1465
+ await adapter2.unregisterMcp();
1466
+ } catch {
1467
+ }
1468
+ }
1469
+ if (skillInstalled && adapter2.uninstallSkill) {
1470
+ try {
1471
+ await adapter2.uninstallSkill(slug);
1472
+ } catch {
1473
+ }
1474
+ }
1475
+ if (writtenFiles.length > 0) {
1476
+ console.log(info(`Rolled back ${writtenFiles.length} file(s) written during install.`));
1477
+ }
1478
+ };
1479
+ try {
1480
+ for (const file of safeFiles) {
1481
+ const dir = dirname3(file.resolvedPath);
1482
+ if (!existsSync7(dir)) {
1483
+ mkdirSync6(dir, { recursive: true });
1484
+ }
1485
+ writeFileSync6(file.resolvedPath, file.content, "utf-8");
1486
+ writtenFiles.push(file.resolvedPath);
1487
+ }
1488
+ if (installInfo.installCommand) {
1280
1489
  execSync3(installInfo.installCommand, { stdio: "pipe" });
1281
- } catch (err) {
1282
- spinner.stop();
1283
- console.error(error(`Install command failed: ${err instanceof Error ? err.message : String(err)}`));
1284
- client.post("/telemetry/install", {
1285
- artifactSlug: slug,
1286
- platform,
1287
- success: false,
1288
- errorMessage: err instanceof Error ? err.message : String(err)
1289
- }).catch(() => {
1290
- });
1291
- process.exit(1);
1292
1490
  }
1293
- }
1294
- if (installInfo.postInstallHook) {
1295
- try {
1296
- execSync3(installInfo.postInstallHook, { stdio: "pipe" });
1297
- } catch {
1491
+ if (installInfo.postInstallHook) {
1492
+ try {
1493
+ execSync3(installInfo.postInstallHook, { stdio: "pipe" });
1494
+ } catch {
1495
+ }
1298
1496
  }
1497
+ } catch (err) {
1498
+ spinner.stop();
1499
+ console.error(error(`Install failed: ${err instanceof Error ? err.message : String(err)}`));
1500
+ await rollback();
1501
+ client.post("/telemetry/install", {
1502
+ artifactSlug: slug,
1503
+ platform: platform2,
1504
+ success: false,
1505
+ errorMessage: err instanceof Error ? err.message : String(err)
1506
+ }).catch(() => {
1507
+ });
1508
+ process.exit(1);
1299
1509
  }
1300
1510
  spinner.stop();
1301
1511
  console.log(success(`Installed ${installInfo.artifact.name}`));
1302
1512
  if (installInfo.contract?.touchedPaths) {
1303
1513
  console.log(info(`Files: ${installInfo.contract.touchedPaths.join(", ")}`));
1304
1514
  }
1305
- const resolvedPlatform = detectedPlatform ?? "generic";
1306
1515
  upsertRegistryEntry({
1307
1516
  slug,
1308
1517
  version: installInfo.artifact.version ?? "0.0.0",
@@ -1312,11 +1521,13 @@ Examples:
1312
1521
  files: safeFiles.map((f) => f.resolvedPath),
1313
1522
  artifactType: installInfo.artifact.primitive ?? "unknown"
1314
1523
  });
1524
+ registryWritten = true;
1315
1525
  const adapter = getPlatformAdapter(resolvedPlatform);
1316
1526
  const primitive = installInfo.artifact.primitive;
1317
1527
  if (primitive === "connector") {
1318
1528
  try {
1319
1529
  await adapter.registerMcp(getDefaultMcpConfig());
1530
+ mcpRegistered = true;
1320
1531
  } catch {
1321
1532
  }
1322
1533
  }
@@ -1324,12 +1535,13 @@ Examples:
1324
1535
  try {
1325
1536
  const content = safeFiles.map((f) => f.content).join("\n");
1326
1537
  await adapter.installSkill(slug, content);
1538
+ skillInstalled = true;
1327
1539
  } catch {
1328
1540
  }
1329
1541
  }
1330
1542
  client.post("/telemetry/install", {
1331
1543
  artifactSlug: slug,
1332
- platform,
1544
+ platform: platform2,
1333
1545
  success: true
1334
1546
  }).catch(() => {
1335
1547
  });
@@ -1339,14 +1551,120 @@ Examples:
1339
1551
  // src/commands/init.ts
1340
1552
  import { createInterface as createInterface2 } from "readline/promises";
1341
1553
  import { stdin, stdout } from "process";
1554
+
1555
+ // src/auth.ts
1556
+ import { createServer } from "http";
1557
+ import { execFile } from "child_process";
1558
+ import { URL as URL2 } from "url";
1559
+ import { platform } from "os";
1560
+ import { randomBytes } from "crypto";
1561
+ var TIMEOUT_MS = 6e4;
1562
+ function openBrowser(url) {
1563
+ const os = platform();
1564
+ return new Promise((resolve3) => {
1565
+ if (os === "darwin") {
1566
+ execFile("open", [url], (err) => resolve3(!err));
1567
+ } else if (os === "win32") {
1568
+ execFile("cmd", ["/c", "start", "", url], (err) => resolve3(!err));
1569
+ } else {
1570
+ execFile("xdg-open", [url], (err) => resolve3(!err));
1571
+ }
1572
+ });
1573
+ }
1574
+ async function authenticate(apiUrl, port = 0) {
1575
+ const state = randomBytes(16).toString("hex");
1576
+ return new Promise((resolve3, reject) => {
1577
+ const server = createServer((req, res) => {
1578
+ const actualPort = server.address().port;
1579
+ const reqUrl = new URL2(req.url ?? "/", `http://localhost:${actualPort}`);
1580
+ if (reqUrl.pathname !== "/callback") {
1581
+ res.writeHead(404, { "Content-Type": "text/plain" });
1582
+ res.end("Not found");
1583
+ return;
1584
+ }
1585
+ const returnedState = reqUrl.searchParams.get("state");
1586
+ if (returnedState !== state) {
1587
+ res.writeHead(403, { "Content-Type": "text/html" });
1588
+ res.end(
1589
+ "<html><body><h2>Authentication failed</h2><p>Invalid state parameter (possible CSRF). You can close this tab.</p></body></html>"
1590
+ );
1591
+ return;
1592
+ }
1593
+ const apiKeyParam = reqUrl.searchParams.get("api_key");
1594
+ if (!apiKeyParam) {
1595
+ res.writeHead(400, { "Content-Type": "text/html" });
1596
+ res.end(
1597
+ "<html><body><h2>Authentication failed</h2><p>No API key received. You can close this tab.</p></body></html>"
1598
+ );
1599
+ cleanup();
1600
+ reject(new Error("No API key received in callback"));
1601
+ return;
1602
+ }
1603
+ setApiKey(apiKeyParam);
1604
+ res.writeHead(200, { "Content-Type": "text/html" });
1605
+ res.end(
1606
+ "<html><body><h2>Authenticated!</h2><p>You can close this tab and return to your terminal.</p></body></html>"
1607
+ );
1608
+ cleanup();
1609
+ resolve3(apiKeyParam);
1610
+ });
1611
+ const timeout = setTimeout(() => {
1612
+ cleanup();
1613
+ reject(new Error("Authentication timed out after 60 seconds"));
1614
+ }, TIMEOUT_MS);
1615
+ function cleanup() {
1616
+ clearTimeout(timeout);
1617
+ server.close();
1618
+ }
1619
+ server.listen(port, async () => {
1620
+ const actualPort = server.address().port;
1621
+ const callbackUrl = `http://localhost:${actualPort}/callback`;
1622
+ const loginUrl = `${apiUrl}/auth/signin?redirect=${encodeURIComponent(callbackUrl)}&state=${encodeURIComponent(state)}`;
1623
+ console.log(info(`Listening on http://localhost:${actualPort} for callback...`));
1624
+ const opened = await openBrowser(loginUrl);
1625
+ if (opened) {
1626
+ console.log(info("Browser opened. Complete sign-in to continue."));
1627
+ } else {
1628
+ console.log(
1629
+ error("Could not open browser automatically.")
1630
+ );
1631
+ console.log(info(`Open this URL manually:
1632
+ ${loginUrl}`));
1633
+ }
1634
+ });
1635
+ server.on("error", (err) => {
1636
+ clearTimeout(timeout);
1637
+ reject(
1638
+ new Error(
1639
+ `Failed to start auth server: ${err.message}`
1640
+ )
1641
+ );
1642
+ });
1643
+ });
1644
+ }
1645
+
1646
+ // src/commands/init.ts
1342
1647
  function registerInitCommand(program2) {
1343
1648
  program2.command("init").description("Configure the CAIK CLI").option("--auth", "Set up authentication (opens browser)").addHelpText("after", `
1344
1649
  Examples:
1345
1650
  caik init
1346
- caik init --auth`).action(async (_opts) => {
1651
+ caik init --auth`).action(async (opts) => {
1652
+ const config = readConfig();
1653
+ if (opts.auth) {
1654
+ console.log(info("Opening browser for authentication..."));
1655
+ try {
1656
+ const apiKey = await authenticate(config.apiUrl);
1657
+ config.apiKey = apiKey;
1658
+ writeConfig(config);
1659
+ console.log(success("Authenticated and saved API key"));
1660
+ console.log(info(`Config saved to ~/.caik/config.json`));
1661
+ } catch (err) {
1662
+ console.log(error(`Authentication failed: ${err instanceof Error ? err.message : String(err)}`));
1663
+ }
1664
+ return;
1665
+ }
1347
1666
  const rl = createInterface2({ input: stdin, output: stdout });
1348
1667
  try {
1349
- const config = readConfig();
1350
1668
  const apiUrl = await rl.question(`API URL (${config.apiUrl}): `);
1351
1669
  if (apiUrl.trim()) {
1352
1670
  config.apiUrl = apiUrl.trim();
@@ -2016,7 +2334,7 @@ Artifact: ${entry.slug} (${entry.artifactType})`));
2016
2334
  if (!globalOpts.json) spinner.start();
2017
2335
  const failedFiles = cleanupFiles(entry);
2018
2336
  const adapter = getPlatformAdapter(entry.platform);
2019
- if (entry.artifactType === "mcp-server") {
2337
+ if (entry.artifactType === "connector" || entry.artifactType === "mcp-server") {
2020
2338
  try {
2021
2339
  await adapter.unregisterMcp();
2022
2340
  } catch (err) {
@@ -2027,7 +2345,8 @@ Artifact: ${entry.slug} (${entry.artifactType})`));
2027
2345
  }
2028
2346
  }
2029
2347
  }
2030
- if ((entry.artifactType === "skill" || entry.artifactType === "subagent" || entry.artifactType === "command") && adapter.uninstallSkill) {
2348
+ const skillTypes = /* @__PURE__ */ new Set(["executable", "composition", "skill", "subagent", "command"]);
2349
+ if (skillTypes.has(entry.artifactType) && adapter.uninstallSkill) {
2031
2350
  try {
2032
2351
  await adapter.uninstallSkill(slug);
2033
2352
  } catch (err) {
@@ -2159,12 +2478,12 @@ function createClient(program2) {
2159
2478
  }
2160
2479
  function registerHookCommand(program2) {
2161
2480
  const hook = program2.command("hook").description("Platform hook callbacks (observation only, never gates agent actions)");
2162
- hook.command("session-start").description("Log session start event").action(async () => {
2481
+ hook.command("session-start").description("Log session start event").option("--platform <name>", "Platform name", "claude-code").action(async (opts) => {
2163
2482
  try {
2164
2483
  const client = createClient(program2);
2165
2484
  const event = {
2166
2485
  type: "session_start",
2167
- platform: "claude-code",
2486
+ platform: opts.platform ?? "claude-code",
2168
2487
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2169
2488
  };
2170
2489
  await postSingleEventWithTimeout(client, event);
@@ -2172,13 +2491,13 @@ function registerHookCommand(program2) {
2172
2491
  }
2173
2492
  process.exit(0);
2174
2493
  });
2175
- hook.command("session-end").description("Flush pending events and log session end").action(async () => {
2494
+ hook.command("session-end").description("Flush pending events and log session end").option("--platform <name>", "Platform name", "claude-code").action(async (opts) => {
2176
2495
  try {
2177
2496
  const client = createClient(program2);
2178
2497
  const pending = readPendingEvents();
2179
2498
  const endEvent = {
2180
2499
  type: "session_end",
2181
- platform: "claude-code",
2500
+ platform: opts.platform ?? "claude-code",
2182
2501
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2183
2502
  };
2184
2503
  pending.push(endEvent);
@@ -2187,11 +2506,11 @@ function registerHookCommand(program2) {
2187
2506
  }
2188
2507
  process.exit(0);
2189
2508
  });
2190
- hook.command("post-tool-use").description("Buffer a tool-use event").option("--tool <name>", "Tool name").option("--success <bool>", "Whether the tool call succeeded").action(async (opts) => {
2509
+ hook.command("post-tool-use").description("Buffer a tool-use event").option("--platform <name>", "Platform name", "claude-code").option("--tool <name>", "Tool name").option("--success <bool>", "Whether the tool call succeeded").action(async (opts) => {
2191
2510
  try {
2192
2511
  const event = {
2193
2512
  type: "tool_use",
2194
- platform: "claude-code",
2513
+ platform: opts.platform ?? "claude-code",
2195
2514
  tool: opts.tool,
2196
2515
  success: opts.success === "true" || opts.success === true,
2197
2516
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
@@ -2255,10 +2574,16 @@ function registerHookCommand(program2) {
2255
2574
 
2256
2575
  // src/commands/setup.ts
2257
2576
  import { createInterface as createInterface5 } from "readline/promises";
2577
+ import { existsSync as existsSync12 } from "fs";
2258
2578
 
2259
2579
  // src/platform/templates/claude-code-skill.ts
2260
2580
  function getClaudeCodeSkillContent() {
2261
- return `# CAIK \u2014 AI Artifact Registry
2581
+ return `---
2582
+ name: caik
2583
+ description: Search, install, and manage AI coding artifacts (skills, rules, prompts, MCP servers, knowledge packs) from the CAIK community registry. Use when the user wants to find reusable building blocks, install artifacts, or contribute back to the community.
2584
+ ---
2585
+
2586
+ # CAIK \u2014 AI Artifact Registry
2262
2587
 
2263
2588
  CAIK is a community-driven registry for AI coding artifacts \u2014 skills, rules, prompts, MCP servers, and knowledge packs. Use CAIK to discover, install, and share reusable building blocks across projects.
2264
2589
 
@@ -2296,7 +2621,12 @@ Ask your agent: "Find CAIK artifacts for [your use case]" \u2014 the agent will
2296
2621
 
2297
2622
  // src/platform/templates/cursor-skill.ts
2298
2623
  function getCursorSkillContent() {
2299
- return `# CAIK \u2014 AI Artifact Registry
2624
+ return `---
2625
+ name: caik
2626
+ description: Search, install, and manage AI coding artifacts (skills, rules, prompts, MCP servers, knowledge packs) from the CAIK community registry. Use when the user wants to find reusable building blocks, install artifacts, or contribute back to the community.
2627
+ ---
2628
+
2629
+ # CAIK \u2014 AI Artifact Registry
2300
2630
 
2301
2631
  CAIK is a community-driven registry for AI coding artifacts. Use CAIK to discover and install rules, prompts, and knowledge packs into your Cursor workspace.
2302
2632
 
@@ -2330,7 +2660,12 @@ Ask Cursor: "Find CAIK artifacts for [your use case]" to search the registry.
2330
2660
 
2331
2661
  // src/platform/templates/openclaw-skill.ts
2332
2662
  function getOpenClawSkillContent() {
2333
- return `# CAIK \u2014 AI Artifact Registry
2663
+ return `---
2664
+ name: caik
2665
+ description: Search, install, and manage AI coding artifacts (skills, rules, prompts, MCP servers, knowledge packs) from the CAIK community registry. Use when the user wants to find reusable building blocks, install artifacts, or contribute back to the community.
2666
+ ---
2667
+
2668
+ # CAIK \u2014 AI Artifact Registry
2334
2669
 
2335
2670
  CAIK is a community-driven registry for AI coding artifacts \u2014 skills, rules, prompts, MCP servers, and knowledge packs. Use CAIK to discover and install reusable building blocks.
2336
2671
 
@@ -2338,9 +2673,9 @@ CAIK is a community-driven registry for AI coding artifacts \u2014 skills, rules
2338
2673
 
2339
2674
  CAIK artifacts integrate with OpenClaw's skill system. Installed skills are available as callable capabilities within your OpenClaw agent.
2340
2675
 
2341
- ## Hook Telemetry
2676
+ ## Contribution Tracking
2342
2677
 
2343
- CAIK uses OpenClaw lifecycle hooks to track artifact usage. When you invoke a CAIK-installed skill, outcome data is reported automatically to improve community rankings. You can disable telemetry with \`caik config set telemetry false\`.
2678
+ CAIK uses OpenClaw lifecycle hooks to track artifact usage. When you invoke a CAIK-installed skill, outcome data is reported automatically to build your contribution level and community karma. You can disable it with \`caik config set contributions false\`.
2344
2679
 
2345
2680
  ## MCP Tools
2346
2681
 
@@ -2367,8 +2702,8 @@ Ask your agent: "Find CAIK artifacts for [your use case]" to search the registry
2367
2702
  }
2368
2703
 
2369
2704
  // src/platform/templates/index.ts
2370
- function getSkillContent(platform) {
2371
- switch (platform) {
2705
+ function getSkillContent(platform2) {
2706
+ switch (platform2) {
2372
2707
  case "claude-code":
2373
2708
  return getClaudeCodeSkillContent();
2374
2709
  case "cursor":
@@ -2410,8 +2745,8 @@ Ask your agent: "Find CAIK artifacts for [your use case]" to search the registry
2410
2745
  }
2411
2746
 
2412
2747
  // src/commands/setup.ts
2413
- function getHookConfig(platform) {
2414
- if (platform === "claude-code") {
2748
+ function getHookConfig(platform2) {
2749
+ if (platform2 === "claude-code") {
2415
2750
  return {
2416
2751
  hooks: {
2417
2752
  SessionStart: [{ type: "command", command: "npx -y @caik.dev/cli hook session-start" }],
@@ -2420,7 +2755,7 @@ function getHookConfig(platform) {
2420
2755
  }
2421
2756
  };
2422
2757
  }
2423
- if (platform === "cursor") {
2758
+ if (platform2 === "cursor") {
2424
2759
  return {
2425
2760
  hooks: {
2426
2761
  sessionStart: "npx -y @caik.dev/cli hook cursor-session-start",
@@ -2431,24 +2766,154 @@ function getHookConfig(platform) {
2431
2766
  }
2432
2767
  return {
2433
2768
  hooks: {
2434
- session_start: "npx -y @caik.dev/cli hook session-start",
2435
- session_end: "npx -y @caik.dev/cli hook session-end",
2436
- after_tool_call: "npx -y @caik.dev/cli hook post-tool-use"
2769
+ session_start: "npx -y @caik.dev/cli hook session-start --platform openclaw",
2770
+ session_end: "npx -y @caik.dev/cli hook session-end --platform openclaw",
2771
+ after_tool_call: "npx -y @caik.dev/cli hook post-tool-use --platform openclaw"
2437
2772
  }
2438
2773
  };
2439
2774
  }
2775
+ async function runRepair() {
2776
+ console.log(heading("\nCAIK Repair"));
2777
+ console.log("\u2550".repeat(40));
2778
+ console.log();
2779
+ const spinner = createSpinner("Detecting platforms...");
2780
+ spinner.start();
2781
+ const detected = detectPlatforms();
2782
+ spinner.stop();
2783
+ if (detected.length === 0) {
2784
+ console.log(warn("No supported agent platforms detected. Nothing to repair."));
2785
+ return;
2786
+ }
2787
+ console.log(info(`Detected ${detected.length} platform(s): ${detected.map((p) => p.name).join(", ")}`));
2788
+ console.log();
2789
+ let repairedMcp = 0;
2790
+ let repairedHooks = 0;
2791
+ let repairedSkills = 0;
2792
+ const registryWarnings = [];
2793
+ for (const platform2 of detected) {
2794
+ console.log(heading(`Checking ${platform2.name}...`));
2795
+ console.log("\u2500".repeat(40));
2796
+ const adapter = getPlatformAdapter(platform2.name);
2797
+ let mcpRegistered = false;
2798
+ try {
2799
+ mcpRegistered = await adapter.isRegistered();
2800
+ } catch {
2801
+ }
2802
+ if (mcpRegistered) {
2803
+ console.log(` MCP server: ${success("registered")}`);
2804
+ } else {
2805
+ const mcpSpinner = createSpinner(" Re-registering MCP server...");
2806
+ mcpSpinner.start();
2807
+ try {
2808
+ const mcpConfig = getDefaultMcpConfig();
2809
+ await adapter.registerMcp(mcpConfig);
2810
+ mcpSpinner.stop();
2811
+ console.log(` MCP server: ${success("repaired")} (re-registered)`);
2812
+ repairedMcp++;
2813
+ } catch (err) {
2814
+ mcpSpinner.stop();
2815
+ console.log(` MCP server: ${error(`repair failed: ${err instanceof Error ? err.message : String(err)}`)}`);
2816
+ }
2817
+ }
2818
+ if (adapter.registerHooks && platform2.capabilities.hooks) {
2819
+ const hookSpinner = createSpinner(" Ensuring hooks are registered...");
2820
+ hookSpinner.start();
2821
+ try {
2822
+ const hookConfig = getHookConfig(platform2.name);
2823
+ await adapter.registerHooks(hookConfig);
2824
+ hookSpinner.stop();
2825
+ console.log(` Hooks: ${success("ensured")}`);
2826
+ repairedHooks++;
2827
+ } catch (err) {
2828
+ hookSpinner.stop();
2829
+ console.log(` Hooks: ${error(`repair failed: ${err instanceof Error ? err.message : String(err)}`)}`);
2830
+ }
2831
+ } else if (!platform2.capabilities.hooks) {
2832
+ console.log(` Hooks: ${dim("not supported on this platform")}`);
2833
+ }
2834
+ if (adapter.installSkill && platform2.capabilities.skills) {
2835
+ const skillSpinner = createSpinner(" Ensuring SKILL.md is installed...");
2836
+ skillSpinner.start();
2837
+ try {
2838
+ const content = getSkillContent(platform2.name);
2839
+ await adapter.installSkill("caik", content);
2840
+ skillSpinner.stop();
2841
+ console.log(` SKILL.md: ${success("ensured")}`);
2842
+ repairedSkills++;
2843
+ } catch (err) {
2844
+ skillSpinner.stop();
2845
+ console.log(` SKILL.md: ${error(`repair failed: ${err instanceof Error ? err.message : String(err)}`)}`);
2846
+ }
2847
+ } else if (!platform2.capabilities.skills) {
2848
+ console.log(` SKILL.md: ${dim("not supported on this platform")}`);
2849
+ }
2850
+ console.log();
2851
+ }
2852
+ const registryEntries = listRegistryEntries();
2853
+ if (registryEntries.length > 0) {
2854
+ console.log(heading("Registry Integrity"));
2855
+ console.log("\u2500".repeat(40));
2856
+ let cleanEntries = 0;
2857
+ for (const entry of registryEntries) {
2858
+ const missing = entry.files.filter((f) => !existsSync12(f));
2859
+ if (missing.length > 0) {
2860
+ const msg = `${entry.slug} (${entry.platform}): ${missing.length} missing file(s)`;
2861
+ registryWarnings.push(msg);
2862
+ console.log(` ${warn(msg)}`);
2863
+ for (const f of missing) {
2864
+ console.log(` ${dim(f)}`);
2865
+ }
2866
+ } else {
2867
+ cleanEntries++;
2868
+ }
2869
+ }
2870
+ if (registryWarnings.length === 0) {
2871
+ console.log(` ${success("All")} ${registryEntries.length} registry entries have their files intact.`);
2872
+ } else if (cleanEntries > 0) {
2873
+ console.log(` ${success(`${cleanEntries}`)} entries OK, ${warn(`${registryWarnings.length}`)} with missing files.`);
2874
+ }
2875
+ if (registryWarnings.length > 0) {
2876
+ console.log();
2877
+ console.log(
2878
+ dim(" Artifact files cannot be auto-repaired. Run `caik install <slug>` to reinstall.")
2879
+ );
2880
+ }
2881
+ console.log();
2882
+ }
2883
+ console.log(heading("Repair Summary"));
2884
+ console.log("\u2550".repeat(40));
2885
+ const actions = [];
2886
+ if (repairedMcp > 0) actions.push(`${repairedMcp} MCP registration(s) repaired`);
2887
+ if (repairedHooks > 0) actions.push(`${repairedHooks} hook registration(s) ensured`);
2888
+ if (repairedSkills > 0) actions.push(`${repairedSkills} skill installation(s) ensured`);
2889
+ if (registryWarnings.length > 0) actions.push(`${registryWarnings.length} registry warning(s)`);
2890
+ if (actions.length === 0) {
2891
+ console.log(success("Everything looks good. No repairs needed."));
2892
+ } else {
2893
+ for (const a of actions) {
2894
+ console.log(` ${info(a)}`);
2895
+ }
2896
+ }
2897
+ console.log();
2898
+ }
2440
2899
  function registerSetupCommand(program2) {
2441
- program2.command("setup").description("Interactive setup wizard \u2014 configure CAIK for your agent platforms").option("--platform <name>", "Skip detection, configure a specific platform").option("--skip-hooks", "Register MCP only, skip hook registration").option("--skip-stacks", "Skip stack recommendations").option("-y, --yes", "Accept all defaults (non-interactive)").addHelpText("after", `
2900
+ program2.command("setup").description("Interactive setup wizard \u2014 configure CAIK for your agent platforms").option("--platform <name>", "Skip detection, configure a specific platform").option("--skip-hooks", "Register MCP only, skip hook registration").option("--skip-stacks", "Skip stack recommendations").option("--repair", "Detect and fix configuration drift (re-register MCP, hooks, skills)").option("-y, --yes", "Accept all defaults (non-interactive)").addHelpText("after", `
2442
2901
  Examples:
2443
2902
  caik setup
2444
2903
  caik setup --platform claude-code
2445
- caik setup --yes --skip-hooks`).action(async (opts) => {
2904
+ caik setup --yes --skip-hooks
2905
+ caik setup --repair`).action(async (opts) => {
2446
2906
  const globalOpts = program2.opts();
2447
2907
  const { apiKey } = resolveConfig(globalOpts);
2448
2908
  const skipHooks = Boolean(opts.skipHooks);
2449
2909
  const skipStacks = Boolean(opts.skipStacks);
2450
2910
  const autoYes = Boolean(opts.yes);
2451
2911
  const platformFlag = opts.platform;
2912
+ const repair = Boolean(opts.repair);
2913
+ if (repair) {
2914
+ await runRepair();
2915
+ return;
2916
+ }
2452
2917
  console.log(heading("\nCAIK Setup Wizard"));
2453
2918
  console.log("\u2550".repeat(40));
2454
2919
  console.log();
@@ -2508,10 +2973,10 @@ Examples:
2508
2973
  }
2509
2974
  }
2510
2975
  console.log();
2511
- for (const platform of selected) {
2512
- console.log(heading(`Configuring ${platform.name}...`));
2976
+ for (const platform2 of selected) {
2977
+ console.log(heading(`Configuring ${platform2.name}...`));
2513
2978
  console.log("\u2500".repeat(40));
2514
- const adapter = getPlatformAdapter(platform.name);
2979
+ const adapter = getPlatformAdapter(platform2.name);
2515
2980
  const mcpConfig = getDefaultMcpConfig();
2516
2981
  const mcpSpinner = createSpinner("Registering MCP server...");
2517
2982
  mcpSpinner.start();
@@ -2523,28 +2988,31 @@ Examples:
2523
2988
  mcpSpinner.stop();
2524
2989
  console.log(error(`Failed to register MCP server: ${err instanceof Error ? err.message : String(err)}`));
2525
2990
  }
2526
- if (!skipHooks && adapter.registerHooks && platform.capabilities.hooks) {
2991
+ if (!skipHooks && adapter.registerHooks && platform2.capabilities.hooks) {
2527
2992
  const hookSpinner = createSpinner("Registering hooks...");
2528
2993
  hookSpinner.start();
2529
2994
  try {
2530
- const hookConfig = getHookConfig(platform.name);
2995
+ const hookConfig = getHookConfig(platform2.name);
2531
2996
  await adapter.registerHooks(hookConfig);
2532
2997
  hookSpinner.stop();
2533
2998
  console.log(success("Hooks registered"));
2999
+ if (platform2.name === "openclaw") {
3000
+ console.log(dim(" Restart the OpenClaw gateway to load hooks: openclaw daemon restart"));
3001
+ }
2534
3002
  } catch (err) {
2535
3003
  hookSpinner.stop();
2536
3004
  console.log(error(`Failed to register hooks: ${err instanceof Error ? err.message : String(err)}`));
2537
3005
  }
2538
- } else if (!skipHooks && !platform.capabilities.hooks) {
3006
+ } else if (!skipHooks && !platform2.capabilities.hooks) {
2539
3007
  console.log(dim(" Hooks not supported on this platform \u2014 skipped"));
2540
3008
  } else if (skipHooks) {
2541
3009
  console.log(dim(" Hooks skipped (--skip-hooks)"));
2542
3010
  }
2543
- if (adapter.installSkill && platform.capabilities.skills) {
3011
+ if (adapter.installSkill && platform2.capabilities.skills) {
2544
3012
  const skillSpinner = createSpinner("Installing SKILL.md...");
2545
3013
  skillSpinner.start();
2546
3014
  try {
2547
- const content = getSkillContent(platform.name);
3015
+ const content = getSkillContent(platform2.name);
2548
3016
  await adapter.installSkill("caik", content);
2549
3017
  skillSpinner.stop();
2550
3018
  console.log(success("SKILL.md installed"));
@@ -2575,13 +3043,147 @@ Examples:
2575
3043
  });
2576
3044
  }
2577
3045
 
3046
+ // src/commands/upgrade.ts
3047
+ import { execSync as execSync5 } from "child_process";
3048
+
3049
+ // src/update-check.ts
3050
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync9, mkdirSync as mkdirSync9, existsSync as existsSync13 } from "fs";
3051
+ import { join as join8 } from "path";
3052
+ import { homedir as homedir7 } from "os";
3053
+ import chalk2 from "chalk";
3054
+ var PKG_NAME = "@caik.dev/cli";
3055
+ var CACHE_PATH = join8(homedir7(), ".caik", "update-check.json");
3056
+ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
3057
+ function readCache() {
3058
+ try {
3059
+ const raw = readFileSync8(CACHE_PATH, "utf-8");
3060
+ return JSON.parse(raw);
3061
+ } catch {
3062
+ return null;
3063
+ }
3064
+ }
3065
+ function writeCache(entry) {
3066
+ const dir = join8(homedir7(), ".caik");
3067
+ if (!existsSync13(dir)) {
3068
+ mkdirSync9(dir, { recursive: true });
3069
+ }
3070
+ writeFileSync9(CACHE_PATH, JSON.stringify(entry) + "\n", "utf-8");
3071
+ }
3072
+ function getCurrentVersion() {
3073
+ try {
3074
+ const pkgPath = join8(
3075
+ new URL(".", import.meta.url).pathname,
3076
+ "..",
3077
+ "package.json"
3078
+ );
3079
+ const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
3080
+ return pkg.version;
3081
+ } catch {
3082
+ return "0.0.0";
3083
+ }
3084
+ }
3085
+ async function fetchLatestVersion() {
3086
+ try {
3087
+ const controller = new AbortController();
3088
+ const timeout = setTimeout(() => controller.abort(), 3e3);
3089
+ const res = await fetch(
3090
+ `https://registry.npmjs.org/${PKG_NAME}/latest`,
3091
+ { signal: controller.signal }
3092
+ );
3093
+ clearTimeout(timeout);
3094
+ if (!res.ok) return null;
3095
+ const data = await res.json();
3096
+ return data.version ?? null;
3097
+ } catch {
3098
+ return null;
3099
+ }
3100
+ }
3101
+ function isNewer(latest, current) {
3102
+ const a = latest.split(".").map(Number);
3103
+ const b = current.split(".").map(Number);
3104
+ for (let i = 0; i < 3; i++) {
3105
+ if ((a[i] ?? 0) > (b[i] ?? 0)) return true;
3106
+ if ((a[i] ?? 0) < (b[i] ?? 0)) return false;
3107
+ }
3108
+ return false;
3109
+ }
3110
+ async function checkForUpdate(opts = {}) {
3111
+ const current = getCurrentVersion();
3112
+ if (!opts.force) {
3113
+ const cache = readCache();
3114
+ if (cache && Date.now() - cache.checkedAt < CHECK_INTERVAL_MS) {
3115
+ return {
3116
+ current,
3117
+ latest: cache.latest,
3118
+ updateAvailable: isNewer(cache.latest, current)
3119
+ };
3120
+ }
3121
+ }
3122
+ const latest = await fetchLatestVersion();
3123
+ if (latest) {
3124
+ writeCache({ latest, checkedAt: Date.now() });
3125
+ }
3126
+ return {
3127
+ current,
3128
+ latest,
3129
+ updateAvailable: latest ? isNewer(latest, current) : false
3130
+ };
3131
+ }
3132
+ async function printUpdateHint() {
3133
+ try {
3134
+ const info2 = await checkForUpdate();
3135
+ if (info2.updateAvailable && info2.latest) {
3136
+ console.error(
3137
+ chalk2.yellow(
3138
+ `
3139
+ Update available: ${info2.current} \u2192 ${info2.latest} Run ${chalk2.bold("caik upgrade")} to update
3140
+ `
3141
+ )
3142
+ );
3143
+ }
3144
+ } catch {
3145
+ }
3146
+ }
3147
+
3148
+ // src/commands/upgrade.ts
3149
+ function registerUpgradeCommand(program2) {
3150
+ program2.command("upgrade").description("Upgrade the CAIK CLI to the latest version").action(async () => {
3151
+ const spinner = createSpinner("Checking for updates...");
3152
+ spinner.start();
3153
+ const update = await checkForUpdate({ force: true });
3154
+ if (!update.latest) {
3155
+ spinner.stop();
3156
+ console.log(error("Could not determine the latest version. Check your network connection."));
3157
+ return;
3158
+ }
3159
+ if (!update.updateAvailable) {
3160
+ spinner.stop();
3161
+ console.log(success(`Already on the latest version (${update.current})`));
3162
+ return;
3163
+ }
3164
+ spinner.text = `Upgrading ${update.current} \u2192 ${update.latest}...`;
3165
+ try {
3166
+ execSync5("npm install -g @caik.dev/cli@latest", {
3167
+ stdio: "pipe",
3168
+ timeout: 6e4
3169
+ });
3170
+ spinner.stop();
3171
+ console.log(success(`Upgraded to ${update.latest}`));
3172
+ } catch (err) {
3173
+ spinner.stop();
3174
+ console.log(error("Upgrade failed. Try manually:"));
3175
+ console.log(info("npm install -g @caik.dev/cli@latest"));
3176
+ }
3177
+ });
3178
+ }
3179
+
2578
3180
  // src/index.ts
2579
3181
  var __filename = fileURLToPath(import.meta.url);
2580
3182
  var __dirname = dirname5(__filename);
2581
3183
  var version = "0.0.1";
2582
3184
  try {
2583
- const pkgPath = join8(__dirname, "..", "package.json");
2584
- const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
3185
+ const pkgPath = join9(__dirname, "..", "package.json");
3186
+ const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
2585
3187
  version = pkg.version;
2586
3188
  } catch {
2587
3189
  }
@@ -2601,15 +3203,17 @@ registerUninstallCommand(program);
2601
3203
  registerKarmaCommand(program);
2602
3204
  registerHookCommand(program);
2603
3205
  registerSetupCommand(program);
3206
+ registerUpgradeCommand(program);
2604
3207
  program.exitOverride();
2605
3208
  async function main() {
3209
+ const updateHint = printUpdateHint();
2606
3210
  try {
2607
3211
  await program.parseAsync(process.argv);
2608
3212
  } catch (err) {
2609
3213
  if (err instanceof CaikError) {
2610
- console.error(chalk2.red(`\u2717 ${err.message}`));
3214
+ console.error(chalk3.red(`\u2717 ${err.message}`));
2611
3215
  if (err.suggestion) {
2612
- console.error(chalk2.dim(` ${err.suggestion}`));
3216
+ console.error(chalk3.dim(` ${err.suggestion}`));
2613
3217
  }
2614
3218
  process.exit(err.exitCode);
2615
3219
  }
@@ -2617,8 +3221,9 @@ async function main() {
2617
3221
  const exitCode = err.exitCode;
2618
3222
  process.exit(exitCode);
2619
3223
  }
2620
- console.error(chalk2.red(`\u2717 Unexpected error: ${err instanceof Error ? err.message : String(err)}`));
3224
+ console.error(chalk3.red(`\u2717 Unexpected error: ${err instanceof Error ? err.message : String(err)}`));
2621
3225
  process.exit(1);
2622
3226
  }
3227
+ await updateHint;
2623
3228
  }
2624
3229
  main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caik.dev/cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "description": "CAIK CLI — Search, install, and publish AI artifacts from your terminal",
6
6
  "keywords": [