@cleocode/caamp 2026.4.4 → 2026.4.5

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.
@@ -1,8 +1,9 @@
1
1
  import {
2
2
  getAllProviders,
3
+ getPrimaryProvider,
3
4
  groupByInstructFile,
4
5
  injectAll
5
- } from "./chunk-CRU25LRL.js";
6
+ } from "./chunk-KWYLZ46H.js";
6
7
  import {
7
8
  __export,
8
9
  getAgentsConfigPath,
@@ -14,7 +15,7 @@ import {
14
15
  getPlatformLocations,
15
16
  resolveProviderProjectPath,
16
17
  resolveProviderSkillsDirs
17
- } from "./chunk-XWQ5WPHC.js";
18
+ } from "./chunk-364OHA2T.js";
18
19
 
19
20
  // src/core/logger.ts
20
21
  var verboseMode = false;
@@ -300,8 +301,8 @@ async function removeSkill(skillName, providers, isGlobal, projectDir) {
300
301
  }
301
302
  async function listCanonicalSkills() {
302
303
  if (!existsSync2(getCanonicalSkillsDir())) return [];
303
- const { readdir: readdir2 } = await import("fs/promises");
304
- const entries = await readdir2(getCanonicalSkillsDir(), { withFileTypes: true });
304
+ const { readdir: readdir3 } = await import("fs/promises");
305
+ const entries = await readdir3(getCanonicalSkillsDir(), { withFileTypes: true });
305
306
  return entries.filter((e) => e.isDirectory() || e.isSymbolicLink()).map((e) => e.name);
306
307
  }
307
308
 
@@ -321,6 +322,7 @@ var AGENTS_CONFIG_PATH = getAgentsConfigPath();
321
322
 
322
323
  // src/core/advanced/orchestration.ts
323
324
  var PRIORITY_ORDER = {
325
+ primary: -1,
324
326
  high: 0,
325
327
  medium: 1,
326
328
  low: 2
@@ -498,20 +500,430 @@ async function updateInstructionsSingleOperation(providers, content, scope = "pr
498
500
  file: filePath,
499
501
  action,
500
502
  providers: selected.map((provider) => provider.id),
501
- configFormats: Array.from(new Set(selected.map((provider) => provider.configFormat)))
503
+ configFormats: Array.from(
504
+ new Set(
505
+ selected.map((provider) => provider.capabilities.mcp?.configFormat).filter((f) => f !== void 0)
506
+ )
507
+ )
502
508
  });
503
509
  }
504
510
  return summary;
505
511
  }
506
512
 
513
+ // src/core/harness/pi.ts
514
+ import { spawn } from "child_process";
515
+ import { existsSync as existsSync4 } from "fs";
516
+ import { cp as cp3, mkdir as mkdir3, readdir, readFile, rename, rm as rm3, writeFile } from "fs/promises";
517
+ import { homedir } from "os";
518
+ import { dirname as dirname2, join as join4 } from "path";
519
+ var MARKER_START = "<!-- CAAMP:START -->";
520
+ var MARKER_END = "<!-- CAAMP:END -->";
521
+ var MARKER_PATTERN = /<!-- CAAMP:START -->[\s\S]*?<!-- CAAMP:END -->/;
522
+ function getPiAgentDir() {
523
+ const env = process.env["PI_CODING_AGENT_DIR"];
524
+ if (env !== void 0 && env.length > 0) {
525
+ if (env === "~") return homedir();
526
+ if (env.startsWith("~/")) return join4(homedir(), env.slice(2));
527
+ return env;
528
+ }
529
+ return join4(homedir(), ".pi", "agent");
530
+ }
531
+ function isPlainObject(v) {
532
+ return typeof v === "object" && v !== null && !Array.isArray(v);
533
+ }
534
+ function deepMerge(target, patch) {
535
+ const out = { ...target };
536
+ for (const [key, value] of Object.entries(patch)) {
537
+ const existing = out[key];
538
+ if (isPlainObject(value) && isPlainObject(existing)) {
539
+ out[key] = deepMerge(existing, value);
540
+ } else {
541
+ out[key] = value;
542
+ }
543
+ }
544
+ return out;
545
+ }
546
+ async function atomicWriteJson(filePath, data) {
547
+ await mkdir3(dirname2(filePath), { recursive: true });
548
+ const tmp = `${filePath}.tmp.${process.pid}`;
549
+ await writeFile(tmp, `${JSON.stringify(data, null, 2)}
550
+ `, "utf8");
551
+ await rename(tmp, filePath);
552
+ }
553
+ var PiHarness = class {
554
+ /**
555
+ * Construct a harness bound to a resolved Pi provider.
556
+ *
557
+ * @param provider - The resolved provider entry for `"pi"`.
558
+ */
559
+ constructor(provider) {
560
+ this.provider = provider;
561
+ }
562
+ /** Provider id, always `"pi"`. */
563
+ id = "pi";
564
+ // ── Path helpers ────────────────────────────────────────────────────
565
+ /**
566
+ * Resolve the skills directory for a given scope.
567
+ */
568
+ skillsDir(scope) {
569
+ return scope.kind === "global" ? join4(getPiAgentDir(), "skills") : join4(scope.projectDir, ".pi", "skills");
570
+ }
571
+ /**
572
+ * Resolve the extensions directory for a given scope.
573
+ */
574
+ extensionsDir(scope) {
575
+ return scope.kind === "global" ? join4(getPiAgentDir(), "extensions") : join4(scope.projectDir, ".pi", "extensions");
576
+ }
577
+ /**
578
+ * Resolve the settings.json path for a given scope.
579
+ */
580
+ settingsPath(scope) {
581
+ return scope.kind === "global" ? join4(getPiAgentDir(), "settings.json") : join4(scope.projectDir, ".pi", "settings.json");
582
+ }
583
+ /**
584
+ * Resolve the AGENTS.md instruction file path for a given scope.
585
+ *
586
+ * @remarks
587
+ * Global scope lives under the Pi state root; project scope lives at
588
+ * the project root (NOT under `.pi/`), matching Pi's convention of
589
+ * auto-discovering `AGENTS.md` from the working directory upwards.
590
+ */
591
+ agentsMdPath(scope) {
592
+ return scope.kind === "global" ? join4(getPiAgentDir(), "AGENTS.md") : join4(scope.projectDir, "AGENTS.md");
593
+ }
594
+ // ── Skills ──────────────────────────────────────────────────────────
595
+ /** {@inheritDoc Harness.installSkill} */
596
+ async installSkill(sourcePath, skillName, scope) {
597
+ const targetDir = join4(this.skillsDir(scope), skillName);
598
+ await rm3(targetDir, { recursive: true, force: true });
599
+ await mkdir3(dirname2(targetDir), { recursive: true });
600
+ await cp3(sourcePath, targetDir, { recursive: true });
601
+ }
602
+ /** {@inheritDoc Harness.removeSkill} */
603
+ async removeSkill(skillName, scope) {
604
+ const targetDir = join4(this.skillsDir(scope), skillName);
605
+ await rm3(targetDir, { recursive: true, force: true });
606
+ }
607
+ /** {@inheritDoc Harness.listSkills} */
608
+ async listSkills(scope) {
609
+ const dir = this.skillsDir(scope);
610
+ if (!existsSync4(dir)) return [];
611
+ const entries = await readdir(dir, { withFileTypes: true });
612
+ return entries.filter((e) => e.isDirectory()).map((e) => e.name);
613
+ }
614
+ // ── Instructions ────────────────────────────────────────────────────
615
+ /** {@inheritDoc Harness.injectInstructions} */
616
+ async injectInstructions(content, scope) {
617
+ const filePath = this.agentsMdPath(scope);
618
+ await mkdir3(dirname2(filePath), { recursive: true });
619
+ const block = `${MARKER_START}
620
+ ${content.trim()}
621
+ ${MARKER_END}`;
622
+ let existing = "";
623
+ if (existsSync4(filePath)) {
624
+ existing = await readFile(filePath, "utf8");
625
+ }
626
+ let updated;
627
+ if (MARKER_PATTERN.test(existing)) {
628
+ updated = existing.replace(MARKER_PATTERN, block);
629
+ } else if (existing.length === 0) {
630
+ updated = `${block}
631
+ `;
632
+ } else {
633
+ const separator = existing.endsWith("\n") ? "\n" : "\n\n";
634
+ updated = `${existing}${separator}${block}
635
+ `;
636
+ }
637
+ await writeFile(filePath, updated, "utf8");
638
+ }
639
+ /** {@inheritDoc Harness.removeInstructions} */
640
+ async removeInstructions(scope) {
641
+ const filePath = this.agentsMdPath(scope);
642
+ if (!existsSync4(filePath)) return;
643
+ const existing = await readFile(filePath, "utf8");
644
+ if (!MARKER_PATTERN.test(existing)) return;
645
+ const stripped = existing.replace(MARKER_PATTERN, "").replace(/\n{3,}/g, "\n\n").trimEnd();
646
+ await writeFile(filePath, stripped.length === 0 ? "" : `${stripped}
647
+ `, "utf8");
648
+ }
649
+ // ── MCP-as-extension scaffold ───────────────────────────────────────
650
+ /**
651
+ * {@inheritDoc Harness.installMcpAsExtension}
652
+ *
653
+ * @remarks
654
+ * Emits a SCAFFOLD Pi extension file under `extensions/mcp-<name>.ts`.
655
+ * The scaffold registers a Pi tool whose `execute` function currently
656
+ * returns an "isError" payload explaining that the MCP bridge runtime
657
+ * is not yet implemented. This preserves the public lifecycle surface
658
+ * (install/list/remove) so orchestration code can treat the bridge as
659
+ * a first-class asset while the concrete JSON-RPC runtime is built out
660
+ * in a later wave.
661
+ */
662
+ async installMcpAsExtension(server, scope) {
663
+ const dir = this.extensionsDir(scope);
664
+ await mkdir3(dir, { recursive: true });
665
+ const filePath = join4(dir, `mcp-${server.name}.ts`);
666
+ const launchConfig = JSON.stringify(
667
+ {
668
+ command: server.command,
669
+ args: server.args ?? [],
670
+ url: server.url,
671
+ env: server.env ?? {},
672
+ headers: server.headers ?? {}
673
+ },
674
+ null,
675
+ 2
676
+ );
677
+ const src = `// AUTO-GENERATED by @cleocode/caamp \u2014 do not edit.
678
+ // MCP-as-Pi-extension bridge scaffold for "${server.name}".
679
+ // TODO: implement the MCP JSON-RPC bridge. Current behavior is a stub
680
+ // that logs every tool invocation. The scaffold exists so that CAAMP
681
+ // can manage the extension lifecycle (install/remove/list) without
682
+ // blocking on a full MCP runtime bridge.
683
+
684
+ const CONFIG = ${launchConfig};
685
+
686
+ export default (pi: unknown) => {
687
+ const api = pi as {
688
+ registerTool: (def: {
689
+ name: string;
690
+ label: string;
691
+ description: string;
692
+ parameters: unknown;
693
+ execute: (...args: unknown[]) => Promise<{ type: 'text'; text: string; isError?: boolean }>;
694
+ }) => void;
695
+ };
696
+
697
+ api.registerTool({
698
+ name: ${JSON.stringify(`mcp_${server.name}`)},
699
+ label: ${JSON.stringify(`MCP: ${server.name}`)},
700
+ description: ${JSON.stringify(
701
+ `MCP server "${server.name}" \u2014 bridge scaffold, not yet implemented.`
702
+ )},
703
+ parameters: { type: 'object', properties: {} },
704
+ execute: async () => ({
705
+ type: 'text',
706
+ text: \`MCP bridge for "${server.name}" is a scaffold. Config: \${JSON.stringify(CONFIG)}\`,
707
+ isError: true,
708
+ }),
709
+ });
710
+ };
711
+ `;
712
+ await writeFile(filePath, src, "utf8");
713
+ }
714
+ // ── Subagent spawn ──────────────────────────────────────────────────
715
+ /**
716
+ * {@inheritDoc Harness.spawnSubagent}
717
+ *
718
+ * @remarks
719
+ * Invokes Pi's configured `spawnCommand` (e.g.
720
+ * `["pi", "--mode", "json", "-p", "--no-session"]`) with the task prompt
721
+ * appended as the trailing positional argument. The {@link SubagentTask.targetProviderId}
722
+ * is a routing hint carried in the prompt stream; Pi's own extension
723
+ * layer dispatches to the correct inner agent.
724
+ *
725
+ * Throws immediately when the provider entry is missing a `spawnCommand`
726
+ * so callers see configuration errors early rather than at child-exit time.
727
+ */
728
+ async spawnSubagent(task) {
729
+ const cmd = this.provider.capabilities.spawn.spawnCommand;
730
+ if (cmd === null || cmd.length === 0) {
731
+ throw new Error(
732
+ "PiHarness.spawnSubagent: provider has no spawn.spawnCommand in capabilities"
733
+ );
734
+ }
735
+ const program = cmd[0];
736
+ if (typeof program !== "string" || program.length === 0) {
737
+ throw new Error("PiHarness.spawnSubagent: invalid spawnCommand (missing program)");
738
+ }
739
+ const baseArgs = cmd.slice(1);
740
+ const args = [...baseArgs, task.prompt];
741
+ const child = spawn(program, args, {
742
+ cwd: task.cwd,
743
+ env: { ...process.env, ...task.env },
744
+ stdio: ["ignore", "pipe", "pipe"]
745
+ });
746
+ let stdout = "";
747
+ let stderr = "";
748
+ child.stdout?.on("data", (chunk) => {
749
+ stdout += chunk.toString("utf8");
750
+ });
751
+ child.stderr?.on("data", (chunk) => {
752
+ stderr += chunk.toString("utf8");
753
+ });
754
+ const result = new Promise((resolve) => {
755
+ child.on("close", (exitCode) => {
756
+ let parsed;
757
+ try {
758
+ parsed = JSON.parse(stdout);
759
+ } catch {
760
+ }
761
+ resolve({ exitCode, stdout, stderr, parsed });
762
+ });
763
+ });
764
+ if (task.signal !== void 0) {
765
+ task.signal.addEventListener("abort", () => {
766
+ child.kill();
767
+ });
768
+ }
769
+ return {
770
+ pid: child.pid ?? null,
771
+ result,
772
+ abort: () => {
773
+ child.kill();
774
+ }
775
+ };
776
+ }
777
+ // ── Settings ────────────────────────────────────────────────────────
778
+ /** {@inheritDoc Harness.readSettings} */
779
+ async readSettings(scope) {
780
+ const filePath = this.settingsPath(scope);
781
+ if (!existsSync4(filePath)) return {};
782
+ const raw = await readFile(filePath, "utf8");
783
+ try {
784
+ return JSON.parse(raw);
785
+ } catch {
786
+ return {};
787
+ }
788
+ }
789
+ /** {@inheritDoc Harness.writeSettings} */
790
+ async writeSettings(patch, scope) {
791
+ const filePath = this.settingsPath(scope);
792
+ const current = await this.readSettings(scope);
793
+ const currentObj = isPlainObject(current) ? current : {};
794
+ const merged = deepMerge(currentObj, patch);
795
+ await atomicWriteJson(filePath, merged);
796
+ }
797
+ /** {@inheritDoc Harness.configureModels} */
798
+ async configureModels(modelPatterns, scope) {
799
+ await this.writeSettings({ enabledModels: modelPatterns }, scope);
800
+ }
801
+ };
802
+
803
+ // src/core/harness/index.ts
804
+ function getHarnessFor(provider) {
805
+ if (provider.id === "pi") return new PiHarness(provider);
806
+ return null;
807
+ }
808
+ function getPrimaryHarness() {
809
+ const primary = getPrimaryProvider();
810
+ if (primary === void 0) return null;
811
+ return getHarnessFor(primary);
812
+ }
813
+ function getAllHarnesses() {
814
+ const result = [];
815
+ for (const provider of getAllProviders()) {
816
+ const harness = getHarnessFor(provider);
817
+ if (harness !== null) result.push(harness);
818
+ }
819
+ return result;
820
+ }
821
+ function resolveDefaultTargetProviders() {
822
+ let primary = null;
823
+ try {
824
+ primary = getPrimaryHarness();
825
+ } catch {
826
+ primary = null;
827
+ }
828
+ const installed = getInstalledProviders();
829
+ if (primary !== null) {
830
+ const primaryId = primary.provider.id;
831
+ const primaryInstalled = installed.some((p) => p.id === primaryId);
832
+ if (primaryInstalled) {
833
+ return [primary.provider];
834
+ }
835
+ }
836
+ const highTier = installed.filter(
837
+ (provider) => provider.priority === "primary" || provider.priority === "high"
838
+ );
839
+ if (highTier.length > 0) {
840
+ return highTier;
841
+ }
842
+ return installed;
843
+ }
844
+ async function dispatchInstallSkillAcrossProviders(sourcePath, skillName, providers, isGlobal, projectDir) {
845
+ const harnessTargets = [];
846
+ const genericTargets = [];
847
+ for (const provider of providers) {
848
+ const harness = getHarnessFor(provider);
849
+ if (harness !== null) {
850
+ harnessTargets.push({ provider, harness });
851
+ } else {
852
+ genericTargets.push(provider);
853
+ }
854
+ }
855
+ const linkedAgents = [];
856
+ const errors = [];
857
+ const scope = isGlobal ? { kind: "global" } : { kind: "project", projectDir: projectDir ?? process.cwd() };
858
+ for (const { provider, harness } of harnessTargets) {
859
+ try {
860
+ await harness.installSkill(sourcePath, skillName, scope);
861
+ linkedAgents.push(provider.id);
862
+ } catch (err) {
863
+ errors.push(`${provider.id}: ${err instanceof Error ? err.message : String(err)}`);
864
+ }
865
+ }
866
+ let canonicalPath = "";
867
+ if (genericTargets.length > 0) {
868
+ const genericResult = projectDir !== void 0 ? await installSkill(sourcePath, skillName, genericTargets, isGlobal, projectDir) : await installSkill(sourcePath, skillName, genericTargets, isGlobal);
869
+ canonicalPath = genericResult.canonicalPath;
870
+ for (const id of genericResult.linkedAgents) {
871
+ linkedAgents.push(id);
872
+ }
873
+ for (const err of genericResult.errors) {
874
+ errors.push(err);
875
+ }
876
+ } else if (linkedAgents.length > 0) {
877
+ canonicalPath = sourcePath;
878
+ }
879
+ return {
880
+ name: skillName,
881
+ canonicalPath,
882
+ linkedAgents,
883
+ errors,
884
+ success: linkedAgents.length > 0
885
+ };
886
+ }
887
+ async function dispatchRemoveSkillAcrossProviders(skillName, providers, isGlobal, projectDir) {
888
+ const harnessTargets = [];
889
+ const genericTargets = [];
890
+ for (const provider of providers) {
891
+ const harness = getHarnessFor(provider);
892
+ if (harness !== null) {
893
+ harnessTargets.push({ provider, harness });
894
+ } else {
895
+ genericTargets.push(provider);
896
+ }
897
+ }
898
+ const removed = [];
899
+ const errors = [];
900
+ const scope = isGlobal ? { kind: "global" } : { kind: "project", projectDir: projectDir ?? process.cwd() };
901
+ for (const { provider, harness } of harnessTargets) {
902
+ try {
903
+ await harness.removeSkill(skillName, scope);
904
+ removed.push(provider.id);
905
+ } catch (err) {
906
+ errors.push(`${provider.id}: ${err instanceof Error ? err.message : String(err)}`);
907
+ }
908
+ }
909
+ const genericResult = projectDir !== void 0 ? await removeSkill(skillName, genericTargets, isGlobal, projectDir) : await removeSkill(skillName, genericTargets, isGlobal);
910
+ for (const id of genericResult.removed) {
911
+ removed.push(id);
912
+ }
913
+ for (const err of genericResult.errors) {
914
+ errors.push(err);
915
+ }
916
+ return { removed, errors };
917
+ }
918
+
507
919
  // src/core/formats/utils.ts
508
- function deepMerge(target, source) {
920
+ function deepMerge2(target, source) {
509
921
  const result = { ...target };
510
922
  for (const key of Object.keys(source)) {
511
923
  const sourceVal = source[key];
512
924
  const targetVal = target[key];
513
925
  if (sourceVal !== null && typeof sourceVal === "object" && !Array.isArray(sourceVal) && targetVal !== null && typeof targetVal === "object" && !Array.isArray(targetVal)) {
514
- result[key] = deepMerge(
926
+ result[key] = deepMerge2(
515
927
  targetVal,
516
928
  sourceVal
517
929
  );
@@ -531,18 +943,18 @@ function getNestedValue(obj, keyPath) {
531
943
  return current;
532
944
  }
533
945
  async function ensureDir(filePath) {
534
- const { mkdir: mkdir4 } = await import("fs/promises");
535
- const { dirname: dirname3 } = await import("path");
536
- await mkdir4(dirname3(filePath), { recursive: true });
946
+ const { mkdir: mkdir5 } = await import("fs/promises");
947
+ const { dirname: dirname4 } = await import("path");
948
+ await mkdir5(dirname4(filePath), { recursive: true });
537
949
  }
538
950
 
539
951
  // src/core/formats/json.ts
540
- import { existsSync as existsSync4 } from "fs";
541
- import { readFile, writeFile } from "fs/promises";
952
+ import { existsSync as existsSync5 } from "fs";
953
+ import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
542
954
  import * as jsonc from "jsonc-parser";
543
955
  async function readJsonConfig(filePath) {
544
- if (!existsSync4(filePath)) return {};
545
- const content = await readFile(filePath, "utf-8");
956
+ if (!existsSync5(filePath)) return {};
957
+ const content = await readFile2(filePath, "utf-8");
546
958
  if (!content.trim()) return {};
547
959
  const errors = [];
548
960
  const result = jsonc.parse(content, errors);
@@ -568,8 +980,8 @@ function detectIndent(content) {
568
980
  async function writeJsonConfig(filePath, configKey, serverName, serverConfig) {
569
981
  await ensureDir(filePath);
570
982
  let content;
571
- if (existsSync4(filePath)) {
572
- content = await readFile(filePath, "utf-8");
983
+ if (existsSync5(filePath)) {
984
+ content = await readFile2(filePath, "utf-8");
573
985
  if (!content.trim()) {
574
986
  content = "{}";
575
987
  }
@@ -591,11 +1003,11 @@ async function writeJsonConfig(filePath, configKey, serverName, serverConfig) {
591
1003
  if (!content.endsWith("\n")) {
592
1004
  content += "\n";
593
1005
  }
594
- await writeFile(filePath, content, "utf-8");
1006
+ await writeFile2(filePath, content, "utf-8");
595
1007
  }
596
1008
  async function removeJsonConfig(filePath, configKey, serverName) {
597
- if (!existsSync4(filePath)) return false;
598
- let content = await readFile(filePath, "utf-8");
1009
+ if (!existsSync5(filePath)) return false;
1010
+ let content = await readFile2(filePath, "utf-8");
599
1011
  if (!content.trim()) return false;
600
1012
  const { tabSize, insertSpaces } = detectIndent(content);
601
1013
  const formatOptions = {
@@ -611,17 +1023,17 @@ async function removeJsonConfig(filePath, configKey, serverName) {
611
1023
  if (!content.endsWith("\n")) {
612
1024
  content += "\n";
613
1025
  }
614
- await writeFile(filePath, content, "utf-8");
1026
+ await writeFile2(filePath, content, "utf-8");
615
1027
  return true;
616
1028
  }
617
1029
 
618
1030
  // src/core/formats/toml.ts
619
- import { existsSync as existsSync5 } from "fs";
620
- import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
1031
+ import { existsSync as existsSync6 } from "fs";
1032
+ import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
621
1033
  import TOML from "@iarna/toml";
622
1034
  async function readTomlConfig(filePath) {
623
- if (!existsSync5(filePath)) return {};
624
- const content = await readFile2(filePath, "utf-8");
1035
+ if (!existsSync6(filePath)) return {};
1036
+ const content = await readFile3(filePath, "utf-8");
625
1037
  if (!content.trim()) return {};
626
1038
  const result = TOML.parse(content);
627
1039
  return result;
@@ -634,12 +1046,12 @@ async function writeTomlConfig(filePath, configKey, serverName, serverConfig) {
634
1046
  for (const part of [...keyParts].reverse()) {
635
1047
  newEntry = { [part]: newEntry };
636
1048
  }
637
- const merged = deepMerge(existing, newEntry);
1049
+ const merged = deepMerge2(existing, newEntry);
638
1050
  const content = TOML.stringify(merged);
639
- await writeFile2(filePath, content, "utf-8");
1051
+ await writeFile3(filePath, content, "utf-8");
640
1052
  }
641
1053
  async function removeTomlConfig(filePath, configKey, serverName) {
642
- if (!existsSync5(filePath)) return false;
1054
+ if (!existsSync6(filePath)) return false;
643
1055
  const existing = await readTomlConfig(filePath);
644
1056
  const keyParts = configKey.split(".");
645
1057
  let current = existing;
@@ -651,17 +1063,17 @@ async function removeTomlConfig(filePath, configKey, serverName) {
651
1063
  if (!(serverName in current)) return false;
652
1064
  delete current[serverName];
653
1065
  const content = TOML.stringify(existing);
654
- await writeFile2(filePath, content, "utf-8");
1066
+ await writeFile3(filePath, content, "utf-8");
655
1067
  return true;
656
1068
  }
657
1069
 
658
1070
  // src/core/formats/yaml.ts
659
- import { existsSync as existsSync6 } from "fs";
660
- import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
1071
+ import { existsSync as existsSync7 } from "fs";
1072
+ import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
661
1073
  import yaml from "js-yaml";
662
1074
  async function readYamlConfig(filePath) {
663
- if (!existsSync6(filePath)) return {};
664
- const content = await readFile3(filePath, "utf-8");
1075
+ if (!existsSync7(filePath)) return {};
1076
+ const content = await readFile4(filePath, "utf-8");
665
1077
  if (!content.trim()) return {};
666
1078
  const result = yaml.load(content);
667
1079
  return result ?? {};
@@ -674,17 +1086,17 @@ async function writeYamlConfig(filePath, configKey, serverName, serverConfig) {
674
1086
  for (const part of [...keyParts].reverse()) {
675
1087
  newEntry = { [part]: newEntry };
676
1088
  }
677
- const merged = deepMerge(existing, newEntry);
1089
+ const merged = deepMerge2(existing, newEntry);
678
1090
  const content = yaml.dump(merged, {
679
1091
  indent: 2,
680
1092
  lineWidth: -1,
681
1093
  noRefs: true,
682
1094
  sortKeys: false
683
1095
  });
684
- await writeFile3(filePath, content, "utf-8");
1096
+ await writeFile4(filePath, content, "utf-8");
685
1097
  }
686
1098
  async function removeYamlConfig(filePath, configKey, serverName) {
687
- if (!existsSync6(filePath)) return false;
1099
+ if (!existsSync7(filePath)) return false;
688
1100
  const existing = await readYamlConfig(filePath);
689
1101
  const keyParts = configKey.split(".");
690
1102
  let current = existing;
@@ -701,7 +1113,7 @@ async function removeYamlConfig(filePath, configKey, serverName) {
701
1113
  noRefs: true,
702
1114
  sortKeys: false
703
1115
  });
704
- await writeFile3(filePath, content, "utf-8");
1116
+ await writeFile4(filePath, content, "utf-8");
705
1117
  return true;
706
1118
  }
707
1119
 
@@ -749,8 +1161,8 @@ async function removeConfig(filePath, format, key, serverName) {
749
1161
  }
750
1162
 
751
1163
  // src/core/skills/audit/scanner.ts
752
- import { existsSync as existsSync7 } from "fs";
753
- import { readFile as readFile4 } from "fs/promises";
1164
+ import { existsSync as existsSync8 } from "fs";
1165
+ import { readFile as readFile5 } from "fs/promises";
754
1166
 
755
1167
  // src/core/skills/audit/rules.ts
756
1168
  function rule(id, name, description, severity, category, pattern) {
@@ -1122,10 +1534,10 @@ var SEVERITY_WEIGHTS = {
1122
1534
  info: 0
1123
1535
  };
1124
1536
  async function scanFile(filePath, rules) {
1125
- if (!existsSync7(filePath)) {
1537
+ if (!existsSync8(filePath)) {
1126
1538
  return { file: filePath, findings: [], score: 100, passed: true };
1127
1539
  }
1128
- const content = await readFile4(filePath, "utf-8");
1540
+ const content = await readFile5(filePath, "utf-8");
1129
1541
  const lines = content.split("\n");
1130
1542
  const activeRules = rules ?? AUDIT_RULES;
1131
1543
  const findings = [];
@@ -1155,15 +1567,15 @@ async function scanFile(filePath, rules) {
1155
1567
  return { file: filePath, findings, score, passed };
1156
1568
  }
1157
1569
  async function scanDirectory(dirPath) {
1158
- const { readdir: readdir2 } = await import("fs/promises");
1159
- const { join: join7 } = await import("path");
1160
- if (!existsSync7(dirPath)) return [];
1161
- const entries = await readdir2(dirPath, { withFileTypes: true });
1570
+ const { readdir: readdir3 } = await import("fs/promises");
1571
+ const { join: join8 } = await import("path");
1572
+ if (!existsSync8(dirPath)) return [];
1573
+ const entries = await readdir3(dirPath, { withFileTypes: true });
1162
1574
  const results = [];
1163
1575
  for (const entry of entries) {
1164
1576
  if (entry.isDirectory() || entry.isSymbolicLink()) {
1165
- const skillFile = join7(dirPath, entry.name, "SKILL.md");
1166
- if (existsSync7(skillFile)) {
1577
+ const skillFile = join8(dirPath, entry.name, "SKILL.md");
1578
+ if (existsSync8(skillFile)) {
1167
1579
  results.push(await scanFile(skillFile));
1168
1580
  }
1169
1581
  }
@@ -1349,7 +1761,7 @@ function parseSource(input) {
1349
1761
  value: input,
1350
1762
  inferredName: inferName(input, "library"),
1351
1763
  owner: libraryMatch[1],
1352
- // This will be the package name, e.g. @cleocode/ct-skills
1764
+ // This will be the package name, e.g. @cleocode/skills
1353
1765
  repo: libraryMatch[2]
1354
1766
  // This will be the skill name, e.g. ct-research-agent
1355
1767
  };
@@ -1384,8 +1796,8 @@ import { promisify } from "util";
1384
1796
  import { simpleGit } from "simple-git";
1385
1797
 
1386
1798
  // src/core/lock-utils.ts
1387
- import { existsSync as existsSync8 } from "fs";
1388
- import { mkdir as mkdir3, open, readFile as readFile5, rename, rm as rm3, stat, writeFile as writeFile4 } from "fs/promises";
1799
+ import { existsSync as existsSync9 } from "fs";
1800
+ import { mkdir as mkdir4, open, readFile as readFile6, rename as rename2, rm as rm4, stat, writeFile as writeFile5 } from "fs/promises";
1389
1801
  var LOCK_GUARD_PATH = `${LOCK_FILE_PATH}.lock`;
1390
1802
  var STALE_LOCK_MS = 5e3;
1391
1803
  function sleep(ms) {
@@ -1395,7 +1807,7 @@ async function removeStaleLock() {
1395
1807
  try {
1396
1808
  const info = await stat(LOCK_GUARD_PATH);
1397
1809
  if (Date.now() - info.mtimeMs > STALE_LOCK_MS) {
1398
- await rm3(LOCK_GUARD_PATH, { force: true });
1810
+ await rm4(LOCK_GUARD_PATH, { force: true });
1399
1811
  return true;
1400
1812
  }
1401
1813
  } catch {
@@ -1403,7 +1815,7 @@ async function removeStaleLock() {
1403
1815
  return false;
1404
1816
  }
1405
1817
  async function acquireLockGuard(retries = 40, delayMs = 25) {
1406
- await mkdir3(AGENTS_HOME, { recursive: true });
1818
+ await mkdir4(AGENTS_HOME, { recursive: true });
1407
1819
  for (let attempt = 0; attempt < retries; attempt += 1) {
1408
1820
  try {
1409
1821
  const handle = await open(LOCK_GUARD_PATH, "wx");
@@ -1423,19 +1835,19 @@ async function acquireLockGuard(retries = 40, delayMs = 25) {
1423
1835
  throw new Error("Timed out waiting for lock file guard");
1424
1836
  }
1425
1837
  async function releaseLockGuard() {
1426
- await rm3(LOCK_GUARD_PATH, { force: true });
1838
+ await rm4(LOCK_GUARD_PATH, { force: true });
1427
1839
  }
1428
1840
  async function writeLockFileUnsafe(lock) {
1429
1841
  const tmpPath = `${LOCK_FILE_PATH}.tmp-${process.pid}-${Date.now()}`;
1430
- await writeFile4(tmpPath, JSON.stringify(lock, null, 2) + "\n", "utf-8");
1431
- await rename(tmpPath, LOCK_FILE_PATH);
1842
+ await writeFile5(tmpPath, JSON.stringify(lock, null, 2) + "\n", "utf-8");
1843
+ await rename2(tmpPath, LOCK_FILE_PATH);
1432
1844
  }
1433
1845
  async function readLockFile() {
1434
1846
  try {
1435
- if (!existsSync8(LOCK_FILE_PATH)) {
1847
+ if (!existsSync9(LOCK_FILE_PATH)) {
1436
1848
  return { version: 1, skills: {}, mcpServers: {} };
1437
1849
  }
1438
- const content = await readFile5(LOCK_FILE_PATH, "utf-8");
1850
+ const content = await readFile6(LOCK_FILE_PATH, "utf-8");
1439
1851
  return JSON.parse(content);
1440
1852
  } catch {
1441
1853
  return { version: 1, skills: {}, mcpServers: {} };
@@ -2163,9 +2575,9 @@ async function recommendSkills2(query, criteria, options = {}) {
2163
2575
  }
2164
2576
 
2165
2577
  // src/core/skills/library-loader.ts
2166
- import { existsSync as existsSync9, readdirSync, readFileSync } from "fs";
2578
+ import { existsSync as existsSync10, readdirSync, readFileSync } from "fs";
2167
2579
  import { createRequire } from "module";
2168
- import { basename as basename2, dirname as dirname2, join as join4 } from "path";
2580
+ import { basename as basename2, dirname as dirname3, join as join5 } from "path";
2169
2581
  var require2 = createRequire(import.meta.url);
2170
2582
  function loadLibraryFromModule(root) {
2171
2583
  let mod;
@@ -2211,16 +2623,16 @@ function loadLibraryFromModule(root) {
2211
2623
  return mod;
2212
2624
  }
2213
2625
  function buildLibraryFromFiles(root) {
2214
- const catalogPath = join4(root, "skills.json");
2215
- if (!existsSync9(catalogPath)) {
2626
+ const catalogPath = join5(root, "skills.json");
2627
+ if (!existsSync10(catalogPath)) {
2216
2628
  throw new Error(`No skills.json found at ${root}`);
2217
2629
  }
2218
2630
  const catalogData = JSON.parse(readFileSync(catalogPath, "utf-8"));
2219
2631
  const entries = catalogData.skills ?? [];
2220
2632
  const version = catalogData.version ?? "0.0.0";
2221
- const manifestPath = join4(root, "skills", "manifest.json");
2633
+ const manifestPath = join5(root, "skills", "manifest.json");
2222
2634
  let manifest;
2223
- if (existsSync9(manifestPath)) {
2635
+ if (existsSync10(manifestPath)) {
2224
2636
  manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
2225
2637
  } else {
2226
2638
  manifest = {
@@ -2230,14 +2642,14 @@ function buildLibraryFromFiles(root) {
2230
2642
  skills: []
2231
2643
  };
2232
2644
  }
2233
- const profilesDir = join4(root, "profiles");
2645
+ const profilesDir = join5(root, "profiles");
2234
2646
  const profiles = /* @__PURE__ */ new Map();
2235
- if (existsSync9(profilesDir)) {
2647
+ if (existsSync10(profilesDir)) {
2236
2648
  for (const file of readdirSync(profilesDir)) {
2237
2649
  if (!file.endsWith(".json")) continue;
2238
2650
  try {
2239
2651
  const profile = JSON.parse(
2240
- readFileSync(join4(profilesDir, file), "utf-8")
2652
+ readFileSync(join5(profilesDir, file), "utf-8")
2241
2653
  );
2242
2654
  profiles.set(profile.name, profile);
2243
2655
  } catch {
@@ -2251,9 +2663,9 @@ function buildLibraryFromFiles(root) {
2251
2663
  function getSkillDir2(name) {
2252
2664
  const entry = skillMap.get(name);
2253
2665
  if (entry) {
2254
- return dirname2(join4(root, entry.path));
2666
+ return dirname3(join5(root, entry.path));
2255
2667
  }
2256
- return join4(root, "skills", name);
2668
+ return join5(root, "skills", name);
2257
2669
  }
2258
2670
  function resolveDeps(names, visited = /* @__PURE__ */ new Set()) {
2259
2671
  const result = [];
@@ -2281,7 +2693,7 @@ function buildLibraryFromFiles(root) {
2281
2693
  return resolveDeps([...new Set(skills)]);
2282
2694
  }
2283
2695
  function discoverFiles(dir, ext) {
2284
- if (!existsSync9(dir)) return [];
2696
+ if (!existsSync10(dir)) return [];
2285
2697
  return readdirSync(dir).filter((f) => f.endsWith(ext)).map((f) => basename2(f, ext));
2286
2698
  }
2287
2699
  const library = {
@@ -2298,14 +2710,14 @@ function buildLibraryFromFiles(root) {
2298
2710
  getSkillPath(name) {
2299
2711
  const entry = skillMap.get(name);
2300
2712
  if (entry) {
2301
- return join4(root, entry.path);
2713
+ return join5(root, entry.path);
2302
2714
  }
2303
- return join4(root, "skills", name, "SKILL.md");
2715
+ return join5(root, "skills", name, "SKILL.md");
2304
2716
  },
2305
2717
  getSkillDir: getSkillDir2,
2306
2718
  readSkillContent(name) {
2307
2719
  const skillPath = library.getSkillPath(name);
2308
- if (!existsSync9(skillPath)) {
2720
+ if (!existsSync10(skillPath)) {
2309
2721
  throw new Error(`Skill content not found: ${skillPath}`);
2310
2722
  }
2311
2723
  return readFileSync(skillPath, "utf-8");
@@ -2332,11 +2744,11 @@ function buildLibraryFromFiles(root) {
2332
2744
  return resolveProfileByName(name);
2333
2745
  },
2334
2746
  listSharedResources() {
2335
- return discoverFiles(join4(root, "skills", "_shared"), ".md");
2747
+ return discoverFiles(join5(root, "skills", "_shared"), ".md");
2336
2748
  },
2337
2749
  getSharedResourcePath(name) {
2338
- const resourcePath = join4(root, "skills", "_shared", `${name}.md`);
2339
- return existsSync9(resourcePath) ? resourcePath : void 0;
2750
+ const resourcePath = join5(root, "skills", "_shared", `${name}.md`);
2751
+ return existsSync10(resourcePath) ? resourcePath : void 0;
2340
2752
  },
2341
2753
  readSharedResource(name) {
2342
2754
  const resourcePath = library.getSharedResourcePath(name);
@@ -2344,15 +2756,15 @@ function buildLibraryFromFiles(root) {
2344
2756
  return readFileSync(resourcePath, "utf-8");
2345
2757
  },
2346
2758
  listProtocols() {
2347
- const rootProtocols = discoverFiles(join4(root, "protocols"), ".md");
2759
+ const rootProtocols = discoverFiles(join5(root, "protocols"), ".md");
2348
2760
  if (rootProtocols.length > 0) return rootProtocols;
2349
- return discoverFiles(join4(root, "skills", "protocols"), ".md");
2761
+ return discoverFiles(join5(root, "skills", "protocols"), ".md");
2350
2762
  },
2351
2763
  getProtocolPath(name) {
2352
- const rootPath = join4(root, "protocols", `${name}.md`);
2353
- if (existsSync9(rootPath)) return rootPath;
2354
- const skillsPath = join4(root, "skills", "protocols", `${name}.md`);
2355
- return existsSync9(skillsPath) ? skillsPath : void 0;
2764
+ const rootPath = join5(root, "protocols", `${name}.md`);
2765
+ if (existsSync10(rootPath)) return rootPath;
2766
+ const skillsPath = join5(root, "skills", "protocols", `${name}.md`);
2767
+ return existsSync10(skillsPath) ? skillsPath : void 0;
2356
2768
  },
2357
2769
  readProtocol(name) {
2358
2770
  const protocolPath = library.getProtocolPath(name);
@@ -2377,8 +2789,8 @@ function buildLibraryFromFiles(root) {
2377
2789
  if (!entry.version) {
2378
2790
  issues.push({ level: "warn", field: "version", message: "Missing version" });
2379
2791
  }
2380
- const skillPath = join4(root, entry.path);
2381
- if (!existsSync9(skillPath)) {
2792
+ const skillPath = join5(root, entry.path);
2793
+ if (!existsSync10(skillPath)) {
2382
2794
  issues.push({
2383
2795
  level: "error",
2384
2796
  field: "path",
@@ -2437,15 +2849,15 @@ __export(catalog_exports, {
2437
2849
  validateAll: () => validateAll,
2438
2850
  validateSkillFrontmatter: () => validateSkillFrontmatter
2439
2851
  });
2440
- import { existsSync as existsSync10 } from "fs";
2441
- import { join as join5 } from "path";
2852
+ import { existsSync as existsSync11 } from "fs";
2853
+ import { join as join6 } from "path";
2442
2854
  var _library = null;
2443
2855
  function registerSkillLibrary(library) {
2444
2856
  _library = library;
2445
2857
  }
2446
2858
  function registerSkillLibraryFromPath(root) {
2447
- const indexPath = join5(root, "index.js");
2448
- if (existsSync10(indexPath)) {
2859
+ const indexPath = join6(root, "index.js");
2860
+ if (existsSync11(indexPath)) {
2449
2861
  _library = loadLibraryFromModule(root);
2450
2862
  return;
2451
2863
  }
@@ -2456,13 +2868,13 @@ function clearRegisteredLibrary() {
2456
2868
  }
2457
2869
  function discoverLibrary() {
2458
2870
  const envPath = process.env["CAAMP_SKILL_LIBRARY"];
2459
- if (envPath && existsSync10(envPath)) {
2871
+ if (envPath && existsSync11(envPath)) {
2460
2872
  try {
2461
- const indexPath = join5(envPath, "index.js");
2462
- if (existsSync10(indexPath)) {
2873
+ const indexPath = join6(envPath, "index.js");
2874
+ if (existsSync11(indexPath)) {
2463
2875
  return loadLibraryFromModule(envPath);
2464
2876
  }
2465
- if (existsSync10(join5(envPath, "skills.json"))) {
2877
+ if (existsSync11(join6(envPath, "skills.json"))) {
2466
2878
  return buildLibraryFromFiles(envPath);
2467
2879
  }
2468
2880
  } catch {
@@ -2569,13 +2981,13 @@ function getLibraryRoot() {
2569
2981
  }
2570
2982
 
2571
2983
  // src/core/skills/discovery.ts
2572
- import { existsSync as existsSync11 } from "fs";
2573
- import { readdir, readFile as readFile6 } from "fs/promises";
2574
- import { join as join6 } from "path";
2984
+ import { existsSync as existsSync12 } from "fs";
2985
+ import { readdir as readdir2, readFile as readFile7 } from "fs/promises";
2986
+ import { join as join7 } from "path";
2575
2987
  import matter from "gray-matter";
2576
2988
  async function parseSkillFile(filePath) {
2577
2989
  try {
2578
- const content = await readFile6(filePath, "utf-8");
2990
+ const content = await readFile7(filePath, "utf-8");
2579
2991
  const { data } = matter(content);
2580
2992
  if (!data.name || !data.description) {
2581
2993
  return null;
@@ -2595,8 +3007,8 @@ async function parseSkillFile(filePath) {
2595
3007
  }
2596
3008
  }
2597
3009
  async function discoverSkill(skillDir) {
2598
- const skillFile = join6(skillDir, "SKILL.md");
2599
- if (!existsSync11(skillFile)) return null;
3010
+ const skillFile = join7(skillDir, "SKILL.md");
3011
+ if (!existsSync12(skillFile)) return null;
2600
3012
  const metadata = await parseSkillFile(skillFile);
2601
3013
  if (!metadata) return null;
2602
3014
  return {
@@ -2607,12 +3019,12 @@ async function discoverSkill(skillDir) {
2607
3019
  };
2608
3020
  }
2609
3021
  async function discoverSkills(rootDir) {
2610
- if (!existsSync11(rootDir)) return [];
2611
- const entries = await readdir(rootDir, { withFileTypes: true });
3022
+ if (!existsSync12(rootDir)) return [];
3023
+ const entries = await readdir2(rootDir, { withFileTypes: true });
2612
3024
  const skills = [];
2613
3025
  for (const entry of entries) {
2614
3026
  if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
2615
- const skillDir = join6(rootDir, entry.name);
3027
+ const skillDir = join7(rootDir, entry.name);
2616
3028
  const skill = await discoverSkill(skillDir);
2617
3029
  if (skill) {
2618
3030
  skills.push(skill);
@@ -2636,8 +3048,8 @@ async function discoverSkillsMulti(dirs) {
2636
3048
  }
2637
3049
 
2638
3050
  // src/core/skills/validator.ts
2639
- import { existsSync as existsSync12 } from "fs";
2640
- import { readFile as readFile7 } from "fs/promises";
3051
+ import { existsSync as existsSync13 } from "fs";
3052
+ import { readFile as readFile8 } from "fs/promises";
2641
3053
  import matter2 from "gray-matter";
2642
3054
  var RESERVED_NAMES = [
2643
3055
  "anthropic",
@@ -2658,14 +3070,14 @@ var WARN_BODY_LINES = 500;
2658
3070
  var WARN_DESCRIPTION_LENGTH = 50;
2659
3071
  async function validateSkill(filePath) {
2660
3072
  const issues = [];
2661
- if (!existsSync12(filePath)) {
3073
+ if (!existsSync13(filePath)) {
2662
3074
  return {
2663
3075
  valid: false,
2664
3076
  issues: [{ level: "error", field: "file", message: "File does not exist" }],
2665
3077
  metadata: null
2666
3078
  };
2667
3079
  }
2668
- const content = await readFile7(filePath, "utf-8");
3080
+ const content = await readFile8(filePath, "utf-8");
2669
3081
  if (!content.startsWith("---")) {
2670
3082
  issues.push({
2671
3083
  level: "error",
@@ -2793,7 +3205,14 @@ export {
2793
3205
  selectProvidersByMinimumPriority,
2794
3206
  installBatchWithRollback,
2795
3207
  updateInstructionsSingleOperation,
2796
- deepMerge,
3208
+ PiHarness,
3209
+ getHarnessFor,
3210
+ getPrimaryHarness,
3211
+ getAllHarnesses,
3212
+ resolveDefaultTargetProviders,
3213
+ dispatchInstallSkillAcrossProviders,
3214
+ dispatchRemoveSkillAcrossProviders,
3215
+ deepMerge2 as deepMerge,
2797
3216
  getNestedValue,
2798
3217
  ensureDir,
2799
3218
  readConfig,
@@ -2839,4 +3258,4 @@ export {
2839
3258
  discoverSkillsMulti,
2840
3259
  validateSkill
2841
3260
  };
2842
- //# sourceMappingURL=chunk-6NBM4CAF.js.map
3261
+ //# sourceMappingURL=chunk-43GULI6J.js.map