@caik.dev/cli 0.1.2 → 0.1.4

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 +693 -87
  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 {
@@ -133,7 +133,7 @@ import { readFileSync, writeFileSync, mkdirSync, chmodSync, existsSync } from "f
133
133
  import { join } from "path";
134
134
  import { homedir } from "os";
135
135
  var DEFAULT_CONFIG = {
136
- apiUrl: "https://caik.dev",
136
+ apiUrl: "https://www.caik.dev",
137
137
  defaultLimit: 10,
138
138
  version: 1
139
139
  };
@@ -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://www.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,121 @@ 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 cliCallbackUrl = `${apiUrl}/api/auth/cli-callback?redirect=${encodeURIComponent(callbackUrl)}&state=${encodeURIComponent(state)}`;
1623
+ const loginUrl = `${apiUrl}/cli-auth?continue=${encodeURIComponent(cliCallbackUrl)}`;
1624
+ console.log(info(`Listening on http://localhost:${actualPort} for callback...`));
1625
+ const opened = await openBrowser(loginUrl);
1626
+ if (opened) {
1627
+ console.log(info("Browser opened. Complete sign-in to continue."));
1628
+ } else {
1629
+ console.log(
1630
+ error("Could not open browser automatically.")
1631
+ );
1632
+ console.log(info(`Open this URL manually:
1633
+ ${loginUrl}`));
1634
+ }
1635
+ });
1636
+ server.on("error", (err) => {
1637
+ clearTimeout(timeout);
1638
+ reject(
1639
+ new Error(
1640
+ `Failed to start auth server: ${err.message}`
1641
+ )
1642
+ );
1643
+ });
1644
+ });
1645
+ }
1646
+
1647
+ // src/commands/init.ts
1342
1648
  function registerInitCommand(program2) {
1343
1649
  program2.command("init").description("Configure the CAIK CLI").option("--auth", "Set up authentication (opens browser)").addHelpText("after", `
1344
1650
  Examples:
1345
1651
  caik init
1346
- caik init --auth`).action(async (_opts) => {
1652
+ caik init --auth`).action(async (opts) => {
1653
+ const config = readConfig();
1654
+ if (opts.auth) {
1655
+ console.log(info("Opening browser for authentication..."));
1656
+ try {
1657
+ const apiKey = await authenticate(config.apiUrl);
1658
+ config.apiKey = apiKey;
1659
+ writeConfig(config);
1660
+ console.log(success("Authenticated and saved API key"));
1661
+ console.log(info(`Config saved to ~/.caik/config.json`));
1662
+ } catch (err) {
1663
+ console.log(error(`Authentication failed: ${err instanceof Error ? err.message : String(err)}`));
1664
+ }
1665
+ return;
1666
+ }
1347
1667
  const rl = createInterface2({ input: stdin, output: stdout });
1348
1668
  try {
1349
- const config = readConfig();
1350
1669
  const apiUrl = await rl.question(`API URL (${config.apiUrl}): `);
1351
1670
  if (apiUrl.trim()) {
1352
1671
  config.apiUrl = apiUrl.trim();
@@ -2016,7 +2335,7 @@ Artifact: ${entry.slug} (${entry.artifactType})`));
2016
2335
  if (!globalOpts.json) spinner.start();
2017
2336
  const failedFiles = cleanupFiles(entry);
2018
2337
  const adapter = getPlatformAdapter(entry.platform);
2019
- if (entry.artifactType === "mcp-server") {
2338
+ if (entry.artifactType === "connector" || entry.artifactType === "mcp-server") {
2020
2339
  try {
2021
2340
  await adapter.unregisterMcp();
2022
2341
  } catch (err) {
@@ -2027,7 +2346,8 @@ Artifact: ${entry.slug} (${entry.artifactType})`));
2027
2346
  }
2028
2347
  }
2029
2348
  }
2030
- if ((entry.artifactType === "skill" || entry.artifactType === "subagent" || entry.artifactType === "command") && adapter.uninstallSkill) {
2349
+ const skillTypes = /* @__PURE__ */ new Set(["executable", "composition", "skill", "subagent", "command"]);
2350
+ if (skillTypes.has(entry.artifactType) && adapter.uninstallSkill) {
2031
2351
  try {
2032
2352
  await adapter.uninstallSkill(slug);
2033
2353
  } catch (err) {
@@ -2159,12 +2479,12 @@ function createClient(program2) {
2159
2479
  }
2160
2480
  function registerHookCommand(program2) {
2161
2481
  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 () => {
2482
+ hook.command("session-start").description("Log session start event").option("--platform <name>", "Platform name", "claude-code").action(async (opts) => {
2163
2483
  try {
2164
2484
  const client = createClient(program2);
2165
2485
  const event = {
2166
2486
  type: "session_start",
2167
- platform: "claude-code",
2487
+ platform: opts.platform ?? "claude-code",
2168
2488
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2169
2489
  };
2170
2490
  await postSingleEventWithTimeout(client, event);
@@ -2172,13 +2492,13 @@ function registerHookCommand(program2) {
2172
2492
  }
2173
2493
  process.exit(0);
2174
2494
  });
2175
- hook.command("session-end").description("Flush pending events and log session end").action(async () => {
2495
+ hook.command("session-end").description("Flush pending events and log session end").option("--platform <name>", "Platform name", "claude-code").action(async (opts) => {
2176
2496
  try {
2177
2497
  const client = createClient(program2);
2178
2498
  const pending = readPendingEvents();
2179
2499
  const endEvent = {
2180
2500
  type: "session_end",
2181
- platform: "claude-code",
2501
+ platform: opts.platform ?? "claude-code",
2182
2502
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2183
2503
  };
2184
2504
  pending.push(endEvent);
@@ -2187,11 +2507,11 @@ function registerHookCommand(program2) {
2187
2507
  }
2188
2508
  process.exit(0);
2189
2509
  });
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) => {
2510
+ 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
2511
  try {
2192
2512
  const event = {
2193
2513
  type: "tool_use",
2194
- platform: "claude-code",
2514
+ platform: opts.platform ?? "claude-code",
2195
2515
  tool: opts.tool,
2196
2516
  success: opts.success === "true" || opts.success === true,
2197
2517
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
@@ -2255,10 +2575,16 @@ function registerHookCommand(program2) {
2255
2575
 
2256
2576
  // src/commands/setup.ts
2257
2577
  import { createInterface as createInterface5 } from "readline/promises";
2578
+ import { existsSync as existsSync12 } from "fs";
2258
2579
 
2259
2580
  // src/platform/templates/claude-code-skill.ts
2260
2581
  function getClaudeCodeSkillContent() {
2261
- return `# CAIK \u2014 AI Artifact Registry
2582
+ return `---
2583
+ name: caik
2584
+ 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.
2585
+ ---
2586
+
2587
+ # CAIK \u2014 AI Artifact Registry
2262
2588
 
2263
2589
  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
2590
 
@@ -2296,7 +2622,12 @@ Ask your agent: "Find CAIK artifacts for [your use case]" \u2014 the agent will
2296
2622
 
2297
2623
  // src/platform/templates/cursor-skill.ts
2298
2624
  function getCursorSkillContent() {
2299
- return `# CAIK \u2014 AI Artifact Registry
2625
+ return `---
2626
+ name: caik
2627
+ 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.
2628
+ ---
2629
+
2630
+ # CAIK \u2014 AI Artifact Registry
2300
2631
 
2301
2632
  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
2633
 
@@ -2330,7 +2661,12 @@ Ask Cursor: "Find CAIK artifacts for [your use case]" to search the registry.
2330
2661
 
2331
2662
  // src/platform/templates/openclaw-skill.ts
2332
2663
  function getOpenClawSkillContent() {
2333
- return `# CAIK \u2014 AI Artifact Registry
2664
+ return `---
2665
+ name: caik
2666
+ 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.
2667
+ ---
2668
+
2669
+ # CAIK \u2014 AI Artifact Registry
2334
2670
 
2335
2671
  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
2672
 
@@ -2338,9 +2674,9 @@ CAIK is a community-driven registry for AI coding artifacts \u2014 skills, rules
2338
2674
 
2339
2675
  CAIK artifacts integrate with OpenClaw's skill system. Installed skills are available as callable capabilities within your OpenClaw agent.
2340
2676
 
2341
- ## Hook Telemetry
2677
+ ## Contribution Tracking
2342
2678
 
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\`.
2679
+ 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
2680
 
2345
2681
  ## MCP Tools
2346
2682
 
@@ -2367,8 +2703,8 @@ Ask your agent: "Find CAIK artifacts for [your use case]" to search the registry
2367
2703
  }
2368
2704
 
2369
2705
  // src/platform/templates/index.ts
2370
- function getSkillContent(platform) {
2371
- switch (platform) {
2706
+ function getSkillContent(platform2) {
2707
+ switch (platform2) {
2372
2708
  case "claude-code":
2373
2709
  return getClaudeCodeSkillContent();
2374
2710
  case "cursor":
@@ -2410,8 +2746,8 @@ Ask your agent: "Find CAIK artifacts for [your use case]" to search the registry
2410
2746
  }
2411
2747
 
2412
2748
  // src/commands/setup.ts
2413
- function getHookConfig(platform) {
2414
- if (platform === "claude-code") {
2749
+ function getHookConfig(platform2) {
2750
+ if (platform2 === "claude-code") {
2415
2751
  return {
2416
2752
  hooks: {
2417
2753
  SessionStart: [{ type: "command", command: "npx -y @caik.dev/cli hook session-start" }],
@@ -2420,7 +2756,7 @@ function getHookConfig(platform) {
2420
2756
  }
2421
2757
  };
2422
2758
  }
2423
- if (platform === "cursor") {
2759
+ if (platform2 === "cursor") {
2424
2760
  return {
2425
2761
  hooks: {
2426
2762
  sessionStart: "npx -y @caik.dev/cli hook cursor-session-start",
@@ -2431,24 +2767,154 @@ function getHookConfig(platform) {
2431
2767
  }
2432
2768
  return {
2433
2769
  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"
2770
+ session_start: "npx -y @caik.dev/cli hook session-start --platform openclaw",
2771
+ session_end: "npx -y @caik.dev/cli hook session-end --platform openclaw",
2772
+ after_tool_call: "npx -y @caik.dev/cli hook post-tool-use --platform openclaw"
2437
2773
  }
2438
2774
  };
2439
2775
  }
2776
+ async function runRepair() {
2777
+ console.log(heading("\nCAIK Repair"));
2778
+ console.log("\u2550".repeat(40));
2779
+ console.log();
2780
+ const spinner = createSpinner("Detecting platforms...");
2781
+ spinner.start();
2782
+ const detected = detectPlatforms();
2783
+ spinner.stop();
2784
+ if (detected.length === 0) {
2785
+ console.log(warn("No supported agent platforms detected. Nothing to repair."));
2786
+ return;
2787
+ }
2788
+ console.log(info(`Detected ${detected.length} platform(s): ${detected.map((p) => p.name).join(", ")}`));
2789
+ console.log();
2790
+ let repairedMcp = 0;
2791
+ let repairedHooks = 0;
2792
+ let repairedSkills = 0;
2793
+ const registryWarnings = [];
2794
+ for (const platform2 of detected) {
2795
+ console.log(heading(`Checking ${platform2.name}...`));
2796
+ console.log("\u2500".repeat(40));
2797
+ const adapter = getPlatformAdapter(platform2.name);
2798
+ let mcpRegistered = false;
2799
+ try {
2800
+ mcpRegistered = await adapter.isRegistered();
2801
+ } catch {
2802
+ }
2803
+ if (mcpRegistered) {
2804
+ console.log(` MCP server: ${success("registered")}`);
2805
+ } else {
2806
+ const mcpSpinner = createSpinner(" Re-registering MCP server...");
2807
+ mcpSpinner.start();
2808
+ try {
2809
+ const mcpConfig = getDefaultMcpConfig();
2810
+ await adapter.registerMcp(mcpConfig);
2811
+ mcpSpinner.stop();
2812
+ console.log(` MCP server: ${success("repaired")} (re-registered)`);
2813
+ repairedMcp++;
2814
+ } catch (err) {
2815
+ mcpSpinner.stop();
2816
+ console.log(` MCP server: ${error(`repair failed: ${err instanceof Error ? err.message : String(err)}`)}`);
2817
+ }
2818
+ }
2819
+ if (adapter.registerHooks && platform2.capabilities.hooks) {
2820
+ const hookSpinner = createSpinner(" Ensuring hooks are registered...");
2821
+ hookSpinner.start();
2822
+ try {
2823
+ const hookConfig = getHookConfig(platform2.name);
2824
+ await adapter.registerHooks(hookConfig);
2825
+ hookSpinner.stop();
2826
+ console.log(` Hooks: ${success("ensured")}`);
2827
+ repairedHooks++;
2828
+ } catch (err) {
2829
+ hookSpinner.stop();
2830
+ console.log(` Hooks: ${error(`repair failed: ${err instanceof Error ? err.message : String(err)}`)}`);
2831
+ }
2832
+ } else if (!platform2.capabilities.hooks) {
2833
+ console.log(` Hooks: ${dim("not supported on this platform")}`);
2834
+ }
2835
+ if (adapter.installSkill && platform2.capabilities.skills) {
2836
+ const skillSpinner = createSpinner(" Ensuring SKILL.md is installed...");
2837
+ skillSpinner.start();
2838
+ try {
2839
+ const content = getSkillContent(platform2.name);
2840
+ await adapter.installSkill("caik", content);
2841
+ skillSpinner.stop();
2842
+ console.log(` SKILL.md: ${success("ensured")}`);
2843
+ repairedSkills++;
2844
+ } catch (err) {
2845
+ skillSpinner.stop();
2846
+ console.log(` SKILL.md: ${error(`repair failed: ${err instanceof Error ? err.message : String(err)}`)}`);
2847
+ }
2848
+ } else if (!platform2.capabilities.skills) {
2849
+ console.log(` SKILL.md: ${dim("not supported on this platform")}`);
2850
+ }
2851
+ console.log();
2852
+ }
2853
+ const registryEntries = listRegistryEntries();
2854
+ if (registryEntries.length > 0) {
2855
+ console.log(heading("Registry Integrity"));
2856
+ console.log("\u2500".repeat(40));
2857
+ let cleanEntries = 0;
2858
+ for (const entry of registryEntries) {
2859
+ const missing = entry.files.filter((f) => !existsSync12(f));
2860
+ if (missing.length > 0) {
2861
+ const msg = `${entry.slug} (${entry.platform}): ${missing.length} missing file(s)`;
2862
+ registryWarnings.push(msg);
2863
+ console.log(` ${warn(msg)}`);
2864
+ for (const f of missing) {
2865
+ console.log(` ${dim(f)}`);
2866
+ }
2867
+ } else {
2868
+ cleanEntries++;
2869
+ }
2870
+ }
2871
+ if (registryWarnings.length === 0) {
2872
+ console.log(` ${success("All")} ${registryEntries.length} registry entries have their files intact.`);
2873
+ } else if (cleanEntries > 0) {
2874
+ console.log(` ${success(`${cleanEntries}`)} entries OK, ${warn(`${registryWarnings.length}`)} with missing files.`);
2875
+ }
2876
+ if (registryWarnings.length > 0) {
2877
+ console.log();
2878
+ console.log(
2879
+ dim(" Artifact files cannot be auto-repaired. Run `caik install <slug>` to reinstall.")
2880
+ );
2881
+ }
2882
+ console.log();
2883
+ }
2884
+ console.log(heading("Repair Summary"));
2885
+ console.log("\u2550".repeat(40));
2886
+ const actions = [];
2887
+ if (repairedMcp > 0) actions.push(`${repairedMcp} MCP registration(s) repaired`);
2888
+ if (repairedHooks > 0) actions.push(`${repairedHooks} hook registration(s) ensured`);
2889
+ if (repairedSkills > 0) actions.push(`${repairedSkills} skill installation(s) ensured`);
2890
+ if (registryWarnings.length > 0) actions.push(`${registryWarnings.length} registry warning(s)`);
2891
+ if (actions.length === 0) {
2892
+ console.log(success("Everything looks good. No repairs needed."));
2893
+ } else {
2894
+ for (const a of actions) {
2895
+ console.log(` ${info(a)}`);
2896
+ }
2897
+ }
2898
+ console.log();
2899
+ }
2440
2900
  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", `
2901
+ 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
2902
  Examples:
2443
2903
  caik setup
2444
2904
  caik setup --platform claude-code
2445
- caik setup --yes --skip-hooks`).action(async (opts) => {
2905
+ caik setup --yes --skip-hooks
2906
+ caik setup --repair`).action(async (opts) => {
2446
2907
  const globalOpts = program2.opts();
2447
2908
  const { apiKey } = resolveConfig(globalOpts);
2448
2909
  const skipHooks = Boolean(opts.skipHooks);
2449
2910
  const skipStacks = Boolean(opts.skipStacks);
2450
2911
  const autoYes = Boolean(opts.yes);
2451
2912
  const platformFlag = opts.platform;
2913
+ const repair = Boolean(opts.repair);
2914
+ if (repair) {
2915
+ await runRepair();
2916
+ return;
2917
+ }
2452
2918
  console.log(heading("\nCAIK Setup Wizard"));
2453
2919
  console.log("\u2550".repeat(40));
2454
2920
  console.log();
@@ -2508,10 +2974,10 @@ Examples:
2508
2974
  }
2509
2975
  }
2510
2976
  console.log();
2511
- for (const platform of selected) {
2512
- console.log(heading(`Configuring ${platform.name}...`));
2977
+ for (const platform2 of selected) {
2978
+ console.log(heading(`Configuring ${platform2.name}...`));
2513
2979
  console.log("\u2500".repeat(40));
2514
- const adapter = getPlatformAdapter(platform.name);
2980
+ const adapter = getPlatformAdapter(platform2.name);
2515
2981
  const mcpConfig = getDefaultMcpConfig();
2516
2982
  const mcpSpinner = createSpinner("Registering MCP server...");
2517
2983
  mcpSpinner.start();
@@ -2523,28 +2989,31 @@ Examples:
2523
2989
  mcpSpinner.stop();
2524
2990
  console.log(error(`Failed to register MCP server: ${err instanceof Error ? err.message : String(err)}`));
2525
2991
  }
2526
- if (!skipHooks && adapter.registerHooks && platform.capabilities.hooks) {
2992
+ if (!skipHooks && adapter.registerHooks && platform2.capabilities.hooks) {
2527
2993
  const hookSpinner = createSpinner("Registering hooks...");
2528
2994
  hookSpinner.start();
2529
2995
  try {
2530
- const hookConfig = getHookConfig(platform.name);
2996
+ const hookConfig = getHookConfig(platform2.name);
2531
2997
  await adapter.registerHooks(hookConfig);
2532
2998
  hookSpinner.stop();
2533
2999
  console.log(success("Hooks registered"));
3000
+ if (platform2.name === "openclaw") {
3001
+ console.log(dim(" Restart the OpenClaw gateway to load hooks: openclaw daemon restart"));
3002
+ }
2534
3003
  } catch (err) {
2535
3004
  hookSpinner.stop();
2536
3005
  console.log(error(`Failed to register hooks: ${err instanceof Error ? err.message : String(err)}`));
2537
3006
  }
2538
- } else if (!skipHooks && !platform.capabilities.hooks) {
3007
+ } else if (!skipHooks && !platform2.capabilities.hooks) {
2539
3008
  console.log(dim(" Hooks not supported on this platform \u2014 skipped"));
2540
3009
  } else if (skipHooks) {
2541
3010
  console.log(dim(" Hooks skipped (--skip-hooks)"));
2542
3011
  }
2543
- if (adapter.installSkill && platform.capabilities.skills) {
3012
+ if (adapter.installSkill && platform2.capabilities.skills) {
2544
3013
  const skillSpinner = createSpinner("Installing SKILL.md...");
2545
3014
  skillSpinner.start();
2546
3015
  try {
2547
- const content = getSkillContent(platform.name);
3016
+ const content = getSkillContent(platform2.name);
2548
3017
  await adapter.installSkill("caik", content);
2549
3018
  skillSpinner.stop();
2550
3019
  console.log(success("SKILL.md installed"));
@@ -2575,17 +3044,151 @@ Examples:
2575
3044
  });
2576
3045
  }
2577
3046
 
3047
+ // src/commands/upgrade.ts
3048
+ import { execSync as execSync5 } from "child_process";
3049
+
3050
+ // src/update-check.ts
3051
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync9, mkdirSync as mkdirSync9, existsSync as existsSync13 } from "fs";
3052
+ import { join as join8 } from "path";
3053
+ import { homedir as homedir7 } from "os";
3054
+ import chalk2 from "chalk";
3055
+ var PKG_NAME = "@caik.dev/cli";
3056
+ var CACHE_PATH = join8(homedir7(), ".caik", "update-check.json");
3057
+ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
3058
+ function readCache() {
3059
+ try {
3060
+ const raw = readFileSync8(CACHE_PATH, "utf-8");
3061
+ return JSON.parse(raw);
3062
+ } catch {
3063
+ return null;
3064
+ }
3065
+ }
3066
+ function writeCache(entry) {
3067
+ const dir = join8(homedir7(), ".caik");
3068
+ if (!existsSync13(dir)) {
3069
+ mkdirSync9(dir, { recursive: true });
3070
+ }
3071
+ writeFileSync9(CACHE_PATH, JSON.stringify(entry) + "\n", "utf-8");
3072
+ }
3073
+ function getCurrentVersion() {
3074
+ try {
3075
+ const pkgPath = join8(
3076
+ new URL(".", import.meta.url).pathname,
3077
+ "..",
3078
+ "package.json"
3079
+ );
3080
+ const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
3081
+ return pkg.version;
3082
+ } catch {
3083
+ return "0.0.0";
3084
+ }
3085
+ }
3086
+ async function fetchLatestVersion() {
3087
+ try {
3088
+ const controller = new AbortController();
3089
+ const timeout = setTimeout(() => controller.abort(), 3e3);
3090
+ const res = await fetch(
3091
+ `https://registry.npmjs.org/${PKG_NAME}/latest`,
3092
+ { signal: controller.signal }
3093
+ );
3094
+ clearTimeout(timeout);
3095
+ if (!res.ok) return null;
3096
+ const data = await res.json();
3097
+ return data.version ?? null;
3098
+ } catch {
3099
+ return null;
3100
+ }
3101
+ }
3102
+ function isNewer(latest, current) {
3103
+ const a = latest.split(".").map(Number);
3104
+ const b = current.split(".").map(Number);
3105
+ for (let i = 0; i < 3; i++) {
3106
+ if ((a[i] ?? 0) > (b[i] ?? 0)) return true;
3107
+ if ((a[i] ?? 0) < (b[i] ?? 0)) return false;
3108
+ }
3109
+ return false;
3110
+ }
3111
+ async function checkForUpdate(opts = {}) {
3112
+ const current = getCurrentVersion();
3113
+ if (!opts.force) {
3114
+ const cache = readCache();
3115
+ if (cache && Date.now() - cache.checkedAt < CHECK_INTERVAL_MS) {
3116
+ return {
3117
+ current,
3118
+ latest: cache.latest,
3119
+ updateAvailable: isNewer(cache.latest, current)
3120
+ };
3121
+ }
3122
+ }
3123
+ const latest = await fetchLatestVersion();
3124
+ if (latest) {
3125
+ writeCache({ latest, checkedAt: Date.now() });
3126
+ }
3127
+ return {
3128
+ current,
3129
+ latest,
3130
+ updateAvailable: latest ? isNewer(latest, current) : false
3131
+ };
3132
+ }
3133
+ async function printUpdateHint() {
3134
+ try {
3135
+ const info2 = await checkForUpdate();
3136
+ if (info2.updateAvailable && info2.latest) {
3137
+ console.error(
3138
+ chalk2.yellow(
3139
+ `
3140
+ Update available: ${info2.current} \u2192 ${info2.latest} Run ${chalk2.bold("caik upgrade")} to update
3141
+ `
3142
+ )
3143
+ );
3144
+ }
3145
+ } catch {
3146
+ }
3147
+ }
3148
+
3149
+ // src/commands/upgrade.ts
3150
+ function registerUpgradeCommand(program2) {
3151
+ program2.command("upgrade").description("Upgrade the CAIK CLI to the latest version").action(async () => {
3152
+ const spinner = createSpinner("Checking for updates...");
3153
+ spinner.start();
3154
+ const update = await checkForUpdate({ force: true });
3155
+ if (!update.latest) {
3156
+ spinner.stop();
3157
+ console.log(error("Could not determine the latest version. Check your network connection."));
3158
+ return;
3159
+ }
3160
+ if (!update.updateAvailable) {
3161
+ spinner.stop();
3162
+ console.log(success(`Already on the latest version (${update.current})`));
3163
+ return;
3164
+ }
3165
+ spinner.text = `Upgrading ${update.current} \u2192 ${update.latest}...`;
3166
+ try {
3167
+ execSync5("npm install -g @caik.dev/cli@latest", {
3168
+ stdio: "pipe",
3169
+ timeout: 6e4
3170
+ });
3171
+ spinner.stop();
3172
+ console.log(success(`Upgraded to ${update.latest}`));
3173
+ } catch (err) {
3174
+ spinner.stop();
3175
+ console.log(error("Upgrade failed. Try manually:"));
3176
+ console.log(info("npm install -g @caik.dev/cli@latest"));
3177
+ }
3178
+ });
3179
+ }
3180
+
2578
3181
  // src/index.ts
2579
3182
  var __filename = fileURLToPath(import.meta.url);
2580
3183
  var __dirname = dirname5(__filename);
2581
3184
  var version = "0.0.1";
2582
3185
  try {
2583
- const pkgPath = join8(__dirname, "..", "package.json");
2584
- const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
3186
+ const pkgPath = join9(__dirname, "..", "package.json");
3187
+ const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
2585
3188
  version = pkg.version;
2586
3189
  } catch {
2587
3190
  }
2588
- var program = new Command().name("caik").description("CAIK \u2014 Collective AI Knowledge CLI\nSearch, install, and publish AI artifacts from your terminal.").version(version, "-v, --version").option("--api-url <url>", "API base URL (default: https://caik.dev)").option("--api-key <key>", "API key for authentication").option("--verbose", "Show detailed output including API calls").option("--json", "Output raw JSON (for scripting)");
3191
+ var program = new Command().name("caik").description("CAIK \u2014 Collective AI Knowledge CLI\nSearch, install, and publish AI artifacts from your terminal.").version(version, "-v, --version").option("--api-url <url>", "API base URL (default: https://www.caik.dev)").option("--api-key <key>", "API key for authentication").option("--verbose", "Show detailed output including API calls").option("--json", "Output raw JSON (for scripting)");
2589
3192
  registerSearchCommand(program);
2590
3193
  registerInstallCommand(program);
2591
3194
  registerInitCommand(program);
@@ -2601,15 +3204,17 @@ registerUninstallCommand(program);
2601
3204
  registerKarmaCommand(program);
2602
3205
  registerHookCommand(program);
2603
3206
  registerSetupCommand(program);
3207
+ registerUpgradeCommand(program);
2604
3208
  program.exitOverride();
2605
3209
  async function main() {
3210
+ const updateHint = printUpdateHint();
2606
3211
  try {
2607
3212
  await program.parseAsync(process.argv);
2608
3213
  } catch (err) {
2609
3214
  if (err instanceof CaikError) {
2610
- console.error(chalk2.red(`\u2717 ${err.message}`));
3215
+ console.error(chalk3.red(`\u2717 ${err.message}`));
2611
3216
  if (err.suggestion) {
2612
- console.error(chalk2.dim(` ${err.suggestion}`));
3217
+ console.error(chalk3.dim(` ${err.suggestion}`));
2613
3218
  }
2614
3219
  process.exit(err.exitCode);
2615
3220
  }
@@ -2617,8 +3222,9 @@ async function main() {
2617
3222
  const exitCode = err.exitCode;
2618
3223
  process.exit(exitCode);
2619
3224
  }
2620
- console.error(chalk2.red(`\u2717 Unexpected error: ${err instanceof Error ? err.message : String(err)}`));
3225
+ console.error(chalk3.red(`\u2717 Unexpected error: ${err instanceof Error ? err.message : String(err)}`));
2621
3226
  process.exit(1);
2622
3227
  }
3228
+ await updateHint;
2623
3229
  }
2624
3230
  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.4",
4
4
  "type": "module",
5
5
  "description": "CAIK CLI — Search, install, and publish AI artifacts from your terminal",
6
6
  "keywords": [