caik-cli 0.1.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +8 -7
  2. package/dist/api-6OX4ICXN.js +9 -0
  3. package/dist/auto-improve-skills-2COKTU5C.js +8 -0
  4. package/dist/autoresearch-Y7WW6L4O.js +24 -0
  5. package/dist/chunk-2YHUDOJL.js +54 -0
  6. package/dist/chunk-3TXNZINH.js +775 -0
  7. package/dist/chunk-5MHNQAV4.js +317 -0
  8. package/dist/chunk-7AIZTHHZ.js +152 -0
  9. package/dist/chunk-D4IM3YRX.js +166 -0
  10. package/dist/chunk-DJJHS7KK.js +62 -0
  11. package/dist/chunk-DKZBQRR3.js +91 -0
  12. package/dist/chunk-FLSHJZLC.js +613 -0
  13. package/dist/chunk-H2ZKCXMJ.js +202 -0
  14. package/dist/chunk-ILMOSMD3.js +83 -0
  15. package/dist/chunk-KYTHKH6V.js +79 -0
  16. package/dist/chunk-LTKHLRM4.js +272 -0
  17. package/dist/chunk-T32AEP3O.js +146 -0
  18. package/dist/chunk-T73Z5UMA.js +14437 -0
  19. package/dist/chunk-TFKT7V7H.js +1545 -0
  20. package/dist/chunk-US4CYDNS.js +524 -0
  21. package/dist/chunk-ZLRN7Q7C.js +27 -0
  22. package/dist/claude-code-6DF4YARB.js +8 -0
  23. package/dist/config-CS7734SA.js +24 -0
  24. package/dist/correction-classifier-TLPKRNLI.js +93 -0
  25. package/dist/cursor-Z4XXDCAM.js +8 -0
  26. package/dist/daemon/autoresearch-2MAEM2YI.js +272 -0
  27. package/dist/daemon/chunk-545XA5CB.js +77 -0
  28. package/dist/daemon/chunk-HEYFAUHL.js +90 -0
  29. package/dist/daemon/chunk-MLKGABMK.js +9 -0
  30. package/dist/daemon/chunk-NJICGNCK.js +150 -0
  31. package/dist/daemon/chunk-OD5NUFH2.js +181 -0
  32. package/dist/daemon/chunk-SM2FSXIP.js +60 -0
  33. package/dist/daemon/chunk-UMDJFPN6.js +163 -0
  34. package/dist/daemon/config-F7HE3JRY.js +23 -0
  35. package/dist/daemon/db-QEXVVTAL.js +15 -0
  36. package/dist/daemon/eval-generator-OR2FAYLB.js +316 -0
  37. package/dist/daemon/improver-TGEK6MPE.js +186 -0
  38. package/dist/daemon/llm-FUJ2TBYT.js +11 -0
  39. package/dist/daemon/nudge-detector-NFRHWZY6.js +140 -0
  40. package/dist/daemon/platform-7N3LQDIB.js +16381 -0
  41. package/dist/daemon/registry-FI4GTO3H.js +20 -0
  42. package/dist/daemon/server.js +356 -0
  43. package/dist/daemon/trace-store-T7XFGQSX.js +19 -0
  44. package/dist/daemon-UXYMG46V.js +85 -0
  45. package/dist/db-TLNRIXLK.js +18 -0
  46. package/dist/eval-generator-GGMRPO3K.js +21 -0
  47. package/dist/eval-runner-EF4K6T5Y.js +15 -0
  48. package/dist/index.js +8033 -568
  49. package/dist/llm-3UUZX6PX.js +12 -0
  50. package/dist/platform-52NREMBS.js +33 -0
  51. package/dist/repo-installer-K6ADOW3E.js +25 -0
  52. package/dist/setup-P744STZE.js +16 -0
  53. package/dist/test-loop-Y7QQE55P.js +127 -0
  54. package/dist/trace-store-FVLMNNDK.js +20 -0
  55. package/package.json +9 -3
@@ -0,0 +1,1545 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ ClaudeCodeAdapter
4
+ } from "./chunk-US4CYDNS.js";
5
+ import {
6
+ CLI_NPX_PACKAGE,
7
+ CursorAdapter
8
+ } from "./chunk-T73Z5UMA.js";
9
+
10
+ // src/platform/detect.ts
11
+ import { existsSync } from "fs";
12
+ import { execSync } from "child_process";
13
+ import { join } from "path";
14
+ import { homedir } from "os";
15
+
16
+ // src/platform/platform-configs.ts
17
+ var platformConfigs = {
18
+ windsurf: {
19
+ label: "Windsurf",
20
+ tier: "cli-mcp",
21
+ homeDirs: [".windsurf"],
22
+ cwdDirs: [".windsurf"],
23
+ binaries: [],
24
+ mcpConfigPath: "~/.windsurf/mcp.json",
25
+ mcpConfigKey: "mcpServers",
26
+ skillsDir: null,
27
+ localSkillsDir: ".agents/skills/caik",
28
+ capabilities: { hooks: false, mcp: true, skills: true }
29
+ },
30
+ copilot: {
31
+ label: "GitHub Copilot",
32
+ tier: "cli-mcp",
33
+ homeDirs: [],
34
+ cwdDirs: [".github/copilot"],
35
+ binaries: [],
36
+ mcpConfigPath: ".github/copilot/mcp.json",
37
+ mcpConfigKey: "mcpServers",
38
+ skillsDir: null,
39
+ localSkillsDir: ".github/copilot/skills/caik",
40
+ capabilities: { hooks: false, mcp: true, skills: true }
41
+ },
42
+ "gemini-cli": {
43
+ label: "Gemini CLI",
44
+ tier: "cli-mcp",
45
+ homeDirs: [".gemini"],
46
+ cwdDirs: [".gemini"],
47
+ binaries: ["gemini"],
48
+ mcpConfigPath: "~/.gemini/settings.json",
49
+ mcpConfigKey: "mcpServers",
50
+ skillsDir: "~/.gemini/skills/caik",
51
+ localSkillsDir: ".gemini/skills/caik",
52
+ capabilities: { hooks: false, mcp: true, skills: true }
53
+ },
54
+ aider: {
55
+ label: "Aider",
56
+ tier: "cli-mcp",
57
+ homeDirs: [".aider"],
58
+ cwdDirs: [],
59
+ binaries: ["aider"],
60
+ mcpConfigPath: "~/.aider.conf.yml",
61
+ mcpConfigKey: "manual",
62
+ skillsDir: null,
63
+ localSkillsDir: null,
64
+ capabilities: { hooks: false, mcp: true, skills: false }
65
+ },
66
+ cline: {
67
+ label: "Cline",
68
+ tier: "cli-mcp",
69
+ homeDirs: [".cline"],
70
+ cwdDirs: [".cline"],
71
+ binaries: [],
72
+ mcpConfigPath: "~/cline_mcp_settings.json",
73
+ mcpConfigKey: "mcpServers",
74
+ skillsDir: "~/.cline/skills/caik",
75
+ localSkillsDir: ".cline/skills/caik",
76
+ capabilities: { hooks: false, mcp: true, skills: true }
77
+ },
78
+ "roo-code": {
79
+ label: "Roo Code",
80
+ tier: "cli-mcp",
81
+ homeDirs: [".roo"],
82
+ cwdDirs: [".roo"],
83
+ binaries: [],
84
+ mcpConfigPath: "~/.roo/mcp.json",
85
+ mcpConfigKey: "mcpServers",
86
+ skillsDir: "~/.roo/skills/caik",
87
+ localSkillsDir: ".roo/skills/caik",
88
+ capabilities: { hooks: false, mcp: true, skills: true }
89
+ },
90
+ "amazon-q": {
91
+ label: "Amazon Q",
92
+ tier: "cli-mcp",
93
+ homeDirs: [".aws/amazonq"],
94
+ cwdDirs: [],
95
+ binaries: ["q"],
96
+ mcpConfigPath: "~/.aws/amazonq/mcp.json",
97
+ mcpConfigKey: "mcpServers",
98
+ skillsDir: null,
99
+ localSkillsDir: null,
100
+ capabilities: { hooks: false, mcp: true, skills: false }
101
+ },
102
+ devin: {
103
+ label: "Devin",
104
+ tier: "cli-mcp",
105
+ homeDirs: [],
106
+ cwdDirs: [],
107
+ binaries: ["devin"],
108
+ mcpConfigPath: "~/devin.json",
109
+ mcpConfigKey: "mcpServers",
110
+ skillsDir: null,
111
+ localSkillsDir: null,
112
+ capabilities: { hooks: false, mcp: true, skills: false }
113
+ },
114
+ goose: {
115
+ label: "Goose",
116
+ tier: "cli-mcp",
117
+ homeDirs: [".goose"],
118
+ cwdDirs: [".goose"],
119
+ binaries: ["goose"],
120
+ mcpConfigPath: "~/.goose/config.yaml",
121
+ mcpConfigKey: "manual",
122
+ skillsDir: null,
123
+ localSkillsDir: null,
124
+ capabilities: { hooks: false, mcp: true, skills: false }
125
+ },
126
+ warp: {
127
+ label: "Warp",
128
+ tier: "cli-mcp",
129
+ homeDirs: [".warp"],
130
+ cwdDirs: [".warp"],
131
+ binaries: ["warp"],
132
+ mcpConfigPath: "~/.warp/mcp.json",
133
+ mcpConfigKey: "mcpServers",
134
+ skillsDir: null,
135
+ localSkillsDir: null,
136
+ capabilities: { hooks: false, mcp: true, skills: false }
137
+ },
138
+ zed: {
139
+ label: "Zed",
140
+ tier: "cli-mcp",
141
+ homeDirs: [".config/zed"],
142
+ cwdDirs: [".zed"],
143
+ binaries: ["zed"],
144
+ mcpConfigPath: "~/.config/zed/settings.json",
145
+ mcpConfigKey: "manual",
146
+ skillsDir: null,
147
+ localSkillsDir: null,
148
+ capabilities: { hooks: false, mcp: true, skills: false }
149
+ },
150
+ augment: {
151
+ label: "Augment",
152
+ tier: "cli-mcp",
153
+ homeDirs: [".augment"],
154
+ cwdDirs: [".augment"],
155
+ binaries: [],
156
+ mcpConfigPath: "~/.augment/mcp.json",
157
+ mcpConfigKey: "mcpServers",
158
+ skillsDir: null,
159
+ localSkillsDir: null,
160
+ capabilities: { hooks: false, mcp: true, skills: false }
161
+ },
162
+ replit: {
163
+ label: "Replit",
164
+ tier: "cli-mcp",
165
+ homeDirs: [".replit"],
166
+ cwdDirs: [".replit"],
167
+ binaries: [],
168
+ mcpConfigPath: "~/.replit/mcp.json",
169
+ mcpConfigKey: "mcpServers",
170
+ skillsDir: null,
171
+ localSkillsDir: null,
172
+ capabilities: { hooks: false, mcp: true, skills: false }
173
+ },
174
+ // Generic fallbacks — no auto-detection, manual config only
175
+ "generic-mcp": {
176
+ label: "Generic MCP",
177
+ tier: "cli-mcp",
178
+ homeDirs: [],
179
+ cwdDirs: [],
180
+ binaries: [],
181
+ mcpConfigPath: "",
182
+ mcpConfigKey: "mcpServers",
183
+ skillsDir: null,
184
+ localSkillsDir: null,
185
+ capabilities: { hooks: false, mcp: true, skills: false }
186
+ },
187
+ "generic-agent": {
188
+ label: "Generic Agent",
189
+ tier: "cli-mcp",
190
+ homeDirs: [],
191
+ cwdDirs: [],
192
+ binaries: [],
193
+ mcpConfigPath: "",
194
+ mcpConfigKey: "mcpServers",
195
+ skillsDir: null,
196
+ localSkillsDir: null,
197
+ capabilities: { hooks: false, mcp: true, skills: false }
198
+ },
199
+ "generic-ide": {
200
+ label: "Generic IDE",
201
+ tier: "cli-mcp",
202
+ homeDirs: [],
203
+ cwdDirs: [],
204
+ binaries: [],
205
+ mcpConfigPath: "",
206
+ mcpConfigKey: "mcpServers",
207
+ skillsDir: null,
208
+ localSkillsDir: null,
209
+ capabilities: { hooks: false, mcp: true, skills: false }
210
+ },
211
+ generic: {
212
+ label: "Generic",
213
+ tier: "cli-mcp",
214
+ homeDirs: [],
215
+ cwdDirs: [],
216
+ binaries: [],
217
+ mcpConfigPath: "",
218
+ mcpConfigKey: "mcpServers",
219
+ skillsDir: null,
220
+ localSkillsDir: null,
221
+ capabilities: { hooks: false, mcp: true, skills: false }
222
+ }
223
+ };
224
+
225
+ // src/platform/detect.ts
226
+ function commandExists(cmd) {
227
+ try {
228
+ execSync(`which ${cmd}`, { stdio: "pipe" });
229
+ return true;
230
+ } catch {
231
+ return false;
232
+ }
233
+ }
234
+ function detectClaudeCode() {
235
+ const home = homedir();
236
+ const claudeDir = join(home, ".claude");
237
+ if (!existsSync(claudeDir)) return null;
238
+ const hasSettings = existsSync(join(claudeDir, "settings.json"));
239
+ const hasMcpConfig = existsSync(join(claudeDir, "mcp.json"));
240
+ const hasAnyMarker = hasSettings || hasMcpConfig || existsSync(join(claudeDir, "statsig"));
241
+ if (!hasAnyMarker) return null;
242
+ const configPaths = [];
243
+ if (hasSettings) configPaths.push(join(claudeDir, "settings.json"));
244
+ if (hasMcpConfig) configPaths.push(join(claudeDir, "mcp.json"));
245
+ return {
246
+ name: "claude-code",
247
+ tier: "full-plugin",
248
+ configPaths,
249
+ capabilities: { hooks: true, mcp: true, skills: true }
250
+ };
251
+ }
252
+ function detectOpenClaw() {
253
+ const home = homedir();
254
+ const openclawDir = join(home, ".openclaw");
255
+ const cwdConfig = join(process.cwd(), "openclaw.json");
256
+ const inPath = commandExists("openclaw");
257
+ if (!existsSync(openclawDir) && !existsSync(cwdConfig) && !inPath) return null;
258
+ const configPaths = [];
259
+ if (existsSync(cwdConfig)) configPaths.push(cwdConfig);
260
+ if (existsSync(join(openclawDir, "openclaw.json"))) {
261
+ configPaths.push(join(openclawDir, "openclaw.json"));
262
+ }
263
+ return {
264
+ name: "openclaw",
265
+ tier: "hook-enabled",
266
+ configPaths,
267
+ capabilities: { hooks: true, mcp: true, skills: true }
268
+ };
269
+ }
270
+ function detectCursor() {
271
+ const home = homedir();
272
+ const cursorDir = join(home, ".cursor");
273
+ const cwdCursor = join(process.cwd(), ".cursor");
274
+ if (!existsSync(cursorDir) && !existsSync(cwdCursor)) return null;
275
+ const configPaths = [];
276
+ if (existsSync(join(cwdCursor, "mcp.json"))) {
277
+ configPaths.push(join(cwdCursor, "mcp.json"));
278
+ }
279
+ if (existsSync(join(cursorDir, "mcp.json"))) {
280
+ configPaths.push(join(cursorDir, "mcp.json"));
281
+ }
282
+ return {
283
+ name: "cursor",
284
+ tier: "hook-enabled",
285
+ configPaths,
286
+ capabilities: { hooks: true, mcp: true, skills: true }
287
+ };
288
+ }
289
+ function detectCodex() {
290
+ const home = homedir();
291
+ const codexDir = join(home, ".codex");
292
+ if (!existsSync(codexDir) && !commandExists("codex")) return null;
293
+ if (existsSync(codexDir)) {
294
+ const hasConfig = existsSync(join(codexDir, "config.toml"));
295
+ const hasHooks = existsSync(join(codexDir, "hooks.json"));
296
+ const hasMcp = existsSync(join(codexDir, "mcp.json"));
297
+ if (!hasConfig && !hasHooks && !hasMcp) {
298
+ if (!commandExists("codex")) return null;
299
+ }
300
+ const configPaths = [];
301
+ if (hasHooks) configPaths.push(join(codexDir, "hooks.json"));
302
+ if (hasMcp) configPaths.push(join(codexDir, "mcp.json"));
303
+ if (hasConfig) configPaths.push(join(codexDir, "config.toml"));
304
+ return {
305
+ name: "codex",
306
+ tier: "full-plugin",
307
+ configPaths,
308
+ capabilities: { hooks: true, mcp: true, skills: true }
309
+ };
310
+ }
311
+ return {
312
+ name: "codex",
313
+ tier: "full-plugin",
314
+ configPaths: [],
315
+ capabilities: { hooks: true, mcp: true, skills: true }
316
+ };
317
+ }
318
+ function expandHome(p) {
319
+ if (p.startsWith("~/")) return join(homedir(), p.slice(2));
320
+ if (p === "~") return homedir();
321
+ return p;
322
+ }
323
+ function createConfigDetector(name, config) {
324
+ return () => {
325
+ const home = homedir();
326
+ const cwd = process.cwd();
327
+ let found = false;
328
+ for (const dir of config.homeDirs) {
329
+ if (existsSync(join(home, dir))) {
330
+ found = true;
331
+ break;
332
+ }
333
+ }
334
+ if (!found) {
335
+ for (const dir of config.cwdDirs) {
336
+ if (existsSync(join(cwd, dir))) {
337
+ found = true;
338
+ break;
339
+ }
340
+ }
341
+ }
342
+ if (!found) {
343
+ for (const bin of config.binaries) {
344
+ if (commandExists(bin)) {
345
+ found = true;
346
+ break;
347
+ }
348
+ }
349
+ }
350
+ if (!found) return null;
351
+ const configPaths = [];
352
+ if (config.mcpConfigPath) {
353
+ const resolved = config.mcpConfigPath.startsWith("~/") ? expandHome(config.mcpConfigPath) : join(cwd, config.mcpConfigPath);
354
+ if (existsSync(resolved)) {
355
+ configPaths.push(resolved);
356
+ }
357
+ }
358
+ return {
359
+ name,
360
+ tier: config.tier,
361
+ configPaths,
362
+ capabilities: config.capabilities
363
+ };
364
+ };
365
+ }
366
+ var customDetectors = [
367
+ detectClaudeCode,
368
+ detectOpenClaw,
369
+ detectCursor,
370
+ detectCodex
371
+ ];
372
+ var genericFallbacks = /* @__PURE__ */ new Set(["generic-mcp", "generic-agent", "generic-ide", "generic"]);
373
+ var configDetectors = [];
374
+ var configDetectorMap = {};
375
+ for (const [name, config] of Object.entries(platformConfigs)) {
376
+ if (genericFallbacks.has(name)) continue;
377
+ const detector = createConfigDetector(name, config);
378
+ configDetectors.push(detector);
379
+ configDetectorMap[name] = detector;
380
+ }
381
+ var allDetectors = [
382
+ ...customDetectors,
383
+ ...configDetectors
384
+ ];
385
+ function detectPlatforms() {
386
+ const detected = [];
387
+ for (const detect of allDetectors) {
388
+ try {
389
+ const result = detect();
390
+ if (result) detected.push(result);
391
+ } catch {
392
+ }
393
+ }
394
+ return detected;
395
+ }
396
+ function detectPlatform(name) {
397
+ const customMap = {
398
+ "claude-code": detectClaudeCode,
399
+ "openclaw": detectOpenClaw,
400
+ "cursor": detectCursor,
401
+ "codex": detectCodex
402
+ };
403
+ const customDetector = customMap[name];
404
+ if (customDetector) {
405
+ try {
406
+ return customDetector();
407
+ } catch {
408
+ return null;
409
+ }
410
+ }
411
+ const configDetector = configDetectorMap[name];
412
+ if (configDetector) {
413
+ try {
414
+ return configDetector();
415
+ } catch {
416
+ return null;
417
+ }
418
+ }
419
+ return null;
420
+ }
421
+
422
+ // src/platform/openclaw.ts
423
+ import {
424
+ existsSync as existsSync2,
425
+ readFileSync,
426
+ writeFileSync,
427
+ mkdirSync,
428
+ rmSync
429
+ } from "fs";
430
+ import { execSync as execSync2 } from "child_process";
431
+ import { join as join2, dirname } from "path";
432
+ import { homedir as homedir2 } from "os";
433
+ var MCP_KEY = "caik";
434
+ var OpenClawAdapter = class {
435
+ name = "openclaw";
436
+ tier = "hook-enabled";
437
+ // --- Path helpers --------------------------------------------------------
438
+ get openclawDir() {
439
+ return join2(homedir2(), ".openclaw");
440
+ }
441
+ get managedHooksDir() {
442
+ return join2(this.openclawDir, "hooks");
443
+ }
444
+ get caikHookDir() {
445
+ return join2(this.managedHooksDir, "caik-contributions");
446
+ }
447
+ /** OpenClaw's own config — NOT where MCP servers go. */
448
+ get openclawConfigPath() {
449
+ return join2(this.openclawDir, "openclaw.json");
450
+ }
451
+ /** Project-level `.mcp.json` — where MCP servers are registered. */
452
+ get mcpConfigPath() {
453
+ return join2(process.cwd(), ".mcp.json");
454
+ }
455
+ /** Skills directory following OpenClaw convention. */
456
+ get skillsBaseDir() {
457
+ return join2(homedir2(), ".agents", "skills");
458
+ }
459
+ get skillDir() {
460
+ return join2(this.skillsBaseDir, "caik");
461
+ }
462
+ get skillLockPath() {
463
+ return join2(homedir2(), ".agents", ".skill-lock.json");
464
+ }
465
+ // --- JSON helpers --------------------------------------------------------
466
+ readJson(path) {
467
+ if (!existsSync2(path)) return {};
468
+ try {
469
+ const raw = readFileSync(path, "utf-8");
470
+ const parsed = JSON.parse(raw);
471
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
472
+ return parsed;
473
+ }
474
+ return {};
475
+ } catch {
476
+ return {};
477
+ }
478
+ }
479
+ writeJson(path, data) {
480
+ const dir = dirname(path);
481
+ if (!existsSync2(dir)) {
482
+ mkdirSync(dir, { recursive: true });
483
+ }
484
+ writeFileSync(path, JSON.stringify(data, null, 2) + "\n", "utf-8");
485
+ }
486
+ readSkillLock() {
487
+ if (!existsSync2(this.skillLockPath)) {
488
+ return { version: 3, skills: {} };
489
+ }
490
+ try {
491
+ const raw = readFileSync(this.skillLockPath, "utf-8");
492
+ const parsed = JSON.parse(raw);
493
+ return {
494
+ version: parsed.version ?? 3,
495
+ skills: parsed.skills ?? {}
496
+ };
497
+ } catch {
498
+ return { version: 3, skills: {} };
499
+ }
500
+ }
501
+ writeSkillLock(lock) {
502
+ const dir = dirname(this.skillLockPath);
503
+ if (!existsSync2(dir)) {
504
+ mkdirSync(dir, { recursive: true });
505
+ }
506
+ writeFileSync(
507
+ this.skillLockPath,
508
+ JSON.stringify(lock, null, 2) + "\n",
509
+ "utf-8"
510
+ );
511
+ }
512
+ /** Check if `openclaw` binary is in PATH. */
513
+ isInPath() {
514
+ try {
515
+ execSync2("which openclaw", { stdio: "ignore" });
516
+ return true;
517
+ } catch {
518
+ return false;
519
+ }
520
+ }
521
+ // --- PlatformAdapter implementation --------------------------------------
522
+ async detect() {
523
+ const hasDir = existsSync2(this.openclawDir);
524
+ const inPath = this.isInPath();
525
+ const hasCwdConfig = existsSync2(join2(process.cwd(), "openclaw.json"));
526
+ if (!hasDir && !inPath && !hasCwdConfig) return null;
527
+ const configPaths = [];
528
+ if (hasCwdConfig) {
529
+ configPaths.push(join2(process.cwd(), "openclaw.json"));
530
+ }
531
+ if (existsSync2(this.openclawConfigPath)) {
532
+ configPaths.push(this.openclawConfigPath);
533
+ }
534
+ if (existsSync2(this.mcpConfigPath)) {
535
+ configPaths.push(this.mcpConfigPath);
536
+ }
537
+ return {
538
+ name: this.name,
539
+ tier: this.tier,
540
+ configPaths,
541
+ capabilities: { hooks: true, mcp: true, skills: true }
542
+ };
543
+ }
544
+ async registerMcp(serverConfig) {
545
+ const config = this.readJson(this.mcpConfigPath);
546
+ const servers = config.mcpServers ?? {};
547
+ servers[MCP_KEY] = {
548
+ type: "stdio",
549
+ command: serverConfig.command,
550
+ args: serverConfig.args,
551
+ ...serverConfig.env && Object.keys(serverConfig.env).length > 0 ? { env: serverConfig.env } : {}
552
+ };
553
+ config.mcpServers = servers;
554
+ this.writeJson(this.mcpConfigPath, config);
555
+ }
556
+ async unregisterMcp() {
557
+ if (!existsSync2(this.mcpConfigPath)) return;
558
+ const config = this.readJson(this.mcpConfigPath);
559
+ const servers = config.mcpServers;
560
+ if (!servers || !(MCP_KEY in servers)) return;
561
+ delete servers[MCP_KEY];
562
+ config.mcpServers = servers;
563
+ this.writeJson(this.mcpConfigPath, config);
564
+ }
565
+ get pluginDir() {
566
+ return join2(homedir2(), ".openclaw", "plugins", "caik");
567
+ }
568
+ async registerHooks(_hookConfig) {
569
+ if (!existsSync2(this.caikHookDir)) {
570
+ mkdirSync(this.caikHookDir, { recursive: true });
571
+ }
572
+ writeFileSync(
573
+ join2(this.caikHookDir, "HOOK.md"),
574
+ HOOK_MD_CONTENT,
575
+ "utf-8"
576
+ );
577
+ writeFileSync(
578
+ join2(this.caikHookDir, "handler.js"),
579
+ HANDLER_JS_CONTENT,
580
+ "utf-8"
581
+ );
582
+ await this.installPlugin();
583
+ }
584
+ async installPlugin() {
585
+ try {
586
+ if (!existsSync2(this.pluginDir)) {
587
+ mkdirSync(this.pluginDir, { recursive: true });
588
+ }
589
+ const thisDir = dirname(new URL(import.meta.url).pathname);
590
+ const pluginSources = [
591
+ join2(thisDir, "..", "..", "openclaw-plugin", "dist"),
592
+ // monorepo sibling
593
+ join2(thisDir, "..", "..", "..", "openclaw-plugin", "dist")
594
+ // npm installed
595
+ ];
596
+ let pluginDistDir = null;
597
+ for (const src of pluginSources) {
598
+ if (existsSync2(join2(src, "index.js"))) {
599
+ pluginDistDir = src;
600
+ break;
601
+ }
602
+ }
603
+ const manifestSources = [
604
+ join2(thisDir, "..", "..", "openclaw-plugin", "openclaw.plugin.json"),
605
+ join2(thisDir, "..", "..", "..", "openclaw-plugin", "openclaw.plugin.json")
606
+ ];
607
+ for (const src of manifestSources) {
608
+ if (existsSync2(src)) {
609
+ const manifest = readFileSync(src, "utf-8");
610
+ writeFileSync(join2(this.pluginDir, "openclaw.plugin.json"), manifest, "utf-8");
611
+ break;
612
+ }
613
+ }
614
+ if (pluginDistDir) {
615
+ const indexJs = readFileSync(join2(pluginDistDir, "index.js"), "utf-8");
616
+ writeFileSync(join2(this.pluginDir, "index.js"), indexJs, "utf-8");
617
+ }
618
+ const config = this.readJson(this.openclawConfigPath);
619
+ if (!config.plugins) config.plugins = {};
620
+ if (!config.plugins.entries) config.plugins.entries = {};
621
+ config.plugins.entries.caik = { enabled: true };
622
+ if (!config.plugins.load) config.plugins.load = {};
623
+ if (!config.plugins.load.paths) config.plugins.load.paths = [];
624
+ const paths = config.plugins.load.paths;
625
+ if (!paths.includes(this.pluginDir)) {
626
+ paths.push(this.pluginDir);
627
+ }
628
+ this.writeJson(this.openclawConfigPath, config);
629
+ } catch {
630
+ }
631
+ }
632
+ async unregisterHooks() {
633
+ if (existsSync2(this.caikHookDir)) {
634
+ try {
635
+ rmSync(this.caikHookDir, { recursive: true, force: true });
636
+ } catch {
637
+ }
638
+ }
639
+ }
640
+ async installSkill(_slug, content, files) {
641
+ if (!existsSync2(this.skillDir)) {
642
+ mkdirSync(this.skillDir, { recursive: true });
643
+ }
644
+ writeFileSync(join2(this.skillDir, "SKILL.md"), content, "utf-8");
645
+ if (files) {
646
+ for (const file of files) {
647
+ const filePath = join2(this.skillDir, file.path);
648
+ const fileDir = dirname(filePath);
649
+ if (!existsSync2(fileDir)) {
650
+ mkdirSync(fileDir, { recursive: true });
651
+ }
652
+ writeFileSync(filePath, file.content, "utf-8");
653
+ }
654
+ }
655
+ const lock = this.readSkillLock();
656
+ const now = (/* @__PURE__ */ new Date()).toISOString();
657
+ const existing = lock.skills["caik"];
658
+ lock.skills["caik"] = {
659
+ source: CLI_NPX_PACKAGE,
660
+ sourceType: "local",
661
+ sourceUrl: "",
662
+ skillPath: "skills/caik/SKILL.md",
663
+ skillFolderHash: "",
664
+ installedAt: existing?.installedAt ?? now,
665
+ updatedAt: now
666
+ };
667
+ this.writeSkillLock(lock);
668
+ }
669
+ async uninstallSkill(_slug) {
670
+ if (existsSync2(this.skillDir)) {
671
+ try {
672
+ rmSync(this.skillDir, { recursive: true, force: true });
673
+ } catch {
674
+ }
675
+ }
676
+ const lock = this.readSkillLock();
677
+ if ("caik" in lock.skills) {
678
+ delete lock.skills["caik"];
679
+ this.writeSkillLock(lock);
680
+ }
681
+ }
682
+ getConfigPath() {
683
+ return this.openclawConfigPath;
684
+ }
685
+ async readConfig() {
686
+ return this.readJson(this.openclawConfigPath);
687
+ }
688
+ async isRegistered() {
689
+ if (!existsSync2(this.mcpConfigPath)) return false;
690
+ const config = this.readJson(this.mcpConfigPath);
691
+ const servers = config.mcpServers;
692
+ return !!servers && MCP_KEY in servers;
693
+ }
694
+ };
695
+ var HOOK_MD_CONTENT = `---
696
+ name: caik-contributions
697
+ description: "Track artifact usage to build your CAIK contribution level and community karma"
698
+ metadata:
699
+ {
700
+ "openclaw":
701
+ {
702
+ "emoji": "\u{1F4E6}",
703
+ "events": ["session_start", "session_end", "after_tool_call", "agent_end", "gateway_start"],
704
+ "install": [{ "id": "local", "kind": "local", "label": "CAIK CLI hook pack" }],
705
+ },
706
+ }
707
+ ---
708
+
709
+ # CAIK Contribution Tracking
710
+
711
+ Reports session lifecycle and tool usage events to the CAIK API to build your contribution level and community karma.
712
+
713
+ ## What It Does
714
+
715
+ - **Session start** (\`session_start\`): Computes stack fingerprint, starts daemon, records session
716
+ - **Tool use** (\`after_tool_call\`): Buffers tool execution events with success/failure signals
717
+ - **Session end** (\`session_end\`, \`agent_end\`): Computes session shape, flushes events, records session end
718
+ - **Gateway start** (\`gateway_start\`): Clears session state on gateway restart
719
+
720
+ ## Privacy
721
+
722
+ - Only sends: event type, platform name, tool name, success/failure, timestamp
723
+ - At Collective level: adds stack fingerprint, session shape, agent model
724
+ - No code, file contents, or conversation data is transmitted
725
+ - Contribution tracking can be disabled: \`caik config set contributionLevel none\`
726
+
727
+ ## Configuration
728
+
729
+ Set \`CAIK_API_URL\` and \`CAIK_API_KEY\` environment variables, or configure via \`caik init --auth\`.
730
+ `;
731
+ var HANDLER_JS_CONTENT = `/**
732
+ * CAIK contribution tracking hook for OpenClaw.
733
+ *
734
+ * Uses OpenClaw lifecycle hooks (session_start, after_tool_call, session_end, agent_end,
735
+ * gateway_start) for rich event data. Delegates to the CLI for session-start/end
736
+ * (full processing: fingerprint, daemon, metrics) and buffers tool events in-process
737
+ * for speed.
738
+ *
739
+ * Fire-and-forget \u2014 never blocks the agent, never throws.
740
+ */
741
+ import { spawn } from "node:child_process";
742
+ import { readFileSync, writeFileSync, appendFileSync, existsSync, mkdirSync } from "node:fs";
743
+ import { join, dirname } from "node:path";
744
+ import { homedir } from "node:os";
745
+ import { fileURLToPath } from "node:url";
746
+
747
+ const __filename = fileURLToPath(import.meta.url);
748
+ const __dirname = dirname(__filename);
749
+
750
+ const CAIK_DIR = join(homedir(), ".caik");
751
+ const PENDING_PATH = join(CAIK_DIR, "pending-events.json");
752
+ const CONFIG_PATH = join(CAIK_DIR, "config.json");
753
+ const DAEMON_PORT = process.env.CAIK_DAEMON_PORT ?? "37778";
754
+
755
+ // \u2500\u2500\u2500 Discovery logging \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
756
+ // Logs first event of each type to learn OpenClaw's event field names.
757
+ // Remove once field names are confirmed.
758
+ const seenEventTypes = new Set();
759
+ function logEventShape(event, ctx) {
760
+ const name = event?.hookName ?? event?.type ?? "unknown";
761
+ if (seenEventTypes.has(name)) return;
762
+ seenEventTypes.add(name);
763
+ try {
764
+ if (!existsSync(CAIK_DIR)) mkdirSync(CAIK_DIR, { recursive: true });
765
+ const summary = {};
766
+ if (ctx) {
767
+ for (const [k, v] of Object.entries(ctx)) {
768
+ if (typeof v === "string" && v.length > 200) summary[k] = v.slice(0, 200) + "...";
769
+ else if (typeof v === "object" && v !== null) summary[k] = "[" + typeof v + ": " + Object.keys(v).join(",") + "]";
770
+ else summary[k] = v;
771
+ }
772
+ }
773
+ appendFileSync(
774
+ join(CAIK_DIR, "openclaw-events-captured.jsonl"),
775
+ JSON.stringify({
776
+ hookName: name,
777
+ eventKeys: Object.keys(event ?? {}),
778
+ ctxKeys: Object.keys(ctx ?? {}),
779
+ event,
780
+ ctxSummary: summary,
781
+ }) + "\\n",
782
+ );
783
+ } catch { /* ignore */ }
784
+ }
785
+
786
+ // \u2500\u2500\u2500 Disk-backed session tracking \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
787
+ // Persisted to disk because OpenClaw runs each hook invocation as a separate
788
+ // process, so in-memory state is lost between events.
789
+ const SESSION_STATE_PATH = join(CAIK_DIR, "openclaw-sessions.json");
790
+
791
+ function loadSessionState() {
792
+ try { return existsSync(SESSION_STATE_PATH) ? JSON.parse(readFileSync(SESSION_STATE_PATH, "utf-8")) : {}; }
793
+ catch { return {}; }
794
+ }
795
+
796
+ function saveSessionState(state) {
797
+ try {
798
+ if (!existsSync(CAIK_DIR)) mkdirSync(CAIK_DIR, { recursive: true });
799
+ writeFileSync(SESSION_STATE_PATH, JSON.stringify(state), "utf-8");
800
+ } catch { /* ignore */ }
801
+ }
802
+
803
+ function getOrCreateSessionId(event) {
804
+ const key = event?.agentId ?? event?.runId ?? event?.sessionId ?? "default";
805
+ const state = loadSessionState();
806
+ if (!state[key]) {
807
+ state[key] = "caik-oc-" + Date.now() + "-" + Math.random().toString(36).slice(2, 8);
808
+ saveSessionState(state);
809
+ }
810
+ return state[key];
811
+ }
812
+
813
+ function removeSessionId(sessionId) {
814
+ const state = loadSessionState();
815
+ for (const k of Object.keys(state)) {
816
+ if (state[k] === sessionId) delete state[k];
817
+ }
818
+ saveSessionState(state);
819
+ }
820
+
821
+ function clearAllSessions() {
822
+ try { writeFileSync(SESSION_STATE_PATH, "{}", "utf-8"); }
823
+ catch { /* ignore */ }
824
+ }
825
+
826
+ // \u2500\u2500\u2500 Config \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
827
+ function loadConfig() {
828
+ try { return JSON.parse(readFileSync(CONFIG_PATH, "utf-8")); }
829
+ catch { return {}; }
830
+ }
831
+
832
+ function shouldSend() {
833
+ const cfg = loadConfig();
834
+ if (cfg.contributions === false || cfg.contribution === false) return false;
835
+ return cfg.contributionLevel !== "none";
836
+ }
837
+
838
+ // \u2500\u2500\u2500 CLI delegation (session-start/end only) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
839
+ function findCli() {
840
+ try {
841
+ const local = join(__dirname, "..", "..", "..", "cli", "dist", "index.js");
842
+ if (existsSync(local)) return [process.execPath, local];
843
+ } catch { /* ignore */ }
844
+ return ["npx", "-y", "${CLI_NPX_PACKAGE}"];
845
+ }
846
+
847
+ function runCliHook(subcommand, stdinData) {
848
+ return new Promise((resolve) => {
849
+ const [cmd, ...args] = findCli();
850
+ const proc = spawn(cmd, [...args, "hook", subcommand], {
851
+ stdio: ["pipe", "inherit", "inherit"],
852
+ timeout: 8000,
853
+ env: { ...process.env, CAIK_OPENCLAW: "1" },
854
+ });
855
+ if (stdinData) {
856
+ proc.stdin.write(JSON.stringify(stdinData));
857
+ proc.stdin.end();
858
+ }
859
+ proc.on("close", resolve);
860
+ proc.on("error", () => resolve(1));
861
+ });
862
+ }
863
+
864
+ // \u2500\u2500\u2500 Fast in-process event buffering \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
865
+ function bufferEvent(event) {
866
+ try {
867
+ if (!existsSync(CAIK_DIR)) mkdirSync(CAIK_DIR, { recursive: true });
868
+ let events = [];
869
+ try { events = JSON.parse(readFileSync(PENDING_PATH, "utf-8")); } catch { /* empty */ }
870
+ if (!Array.isArray(events)) events = [];
871
+ events.push(event);
872
+ writeFileSync(PENDING_PATH, JSON.stringify(events), "utf-8");
873
+ } catch { /* ignore */ }
874
+ }
875
+
876
+ // \u2500\u2500\u2500 Daemon observation posting \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
877
+ function postToDaemon(obs) {
878
+ try {
879
+ fetch("http://127.0.0.1:" + DAEMON_PORT + "/observations", {
880
+ method: "POST",
881
+ headers: { "Content-Type": "application/json" },
882
+ body: JSON.stringify(obs),
883
+ signal: AbortSignal.timeout(1000),
884
+ }).catch(() => {});
885
+ } catch { /* ignore */ }
886
+ }
887
+
888
+ // \u2500\u2500\u2500 Main handler \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
889
+ const handler = async (event, ctx) => {
890
+ try {
891
+ logEventShape(event, ctx);
892
+ if (!shouldSend()) return;
893
+ // If the native plugin is active, skip hook pack to avoid double-counting
894
+ if (process.env.CAIK_PLUGIN_ACTIVE === "1") return;
895
+
896
+ const hookName = event?.hookName ?? event?.type ?? "";
897
+ const timestamp = new Date().toISOString();
898
+
899
+ switch (hookName) {
900
+ case "session_start": {
901
+ const sessionId = getOrCreateSessionId(event);
902
+ await runCliHook("session-start", {
903
+ hook_event_name: "session-start",
904
+ session_id: sessionId,
905
+ model: event?.model ?? ctx?.model ?? undefined,
906
+ cwd: event?.cwd ?? ctx?.cwd ?? ctx?.workspace ?? process.cwd(),
907
+ });
908
+ break;
909
+ }
910
+
911
+ case "after_tool_call": {
912
+ const sessionId = getOrCreateSessionId(event);
913
+ const toolName = event?.toolName ?? event?.tool ?? event?.name ?? event?.action ?? "unknown";
914
+ const success = event?.error == null && event?.success !== false;
915
+
916
+ bufferEvent({
917
+ type: "tool_use",
918
+ platform: "openclaw",
919
+ sessionId,
920
+ tool: toolName,
921
+ success,
922
+ timestamp,
923
+ });
924
+
925
+ postToDaemon({
926
+ sessionId,
927
+ slug: toolName,
928
+ tool: toolName,
929
+ success,
930
+ platform: "openclaw",
931
+ timestamp,
932
+ });
933
+ break;
934
+ }
935
+
936
+ case "session_end":
937
+ case "agent_end": {
938
+ const key = event?.agentId ?? event?.runId ?? event?.sessionId ?? "default";
939
+ const state = loadSessionState();
940
+ const sessionId = state[key] ?? "unknown";
941
+ await runCliHook("session-end", {
942
+ hook_event_name: "session-end",
943
+ session_id: sessionId,
944
+ reason: hookName,
945
+ cwd: event?.cwd ?? ctx?.cwd ?? ctx?.workspace ?? process.cwd(),
946
+ });
947
+ removeSessionId(sessionId);
948
+ break;
949
+ }
950
+
951
+ case "gateway_start": {
952
+ clearAllSessions();
953
+ break;
954
+ }
955
+ }
956
+ } catch { /* never fail */ }
957
+ };
958
+
959
+ export default handler;
960
+ `;
961
+
962
+ // src/platform/codex.ts
963
+ import {
964
+ existsSync as existsSync3,
965
+ readFileSync as readFileSync2,
966
+ writeFileSync as writeFileSync2,
967
+ mkdirSync as mkdirSync2,
968
+ readdirSync,
969
+ rmSync as rmSync2
970
+ } from "fs";
971
+ import { execSync as execSync3 } from "child_process";
972
+ import { join as join3 } from "path";
973
+ import { homedir as homedir3 } from "os";
974
+ var MCP_KEY2 = "caik";
975
+ var PLUGIN_MANAGED_SKILLS = [
976
+ "caik",
977
+ "caik-discover",
978
+ "caik-observe",
979
+ "caik-improve",
980
+ "caik-status"
981
+ ];
982
+ var CodexAdapter = class {
983
+ name = "codex";
984
+ tier = "full-plugin";
985
+ get codexDir() {
986
+ return join3(homedir3(), ".codex");
987
+ }
988
+ get configTomlPath() {
989
+ return join3(this.codexDir, "config.toml");
990
+ }
991
+ get hooksPath() {
992
+ return join3(this.codexDir, "hooks.json");
993
+ }
994
+ get skillsDir() {
995
+ return join3(this.codexDir, "skills");
996
+ }
997
+ get localSkillsDir() {
998
+ return join3(process.cwd(), ".agents", "skills");
999
+ }
1000
+ get pluginsCacheDir() {
1001
+ return join3(this.codexDir, "plugins", "cache");
1002
+ }
1003
+ // ---------------------------------------------------------------------------
1004
+ // JSON helpers (for hooks.json)
1005
+ // ---------------------------------------------------------------------------
1006
+ readJson(path) {
1007
+ if (!existsSync3(path)) return {};
1008
+ try {
1009
+ const raw = readFileSync2(path, "utf-8");
1010
+ const parsed = JSON.parse(raw);
1011
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
1012
+ return parsed;
1013
+ }
1014
+ return {};
1015
+ } catch {
1016
+ return {};
1017
+ }
1018
+ }
1019
+ writeJson(path, data) {
1020
+ const dir = join3(path, "..");
1021
+ if (!existsSync3(dir)) {
1022
+ mkdirSync2(dir, { recursive: true });
1023
+ }
1024
+ writeFileSync2(path, JSON.stringify(data, null, 2) + "\n", "utf-8");
1025
+ }
1026
+ // ---------------------------------------------------------------------------
1027
+ // TOML helpers (for config.toml — read-only, writes go through codex CLI)
1028
+ // ---------------------------------------------------------------------------
1029
+ /**
1030
+ * Check if a key exists in config.toml (simple regex, no TOML parser needed).
1031
+ */
1032
+ configTomlHasKey(key) {
1033
+ if (!existsSync3(this.configTomlPath)) return false;
1034
+ try {
1035
+ const content = readFileSync2(this.configTomlPath, "utf-8");
1036
+ return content.includes(`[mcp_servers.${key}]`);
1037
+ } catch {
1038
+ return false;
1039
+ }
1040
+ }
1041
+ /**
1042
+ * Check if `codex` CLI is available in PATH.
1043
+ */
1044
+ codexCliAvailable() {
1045
+ try {
1046
+ execSync3("which codex", { stdio: "pipe" });
1047
+ return true;
1048
+ } catch {
1049
+ return false;
1050
+ }
1051
+ }
1052
+ /**
1053
+ * Ensure `codex_hooks = true` is set in config.toml [features] section.
1054
+ * Codex requires this feature flag for hooks to fire.
1055
+ */
1056
+ ensureHooksEnabled() {
1057
+ if (!existsSync3(this.configTomlPath)) return;
1058
+ try {
1059
+ const content = readFileSync2(this.configTomlPath, "utf-8");
1060
+ if (content.includes("codex_hooks")) return;
1061
+ if (content.includes("[features]")) {
1062
+ const updated = content.replace(
1063
+ /(\[features\]\n)/,
1064
+ "$1codex_hooks = true\n"
1065
+ );
1066
+ writeFileSync2(this.configTomlPath, updated, "utf-8");
1067
+ } else {
1068
+ writeFileSync2(
1069
+ this.configTomlPath,
1070
+ content.trimEnd() + "\n\n[features]\ncodex_hooks = true\n",
1071
+ "utf-8"
1072
+ );
1073
+ }
1074
+ } catch {
1075
+ }
1076
+ }
1077
+ // ---------------------------------------------------------------------------
1078
+ // Plugin detection
1079
+ // ---------------------------------------------------------------------------
1080
+ /**
1081
+ * Check if CAIK is installed as a Codex plugin.
1082
+ * Checks plugins cache directory and config.toml [plugins] section.
1083
+ */
1084
+ isPluginInstalled() {
1085
+ if (existsSync3(this.pluginsCacheDir)) {
1086
+ try {
1087
+ const publishers = readdirSync(this.pluginsCacheDir);
1088
+ for (const publisher of publishers) {
1089
+ const caikDir = join3(this.pluginsCacheDir, publisher, "caik");
1090
+ if (existsSync3(caikDir)) {
1091
+ return true;
1092
+ }
1093
+ }
1094
+ } catch {
1095
+ }
1096
+ }
1097
+ if (existsSync3(this.configTomlPath)) {
1098
+ try {
1099
+ const content = readFileSync2(this.configTomlPath, "utf-8");
1100
+ if (/\[plugins\.[^\]]*caik/i.test(content)) {
1101
+ return true;
1102
+ }
1103
+ } catch {
1104
+ }
1105
+ }
1106
+ return false;
1107
+ }
1108
+ // ---------------------------------------------------------------------------
1109
+ // PlatformAdapter implementation
1110
+ // ---------------------------------------------------------------------------
1111
+ async detect() {
1112
+ if (!existsSync3(this.codexDir)) return null;
1113
+ const hasHooks = existsSync3(this.hooksPath);
1114
+ const hasConfig = existsSync3(this.configTomlPath);
1115
+ if (!hasHooks && !hasConfig) return null;
1116
+ const configPaths = [];
1117
+ if (hasConfig) configPaths.push(this.configTomlPath);
1118
+ if (hasHooks) configPaths.push(this.hooksPath);
1119
+ return {
1120
+ name: this.name,
1121
+ tier: this.tier,
1122
+ configPaths,
1123
+ capabilities: { hooks: true, mcp: true, skills: true }
1124
+ };
1125
+ }
1126
+ async registerMcp(serverConfig) {
1127
+ if (this.isPluginInstalled()) return;
1128
+ const serverKey = serverConfig.key ?? MCP_KEY2;
1129
+ if (this.configTomlHasKey(serverKey)) return;
1130
+ if (this.codexCliAvailable()) {
1131
+ try {
1132
+ const args = [serverConfig.command, ...serverConfig.args];
1133
+ const envFlags = serverConfig.env ? Object.entries(serverConfig.env).map(([k, v]) => `--env ${k}=${v}`).join(" ") : "";
1134
+ const cmd = `codex mcp add ${serverKey} ${envFlags} -- ${args.join(" ")}`;
1135
+ execSync3(cmd, { stdio: "pipe", timeout: 1e4 });
1136
+ return;
1137
+ } catch {
1138
+ }
1139
+ }
1140
+ const envSection = serverConfig.env ? Object.entries(serverConfig.env).map(([k, v]) => `${k} = "${v}"`).join("\n") : "";
1141
+ const tomlSnippet = [
1142
+ `[mcp_servers.${serverKey}]`,
1143
+ `command = "${serverConfig.command}"`,
1144
+ `args = ${JSON.stringify(serverConfig.args)}`,
1145
+ ...envSection ? [`
1146
+ [mcp_servers.${serverKey}.env]`, envSection] : []
1147
+ ].join("\n");
1148
+ process.stdout.write(
1149
+ `
1150
+ Add this to ~/.codex/config.toml:
1151
+
1152
+ ${tomlSnippet}
1153
+
1154
+ Or run: codex mcp add ${serverKey} -- ${serverConfig.command} ${serverConfig.args.join(" ")}
1155
+
1156
+ `
1157
+ );
1158
+ }
1159
+ async unregisterMcp() {
1160
+ if (!this.configTomlHasKey(MCP_KEY2)) return;
1161
+ if (this.codexCliAvailable()) {
1162
+ try {
1163
+ execSync3(`codex mcp remove ${MCP_KEY2}`, { stdio: "pipe", timeout: 1e4 });
1164
+ return;
1165
+ } catch {
1166
+ }
1167
+ }
1168
+ process.stdout.write(
1169
+ `
1170
+ Remove [mcp_servers.${MCP_KEY2}] from ~/.codex/config.toml
1171
+ Or run: codex mcp remove ${MCP_KEY2}
1172
+
1173
+ `
1174
+ );
1175
+ }
1176
+ async registerHooks(hookConfig) {
1177
+ if (this.isPluginInstalled()) return;
1178
+ this.ensureHooksEnabled();
1179
+ const hooksFile = this.readJson(this.hooksPath);
1180
+ const existingHooks = hooksFile.hooks ?? {};
1181
+ for (const [event, entries] of Object.entries(hookConfig.hooks)) {
1182
+ const newEntries = Array.isArray(entries) ? entries : [entries];
1183
+ const existing = Array.isArray(existingHooks[event]) ? existingHooks[event] : [];
1184
+ const filtered = existing.filter((group) => {
1185
+ if (typeof group === "object" && group !== null) {
1186
+ const g = group;
1187
+ if (Array.isArray(g.hooks)) {
1188
+ const hasCAIK = g.hooks.some(
1189
+ (h) => typeof h.command === "string" && h.command.toLowerCase().includes("caik")
1190
+ );
1191
+ if (hasCAIK) return false;
1192
+ }
1193
+ if ("command" in g) {
1194
+ return !g.command.toLowerCase().includes("caik");
1195
+ }
1196
+ }
1197
+ return true;
1198
+ });
1199
+ const wrappedEntries = newEntries.map((entry) => {
1200
+ if (typeof entry === "object" && entry !== null) {
1201
+ const e = entry;
1202
+ const hookEntry = {
1203
+ type: "command",
1204
+ timeout: 10,
1205
+ ...e
1206
+ };
1207
+ return { hooks: [hookEntry] };
1208
+ }
1209
+ return entry;
1210
+ });
1211
+ existingHooks[event] = [...filtered, ...wrappedEntries];
1212
+ }
1213
+ hooksFile.hooks = existingHooks;
1214
+ this.writeJson(this.hooksPath, hooksFile);
1215
+ }
1216
+ async unregisterHooks() {
1217
+ if (!existsSync3(this.hooksPath)) return;
1218
+ const hooksFile = this.readJson(this.hooksPath);
1219
+ const hooks = hooksFile.hooks;
1220
+ if (!hooks) return;
1221
+ for (const event of Object.keys(hooks)) {
1222
+ const entries = hooks[event];
1223
+ if (!Array.isArray(entries)) continue;
1224
+ hooks[event] = entries.filter((group) => {
1225
+ if (typeof group === "object" && group !== null) {
1226
+ const g = group;
1227
+ if (Array.isArray(g.hooks)) {
1228
+ const hasCAIK = g.hooks.some(
1229
+ (h) => typeof h.command === "string" && h.command.includes("caik")
1230
+ );
1231
+ if (hasCAIK) return false;
1232
+ }
1233
+ if ("command" in g) {
1234
+ return !g.command.includes("caik");
1235
+ }
1236
+ }
1237
+ return true;
1238
+ });
1239
+ if (hooks[event].length === 0) {
1240
+ delete hooks[event];
1241
+ }
1242
+ }
1243
+ if (Object.keys(hooks).length === 0) {
1244
+ delete hooksFile.hooks;
1245
+ } else {
1246
+ hooksFile.hooks = hooks;
1247
+ }
1248
+ this.writeJson(this.hooksPath, hooksFile);
1249
+ }
1250
+ async installSkill(slug, content, files, local) {
1251
+ if (!local && this.isPluginInstalled() && PLUGIN_MANAGED_SKILLS.includes(slug)) {
1252
+ return;
1253
+ }
1254
+ const baseDir = local ? this.localSkillsDir : this.skillsDir;
1255
+ const skillDir = join3(baseDir, slug);
1256
+ if (!existsSync3(skillDir)) {
1257
+ mkdirSync2(skillDir, { recursive: true });
1258
+ }
1259
+ writeFileSync2(join3(skillDir, "SKILL.md"), content, "utf-8");
1260
+ if (files) {
1261
+ for (const file of files) {
1262
+ const filePath = join3(skillDir, file.path);
1263
+ const fileDir = join3(filePath, "..");
1264
+ if (!existsSync3(fileDir)) {
1265
+ mkdirSync2(fileDir, { recursive: true });
1266
+ }
1267
+ writeFileSync2(filePath, file.content, "utf-8");
1268
+ }
1269
+ }
1270
+ }
1271
+ async uninstallSkill(slug, local) {
1272
+ const baseDir = local ? this.localSkillsDir : this.skillsDir;
1273
+ const skillDir = join3(baseDir, slug);
1274
+ if (!existsSync3(skillDir)) return;
1275
+ try {
1276
+ rmSync2(skillDir, { recursive: true, force: true });
1277
+ } catch {
1278
+ }
1279
+ }
1280
+ getConfigPath() {
1281
+ return this.configTomlPath;
1282
+ }
1283
+ async readConfig() {
1284
+ if (!existsSync3(this.configTomlPath)) return {};
1285
+ try {
1286
+ const content = readFileSync2(this.configTomlPath, "utf-8");
1287
+ return { _raw: content, _format: "toml" };
1288
+ } catch {
1289
+ return {};
1290
+ }
1291
+ }
1292
+ async isRegistered() {
1293
+ if (this.isPluginInstalled()) return true;
1294
+ return this.configTomlHasKey(MCP_KEY2);
1295
+ }
1296
+ };
1297
+
1298
+ // src/platform/generic.ts
1299
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3, rmSync as rmSync3 } from "fs";
1300
+ import { execSync as execSync4 } from "child_process";
1301
+ import { join as join4, dirname as dirname2 } from "path";
1302
+ import { homedir as homedir4 } from "os";
1303
+ var MCP_KEY3 = "caik";
1304
+ function expandHome2(p) {
1305
+ if (p.startsWith("~/")) return join4(homedir4(), p.slice(2));
1306
+ if (p === "~") return homedir4();
1307
+ return p;
1308
+ }
1309
+ function commandExists2(cmd) {
1310
+ try {
1311
+ execSync4(`which ${cmd}`, { stdio: "pipe" });
1312
+ return true;
1313
+ } catch {
1314
+ return false;
1315
+ }
1316
+ }
1317
+ function readJsonFile(path) {
1318
+ try {
1319
+ if (!existsSync4(path)) return null;
1320
+ const raw = readFileSync3(path, "utf-8");
1321
+ return JSON.parse(raw);
1322
+ } catch {
1323
+ return null;
1324
+ }
1325
+ }
1326
+ function writeJsonFile(path, data) {
1327
+ mkdirSync3(dirname2(path), { recursive: true });
1328
+ writeFileSync3(path, JSON.stringify(data, null, 2) + "\n", "utf-8");
1329
+ }
1330
+ var GenericAdapter = class {
1331
+ name;
1332
+ tier;
1333
+ config;
1334
+ constructor(name, config) {
1335
+ this.name = name;
1336
+ this.config = config ?? null;
1337
+ this.tier = config?.tier ?? "cli-mcp";
1338
+ }
1339
+ // ── Detection ─────────────────────────────────────────────
1340
+ async detect() {
1341
+ if (!this.config) return null;
1342
+ const home = homedir4();
1343
+ const cwd = process.cwd();
1344
+ let found = false;
1345
+ for (const dir of this.config.homeDirs) {
1346
+ if (existsSync4(join4(home, dir))) {
1347
+ found = true;
1348
+ break;
1349
+ }
1350
+ }
1351
+ if (!found) {
1352
+ for (const dir of this.config.cwdDirs) {
1353
+ if (existsSync4(join4(cwd, dir))) {
1354
+ found = true;
1355
+ break;
1356
+ }
1357
+ }
1358
+ }
1359
+ if (!found) {
1360
+ for (const bin of this.config.binaries) {
1361
+ if (commandExists2(bin)) {
1362
+ found = true;
1363
+ break;
1364
+ }
1365
+ }
1366
+ }
1367
+ if (!found) return null;
1368
+ const configPaths = [];
1369
+ if (this.config.mcpConfigPath) {
1370
+ const resolved = this.resolveConfigPath();
1371
+ if (resolved && existsSync4(resolved)) {
1372
+ configPaths.push(resolved);
1373
+ }
1374
+ }
1375
+ return {
1376
+ name: this.name,
1377
+ tier: this.tier,
1378
+ configPaths,
1379
+ capabilities: this.config.capabilities
1380
+ };
1381
+ }
1382
+ // ── MCP Registration ──────────────────────────────────────
1383
+ async registerMcp(serverConfig) {
1384
+ const configPath = this.resolveConfigPath();
1385
+ if (!configPath || this.config?.mcpConfigKey === "manual") {
1386
+ this.printManualInstructions(serverConfig);
1387
+ return;
1388
+ }
1389
+ const config = readJsonFile(configPath) ?? {};
1390
+ const key = this.config?.mcpConfigKey ?? "mcpServers";
1391
+ const servers = config[key] ?? {};
1392
+ const serverKey = serverConfig.key ?? MCP_KEY3;
1393
+ const { key: _key, ...mcpEntry } = serverConfig;
1394
+ servers[serverKey] = mcpEntry;
1395
+ config[key] = servers;
1396
+ writeJsonFile(configPath, config);
1397
+ }
1398
+ async unregisterMcp() {
1399
+ const configPath = this.resolveConfigPath();
1400
+ if (!configPath || this.config?.mcpConfigKey === "manual") {
1401
+ process.stdout.write(
1402
+ `
1403
+ To unregister CAIK from ${this.name}, remove the "caik" entry
1404
+ from the MCP section in your configuration file.
1405
+
1406
+ `
1407
+ );
1408
+ return;
1409
+ }
1410
+ const config = readJsonFile(configPath);
1411
+ if (!config) return;
1412
+ const key = this.config?.mcpConfigKey ?? "mcpServers";
1413
+ const servers = config[key];
1414
+ if (!servers?.[MCP_KEY3]) return;
1415
+ delete servers[MCP_KEY3];
1416
+ if (Object.keys(servers).length === 0) {
1417
+ delete config[key];
1418
+ }
1419
+ writeJsonFile(configPath, config);
1420
+ }
1421
+ // ── Skills ────────────────────────────────────────────────
1422
+ async installSkill(slug, content, files, local) {
1423
+ const baseDir = this.resolveSkillsDir(local);
1424
+ if (!baseDir) return;
1425
+ const skillDir = join4(baseDir, slug);
1426
+ mkdirSync3(skillDir, { recursive: true });
1427
+ writeFileSync3(join4(skillDir, "SKILL.md"), content, "utf-8");
1428
+ if (files) {
1429
+ for (const file of files) {
1430
+ const filePath = join4(skillDir, file.path);
1431
+ mkdirSync3(dirname2(filePath), { recursive: true });
1432
+ writeFileSync3(filePath, file.content, "utf-8");
1433
+ }
1434
+ }
1435
+ }
1436
+ async uninstallSkill(slug, local) {
1437
+ const baseDir = this.resolveSkillsDir(local);
1438
+ if (!baseDir) return;
1439
+ const skillDir = join4(baseDir, slug);
1440
+ try {
1441
+ if (existsSync4(skillDir)) {
1442
+ rmSync3(skillDir, { recursive: true, force: true });
1443
+ }
1444
+ } catch {
1445
+ }
1446
+ }
1447
+ // ── Config Accessors ──────────────────────────────────────
1448
+ getConfigPath() {
1449
+ return this.resolveConfigPath() ?? "";
1450
+ }
1451
+ async readConfig() {
1452
+ const configPath = this.resolveConfigPath();
1453
+ if (!configPath) return {};
1454
+ return readJsonFile(configPath) ?? {};
1455
+ }
1456
+ async isRegistered() {
1457
+ const configPath = this.resolveConfigPath();
1458
+ if (!configPath || this.config?.mcpConfigKey === "manual") return false;
1459
+ const config = readJsonFile(configPath);
1460
+ if (!config) return false;
1461
+ const key = this.config?.mcpConfigKey ?? "mcpServers";
1462
+ const servers = config[key];
1463
+ return Boolean(servers?.[MCP_KEY3]);
1464
+ }
1465
+ // ── Private Helpers ───────────────────────────────────────
1466
+ /** Resolve the MCP config path, expanding ~ and handling cwd-relative paths */
1467
+ resolveConfigPath() {
1468
+ if (!this.config?.mcpConfigPath) return null;
1469
+ const raw = this.config.mcpConfigPath;
1470
+ if (raw.startsWith("~/") || raw.startsWith("~")) {
1471
+ return expandHome2(raw);
1472
+ }
1473
+ return join4(process.cwd(), raw);
1474
+ }
1475
+ /** Resolve the skills directory for global or local scope */
1476
+ resolveSkillsDir(local) {
1477
+ if (!this.config) return null;
1478
+ if (local && this.config.localSkillsDir) {
1479
+ return join4(process.cwd(), this.config.localSkillsDir);
1480
+ }
1481
+ if (!local && this.config.skillsDir) {
1482
+ return expandHome2(this.config.skillsDir);
1483
+ }
1484
+ if (local && this.config.skillsDir) {
1485
+ return expandHome2(this.config.skillsDir);
1486
+ }
1487
+ if (!local && this.config.localSkillsDir) {
1488
+ return join4(process.cwd(), this.config.localSkillsDir);
1489
+ }
1490
+ return null;
1491
+ }
1492
+ /** Print manual MCP setup instructions for platforms with non-JSON configs */
1493
+ printManualInstructions(serverConfig) {
1494
+ const snippet = {
1495
+ mcpServers: {
1496
+ caik: {
1497
+ command: serverConfig.command,
1498
+ args: serverConfig.args,
1499
+ ...serverConfig.env && Object.keys(serverConfig.env).length > 0 ? { env: serverConfig.env } : {}
1500
+ }
1501
+ }
1502
+ };
1503
+ const json = JSON.stringify(snippet, null, 2);
1504
+ const configFile = this.config?.mcpConfigPath ? `Config file: ${this.config.mcpConfigPath}` : `Refer to your platform's documentation for the correct config file location.`;
1505
+ process.stdout.write(
1506
+ `
1507
+ CAIK MCP server configuration for ${this.config?.label ?? this.name}:
1508
+
1509
+ Add the following to your MCP configuration:
1510
+
1511
+ ${json}
1512
+
1513
+ ${configFile}
1514
+
1515
+ `
1516
+ );
1517
+ }
1518
+ };
1519
+
1520
+ // src/platform/index.ts
1521
+ var customAdapters = {
1522
+ "claude-code": () => new ClaudeCodeAdapter(),
1523
+ "openclaw": () => new OpenClawAdapter(),
1524
+ "cursor": () => new CursorAdapter(),
1525
+ "codex": () => new CodexAdapter()
1526
+ };
1527
+ function getPlatformAdapter(name) {
1528
+ const custom = customAdapters[name];
1529
+ if (custom) return custom();
1530
+ const config = platformConfigs[name];
1531
+ return new GenericAdapter(name, config);
1532
+ }
1533
+ function getDefaultMcpConfig() {
1534
+ return {
1535
+ command: "npx",
1536
+ args: ["-y", "@caik.dev/mcp"]
1537
+ };
1538
+ }
1539
+
1540
+ export {
1541
+ detectPlatforms,
1542
+ detectPlatform,
1543
+ getPlatformAdapter,
1544
+ getDefaultMcpConfig
1545
+ };