@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.
- package/dist/index.js +690 -85
- 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
|
|
5
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
|
-
import { dirname as dirname5, join as
|
|
8
|
-
import
|
|
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
|
-
|
|
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,
|
|
462
|
+
function removeRegistryEntry(slug, platform2) {
|
|
457
463
|
const registry = readRegistry();
|
|
458
464
|
const idx = registry.entries.findIndex(
|
|
459
|
-
(e) => e.slug === slug && e.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,
|
|
472
|
+
function findRegistryEntry(slug, platform2) {
|
|
467
473
|
const registry = readRegistry();
|
|
468
474
|
return registry.entries.find(
|
|
469
|
-
(e) => e.slug === slug && (!
|
|
475
|
+
(e) => e.slug === slug && (!platform2 || e.platform === platform2)
|
|
470
476
|
) ?? null;
|
|
471
477
|
}
|
|
472
|
-
function listRegistryEntries(
|
|
478
|
+
function listRegistryEntries(platform2) {
|
|
473
479
|
const registry = readRegistry();
|
|
474
|
-
if (!
|
|
475
|
-
return registry.entries.filter((e) => e.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
|
-
|
|
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
|
-
|
|
890
|
-
|
|
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
|
-
|
|
895
|
-
|
|
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 >
|
|
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
|
|
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
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
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
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
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
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
##
|
|
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
|
|
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(
|
|
2371
|
-
switch (
|
|
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(
|
|
2414
|
-
if (
|
|
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 (
|
|
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
|
|
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
|
|
2512
|
-
console.log(heading(`Configuring ${
|
|
2976
|
+
for (const platform2 of selected) {
|
|
2977
|
+
console.log(heading(`Configuring ${platform2.name}...`));
|
|
2513
2978
|
console.log("\u2500".repeat(40));
|
|
2514
|
-
const adapter = getPlatformAdapter(
|
|
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 &&
|
|
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(
|
|
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 && !
|
|
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 &&
|
|
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(
|
|
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 =
|
|
2584
|
-
const pkg = JSON.parse(
|
|
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(
|
|
3214
|
+
console.error(chalk3.red(`\u2717 ${err.message}`));
|
|
2611
3215
|
if (err.suggestion) {
|
|
2612
|
-
console.error(
|
|
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(
|
|
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();
|