@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.
- package/dist/index.js +693 -87
- 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 {
|
|
@@ -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
|
-
|
|
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://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 >
|
|
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,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 (
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
##
|
|
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
|
|
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(
|
|
2371
|
-
switch (
|
|
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(
|
|
2414
|
-
if (
|
|
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 (
|
|
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
|
|
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
|
|
2512
|
-
console.log(heading(`Configuring ${
|
|
2977
|
+
for (const platform2 of selected) {
|
|
2978
|
+
console.log(heading(`Configuring ${platform2.name}...`));
|
|
2513
2979
|
console.log("\u2500".repeat(40));
|
|
2514
|
-
const adapter = getPlatformAdapter(
|
|
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 &&
|
|
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(
|
|
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 && !
|
|
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 &&
|
|
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(
|
|
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 =
|
|
2584
|
-
const pkg = JSON.parse(
|
|
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(
|
|
3215
|
+
console.error(chalk3.red(`\u2717 ${err.message}`));
|
|
2611
3216
|
if (err.suggestion) {
|
|
2612
|
-
console.error(
|
|
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(
|
|
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();
|