@caik.dev/cli 0.1.0 → 0.1.2

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 (3) hide show
  1. package/README.md +92 -0
  2. package/dist/index.js +1857 -49
  3. package/package.json +21 -2
package/dist/index.js CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
- import { readFileSync as readFileSync3 } from "fs";
5
+ import { readFileSync as readFileSync8 } from "fs";
6
6
  import { fileURLToPath } from "url";
7
- import { dirname as dirname2, join as join2 } from "path";
7
+ import { dirname as dirname5, join as join8 } from "path";
8
8
  import chalk2 from "chalk";
9
9
 
10
10
  // src/errors.ts
@@ -186,6 +186,9 @@ function error(msg) {
186
186
  function info(msg) {
187
187
  return chalk.cyan(`\u2192 ${msg}`);
188
188
  }
189
+ function warn(msg) {
190
+ return chalk.yellow(`\u26A0 ${msg}`);
191
+ }
189
192
  function dim(msg) {
190
193
  return chalk.dim(msg);
191
194
  }
@@ -291,17 +294,908 @@ ${result.total} result${result.total !== 1 ? "s" : ""} found.`);
291
294
  }
292
295
 
293
296
  // src/commands/install.ts
297
+ import { existsSync as existsSync7 } from "fs";
298
+ import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync6 } from "fs";
299
+ import { dirname as dirname3, resolve } from "path";
300
+ import { execSync as execSync3 } from "child_process";
301
+ import { createInterface } from "readline/promises";
302
+
303
+ // src/platform/detect.ts
294
304
  import { existsSync as existsSync2 } from "fs";
295
- import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
296
- import { dirname, resolve } from "path";
297
305
  import { execSync } from "child_process";
298
- import { createInterface } from "readline/promises";
299
- function detectPlatform() {
300
- if (existsSync2(".claude") || existsSync2("CLAUDE.md")) return "claude";
301
- if (existsSync2(".cursor")) return "cursor";
302
- if (existsSync2("node_modules") || existsSync2("package.json")) return "node";
303
- return void 0;
306
+ import { join as join2 } from "path";
307
+ import { homedir as homedir2 } from "os";
308
+ function commandExists(cmd) {
309
+ try {
310
+ execSync(`which ${cmd}`, { stdio: "pipe" });
311
+ return true;
312
+ } catch {
313
+ return false;
314
+ }
315
+ }
316
+ function detectClaudeCode() {
317
+ const home = homedir2();
318
+ const claudeDir = join2(home, ".claude");
319
+ if (!existsSync2(claudeDir)) return null;
320
+ const hasSettings = existsSync2(join2(claudeDir, "settings.json"));
321
+ const hasMcpConfig = existsSync2(join2(claudeDir, "mcp.json"));
322
+ const hasAnyMarker = hasSettings || hasMcpConfig || existsSync2(join2(claudeDir, "statsig"));
323
+ if (!hasAnyMarker) return null;
324
+ const configPaths = [];
325
+ if (hasSettings) configPaths.push(join2(claudeDir, "settings.json"));
326
+ if (hasMcpConfig) configPaths.push(join2(claudeDir, "mcp.json"));
327
+ return {
328
+ name: "claude-code",
329
+ tier: "full-plugin",
330
+ configPaths,
331
+ capabilities: { hooks: true, mcp: true, skills: true }
332
+ };
333
+ }
334
+ function detectOpenClaw() {
335
+ const home = homedir2();
336
+ const openclawDir = join2(home, ".openclaw");
337
+ const cwdConfig = join2(process.cwd(), "openclaw.json");
338
+ if (!existsSync2(openclawDir) && !existsSync2(cwdConfig)) return null;
339
+ const configPaths = [];
340
+ if (existsSync2(cwdConfig)) configPaths.push(cwdConfig);
341
+ if (existsSync2(join2(openclawDir, "openclaw.json"))) {
342
+ configPaths.push(join2(openclawDir, "openclaw.json"));
343
+ }
344
+ return {
345
+ name: "openclaw",
346
+ tier: "hook-enabled",
347
+ configPaths,
348
+ capabilities: { hooks: true, mcp: true, skills: true }
349
+ };
350
+ }
351
+ function detectCursor() {
352
+ const home = homedir2();
353
+ const cursorDir = join2(home, ".cursor");
354
+ const cwdCursor = join2(process.cwd(), ".cursor");
355
+ if (!existsSync2(cursorDir) && !existsSync2(cwdCursor)) return null;
356
+ const configPaths = [];
357
+ if (existsSync2(join2(cwdCursor, "mcp.json"))) {
358
+ configPaths.push(join2(cwdCursor, "mcp.json"));
359
+ }
360
+ if (existsSync2(join2(cursorDir, "mcp.json"))) {
361
+ configPaths.push(join2(cursorDir, "mcp.json"));
362
+ }
363
+ return {
364
+ name: "cursor",
365
+ tier: "hook-enabled",
366
+ configPaths,
367
+ capabilities: { hooks: true, mcp: true, skills: true }
368
+ };
369
+ }
370
+ function detectCodex() {
371
+ if (!commandExists("codex")) return null;
372
+ return {
373
+ name: "codex",
374
+ tier: "cli-mcp",
375
+ configPaths: [],
376
+ capabilities: { hooks: false, mcp: true, skills: false }
377
+ };
378
+ }
379
+ function detectWindsurf() {
380
+ const home = homedir2();
381
+ const windsurfDir = join2(home, ".windsurf");
382
+ const cwdWindsurf = join2(process.cwd(), ".windsurf");
383
+ if (!existsSync2(windsurfDir) && !existsSync2(cwdWindsurf)) return null;
384
+ const configPaths = [];
385
+ if (existsSync2(join2(cwdWindsurf, "mcp.json"))) {
386
+ configPaths.push(join2(cwdWindsurf, "mcp.json"));
387
+ }
388
+ return {
389
+ name: "windsurf",
390
+ tier: "cli-mcp",
391
+ configPaths,
392
+ capabilities: { hooks: false, mcp: true, skills: false }
393
+ };
394
+ }
395
+ var detectors = [
396
+ detectClaudeCode,
397
+ detectOpenClaw,
398
+ detectCursor,
399
+ detectCodex,
400
+ detectWindsurf
401
+ ];
402
+ function detectPlatforms() {
403
+ const detected = [];
404
+ for (const detect of detectors) {
405
+ try {
406
+ const result = detect();
407
+ if (result) detected.push(result);
408
+ } catch {
409
+ }
410
+ }
411
+ return detected;
412
+ }
413
+
414
+ // src/platform/registry.ts
415
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2, unlinkSync } from "fs";
416
+ import { join as join3 } from "path";
417
+ var REGISTRY_VERSION = 1;
418
+ function getRegistryPath() {
419
+ return join3(getConfigDir(), "registry.json");
420
+ }
421
+ function emptyRegistry() {
422
+ return { version: REGISTRY_VERSION, entries: [] };
423
+ }
424
+ function readRegistry() {
425
+ const path = getRegistryPath();
426
+ if (!existsSync3(path)) return emptyRegistry();
427
+ try {
428
+ const raw = readFileSync2(path, "utf-8");
429
+ const parsed = JSON.parse(raw);
430
+ if (!parsed.entries || !Array.isArray(parsed.entries)) return emptyRegistry();
431
+ return parsed;
432
+ } catch {
433
+ return emptyRegistry();
434
+ }
435
+ }
436
+ function writeRegistry(registry) {
437
+ const dir = getConfigDir();
438
+ if (!existsSync3(dir)) {
439
+ mkdirSync2(dir, { recursive: true, mode: 448 });
440
+ }
441
+ const path = getRegistryPath();
442
+ writeFileSync2(path, JSON.stringify(registry, null, 2) + "\n", "utf-8");
443
+ }
444
+ function upsertRegistryEntry(entry) {
445
+ const registry = readRegistry();
446
+ const idx = registry.entries.findIndex(
447
+ (e) => e.slug === entry.slug && e.platform === entry.platform
448
+ );
449
+ if (idx >= 0) {
450
+ registry.entries[idx] = entry;
451
+ } else {
452
+ registry.entries.push(entry);
453
+ }
454
+ writeRegistry(registry);
455
+ }
456
+ function removeRegistryEntry(slug, platform) {
457
+ const registry = readRegistry();
458
+ const idx = registry.entries.findIndex(
459
+ (e) => e.slug === slug && e.platform === platform
460
+ );
461
+ if (idx < 0) return null;
462
+ const [removed] = registry.entries.splice(idx, 1);
463
+ writeRegistry(registry);
464
+ return removed;
465
+ }
466
+ function findRegistryEntry(slug, platform) {
467
+ const registry = readRegistry();
468
+ return registry.entries.find(
469
+ (e) => e.slug === slug && (!platform || e.platform === platform)
470
+ ) ?? null;
471
+ }
472
+ function listRegistryEntries(platform) {
473
+ const registry = readRegistry();
474
+ if (!platform) return registry.entries;
475
+ return registry.entries.filter((e) => e.platform === platform);
476
+ }
477
+ function cleanupFiles(entry) {
478
+ const failed = [];
479
+ for (const file of entry.files) {
480
+ try {
481
+ if (existsSync3(file)) {
482
+ unlinkSync(file);
483
+ }
484
+ } catch {
485
+ failed.push(file);
486
+ }
487
+ }
488
+ return failed;
489
+ }
490
+
491
+ // src/platform/claude-code.ts
492
+ import {
493
+ existsSync as existsSync4,
494
+ readFileSync as readFileSync3,
495
+ writeFileSync as writeFileSync3,
496
+ mkdirSync as mkdirSync3,
497
+ readdirSync,
498
+ rmSync
499
+ } from "fs";
500
+ import { join as join4 } from "path";
501
+ import { homedir as homedir3 } from "os";
502
+ var MCP_KEY = "caik";
503
+ var ClaudeCodeAdapter = class {
504
+ name = "claude-code";
505
+ tier = "full-plugin";
506
+ get claudeDir() {
507
+ return join4(homedir3(), ".claude");
508
+ }
509
+ get mcpPath() {
510
+ return join4(this.claudeDir, "mcp.json");
511
+ }
512
+ get settingsPath() {
513
+ return join4(this.claudeDir, "settings.json");
514
+ }
515
+ get skillsDir() {
516
+ return join4(this.claudeDir, "skills", "caik");
517
+ }
518
+ get pluginsCacheDir() {
519
+ return join4(this.claudeDir, "plugins", "cache");
520
+ }
521
+ // ---------------------------------------------------------------------------
522
+ // JSON helpers
523
+ // ---------------------------------------------------------------------------
524
+ readJson(path) {
525
+ if (!existsSync4(path)) return {};
526
+ try {
527
+ const raw = readFileSync3(path, "utf-8");
528
+ const parsed = JSON.parse(raw);
529
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
530
+ return parsed;
531
+ }
532
+ return {};
533
+ } catch {
534
+ return {};
535
+ }
536
+ }
537
+ writeJson(path, data) {
538
+ const dir = join4(path, "..");
539
+ if (!existsSync4(dir)) {
540
+ mkdirSync3(dir, { recursive: true });
541
+ }
542
+ writeFileSync3(path, JSON.stringify(data, null, 2) + "\n", "utf-8");
543
+ }
544
+ // ---------------------------------------------------------------------------
545
+ // Plugin detection
546
+ // ---------------------------------------------------------------------------
547
+ /**
548
+ * Check if CAIK is installed as a Claude Code plugin.
549
+ * Looks for "caik" in enabledPlugins in settings.json, or for a caik
550
+ * directory in the plugins cache.
551
+ */
552
+ isPluginInstalled() {
553
+ const settings = this.readJson(this.settingsPath);
554
+ const enabledPlugins = settings.enabledPlugins;
555
+ if (enabledPlugins) {
556
+ for (const key of Object.keys(enabledPlugins)) {
557
+ if (key.toLowerCase().includes("caik") && enabledPlugins[key] !== false) {
558
+ return true;
559
+ }
560
+ }
561
+ }
562
+ if (existsSync4(this.pluginsCacheDir)) {
563
+ try {
564
+ const publishers = readdirSync(this.pluginsCacheDir);
565
+ for (const publisher of publishers) {
566
+ const publisherDir = join4(this.pluginsCacheDir, publisher);
567
+ const caikDir = join4(publisherDir, "caik");
568
+ if (existsSync4(caikDir)) {
569
+ return true;
570
+ }
571
+ }
572
+ } catch {
573
+ }
574
+ }
575
+ return false;
576
+ }
577
+ /**
578
+ * Returns the current install method:
579
+ * - "plugin" if CAIK plugin is installed via marketplace
580
+ * - "manual" if CAIK is registered in mcp.json manually
581
+ * - "none" if not installed
582
+ */
583
+ getInstallMethod() {
584
+ if (this.isPluginInstalled()) return "plugin";
585
+ if (existsSync4(this.mcpPath)) {
586
+ const config = this.readJson(this.mcpPath);
587
+ const servers = config.mcpServers;
588
+ if (servers && MCP_KEY in servers) return "manual";
589
+ }
590
+ return "none";
591
+ }
592
+ // ---------------------------------------------------------------------------
593
+ // PlatformAdapter implementation
594
+ // ---------------------------------------------------------------------------
595
+ async detect() {
596
+ if (!existsSync4(this.claudeDir)) return null;
597
+ const hasSettings = existsSync4(this.settingsPath);
598
+ const hasMcp = existsSync4(this.mcpPath);
599
+ const hasStatsig = existsSync4(join4(this.claudeDir, "statsig"));
600
+ if (!hasSettings && !hasMcp && !hasStatsig) return null;
601
+ const configPaths = [];
602
+ if (hasSettings) configPaths.push(this.settingsPath);
603
+ if (hasMcp) configPaths.push(this.mcpPath);
604
+ return {
605
+ name: this.name,
606
+ tier: this.tier,
607
+ configPaths,
608
+ capabilities: { hooks: true, mcp: true, skills: true }
609
+ };
610
+ }
611
+ async registerMcp(serverConfig) {
612
+ if (this.isPluginInstalled()) {
613
+ console.log(
614
+ "CAIK plugin is installed \u2014 MCP server is managed by the plugin."
615
+ );
616
+ return;
617
+ }
618
+ const config = this.readJson(this.mcpPath);
619
+ const servers = config.mcpServers ?? {};
620
+ servers[MCP_KEY] = {
621
+ command: serverConfig.command,
622
+ args: serverConfig.args,
623
+ ...serverConfig.env && Object.keys(serverConfig.env).length > 0 ? { env: serverConfig.env } : {}
624
+ };
625
+ config.mcpServers = servers;
626
+ this.writeJson(this.mcpPath, config);
627
+ }
628
+ async unregisterMcp() {
629
+ if (!existsSync4(this.mcpPath)) return;
630
+ const config = this.readJson(this.mcpPath);
631
+ const servers = config.mcpServers;
632
+ if (!servers || !(MCP_KEY in servers)) return;
633
+ delete servers[MCP_KEY];
634
+ config.mcpServers = servers;
635
+ this.writeJson(this.mcpPath, config);
636
+ }
637
+ async registerHooks(hookConfig) {
638
+ if (this.isPluginInstalled()) {
639
+ console.log(
640
+ "CAIK plugin is installed \u2014 hooks are managed by the plugin."
641
+ );
642
+ return;
643
+ }
644
+ const settings = this.readJson(this.settingsPath);
645
+ const existingHooks = settings.hooks ?? {};
646
+ for (const [event, entries] of Object.entries(hookConfig.hooks)) {
647
+ const newEntries = Array.isArray(entries) ? entries : [entries];
648
+ const existing = Array.isArray(existingHooks[event]) ? existingHooks[event] : [];
649
+ const filtered = existing.filter((group) => {
650
+ if (typeof group === "object" && group !== null) {
651
+ const g = group;
652
+ if (Array.isArray(g.hooks)) {
653
+ const hasCAIK = g.hooks.some(
654
+ (h) => typeof h.command === "string" && h.command.includes("caik")
655
+ );
656
+ if (hasCAIK) return false;
657
+ }
658
+ if ("command" in g) {
659
+ return !g.command.includes("caik");
660
+ }
661
+ }
662
+ return true;
663
+ });
664
+ const wrappedEntries = newEntries.map((entry) => {
665
+ if (typeof entry === "object" && entry !== null) {
666
+ const e = entry;
667
+ const hookEntry = {
668
+ type: "command",
669
+ timeout: 10,
670
+ ...e
671
+ };
672
+ return { hooks: [hookEntry] };
673
+ }
674
+ return entry;
675
+ });
676
+ existingHooks[event] = [...filtered, ...wrappedEntries];
677
+ }
678
+ settings.hooks = existingHooks;
679
+ this.writeJson(this.settingsPath, settings);
680
+ }
681
+ async unregisterHooks() {
682
+ if (!existsSync4(this.settingsPath)) return;
683
+ const settings = this.readJson(this.settingsPath);
684
+ const hooks = settings.hooks;
685
+ if (!hooks) return;
686
+ for (const event of Object.keys(hooks)) {
687
+ const entries = hooks[event];
688
+ if (!Array.isArray(entries)) continue;
689
+ hooks[event] = entries.filter((group) => {
690
+ if (typeof group === "object" && group !== null) {
691
+ const g = group;
692
+ if (Array.isArray(g.hooks)) {
693
+ const hasCAIK = g.hooks.some(
694
+ (h) => typeof h.command === "string" && h.command.includes("caik")
695
+ );
696
+ if (hasCAIK) return false;
697
+ }
698
+ if ("command" in g) {
699
+ return !g.command.includes("caik");
700
+ }
701
+ }
702
+ return true;
703
+ });
704
+ if (hooks[event].length === 0) {
705
+ delete hooks[event];
706
+ }
707
+ }
708
+ if (Object.keys(hooks).length === 0) {
709
+ delete settings.hooks;
710
+ } else {
711
+ settings.hooks = hooks;
712
+ }
713
+ this.writeJson(this.settingsPath, settings);
714
+ }
715
+ async installSkill(slug, content, files) {
716
+ const skillDir = join4(this.skillsDir, slug);
717
+ if (!existsSync4(skillDir)) {
718
+ mkdirSync3(skillDir, { recursive: true });
719
+ }
720
+ writeFileSync3(join4(skillDir, "SKILL.md"), content, "utf-8");
721
+ if (files) {
722
+ for (const file of files) {
723
+ const filePath = join4(skillDir, file.path);
724
+ const fileDir = join4(filePath, "..");
725
+ if (!existsSync4(fileDir)) {
726
+ mkdirSync3(fileDir, { recursive: true });
727
+ }
728
+ writeFileSync3(filePath, file.content, "utf-8");
729
+ }
730
+ }
731
+ }
732
+ async uninstallSkill(slug) {
733
+ const skillDir = join4(this.skillsDir, slug);
734
+ if (!existsSync4(skillDir)) return;
735
+ try {
736
+ rmSync(skillDir, { recursive: true, force: true });
737
+ } catch {
738
+ }
739
+ }
740
+ getConfigPath() {
741
+ return this.mcpPath;
742
+ }
743
+ async readConfig() {
744
+ return this.readJson(this.mcpPath);
745
+ }
746
+ async isRegistered() {
747
+ if (this.isPluginInstalled()) return true;
748
+ if (!existsSync4(this.mcpPath)) return false;
749
+ const config = this.readJson(this.mcpPath);
750
+ const servers = config.mcpServers;
751
+ return !!servers && MCP_KEY in servers;
752
+ }
753
+ };
754
+
755
+ // src/platform/openclaw.ts
756
+ import {
757
+ existsSync as existsSync5,
758
+ readFileSync as readFileSync4,
759
+ writeFileSync as writeFileSync4,
760
+ mkdirSync as mkdirSync4,
761
+ rmSync as rmSync2
762
+ } from "fs";
763
+ import { execSync as execSync2 } from "child_process";
764
+ import { join as join5, dirname } from "path";
765
+ import { homedir as homedir4 } from "os";
766
+ var MCP_KEY2 = "caik";
767
+ var OpenClawAdapter = class {
768
+ name = "openclaw";
769
+ tier = "hook-enabled";
770
+ // --- Path helpers --------------------------------------------------------
771
+ get openclawDir() {
772
+ return join5(homedir4(), ".openclaw");
773
+ }
774
+ /** OpenClaw's own config — NOT where MCP servers go. */
775
+ get openclawConfigPath() {
776
+ return join5(this.openclawDir, "openclaw.json");
777
+ }
778
+ /** Project-level `.mcp.json` — where MCP servers are registered. */
779
+ get mcpConfigPath() {
780
+ return join5(process.cwd(), ".mcp.json");
781
+ }
782
+ /** Skills directory following OpenClaw convention. */
783
+ get skillsBaseDir() {
784
+ return join5(homedir4(), ".agents", "skills");
785
+ }
786
+ get skillDir() {
787
+ return join5(this.skillsBaseDir, "caik");
788
+ }
789
+ get skillLockPath() {
790
+ return join5(homedir4(), ".agents", ".skill-lock.json");
791
+ }
792
+ // --- JSON helpers --------------------------------------------------------
793
+ readJson(path) {
794
+ if (!existsSync5(path)) return {};
795
+ try {
796
+ const raw = readFileSync4(path, "utf-8");
797
+ const parsed = JSON.parse(raw);
798
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
799
+ return parsed;
800
+ }
801
+ return {};
802
+ } catch {
803
+ return {};
804
+ }
805
+ }
806
+ writeJson(path, data) {
807
+ const dir = dirname(path);
808
+ if (!existsSync5(dir)) {
809
+ mkdirSync4(dir, { recursive: true });
810
+ }
811
+ writeFileSync4(path, JSON.stringify(data, null, 2) + "\n", "utf-8");
812
+ }
813
+ readSkillLock() {
814
+ if (!existsSync5(this.skillLockPath)) {
815
+ return { version: 3, skills: {} };
816
+ }
817
+ try {
818
+ const raw = readFileSync4(this.skillLockPath, "utf-8");
819
+ const parsed = JSON.parse(raw);
820
+ return {
821
+ version: parsed.version ?? 3,
822
+ skills: parsed.skills ?? {}
823
+ };
824
+ } catch {
825
+ return { version: 3, skills: {} };
826
+ }
827
+ }
828
+ writeSkillLock(lock) {
829
+ const dir = dirname(this.skillLockPath);
830
+ if (!existsSync5(dir)) {
831
+ mkdirSync4(dir, { recursive: true });
832
+ }
833
+ writeFileSync4(
834
+ this.skillLockPath,
835
+ JSON.stringify(lock, null, 2) + "\n",
836
+ "utf-8"
837
+ );
838
+ }
839
+ /** Check if `openclaw` binary is in PATH. */
840
+ isInPath() {
841
+ try {
842
+ execSync2("which openclaw", { stdio: "ignore" });
843
+ return true;
844
+ } catch {
845
+ return false;
846
+ }
847
+ }
848
+ // --- PlatformAdapter implementation --------------------------------------
849
+ async detect() {
850
+ const hasDir = existsSync5(this.openclawDir);
851
+ const inPath = this.isInPath();
852
+ if (!hasDir && !inPath) return null;
853
+ const configPaths = [];
854
+ if (existsSync5(this.openclawConfigPath)) {
855
+ configPaths.push(this.openclawConfigPath);
856
+ }
857
+ if (existsSync5(this.mcpConfigPath)) {
858
+ configPaths.push(this.mcpConfigPath);
859
+ }
860
+ return {
861
+ name: this.name,
862
+ tier: this.tier,
863
+ configPaths,
864
+ capabilities: { hooks: true, mcp: true, skills: true }
865
+ };
866
+ }
867
+ async registerMcp(serverConfig) {
868
+ const config = this.readJson(this.mcpConfigPath);
869
+ const servers = config.mcpServers ?? {};
870
+ servers[MCP_KEY2] = {
871
+ type: "stdio",
872
+ command: serverConfig.command,
873
+ args: serverConfig.args,
874
+ ...serverConfig.env && Object.keys(serverConfig.env).length > 0 ? { env: serverConfig.env } : {}
875
+ };
876
+ config.mcpServers = servers;
877
+ this.writeJson(this.mcpConfigPath, config);
878
+ }
879
+ async unregisterMcp() {
880
+ if (!existsSync5(this.mcpConfigPath)) return;
881
+ const config = this.readJson(this.mcpConfigPath);
882
+ const servers = config.mcpServers;
883
+ if (!servers || !(MCP_KEY2 in servers)) return;
884
+ delete servers[MCP_KEY2];
885
+ config.mcpServers = servers;
886
+ this.writeJson(this.mcpConfigPath, config);
887
+ }
888
+ async registerHooks(_hookConfig) {
889
+ console.error(
890
+ "[caik] To enable CAIK telemetry hooks in OpenClaw, install the CAIK hook pack:\n openclaw hooks install @caik.dev/openclaw-hooks\n (Hook pack not yet available \u2014 hooks are optional, CAIK works without them.)"
891
+ );
892
+ }
893
+ async unregisterHooks() {
894
+ console.error(
895
+ "[caik] To remove CAIK hooks from OpenClaw, run:\n openclaw hooks uninstall @caik.dev/openclaw-hooks"
896
+ );
897
+ }
898
+ async installSkill(_slug, content, files) {
899
+ if (!existsSync5(this.skillDir)) {
900
+ mkdirSync4(this.skillDir, { recursive: true });
901
+ }
902
+ writeFileSync4(join5(this.skillDir, "SKILL.md"), content, "utf-8");
903
+ if (files) {
904
+ for (const file of files) {
905
+ const filePath = join5(this.skillDir, file.path);
906
+ const fileDir = dirname(filePath);
907
+ if (!existsSync5(fileDir)) {
908
+ mkdirSync4(fileDir, { recursive: true });
909
+ }
910
+ writeFileSync4(filePath, file.content, "utf-8");
911
+ }
912
+ }
913
+ const lock = this.readSkillLock();
914
+ const now = (/* @__PURE__ */ new Date()).toISOString();
915
+ const existing = lock.skills["caik"];
916
+ lock.skills["caik"] = {
917
+ source: "@caik.dev/cli",
918
+ sourceType: "local",
919
+ sourceUrl: "",
920
+ skillPath: "skills/caik/SKILL.md",
921
+ skillFolderHash: "",
922
+ installedAt: existing?.installedAt ?? now,
923
+ updatedAt: now
924
+ };
925
+ this.writeSkillLock(lock);
926
+ }
927
+ async uninstallSkill(_slug) {
928
+ if (existsSync5(this.skillDir)) {
929
+ try {
930
+ rmSync2(this.skillDir, { recursive: true, force: true });
931
+ } catch {
932
+ }
933
+ }
934
+ const lock = this.readSkillLock();
935
+ if ("caik" in lock.skills) {
936
+ delete lock.skills["caik"];
937
+ this.writeSkillLock(lock);
938
+ }
939
+ }
940
+ getConfigPath() {
941
+ return this.openclawConfigPath;
942
+ }
943
+ async readConfig() {
944
+ return this.readJson(this.openclawConfigPath);
945
+ }
946
+ async isRegistered() {
947
+ if (!existsSync5(this.mcpConfigPath)) return false;
948
+ const config = this.readJson(this.mcpConfigPath);
949
+ const servers = config.mcpServers;
950
+ return !!servers && MCP_KEY2 in servers;
951
+ }
952
+ };
953
+
954
+ // src/platform/cursor.ts
955
+ import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5, rmSync as rmSync3 } from "fs";
956
+ import { join as join6, dirname as dirname2 } from "path";
957
+ import { homedir as homedir5 } from "os";
958
+ var MCP_KEY3 = "caik";
959
+ function readJsonFile(path) {
960
+ try {
961
+ if (!existsSync6(path)) return null;
962
+ const raw = readFileSync5(path, "utf-8");
963
+ return JSON.parse(raw);
964
+ } catch {
965
+ return null;
966
+ }
967
+ }
968
+ function writeJsonFile(path, data) {
969
+ mkdirSync5(dirname2(path), { recursive: true });
970
+ writeFileSync5(path, JSON.stringify(data, null, 2) + "\n", "utf-8");
971
+ }
972
+ var CursorAdapter = class {
973
+ name = "cursor";
974
+ tier = "hook-enabled";
975
+ /** Project-level .cursor dir (preferred for MCP config). */
976
+ get projectDir() {
977
+ return join6(process.cwd(), ".cursor");
978
+ }
979
+ /** Global ~/.cursor dir. */
980
+ get globalDir() {
981
+ return join6(homedir5(), ".cursor");
982
+ }
983
+ /** Preferred MCP config path (project-level). */
984
+ get mcpConfigPath() {
985
+ return join6(this.projectDir, "mcp.json");
986
+ }
987
+ /** Global MCP config path (fallback). */
988
+ get globalMcpConfigPath() {
989
+ return join6(this.globalDir, "mcp.json");
990
+ }
991
+ /** Hooks config path (project-level). */
992
+ get hooksConfigPath() {
993
+ return join6(this.projectDir, "hooks.json");
994
+ }
995
+ /** Skills directory. */
996
+ get skillsDir() {
997
+ return join6(this.globalDir, "skills", "caik");
998
+ }
999
+ // ── Detection ─────────────────────────────────────────────
1000
+ async detect() {
1001
+ const hasGlobal = existsSync6(this.globalDir);
1002
+ const hasProject = existsSync6(this.projectDir);
1003
+ if (!hasGlobal && !hasProject) return null;
1004
+ const configPaths = [];
1005
+ if (existsSync6(this.mcpConfigPath)) configPaths.push(this.mcpConfigPath);
1006
+ if (existsSync6(this.globalMcpConfigPath)) configPaths.push(this.globalMcpConfigPath);
1007
+ return {
1008
+ name: "cursor",
1009
+ tier: "hook-enabled",
1010
+ configPaths,
1011
+ capabilities: { hooks: true, mcp: true, skills: true }
1012
+ };
1013
+ }
1014
+ // ── MCP Registration ──────────────────────────────────────
1015
+ async registerMcp(serverConfig) {
1016
+ const configPath = existsSync6(this.projectDir) ? this.mcpConfigPath : this.globalMcpConfigPath;
1017
+ const config = readJsonFile(configPath) ?? {};
1018
+ config.mcpServers = config.mcpServers ?? {};
1019
+ config.mcpServers[MCP_KEY3] = serverConfig;
1020
+ writeJsonFile(configPath, config);
1021
+ }
1022
+ async unregisterMcp() {
1023
+ for (const configPath of [this.mcpConfigPath, this.globalMcpConfigPath]) {
1024
+ const config = readJsonFile(configPath);
1025
+ if (!config?.mcpServers?.[MCP_KEY3]) continue;
1026
+ delete config.mcpServers[MCP_KEY3];
1027
+ if (Object.keys(config.mcpServers).length === 0) {
1028
+ delete config.mcpServers;
1029
+ }
1030
+ writeJsonFile(configPath, config);
1031
+ }
1032
+ }
1033
+ // ── Hooks ─────────────────────────────────────────────────
1034
+ async registerHooks(_hookConfig) {
1035
+ const defaultHooks = [
1036
+ { event: "sessionStart", script: "npx -y @caik.dev/cli hook cursor-session-start", decision: "allow" },
1037
+ { event: "beforeMCPExecution", script: "npx -y @caik.dev/cli hook cursor-mcp-exec --server=$SERVER --tool=$TOOL", decision: "allow" },
1038
+ { event: "stop", script: "npx -y @caik.dev/cli hook cursor-session-end", decision: "allow" }
1039
+ ];
1040
+ try {
1041
+ const config = readJsonFile(this.hooksConfigPath) ?? {};
1042
+ const existing = config.hooks ?? [];
1043
+ const filtered = existing.filter(
1044
+ (h) => !h.script?.includes("caik") || !h.script?.includes("hook cursor-")
1045
+ );
1046
+ config.hooks = [...filtered, ...defaultHooks];
1047
+ writeJsonFile(this.hooksConfigPath, config);
1048
+ } catch {
1049
+ process.stderr.write(
1050
+ "[caik] Warning: Could not register hooks \u2014 Cursor hooks require v1.7+. Skipping.\n"
1051
+ );
1052
+ }
1053
+ }
1054
+ async unregisterHooks() {
1055
+ try {
1056
+ const config = readJsonFile(this.hooksConfigPath);
1057
+ if (!config?.hooks) return;
1058
+ config.hooks = config.hooks.filter(
1059
+ (h) => !h.script?.includes("caik") || !h.script?.includes("hook cursor-")
1060
+ );
1061
+ if (config.hooks.length === 0) {
1062
+ delete config.hooks;
1063
+ }
1064
+ writeJsonFile(this.hooksConfigPath, config);
1065
+ } catch {
1066
+ }
1067
+ }
1068
+ // ── Skills ────────────────────────────────────────────────
1069
+ async installSkill(slug, content, files) {
1070
+ const skillDir = join6(this.skillsDir, slug);
1071
+ mkdirSync5(skillDir, { recursive: true });
1072
+ writeFileSync5(join6(skillDir, "SKILL.md"), content, "utf-8");
1073
+ if (files) {
1074
+ for (const file of files) {
1075
+ const filePath = join6(skillDir, file.path);
1076
+ mkdirSync5(dirname2(filePath), { recursive: true });
1077
+ writeFileSync5(filePath, file.content, "utf-8");
1078
+ }
1079
+ }
1080
+ }
1081
+ async uninstallSkill(slug) {
1082
+ const skillDir = join6(this.skillsDir, slug);
1083
+ try {
1084
+ if (existsSync6(skillDir)) {
1085
+ rmSync3(skillDir, { recursive: true, force: true });
1086
+ }
1087
+ } catch {
1088
+ }
1089
+ }
1090
+ // ── Config Accessors ──────────────────────────────────────
1091
+ getConfigPath() {
1092
+ if (existsSync6(this.mcpConfigPath)) return this.mcpConfigPath;
1093
+ if (existsSync6(this.globalMcpConfigPath)) return this.globalMcpConfigPath;
1094
+ return this.mcpConfigPath;
1095
+ }
1096
+ async readConfig() {
1097
+ const configPath = this.getConfigPath();
1098
+ return readJsonFile(configPath) ?? {};
1099
+ }
1100
+ async isRegistered() {
1101
+ for (const configPath of [this.mcpConfigPath, this.globalMcpConfigPath]) {
1102
+ const config = readJsonFile(configPath);
1103
+ if (config?.mcpServers?.[MCP_KEY3]) return true;
1104
+ }
1105
+ return false;
1106
+ }
1107
+ };
1108
+
1109
+ // src/platform/generic.ts
1110
+ var GenericAdapter = class {
1111
+ name;
1112
+ tier = "cli-mcp";
1113
+ constructor(name) {
1114
+ this.name = name;
1115
+ }
1116
+ // ── Detection ─────────────────────────────────────────────
1117
+ async detect() {
1118
+ return null;
1119
+ }
1120
+ // ── MCP Registration ──────────────────────────────────────
1121
+ async registerMcp(serverConfig) {
1122
+ const snippet = {
1123
+ mcpServers: {
1124
+ caik: {
1125
+ command: serverConfig.command,
1126
+ args: serverConfig.args,
1127
+ ...serverConfig.env && Object.keys(serverConfig.env).length > 0 ? { env: serverConfig.env } : {}
1128
+ }
1129
+ }
1130
+ };
1131
+ const json = JSON.stringify(snippet, null, 2);
1132
+ process.stdout.write(
1133
+ `
1134
+ CAIK MCP server configuration for ${this.name}:
1135
+
1136
+ Add the following to your MCP configuration file:
1137
+
1138
+ ${json}
1139
+
1140
+ Refer to your platform's documentation for the correct config file location.
1141
+
1142
+ `
1143
+ );
1144
+ }
1145
+ async unregisterMcp() {
1146
+ process.stdout.write(
1147
+ `
1148
+ To unregister CAIK from ${this.name}, remove the "caik" entry
1149
+ from the "mcpServers" section in your MCP configuration file.
1150
+
1151
+ `
1152
+ );
1153
+ }
1154
+ // ── No hooks support ──────────────────────────────────────
1155
+ // registerHooks / unregisterHooks intentionally omitted (cli-mcp tier)
1156
+ // ── No skills support ─────────────────────────────────────
1157
+ // installSkill / uninstallSkill intentionally omitted (cli-mcp tier)
1158
+ // ── Config Accessors ──────────────────────────────────────
1159
+ getConfigPath() {
1160
+ return "";
1161
+ }
1162
+ async readConfig() {
1163
+ return {};
1164
+ }
1165
+ async isRegistered() {
1166
+ return false;
1167
+ }
1168
+ };
1169
+
1170
+ // src/platform/index.ts
1171
+ var adapters = {
1172
+ "claude-code": () => new ClaudeCodeAdapter(),
1173
+ "openclaw": () => new OpenClawAdapter(),
1174
+ "cursor": () => new CursorAdapter(),
1175
+ "codex": () => new GenericAdapter("codex"),
1176
+ "windsurf": () => new GenericAdapter("windsurf"),
1177
+ "generic": () => new GenericAdapter("generic")
1178
+ };
1179
+ function getPlatformAdapter(name) {
1180
+ const factory = adapters[name];
1181
+ if (!factory) {
1182
+ return new GenericAdapter(name);
1183
+ }
1184
+ return factory();
1185
+ }
1186
+ function getDefaultMcpConfig() {
1187
+ return {
1188
+ command: "npx",
1189
+ args: ["-y", "@caik.dev/mcp"]
1190
+ };
304
1191
  }
1192
+
1193
+ // src/commands/install.ts
1194
+ var legacyPlatformMap = {
1195
+ claude: "claude-code",
1196
+ cursor: "cursor",
1197
+ node: "generic"
1198
+ };
305
1199
  function registerInstallCommand(program2) {
306
1200
  program2.command("install <slug>").description("Install an artifact from the CAIK registry").option("--platform <platform>", "Target platform (auto-detected if not specified)").option("-y, --yes", "Skip confirmation prompts (for CI/scripting)").addHelpText("after", `
307
1201
  Examples:
@@ -310,7 +1204,17 @@ Examples:
310
1204
  const globalOpts = program2.opts();
311
1205
  const { apiUrl, apiKey } = resolveConfig(globalOpts);
312
1206
  const client = new CaikApiClient({ apiUrl, apiKey, verbose: globalOpts.verbose });
313
- const platform = opts.platform ?? detectPlatform();
1207
+ let detectedPlatform;
1208
+ if (opts.platform) {
1209
+ const raw = opts.platform;
1210
+ detectedPlatform = legacyPlatformMap[raw] ?? raw;
1211
+ } else {
1212
+ const detected = detectPlatforms();
1213
+ if (detected.length > 0) {
1214
+ detectedPlatform = detected[0].name;
1215
+ }
1216
+ }
1217
+ const platform = detectedPlatform;
314
1218
  const spinner = createSpinner(`Fetching ${slug}...`);
315
1219
  if (!globalOpts.json) spinner.start();
316
1220
  const params = { platform };
@@ -333,7 +1237,7 @@ Examples:
333
1237
  spinner.start();
334
1238
  continue;
335
1239
  }
336
- safeFiles.push({ resolvedPath, content: file.content });
1240
+ safeFiles.push({ resolvedPath, content: file.content ?? "" });
337
1241
  }
338
1242
  }
339
1243
  const hasFiles = safeFiles.length > 0;
@@ -365,15 +1269,15 @@ Examples:
365
1269
  spinner.start();
366
1270
  }
367
1271
  for (const file of safeFiles) {
368
- const dir = dirname(file.resolvedPath);
369
- if (!existsSync2(dir)) {
370
- mkdirSync2(dir, { recursive: true });
1272
+ const dir = dirname3(file.resolvedPath);
1273
+ if (!existsSync7(dir)) {
1274
+ mkdirSync6(dir, { recursive: true });
371
1275
  }
372
- writeFileSync2(file.resolvedPath, file.content, "utf-8");
1276
+ writeFileSync6(file.resolvedPath, file.content, "utf-8");
373
1277
  }
374
1278
  if (installInfo.installCommand) {
375
1279
  try {
376
- execSync(installInfo.installCommand, { stdio: "pipe" });
1280
+ execSync3(installInfo.installCommand, { stdio: "pipe" });
377
1281
  } catch (err) {
378
1282
  spinner.stop();
379
1283
  console.error(error(`Install command failed: ${err instanceof Error ? err.message : String(err)}`));
@@ -389,7 +1293,7 @@ Examples:
389
1293
  }
390
1294
  if (installInfo.postInstallHook) {
391
1295
  try {
392
- execSync(installInfo.postInstallHook, { stdio: "pipe" });
1296
+ execSync3(installInfo.postInstallHook, { stdio: "pipe" });
393
1297
  } catch {
394
1298
  }
395
1299
  }
@@ -398,6 +1302,31 @@ Examples:
398
1302
  if (installInfo.contract?.touchedPaths) {
399
1303
  console.log(info(`Files: ${installInfo.contract.touchedPaths.join(", ")}`));
400
1304
  }
1305
+ const resolvedPlatform = detectedPlatform ?? "generic";
1306
+ upsertRegistryEntry({
1307
+ slug,
1308
+ version: installInfo.artifact.version ?? "0.0.0",
1309
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
1310
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1311
+ platform: resolvedPlatform,
1312
+ files: safeFiles.map((f) => f.resolvedPath),
1313
+ artifactType: installInfo.artifact.primitive ?? "unknown"
1314
+ });
1315
+ const adapter = getPlatformAdapter(resolvedPlatform);
1316
+ const primitive = installInfo.artifact.primitive;
1317
+ if (primitive === "connector") {
1318
+ try {
1319
+ await adapter.registerMcp(getDefaultMcpConfig());
1320
+ } catch {
1321
+ }
1322
+ }
1323
+ if ((primitive === "executable" || primitive === "composition") && adapter.installSkill) {
1324
+ try {
1325
+ const content = safeFiles.map((f) => f.content).join("\n");
1326
+ await adapter.installSkill(slug, content);
1327
+ } catch {
1328
+ }
1329
+ }
401
1330
  client.post("/telemetry/install", {
402
1331
  artifactSlug: slug,
403
1332
  platform,
@@ -414,7 +1343,7 @@ function registerInitCommand(program2) {
414
1343
  program2.command("init").description("Configure the CAIK CLI").option("--auth", "Set up authentication (opens browser)").addHelpText("after", `
415
1344
  Examples:
416
1345
  caik init
417
- caik init --auth`).action(async (opts) => {
1346
+ caik init --auth`).action(async (_opts) => {
418
1347
  const rl = createInterface2({ input: stdin, output: stdout });
419
1348
  try {
420
1349
  const config = readConfig();
@@ -432,12 +1361,36 @@ Examples:
432
1361
  if (config.apiKey) {
433
1362
  const client = new CaikApiClient({ apiUrl: config.apiUrl, apiKey: config.apiKey });
434
1363
  try {
435
- const karma = await client.get("/me/karma");
1364
+ await client.get("/me/karma");
436
1365
  console.log(success("API connection verified \u2014 authenticated"));
437
1366
  } catch {
438
1367
  console.log(error("Could not verify API connection. Check your API key."));
439
1368
  }
440
1369
  }
1370
+ const detected = detectPlatforms();
1371
+ if (detected.length > 0) {
1372
+ console.log("");
1373
+ console.log(heading("Platform Integration"));
1374
+ console.log("\u2500".repeat(40));
1375
+ for (const p of detected) {
1376
+ console.log(` ${success(p.name)} detected`);
1377
+ }
1378
+ console.log("");
1379
+ const hasClaude = detected.some((p) => p.name === "claude-code");
1380
+ const hasOpenClaw = detected.some((p) => p.name === "openclaw");
1381
+ if (hasClaude) {
1382
+ console.log(info("Claude Code: Install the CAIK plugin for the best experience:"));
1383
+ console.log(dim(" claude plugin marketplace add https://github.com/caik-dev/claude-code-plugin"));
1384
+ console.log(dim(" claude plugin install caik@caik-dev"));
1385
+ console.log("");
1386
+ }
1387
+ if (hasOpenClaw) {
1388
+ console.log(info("OpenClaw: Add the CAIK MCP server to your project's .mcp.json:"));
1389
+ console.log(dim(' { "mcpServers": { "caik": { "type": "stdio", "command": "npx", "args": ["-y", "@caik.dev/mcp"] } } }'));
1390
+ console.log("");
1391
+ }
1392
+ console.log(info("Or run `caik setup` for interactive platform configuration."));
1393
+ }
441
1394
  } finally {
442
1395
  rl.close();
443
1396
  }
@@ -445,22 +1398,56 @@ Examples:
445
1398
  }
446
1399
 
447
1400
  // src/commands/status.ts
448
- import { existsSync as existsSync3 } from "fs";
1401
+ import { existsSync as existsSync8 } from "fs";
449
1402
  function registerStatusCommand(program2) {
450
1403
  program2.command("status").description("Show CLI configuration and connectivity status").action(async () => {
451
1404
  const globalOpts = program2.opts();
452
- const config = readConfig();
453
1405
  const { apiUrl, apiKey } = resolveConfig(globalOpts);
454
- if (globalOpts.json) {
455
- const data = {
456
- apiUrl,
457
- apiKeyConfigured: !!apiKey,
458
- platforms: {
459
- claude: existsSync3(".claude") || existsSync3("CLAUDE.md"),
460
- cursor: existsSync3(".cursor"),
461
- node: existsSync3("node_modules") || existsSync3("package.json")
462
- }
1406
+ const detected = detectPlatforms();
1407
+ const platformResults = [];
1408
+ for (const dp of detected) {
1409
+ const adapter = getPlatformAdapter(dp.name);
1410
+ let registered = false;
1411
+ try {
1412
+ registered = await adapter.isRegistered();
1413
+ } catch {
1414
+ }
1415
+ platformResults.push({
1416
+ name: dp.name,
1417
+ tier: dp.tier,
1418
+ registered,
1419
+ driftWarnings: []
1420
+ });
1421
+ }
1422
+ const registryEntries = listRegistryEntries();
1423
+ const driftEntries = [];
1424
+ for (const entry of registryEntries) {
1425
+ const missing = entry.files.filter((f) => !existsSync8(f));
1426
+ if (missing.length > 0) {
1427
+ driftEntries.push({ slug: entry.slug, missingFiles: missing });
1428
+ }
1429
+ }
1430
+ for (const drift of driftEntries) {
1431
+ const entry = registryEntries.find((e) => e.slug === drift.slug);
1432
+ if (entry) {
1433
+ const pr = platformResults.find((p) => p.name === entry.platform);
1434
+ if (pr) {
1435
+ pr.driftWarnings.push(
1436
+ `${drift.slug}: ${drift.missingFiles.length} missing file(s)`
1437
+ );
1438
+ }
1439
+ }
1440
+ }
1441
+ if (globalOpts.json) {
1442
+ const data = {
1443
+ apiUrl,
1444
+ apiKeyConfigured: !!apiKey,
1445
+ platforms: platformResults,
1446
+ installedArtifacts: registryEntries.length
463
1447
  };
1448
+ if (driftEntries.length > 0) {
1449
+ data.driftWarnings = driftEntries;
1450
+ }
464
1451
  const client2 = new CaikApiClient({ apiUrl, apiKey, verbose: globalOpts.verbose });
465
1452
  try {
466
1453
  if (apiKey) {
@@ -493,15 +1480,26 @@ function registerStatusCommand(program2) {
493
1480
  console.log(`API Reachable: ${error("no")}`);
494
1481
  }
495
1482
  console.log("");
496
- console.log(heading("Local Platforms Detected"));
1483
+ console.log(heading("Platform Integration"));
497
1484
  console.log("\u2500".repeat(40));
498
- const platforms = [
499
- { name: "Claude", check: existsSync3(".claude") || existsSync3("CLAUDE.md") },
500
- { name: "Cursor", check: existsSync3(".cursor") },
501
- { name: "Node.js", check: existsSync3("node_modules") || existsSync3("package.json") }
502
- ];
503
- for (const p of platforms) {
504
- console.log(` ${p.name}: ${p.check ? success("detected") : dim("not found")}`);
1485
+ if (platformResults.length === 0) {
1486
+ console.log(` ${dim("No platforms detected")}`);
1487
+ } else {
1488
+ const maxLen = Math.max(...platformResults.map((p) => p.name.length));
1489
+ for (const p of platformResults) {
1490
+ const label = `${p.name}:`.padEnd(maxLen + 2);
1491
+ const mcpStatus = p.registered ? `(MCP: ${success("registered")})` : `(MCP: ${dim("not registered")})`;
1492
+ console.log(` ${label}${success("detected")} ${mcpStatus}`);
1493
+ }
1494
+ }
1495
+ console.log("");
1496
+ console.log(`Installed Artifacts: ${registryEntries.length}`);
1497
+ if (driftEntries.length > 0) {
1498
+ const count = driftEntries.length;
1499
+ const noun = count === 1 ? "artifact has" : "artifacts have";
1500
+ console.log(
1501
+ ` ${warn(`${count} ${noun} missing files`)} \u2014 run \`caik setup\` to reconfigure`
1502
+ );
505
1503
  }
506
1504
  });
507
1505
  }
@@ -552,7 +1550,7 @@ Examples:
552
1550
  }
553
1551
 
554
1552
  // src/commands/publish.ts
555
- import { readFileSync as readFileSync2, existsSync as existsSync4 } from "fs";
1553
+ import { readFileSync as readFileSync6, existsSync as existsSync9 } from "fs";
556
1554
  function registerPublishCommand(program2) {
557
1555
  program2.command("publish [path]").description("Publish an artifact to the CAIK registry").requiredOption("--name <name>", "Artifact name").requiredOption("--description <desc>", "Artifact description (min 20 chars)").option("--slug <slug>", "Custom slug (auto-generated from name if omitted)").option("--primitive <type>", "Primitive type", "executable").option("--platform <platform>", "Target platform(s) (comma-separated)", "claude").option("--tag <tag>", "Add a tag (can be repeated)", (val, prev) => [...prev, val], []).option("--source-url <url>", "Source code URL").addHelpText("after", `
558
1556
  Examples:
@@ -566,10 +1564,10 @@ Examples:
566
1564
  const client = new CaikApiClient({ apiUrl, apiKey, verbose: globalOpts.verbose });
567
1565
  let content;
568
1566
  if (path) {
569
- if (!existsSync4(path)) {
1567
+ if (!existsSync9(path)) {
570
1568
  throw new CaikError(`File not found: ${path}`);
571
1569
  }
572
- content = readFileSync2(path, "utf-8");
1570
+ content = readFileSync6(path, "utf-8");
573
1571
  }
574
1572
  const tags = opts.tag.length > 0 ? opts.tag : ["general"];
575
1573
  const platforms = opts.platform.split(",").map((p) => p.trim());
@@ -730,16 +1728,340 @@ Examples:
730
1728
  }
731
1729
 
732
1730
  // src/commands/update.ts
1731
+ import { existsSync as existsSync10 } from "fs";
1732
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync7 } from "fs";
1733
+ import { dirname as dirname4, resolve as resolve2 } from "path";
1734
+ import { execSync as execSync4 } from "child_process";
1735
+ import { createInterface as createInterface3 } from "readline/promises";
733
1736
  function registerUpdateCommand(program2) {
734
- program2.command("update [slug]").description("Check for and apply artifact updates").option("--yes", "Skip confirmation prompt").action(async (slug) => {
735
- console.log(info("Update checking is not yet available. Reinstall artifacts with 'caik install <slug>' to get the latest version."));
1737
+ program2.command("update [slug]").description("Check for and apply artifact updates").option("--platform <platform>", "Filter by platform").option("-y, --yes", "Skip confirmation prompts").addHelpText("after", `
1738
+ Examples:
1739
+ caik update # Check all installed artifacts for updates
1740
+ caik update auth-skill # Update a specific artifact
1741
+ caik update --yes # Update all without confirmation`).action(async (slug, opts) => {
1742
+ const globalOpts = program2.opts();
1743
+ const { apiUrl, apiKey } = resolveConfig(globalOpts);
1744
+ const client = new CaikApiClient({ apiUrl, apiKey, verbose: globalOpts.verbose });
1745
+ const skipConfirm = Boolean(opts?.yes);
1746
+ const platformFilter = opts?.platform;
1747
+ if (slug) {
1748
+ const entry = findRegistryEntry(slug, platformFilter);
1749
+ if (!entry) {
1750
+ console.log(error(`Artifact "${slug}" is not in the install registry.`));
1751
+ console.log(info("Use 'caik install <slug>' to install it first."));
1752
+ process.exit(1);
1753
+ }
1754
+ const spinner = createSpinner(`Checking for updates to ${slug}...`);
1755
+ if (!globalOpts.json) spinner.start();
1756
+ let remote;
1757
+ try {
1758
+ remote = await client.get(`/artifacts/${encodeURIComponent(slug)}`);
1759
+ } catch (err) {
1760
+ spinner.stop();
1761
+ console.log(error(`Failed to fetch artifact info: ${err instanceof Error ? err.message : String(err)}`));
1762
+ process.exit(1);
1763
+ }
1764
+ if (globalOpts.json) {
1765
+ spinner.stop();
1766
+ outputResult({
1767
+ slug,
1768
+ installedVersion: entry.version,
1769
+ remoteUpdatedAt: remote.updatedAt,
1770
+ localUpdatedAt: entry.updatedAt,
1771
+ updateAvailable: remote.updatedAt > entry.updatedAt
1772
+ }, { json: true });
1773
+ return;
1774
+ }
1775
+ if (remote.updatedAt <= entry.updatedAt) {
1776
+ spinner.stop();
1777
+ console.log(success(`${slug} is already up to date.`));
1778
+ return;
1779
+ }
1780
+ spinner.stop();
1781
+ console.log(info(`Update available for ${slug} (installed: ${entry.updatedAt.slice(0, 10)}, latest: ${remote.updatedAt.slice(0, 10)})`));
1782
+ await performUpdate(client, entry, skipConfirm, globalOpts);
1783
+ } else {
1784
+ const entries = listRegistryEntries(platformFilter);
1785
+ if (entries.length === 0) {
1786
+ console.log(info("No installed artifacts found."));
1787
+ console.log(info("Install artifacts with 'caik install <slug>' first."));
1788
+ return;
1789
+ }
1790
+ const spinner = createSpinner(`Checking ${entries.length} artifact(s) for updates...`);
1791
+ if (!globalOpts.json) spinner.start();
1792
+ const candidates = [];
1793
+ const errors = [];
1794
+ for (const entry of entries) {
1795
+ try {
1796
+ const remote = await client.get(`/artifacts/${encodeURIComponent(entry.slug)}`);
1797
+ if (remote.updatedAt > entry.updatedAt) {
1798
+ candidates.push({ entry, remote });
1799
+ }
1800
+ } catch (err) {
1801
+ errors.push({
1802
+ slug: entry.slug,
1803
+ message: err instanceof Error ? err.message : String(err)
1804
+ });
1805
+ }
1806
+ }
1807
+ spinner.stop();
1808
+ if (globalOpts.json) {
1809
+ outputResult({
1810
+ total: entries.length,
1811
+ updatesAvailable: candidates.length,
1812
+ updates: candidates.map((c) => ({
1813
+ slug: c.entry.slug,
1814
+ platform: c.entry.platform,
1815
+ installedAt: c.entry.updatedAt,
1816
+ remoteUpdatedAt: c.remote.updatedAt
1817
+ })),
1818
+ errors
1819
+ }, { json: true });
1820
+ return;
1821
+ }
1822
+ if (errors.length > 0) {
1823
+ for (const e of errors) {
1824
+ console.log(warn(`Could not check ${e.slug}: ${e.message}`));
1825
+ }
1826
+ }
1827
+ if (candidates.length === 0) {
1828
+ console.log(success("All installed artifacts are up to date."));
1829
+ return;
1830
+ }
1831
+ const rows = candidates.map((c) => [
1832
+ c.entry.slug,
1833
+ c.entry.platform,
1834
+ c.entry.updatedAt.slice(0, 10),
1835
+ c.remote.updatedAt.slice(0, 10)
1836
+ ]);
1837
+ console.log(renderTable(["Artifact", "Platform", "Installed", "Latest"], rows));
1838
+ console.log(info(`${candidates.length} update(s) available.`));
1839
+ if (!skipConfirm) {
1840
+ const rl = createInterface3({ input: process.stdin, output: process.stdout });
1841
+ const answer = await rl.question("\nUpdate all? (y/N) ");
1842
+ rl.close();
1843
+ if (answer.toLowerCase() !== "y") {
1844
+ console.log(info("Update cancelled."));
1845
+ return;
1846
+ }
1847
+ }
1848
+ let updated = 0;
1849
+ for (const candidate of candidates) {
1850
+ try {
1851
+ await performUpdate(client, candidate.entry, true, globalOpts);
1852
+ updated++;
1853
+ } catch (err) {
1854
+ console.log(error(`Failed to update ${candidate.entry.slug}: ${err instanceof Error ? err.message : String(err)}`));
1855
+ }
1856
+ }
1857
+ console.log(success(`Updated ${updated}/${candidates.length} artifact(s).`));
1858
+ }
1859
+ });
1860
+ }
1861
+ async function performUpdate(client, entry, skipConfirm, globalOpts) {
1862
+ const spinner = createSpinner(`Updating ${entry.slug}...`);
1863
+ if (!globalOpts.json) spinner.start();
1864
+ const params = { platform: entry.platform };
1865
+ let installInfo;
1866
+ try {
1867
+ installInfo = await client.get(`/install/${encodeURIComponent(entry.slug)}`, params);
1868
+ } catch (err) {
1869
+ spinner.stop();
1870
+ throw err;
1871
+ }
1872
+ const cwd = process.cwd();
1873
+ const safeFiles = [];
1874
+ if (installInfo.files && installInfo.files.length > 0) {
1875
+ for (const file of installInfo.files) {
1876
+ const resolvedPath = resolve2(cwd, file.path);
1877
+ if (!resolvedPath.startsWith(cwd + "/") && resolvedPath !== cwd) {
1878
+ spinner.stop();
1879
+ console.log(error(`Rejected file path outside working directory: ${file.path}`));
1880
+ spinner.start();
1881
+ continue;
1882
+ }
1883
+ safeFiles.push({ resolvedPath, content: file.content });
1884
+ }
1885
+ }
1886
+ if (!skipConfirm && safeFiles.length > 0) {
1887
+ spinner.stop();
1888
+ console.log(info("\n--- Update plan ---"));
1889
+ console.log(info("Files to write:"));
1890
+ for (const f of safeFiles) {
1891
+ console.log(info(` ${f.resolvedPath}`));
1892
+ }
1893
+ if (installInfo.installCommand) {
1894
+ console.log(info(`Install command: ${installInfo.installCommand}`));
1895
+ }
1896
+ console.log();
1897
+ const rl = createInterface3({ input: process.stdin, output: process.stdout });
1898
+ const answer = await rl.question("Continue? (y/N) ");
1899
+ rl.close();
1900
+ if (answer.toLowerCase() !== "y") {
1901
+ console.log(info("Update cancelled."));
1902
+ return;
1903
+ }
1904
+ spinner.start();
1905
+ }
1906
+ const writtenFiles = [];
1907
+ for (const file of safeFiles) {
1908
+ const dir = dirname4(file.resolvedPath);
1909
+ if (!existsSync10(dir)) {
1910
+ mkdirSync7(dir, { recursive: true });
1911
+ }
1912
+ writeFileSync7(file.resolvedPath, file.content, "utf-8");
1913
+ writtenFiles.push(file.resolvedPath);
1914
+ }
1915
+ if (installInfo.installCommand) {
1916
+ try {
1917
+ execSync4(installInfo.installCommand, { stdio: "pipe" });
1918
+ } catch (err) {
1919
+ spinner.stop();
1920
+ console.log(error(`Install command failed: ${err instanceof Error ? err.message : String(err)}`));
1921
+ client.post("/telemetry/install", {
1922
+ artifactSlug: entry.slug,
1923
+ platform: entry.platform,
1924
+ success: false,
1925
+ errorMessage: err instanceof Error ? err.message : String(err)
1926
+ }).catch(() => {
1927
+ });
1928
+ throw err;
1929
+ }
1930
+ }
1931
+ if (installInfo.postInstallHook) {
1932
+ try {
1933
+ execSync4(installInfo.postInstallHook, { stdio: "pipe" });
1934
+ } catch {
1935
+ }
1936
+ }
1937
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1938
+ upsertRegistryEntry({
1939
+ ...entry,
1940
+ updatedAt: now,
1941
+ files: writtenFiles.length > 0 ? writtenFiles : entry.files
1942
+ });
1943
+ spinner.stop();
1944
+ console.log(success(`Updated ${installInfo.artifact.name}`));
1945
+ client.post("/telemetry/install", {
1946
+ artifactSlug: entry.slug,
1947
+ platform: entry.platform,
1948
+ success: true
1949
+ }).catch(() => {
736
1950
  });
737
1951
  }
738
1952
 
739
1953
  // src/commands/uninstall.ts
1954
+ import { createInterface as createInterface4 } from "readline/promises";
740
1955
  function registerUninstallCommand(program2) {
741
- program2.command("uninstall <slug>").description("Remove an installed artifact").action(async (slug) => {
742
- console.log(info("Uninstall is not yet available. Manually remove the artifact files to uninstall."));
1956
+ program2.command("uninstall <slug>").description("Remove an installed artifact").option("--platform <platform>", "Target platform (disambiguates if installed on multiple)").option("-y, --yes", "Skip confirmation prompts").addHelpText("after", `
1957
+ Examples:
1958
+ caik uninstall auth-skill
1959
+ caik uninstall my-mcp --platform claude-code
1960
+ caik uninstall old-rules --yes`).action(async (slug, opts) => {
1961
+ const globalOpts = program2.opts();
1962
+ const { apiUrl, apiKey } = resolveConfig(globalOpts);
1963
+ const client = new CaikApiClient({ apiUrl, apiKey, verbose: globalOpts.verbose });
1964
+ const skipConfirm = Boolean(opts.yes);
1965
+ const platformOpt = opts.platform;
1966
+ const allEntries = listRegistryEntries();
1967
+ const matchingEntries = allEntries.filter(
1968
+ (e) => e.slug === slug && (!platformOpt || e.platform === platformOpt)
1969
+ );
1970
+ if (matchingEntries.length === 0) {
1971
+ if (globalOpts.json) {
1972
+ outputResult({ error: "not_found", slug }, { json: true });
1973
+ return;
1974
+ }
1975
+ console.log(error(`Artifact "${slug}" not found in install registry.`));
1976
+ console.log(info("It may have been installed manually or already uninstalled."));
1977
+ process.exit(1);
1978
+ }
1979
+ if (matchingEntries.length > 1 && !platformOpt) {
1980
+ if (globalOpts.json) {
1981
+ outputResult({
1982
+ error: "ambiguous",
1983
+ slug,
1984
+ platforms: matchingEntries.map((e) => e.platform)
1985
+ }, { json: true });
1986
+ return;
1987
+ }
1988
+ console.log(warn(`"${slug}" is installed on multiple platforms: ${matchingEntries.map((e) => e.platform).join(", ")}`));
1989
+ console.log(info("Use --platform <platform> to specify which to uninstall."));
1990
+ process.exit(1);
1991
+ }
1992
+ const entry = matchingEntries[0];
1993
+ if (globalOpts.json) {
1994
+ } else {
1995
+ console.log(info(`
1996
+ Artifact: ${entry.slug} (${entry.artifactType})`));
1997
+ console.log(info(`Platform: ${entry.platform}`));
1998
+ console.log(info(`Installed: ${entry.installedAt.slice(0, 10)}`));
1999
+ if (entry.files.length > 0) {
2000
+ console.log(info("Files to remove:"));
2001
+ for (const f of entry.files) {
2002
+ console.log(info(` ${f}`));
2003
+ }
2004
+ }
2005
+ if (!skipConfirm) {
2006
+ const rl = createInterface4({ input: process.stdin, output: process.stdout });
2007
+ const answer = await rl.question("\nUninstall? (y/N) ");
2008
+ rl.close();
2009
+ if (answer.toLowerCase() !== "y") {
2010
+ console.log(info("Uninstall cancelled."));
2011
+ return;
2012
+ }
2013
+ }
2014
+ }
2015
+ const spinner = createSpinner(`Uninstalling ${slug}...`);
2016
+ if (!globalOpts.json) spinner.start();
2017
+ const failedFiles = cleanupFiles(entry);
2018
+ const adapter = getPlatformAdapter(entry.platform);
2019
+ if (entry.artifactType === "mcp-server") {
2020
+ try {
2021
+ await adapter.unregisterMcp();
2022
+ } catch (err) {
2023
+ if (!globalOpts.json) {
2024
+ spinner.stop();
2025
+ console.log(warn(`Could not unregister MCP server: ${err instanceof Error ? err.message : String(err)}`));
2026
+ spinner.start();
2027
+ }
2028
+ }
2029
+ }
2030
+ if ((entry.artifactType === "skill" || entry.artifactType === "subagent" || entry.artifactType === "command") && adapter.uninstallSkill) {
2031
+ try {
2032
+ await adapter.uninstallSkill(slug);
2033
+ } catch (err) {
2034
+ if (!globalOpts.json) {
2035
+ spinner.stop();
2036
+ console.log(warn(`Could not uninstall skill: ${err instanceof Error ? err.message : String(err)}`));
2037
+ spinner.start();
2038
+ }
2039
+ }
2040
+ }
2041
+ removeRegistryEntry(slug, entry.platform);
2042
+ spinner.stop();
2043
+ if (globalOpts.json) {
2044
+ outputResult({
2045
+ slug,
2046
+ platform: entry.platform,
2047
+ removed: true,
2048
+ filesRemoved: entry.files.length - failedFiles.length,
2049
+ filesFailed: failedFiles
2050
+ }, { json: true });
2051
+ return;
2052
+ }
2053
+ console.log(success(`Uninstalled ${slug} from ${entry.platform}`));
2054
+ if (failedFiles.length > 0) {
2055
+ console.log(warn(`Could not remove ${failedFiles.length} file(s):`));
2056
+ for (const f of failedFiles) {
2057
+ console.log(warn(` ${f}`));
2058
+ }
2059
+ }
2060
+ client.post("/telemetry/uninstall", {
2061
+ artifactSlug: slug,
2062
+ platform: entry.platform
2063
+ }).catch(() => {
2064
+ });
743
2065
  });
744
2066
  }
745
2067
 
@@ -769,13 +2091,497 @@ function registerKarmaCommand(program2) {
769
2091
  });
770
2092
  }
771
2093
 
2094
+ // src/commands/hook.ts
2095
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8, existsSync as existsSync11 } from "fs";
2096
+ import { join as join7 } from "path";
2097
+ import { homedir as homedir6 } from "os";
2098
+ var PENDING_EVENTS_PATH = join7(homedir6(), ".caik", "pending-events.json");
2099
+ var FLUSH_THRESHOLD = 50;
2100
+ var API_TIMEOUT_MS = 2e3;
2101
+ function readPendingEvents() {
2102
+ try {
2103
+ if (!existsSync11(PENDING_EVENTS_PATH)) return [];
2104
+ const raw = readFileSync7(PENDING_EVENTS_PATH, "utf-8");
2105
+ const parsed = JSON.parse(raw);
2106
+ return Array.isArray(parsed) ? parsed : [];
2107
+ } catch {
2108
+ return [];
2109
+ }
2110
+ }
2111
+ function writePendingEvents(events) {
2112
+ const dir = join7(homedir6(), ".caik");
2113
+ if (!existsSync11(dir)) {
2114
+ mkdirSync8(dir, { recursive: true, mode: 448 });
2115
+ }
2116
+ writeFileSync8(PENDING_EVENTS_PATH, JSON.stringify(events, null, 2) + "\n", "utf-8");
2117
+ }
2118
+ function bufferEvent(event) {
2119
+ const events = readPendingEvents();
2120
+ events.push(event);
2121
+ writePendingEvents(events);
2122
+ return events;
2123
+ }
2124
+ function clearPendingEvents() {
2125
+ writePendingEvents([]);
2126
+ }
2127
+ async function postEventsWithTimeout(client, events) {
2128
+ if (events.length === 0) return;
2129
+ const controller = new AbortController();
2130
+ const timeout = setTimeout(() => controller.abort(), API_TIMEOUT_MS);
2131
+ try {
2132
+ await Promise.race([
2133
+ client.post("/events", { events }),
2134
+ new Promise(
2135
+ (_, reject) => setTimeout(() => reject(new Error("timeout")), API_TIMEOUT_MS)
2136
+ )
2137
+ ]);
2138
+ clearPendingEvents();
2139
+ } catch {
2140
+ } finally {
2141
+ clearTimeout(timeout);
2142
+ }
2143
+ }
2144
+ async function postSingleEventWithTimeout(client, event) {
2145
+ try {
2146
+ await Promise.race([
2147
+ client.post("/events", { events: [event] }),
2148
+ new Promise(
2149
+ (_, reject) => setTimeout(() => reject(new Error("timeout")), API_TIMEOUT_MS)
2150
+ )
2151
+ ]);
2152
+ } catch {
2153
+ }
2154
+ }
2155
+ function createClient(program2) {
2156
+ const globalOpts = program2.opts();
2157
+ const { apiUrl, apiKey } = resolveConfig(globalOpts);
2158
+ return new CaikApiClient({ apiUrl, apiKey, verbose: globalOpts.verbose });
2159
+ }
2160
+ function registerHookCommand(program2) {
2161
+ 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 () => {
2163
+ try {
2164
+ const client = createClient(program2);
2165
+ const event = {
2166
+ type: "session_start",
2167
+ platform: "claude-code",
2168
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2169
+ };
2170
+ await postSingleEventWithTimeout(client, event);
2171
+ } catch {
2172
+ }
2173
+ process.exit(0);
2174
+ });
2175
+ hook.command("session-end").description("Flush pending events and log session end").action(async () => {
2176
+ try {
2177
+ const client = createClient(program2);
2178
+ const pending = readPendingEvents();
2179
+ const endEvent = {
2180
+ type: "session_end",
2181
+ platform: "claude-code",
2182
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2183
+ };
2184
+ pending.push(endEvent);
2185
+ await postEventsWithTimeout(client, pending);
2186
+ } catch {
2187
+ }
2188
+ process.exit(0);
2189
+ });
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) => {
2191
+ try {
2192
+ const event = {
2193
+ type: "tool_use",
2194
+ platform: "claude-code",
2195
+ tool: opts.tool,
2196
+ success: opts.success === "true" || opts.success === true,
2197
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2198
+ };
2199
+ const events = bufferEvent(event);
2200
+ if (events.length >= FLUSH_THRESHOLD) {
2201
+ const client = createClient(program2);
2202
+ await postEventsWithTimeout(client, events);
2203
+ }
2204
+ } catch {
2205
+ }
2206
+ process.exit(0);
2207
+ });
2208
+ hook.command("cursor-session-start").description("Log Cursor session start event").action(async () => {
2209
+ try {
2210
+ const client = createClient(program2);
2211
+ const event = {
2212
+ type: "session_start",
2213
+ platform: "cursor",
2214
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2215
+ };
2216
+ await postSingleEventWithTimeout(client, event);
2217
+ } catch {
2218
+ }
2219
+ process.exit(0);
2220
+ });
2221
+ hook.command("cursor-mcp-exec").description("Buffer a Cursor MCP tool execution event").option("--server <name>", "MCP server name").option("--tool <name>", "Tool name").action(async (opts) => {
2222
+ try {
2223
+ const event = {
2224
+ type: "mcp_exec",
2225
+ platform: "cursor",
2226
+ server: opts.server,
2227
+ tool: opts.tool,
2228
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2229
+ };
2230
+ const events = bufferEvent(event);
2231
+ if (events.length >= FLUSH_THRESHOLD) {
2232
+ const client = createClient(program2);
2233
+ await postEventsWithTimeout(client, events);
2234
+ }
2235
+ } catch {
2236
+ }
2237
+ process.exit(0);
2238
+ });
2239
+ hook.command("cursor-session-end").description("Flush pending events and log Cursor session end").action(async () => {
2240
+ try {
2241
+ const client = createClient(program2);
2242
+ const pending = readPendingEvents();
2243
+ const endEvent = {
2244
+ type: "session_end",
2245
+ platform: "cursor",
2246
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2247
+ };
2248
+ pending.push(endEvent);
2249
+ await postEventsWithTimeout(client, pending);
2250
+ } catch {
2251
+ }
2252
+ process.exit(0);
2253
+ });
2254
+ }
2255
+
2256
+ // src/commands/setup.ts
2257
+ import { createInterface as createInterface5 } from "readline/promises";
2258
+
2259
+ // src/platform/templates/claude-code-skill.ts
2260
+ function getClaudeCodeSkillContent() {
2261
+ return `# CAIK \u2014 AI Artifact Registry
2262
+
2263
+ 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
+
2265
+ ## Available MCP Tools
2266
+
2267
+ When the CAIK MCP server is running, these tools are available:
2268
+
2269
+ | Tool | Description |
2270
+ |------|-------------|
2271
+ | \`search\` | Search the CAIK registry for artifacts |
2272
+ | \`get_artifact\` | Get full details for an artifact by slug |
2273
+ | \`install_artifact\` | Install an artifact into the current project |
2274
+ | \`report_outcome\` | Report whether an artifact worked well or poorly |
2275
+ | \`get_alternatives\` | Find similar artifacts |
2276
+
2277
+ ## Quick Commands
2278
+
2279
+ If MCP is unavailable, use the CLI directly:
2280
+
2281
+ - **Search:** \`caik search <query>\`
2282
+ - **Install:** \`caik install <slug>\`
2283
+ - **Status:** \`caik status\`
2284
+ - **Update:** \`caik update [slug]\`
2285
+ - **Uninstall:** \`caik uninstall <slug>\`
2286
+
2287
+ ## Contributing Back
2288
+
2289
+ When an artifact works well for a task, use \`report_outcome\` (MCP) or \`caik flag --positive <slug>\` to help the community. Your contributions earn karma on the CAIK leaderboard.
2290
+
2291
+ ## Discovery
2292
+
2293
+ Ask your agent: "Find CAIK artifacts for [your use case]" \u2014 the agent will use the \`search\` tool to find relevant artifacts from the registry.
2294
+ `;
2295
+ }
2296
+
2297
+ // src/platform/templates/cursor-skill.ts
2298
+ function getCursorSkillContent() {
2299
+ return `# CAIK \u2014 AI Artifact Registry
2300
+
2301
+ 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
+
2303
+ ## Cursor Rules Integration
2304
+
2305
+ CAIK artifacts install directly into your \`.cursorrules\` file. Installed rules are automatically loaded by Cursor's AI features.
2306
+
2307
+ ## MCP Tools
2308
+
2309
+ If the CAIK MCP server is configured, these tools are available:
2310
+
2311
+ | Tool | Description |
2312
+ |------|-------------|
2313
+ | \`search\` | Search the CAIK registry for artifacts |
2314
+ | \`get_artifact\` | Get full details for an artifact by slug |
2315
+ | \`install_artifact\` | Install an artifact into the current project |
2316
+ | \`report_outcome\` | Report whether an artifact worked well or poorly |
2317
+
2318
+ ## CLI Fallback
2319
+
2320
+ - **Search:** \`caik search <query>\`
2321
+ - **Install:** \`caik install <slug>\`
2322
+ - **Status:** \`caik status\`
2323
+ - **Uninstall:** \`caik uninstall <slug>\`
2324
+
2325
+ ## Discovery
2326
+
2327
+ Ask Cursor: "Find CAIK artifacts for [your use case]" to search the registry.
2328
+ `;
2329
+ }
2330
+
2331
+ // src/platform/templates/openclaw-skill.ts
2332
+ function getOpenClawSkillContent() {
2333
+ return `# CAIK \u2014 AI Artifact Registry
2334
+
2335
+ 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
+
2337
+ ## Skill Capabilities
2338
+
2339
+ CAIK artifacts integrate with OpenClaw's skill system. Installed skills are available as callable capabilities within your OpenClaw agent.
2340
+
2341
+ ## Hook Telemetry
2342
+
2343
+ CAIK uses OpenClaw lifecycle hooks to track artifact usage. When you invoke a CAIK-installed skill, outcome data is reported automatically to improve community rankings. You can disable telemetry with \`caik config set telemetry false\`.
2344
+
2345
+ ## MCP Tools
2346
+
2347
+ | Tool | Description |
2348
+ |------|-------------|
2349
+ | \`search\` | Search the CAIK registry for artifacts |
2350
+ | \`get_artifact\` | Get full details for an artifact by slug |
2351
+ | \`install_artifact\` | Install an artifact into the current project |
2352
+ | \`report_outcome\` | Report whether an artifact worked well or poorly |
2353
+ | \`get_alternatives\` | Find similar artifacts |
2354
+
2355
+ ## CLI Fallback
2356
+
2357
+ - **Search:** \`caik search <query>\`
2358
+ - **Install:** \`caik install <slug>\`
2359
+ - **Status:** \`caik status\`
2360
+ - **Update:** \`caik update [slug]\`
2361
+ - **Uninstall:** \`caik uninstall <slug>\`
2362
+
2363
+ ## Discovery
2364
+
2365
+ Ask your agent: "Find CAIK artifacts for [your use case]" to search the registry.
2366
+ `;
2367
+ }
2368
+
2369
+ // src/platform/templates/index.ts
2370
+ function getSkillContent(platform) {
2371
+ switch (platform) {
2372
+ case "claude-code":
2373
+ return getClaudeCodeSkillContent();
2374
+ case "cursor":
2375
+ return getCursorSkillContent();
2376
+ case "openclaw":
2377
+ return getOpenClawSkillContent();
2378
+ default:
2379
+ return getGenericSkillContent();
2380
+ }
2381
+ }
2382
+ function getGenericSkillContent() {
2383
+ return `# CAIK \u2014 AI Artifact Registry
2384
+
2385
+ CAIK is a community-driven registry for AI coding artifacts \u2014 skills, rules, prompts, MCP servers, and knowledge packs.
2386
+
2387
+ ## CLI Commands
2388
+
2389
+ - **Search:** \`caik search <query>\`
2390
+ - **Install:** \`caik install <slug>\`
2391
+ - **Status:** \`caik status\`
2392
+ - **Update:** \`caik update [slug]\`
2393
+ - **Uninstall:** \`caik uninstall <slug>\`
2394
+
2395
+ ## MCP Tools
2396
+
2397
+ If the CAIK MCP server is configured, these tools are available:
2398
+
2399
+ | Tool | Description |
2400
+ |------|-------------|
2401
+ | \`search\` | Search the CAIK registry for artifacts |
2402
+ | \`get_artifact\` | Get full details for an artifact by slug |
2403
+ | \`install_artifact\` | Install an artifact into the current project |
2404
+ | \`report_outcome\` | Report whether an artifact worked well or poorly |
2405
+
2406
+ ## Discovery
2407
+
2408
+ Ask your agent: "Find CAIK artifacts for [your use case]" to search the registry.
2409
+ `;
2410
+ }
2411
+
2412
+ // src/commands/setup.ts
2413
+ function getHookConfig(platform) {
2414
+ if (platform === "claude-code") {
2415
+ return {
2416
+ hooks: {
2417
+ SessionStart: [{ type: "command", command: "npx -y @caik.dev/cli hook session-start" }],
2418
+ PostToolUse: [{ type: "command", command: "npx -y @caik.dev/cli hook post-tool-use" }],
2419
+ SessionEnd: [{ type: "command", command: "npx -y @caik.dev/cli hook session-end" }]
2420
+ }
2421
+ };
2422
+ }
2423
+ if (platform === "cursor") {
2424
+ return {
2425
+ hooks: {
2426
+ sessionStart: "npx -y @caik.dev/cli hook cursor-session-start",
2427
+ beforeMCPExecution: "npx -y @caik.dev/cli hook cursor-mcp-exec",
2428
+ stop: "npx -y @caik.dev/cli hook cursor-session-end"
2429
+ }
2430
+ };
2431
+ }
2432
+ return {
2433
+ 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"
2437
+ }
2438
+ };
2439
+ }
2440
+ 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", `
2442
+ Examples:
2443
+ caik setup
2444
+ caik setup --platform claude-code
2445
+ caik setup --yes --skip-hooks`).action(async (opts) => {
2446
+ const globalOpts = program2.opts();
2447
+ const { apiKey } = resolveConfig(globalOpts);
2448
+ const skipHooks = Boolean(opts.skipHooks);
2449
+ const skipStacks = Boolean(opts.skipStacks);
2450
+ const autoYes = Boolean(opts.yes);
2451
+ const platformFlag = opts.platform;
2452
+ console.log(heading("\nCAIK Setup Wizard"));
2453
+ console.log("\u2550".repeat(40));
2454
+ console.log();
2455
+ let detected;
2456
+ if (platformFlag) {
2457
+ const adapter = getPlatformAdapter(platformFlag);
2458
+ const det = await adapter.detect();
2459
+ if (det) {
2460
+ detected = [det];
2461
+ } else {
2462
+ detected = [{
2463
+ name: platformFlag,
2464
+ tier: "cli-mcp",
2465
+ configPaths: [],
2466
+ capabilities: { hooks: false, mcp: true, skills: false }
2467
+ }];
2468
+ console.log(warn(`Platform "${platformFlag}" was not detected but will be configured anyway.`));
2469
+ }
2470
+ } else {
2471
+ const spinner = createSpinner("Detecting installed platforms...");
2472
+ spinner.start();
2473
+ detected = detectPlatforms();
2474
+ spinner.stop();
2475
+ if (detected.length === 0) {
2476
+ console.log(warn("No supported agent platforms detected."));
2477
+ console.log(info("Supported platforms: claude-code, cursor, openclaw, codex, windsurf"));
2478
+ console.log(info("Use --platform <name> to configure a specific platform."));
2479
+ return;
2480
+ }
2481
+ console.log(info(`Detected ${detected.length} platform(s):`));
2482
+ for (const p of detected) {
2483
+ console.log(` ${success(p.name)} ${dim(`(${p.tier})`)}`);
2484
+ }
2485
+ console.log();
2486
+ }
2487
+ let selected;
2488
+ if (autoYes || detected.length === 1 || platformFlag) {
2489
+ selected = detected;
2490
+ } else {
2491
+ const rl = createInterface5({ input: process.stdin, output: process.stdout });
2492
+ console.log(info("Which platforms would you like to configure?"));
2493
+ for (let i = 0; i < detected.length; i++) {
2494
+ console.log(` ${i + 1}. ${detected[i].name}`);
2495
+ }
2496
+ console.log(` a. All`);
2497
+ const answer = await rl.question("\nSelect (numbers separated by commas, or 'a' for all): ");
2498
+ rl.close();
2499
+ if (answer.toLowerCase() === "a" || answer.trim() === "") {
2500
+ selected = detected;
2501
+ } else {
2502
+ const indices = answer.split(",").map((s) => parseInt(s.trim(), 10) - 1);
2503
+ selected = indices.filter((i) => i >= 0 && i < detected.length).map((i) => detected[i]);
2504
+ if (selected.length === 0) {
2505
+ console.log(warn("No valid platforms selected. Exiting."));
2506
+ return;
2507
+ }
2508
+ }
2509
+ }
2510
+ console.log();
2511
+ for (const platform of selected) {
2512
+ console.log(heading(`Configuring ${platform.name}...`));
2513
+ console.log("\u2500".repeat(40));
2514
+ const adapter = getPlatformAdapter(platform.name);
2515
+ const mcpConfig = getDefaultMcpConfig();
2516
+ const mcpSpinner = createSpinner("Registering MCP server...");
2517
+ mcpSpinner.start();
2518
+ try {
2519
+ await adapter.registerMcp(mcpConfig);
2520
+ mcpSpinner.stop();
2521
+ console.log(success("MCP server registered"));
2522
+ } catch (err) {
2523
+ mcpSpinner.stop();
2524
+ console.log(error(`Failed to register MCP server: ${err instanceof Error ? err.message : String(err)}`));
2525
+ }
2526
+ if (!skipHooks && adapter.registerHooks && platform.capabilities.hooks) {
2527
+ const hookSpinner = createSpinner("Registering hooks...");
2528
+ hookSpinner.start();
2529
+ try {
2530
+ const hookConfig = getHookConfig(platform.name);
2531
+ await adapter.registerHooks(hookConfig);
2532
+ hookSpinner.stop();
2533
+ console.log(success("Hooks registered"));
2534
+ } catch (err) {
2535
+ hookSpinner.stop();
2536
+ console.log(error(`Failed to register hooks: ${err instanceof Error ? err.message : String(err)}`));
2537
+ }
2538
+ } else if (!skipHooks && !platform.capabilities.hooks) {
2539
+ console.log(dim(" Hooks not supported on this platform \u2014 skipped"));
2540
+ } else if (skipHooks) {
2541
+ console.log(dim(" Hooks skipped (--skip-hooks)"));
2542
+ }
2543
+ if (adapter.installSkill && platform.capabilities.skills) {
2544
+ const skillSpinner = createSpinner("Installing SKILL.md...");
2545
+ skillSpinner.start();
2546
+ try {
2547
+ const content = getSkillContent(platform.name);
2548
+ await adapter.installSkill("caik", content);
2549
+ skillSpinner.stop();
2550
+ console.log(success("SKILL.md installed"));
2551
+ } catch (err) {
2552
+ skillSpinner.stop();
2553
+ console.log(error(`Failed to install SKILL.md: ${err instanceof Error ? err.message : String(err)}`));
2554
+ }
2555
+ }
2556
+ console.log();
2557
+ }
2558
+ if (!skipStacks) {
2559
+ console.log(dim("Stack recommendations: coming soon"));
2560
+ console.log();
2561
+ }
2562
+ if (!apiKey) {
2563
+ console.log(warn("Not authenticated. Run `caik init --auth` to connect your account."));
2564
+ console.log();
2565
+ }
2566
+ console.log(heading("Setup Complete"));
2567
+ console.log("\u2550".repeat(40));
2568
+ console.log(success(`Configured ${selected.length} platform(s): ${selected.map((p) => p.name).join(", ")}`));
2569
+ if (!apiKey) {
2570
+ console.log(info("Next step: run `caik init --auth` to authenticate"));
2571
+ } else {
2572
+ console.log(info("Next step: run `caik search <query>` to find artifacts"));
2573
+ }
2574
+ console.log();
2575
+ });
2576
+ }
2577
+
772
2578
  // src/index.ts
773
2579
  var __filename = fileURLToPath(import.meta.url);
774
- var __dirname = dirname2(__filename);
2580
+ var __dirname = dirname5(__filename);
775
2581
  var version = "0.0.1";
776
2582
  try {
777
- const pkgPath = join2(__dirname, "..", "package.json");
778
- const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
2583
+ const pkgPath = join8(__dirname, "..", "package.json");
2584
+ const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
779
2585
  version = pkg.version;
780
2586
  } catch {
781
2587
  }
@@ -793,6 +2599,8 @@ registerAlternativesCommand(program);
793
2599
  registerUpdateCommand(program);
794
2600
  registerUninstallCommand(program);
795
2601
  registerKarmaCommand(program);
2602
+ registerHookCommand(program);
2603
+ registerSetupCommand(program);
796
2604
  program.exitOverride();
797
2605
  async function main() {
798
2606
  try {