@cleocode/caamp 2026.4.6 → 2026.4.7

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.
@@ -13,6 +13,7 @@ import {
13
13
  getCanonicalSkillsDir,
14
14
  getLockFilePath,
15
15
  getPlatformLocations,
16
+ resolveProviderConfigPath,
16
17
  resolveProviderProjectPath,
17
18
  resolveProviderSkillsDirs
18
19
  } from "./chunk-364OHA2T.js";
@@ -220,8 +221,8 @@ async function linkToAgent(canonicalPath, provider, skillName, isGlobal, project
220
221
  await mkdir(targetSkillsDir, { recursive: true });
221
222
  const linkPath = join2(targetSkillsDir, skillName);
222
223
  if (existsSync2(linkPath)) {
223
- const stat2 = lstatSync(linkPath);
224
- if (stat2.isSymbolicLink()) {
224
+ const stat4 = lstatSync(linkPath);
225
+ if (stat4.isSymbolicLink()) {
225
226
  await rm(linkPath);
226
227
  } else {
227
228
  await rm(linkPath, { recursive: true });
@@ -352,8 +353,8 @@ async function snapshotSkillState(providerTargets, operation, projectDir, backup
352
353
  pathSnapshots.push({ linkPath, state: "missing" });
353
354
  continue;
354
355
  }
355
- const stat2 = lstatSync2(linkPath);
356
- if (stat2.isSymbolicLink()) {
356
+ const stat4 = lstatSync2(linkPath);
357
+ if (stat4.isSymbolicLink()) {
357
358
  pathSnapshots.push({
358
359
  linkPath,
359
360
  state: "symlink",
@@ -363,7 +364,7 @@ async function snapshotSkillState(providerTargets, operation, projectDir, backup
363
364
  }
364
365
  const backupPath = join3(backupRoot, "links", provider.id, `${skillName}-${basename(linkPath)}`);
365
366
  await mkdir2(dirname(backupPath), { recursive: true });
366
- if (stat2.isDirectory()) {
367
+ if (stat4.isDirectory()) {
367
368
  await cp2(linkPath, backupPath, { recursive: true });
368
369
  pathSnapshots.push({ linkPath, state: "directory", backupPath });
369
370
  continue;
@@ -513,12 +514,14 @@ async function updateInstructionsSingleOperation(providers, content, scope = "pr
513
514
  // src/core/harness/pi.ts
514
515
  import { spawn } from "child_process";
515
516
  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 { cp as cp3, mkdir as mkdir3, open, readdir, readFile, rename, rm as rm3, stat, writeFile } from "fs/promises";
518
+ import { homedir as homedir2 } from "os";
519
+ import { basename as basename2, dirname as dirname2, extname, join as join5 } from "path";
520
+
521
+ // src/core/harness/scope.ts
517
522
  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 -->/;
523
+ import { join as join4 } from "path";
524
+ var TIER_PRECEDENCE = ["project", "user", "global"];
522
525
  function getPiAgentDir() {
523
526
  const env = process.env["PI_CODING_AGENT_DIR"];
524
527
  if (env !== void 0 && env.length > 0) {
@@ -528,6 +531,79 @@ function getPiAgentDir() {
528
531
  }
529
532
  return join4(homedir(), ".pi", "agent");
530
533
  }
534
+ function getCleoHomeDir() {
535
+ const env = process.env["CLEO_HOME"];
536
+ if (env !== void 0 && env.trim().length > 0) {
537
+ return env.trim();
538
+ }
539
+ if (process.platform === "win32") {
540
+ const localAppData = process.env["LOCALAPPDATA"];
541
+ if (localAppData !== void 0 && localAppData.length > 0) {
542
+ return join4(localAppData, "cleo", "Data");
543
+ }
544
+ return join4(homedir(), "AppData", "Local", "cleo", "Data");
545
+ }
546
+ if (process.platform === "darwin") {
547
+ return join4(homedir(), "Library", "Application Support", "cleo");
548
+ }
549
+ const xdgData = process.env["XDG_DATA_HOME"];
550
+ if (xdgData !== void 0 && xdgData.length > 0) {
551
+ return join4(xdgData, "cleo");
552
+ }
553
+ return join4(homedir(), ".local", "share", "cleo");
554
+ }
555
+ function assetDirName(kind) {
556
+ switch (kind) {
557
+ case "extensions":
558
+ return { native: "extensions", hubSuffix: "pi-extensions" };
559
+ case "prompts":
560
+ return { native: "prompts", hubSuffix: "pi-prompts" };
561
+ case "themes":
562
+ return { native: "themes", hubSuffix: "pi-themes" };
563
+ case "sessions":
564
+ return { native: "sessions", hubSuffix: "pi-sessions" };
565
+ case "cant":
566
+ return { native: "cant", hubSuffix: "pi-cant" };
567
+ }
568
+ }
569
+ function resolveTierDir(opts) {
570
+ const { tier, kind } = opts;
571
+ const names = assetDirName(kind);
572
+ if (tier === "project") {
573
+ if (opts.projectDir === void 0 || opts.projectDir.length === 0) {
574
+ throw new Error("resolveTierDir: 'project' tier requires a projectDir argument");
575
+ }
576
+ return join4(opts.projectDir, ".pi", names.native);
577
+ }
578
+ if (tier === "user") {
579
+ return join4(getPiAgentDir(), names.native);
580
+ }
581
+ return join4(getCleoHomeDir(), names.hubSuffix);
582
+ }
583
+ function resolveAllTiers(kind, projectDir) {
584
+ const out = [];
585
+ for (const tier of TIER_PRECEDENCE) {
586
+ if (tier === "project" && (projectDir === void 0 || projectDir.length === 0)) {
587
+ continue;
588
+ }
589
+ out.push({ tier, dir: resolveTierDir({ tier, kind, projectDir }) });
590
+ }
591
+ return out;
592
+ }
593
+
594
+ // src/core/harness/pi.ts
595
+ var MARKER_START = "<!-- CAAMP:START -->";
596
+ var MARKER_END = "<!-- CAAMP:END -->";
597
+ var MARKER_PATTERN = /<!-- CAAMP:START -->[\s\S]*?<!-- CAAMP:END -->/;
598
+ function getPiAgentDir2() {
599
+ const env = process.env["PI_CODING_AGENT_DIR"];
600
+ if (env !== void 0 && env.length > 0) {
601
+ if (env === "~") return homedir2();
602
+ if (env.startsWith("~/")) return join5(homedir2(), env.slice(2));
603
+ return env;
604
+ }
605
+ return join5(homedir2(), ".pi", "agent");
606
+ }
531
607
  function isPlainObject(v) {
532
608
  return typeof v === "object" && v !== null && !Array.isArray(v);
533
609
  }
@@ -566,19 +642,13 @@ var PiHarness = class {
566
642
  * Resolve the skills directory for a given scope.
567
643
  */
568
644
  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");
645
+ return scope.kind === "global" ? join5(getPiAgentDir2(), "skills") : join5(scope.projectDir, ".pi", "skills");
576
646
  }
577
647
  /**
578
648
  * Resolve the settings.json path for a given scope.
579
649
  */
580
650
  settingsPath(scope) {
581
- return scope.kind === "global" ? join4(getPiAgentDir(), "settings.json") : join4(scope.projectDir, ".pi", "settings.json");
651
+ return scope.kind === "global" ? join5(getPiAgentDir2(), "settings.json") : join5(scope.projectDir, ".pi", "settings.json");
582
652
  }
583
653
  /**
584
654
  * Resolve the AGENTS.md instruction file path for a given scope.
@@ -589,19 +659,19 @@ var PiHarness = class {
589
659
  * auto-discovering `AGENTS.md` from the working directory upwards.
590
660
  */
591
661
  agentsMdPath(scope) {
592
- return scope.kind === "global" ? join4(getPiAgentDir(), "AGENTS.md") : join4(scope.projectDir, "AGENTS.md");
662
+ return scope.kind === "global" ? join5(getPiAgentDir2(), "AGENTS.md") : join5(scope.projectDir, "AGENTS.md");
593
663
  }
594
664
  // ── Skills ──────────────────────────────────────────────────────────
595
665
  /** {@inheritDoc Harness.installSkill} */
596
666
  async installSkill(sourcePath, skillName, scope) {
597
- const targetDir = join4(this.skillsDir(scope), skillName);
667
+ const targetDir = join5(this.skillsDir(scope), skillName);
598
668
  await rm3(targetDir, { recursive: true, force: true });
599
669
  await mkdir3(dirname2(targetDir), { recursive: true });
600
670
  await cp3(sourcePath, targetDir, { recursive: true });
601
671
  }
602
672
  /** {@inheritDoc Harness.removeSkill} */
603
673
  async removeSkill(skillName, scope) {
604
- const targetDir = join4(this.skillsDir(scope), skillName);
674
+ const targetDir = join5(this.skillsDir(scope), skillName);
605
675
  await rm3(targetDir, { recursive: true, force: true });
606
676
  }
607
677
  /** {@inheritDoc Harness.listSkills} */
@@ -646,71 +716,6 @@ ${MARKER_END}`;
646
716
  await writeFile(filePath, stripped.length === 0 ? "" : `${stripped}
647
717
  `, "utf8");
648
718
  }
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
719
  // ── Subagent spawn ──────────────────────────────────────────────────
715
720
  /**
716
721
  * {@inheritDoc Harness.spawnSubagent}
@@ -798,7 +803,446 @@ export default (pi: unknown) => {
798
803
  async configureModels(modelPatterns, scope) {
799
804
  await this.writeSettings({ enabledModels: modelPatterns }, scope);
800
805
  }
806
+ // ── Wave-1 three-tier helpers ───────────────────────────────────────
807
+ /**
808
+ * Resolve the `models.json` path for a given legacy two-tier scope.
809
+ *
810
+ * @remarks
811
+ * Lives next to `settings.json`. Global scope uses the Pi state root,
812
+ * project scope uses the project's `.pi/` directory, matching the
813
+ * dual-file authority model documented in ADR-035 §D3.
814
+ */
815
+ modelsConfigPath(scope) {
816
+ return scope.kind === "global" ? join5(getPiAgentDir2(), "models.json") : join5(scope.projectDir, ".pi", "models.json");
817
+ }
818
+ /**
819
+ * Resolve the sessions directory — always user-tier because Pi owns
820
+ * session storage and the three-tier model folds session listings to
821
+ * the single authoritative location per ADR-035 §D2.
822
+ */
823
+ sessionsDir() {
824
+ return join5(getPiAgentDir2(), "sessions");
825
+ }
826
+ // ── Extensions (Wave-1, T263) ───────────────────────────────────────
827
+ /** {@inheritDoc Harness.installExtension} */
828
+ async installExtension(sourcePath, name, tier, projectDir, opts) {
829
+ if (!existsSync4(sourcePath)) {
830
+ throw new Error(`installExtension: source file does not exist: ${sourcePath}`);
831
+ }
832
+ const stats = await stat(sourcePath);
833
+ if (!stats.isFile()) {
834
+ throw new Error(`installExtension: source path is not a regular file: ${sourcePath}`);
835
+ }
836
+ const ext = extname(sourcePath);
837
+ if (ext !== ".ts" && ext !== ".tsx" && ext !== ".mts") {
838
+ throw new Error(
839
+ `installExtension: expected a TypeScript source file (.ts/.tsx/.mts), got: ${ext || "(no extension)"}`
840
+ );
841
+ }
842
+ const contents = await readFile(sourcePath, "utf8");
843
+ if (!/\bexport\s+default\b/.test(contents)) {
844
+ throw new Error(
845
+ `installExtension: source file is missing an 'export default' \u2014 Pi extensions must export a default function`
846
+ );
847
+ }
848
+ const dir = resolveTierDir({ tier, kind: "extensions", projectDir });
849
+ const targetPath = join5(dir, `${name}.ts`);
850
+ if (existsSync4(targetPath) && opts?.force !== true) {
851
+ throw new Error(
852
+ `installExtension: target already exists at ${targetPath} (pass --force to overwrite)`
853
+ );
854
+ }
855
+ await mkdir3(dir, { recursive: true });
856
+ await writeFile(targetPath, contents, "utf8");
857
+ return { targetPath, tier };
858
+ }
859
+ /** {@inheritDoc Harness.removeExtension} */
860
+ async removeExtension(name, tier, projectDir) {
861
+ const dir = resolveTierDir({ tier, kind: "extensions", projectDir });
862
+ const targetPath = join5(dir, `${name}.ts`);
863
+ if (!existsSync4(targetPath)) return false;
864
+ await rm3(targetPath, { force: true });
865
+ return true;
866
+ }
867
+ /** {@inheritDoc Harness.listExtensions} */
868
+ async listExtensions(projectDir) {
869
+ const tiers = resolveAllTiers("extensions", projectDir);
870
+ const out = [];
871
+ const seenNames = /* @__PURE__ */ new Set();
872
+ for (const { tier, dir } of tiers) {
873
+ if (!existsSync4(dir)) continue;
874
+ let entries;
875
+ try {
876
+ entries = await readdir(dir, { withFileTypes: true });
877
+ } catch {
878
+ continue;
879
+ }
880
+ for (const entry of entries) {
881
+ if (!entry.isFile()) continue;
882
+ const fileName = entry.name;
883
+ if (!fileName.endsWith(".ts")) continue;
884
+ const name = fileName.slice(0, -".ts".length);
885
+ const shadowed = seenNames.has(name);
886
+ out.push({
887
+ name,
888
+ tier,
889
+ path: join5(dir, fileName),
890
+ shadowed
891
+ });
892
+ seenNames.add(name);
893
+ }
894
+ }
895
+ return out;
896
+ }
897
+ // ── Sessions (Wave-1, T264) ─────────────────────────────────────────
898
+ /** {@inheritDoc Harness.listSessions} */
899
+ async listSessions(opts) {
900
+ const rootDir = this.sessionsDir();
901
+ if (!existsSync4(rootDir)) return [];
902
+ const files = [];
903
+ let rootEntries;
904
+ try {
905
+ rootEntries = await readdir(rootDir, { withFileTypes: true });
906
+ } catch {
907
+ return [];
908
+ }
909
+ for (const entry of rootEntries) {
910
+ if (entry.isFile() && entry.name.endsWith(".jsonl")) {
911
+ files.push(join5(rootDir, entry.name));
912
+ }
913
+ }
914
+ if (opts?.includeSubagents !== false) {
915
+ const subDir = join5(rootDir, "subagents");
916
+ if (existsSync4(subDir)) {
917
+ try {
918
+ const subEntries = await readdir(subDir, { withFileTypes: true });
919
+ for (const entry of subEntries) {
920
+ if (entry.isFile() && entry.name.endsWith(".jsonl")) {
921
+ files.push(join5(subDir, entry.name));
922
+ }
923
+ }
924
+ } catch {
925
+ }
926
+ }
927
+ }
928
+ const summaries = [];
929
+ for (const filePath of files) {
930
+ const summary = await readSessionHeader(filePath);
931
+ if (summary !== null) {
932
+ summaries.push(summary);
933
+ }
934
+ }
935
+ summaries.sort((a, b) => b.mtimeMs - a.mtimeMs);
936
+ return summaries;
937
+ }
938
+ /** {@inheritDoc Harness.showSession} */
939
+ async showSession(id) {
940
+ const summaries = await this.listSessions({ includeSubagents: true });
941
+ const match = summaries.find((s) => s.id === id);
942
+ if (match === void 0) {
943
+ throw new Error(`showSession: no session found with id ${id}`);
944
+ }
945
+ const raw = await readFile(match.filePath, "utf8");
946
+ const allLines = raw.split("\n");
947
+ while (allLines.length > 0 && allLines[allLines.length - 1] === "") {
948
+ allLines.pop();
949
+ }
950
+ const entries = allLines.slice(1);
951
+ return { summary: match, entries };
952
+ }
953
+ // ── Models (Wave-1, T265) ───────────────────────────────────────────
954
+ /** {@inheritDoc Harness.readModelsConfig} */
955
+ async readModelsConfig(scope) {
956
+ const filePath = this.modelsConfigPath(scope);
957
+ if (!existsSync4(filePath)) return { providers: {} };
958
+ let raw;
959
+ try {
960
+ raw = await readFile(filePath, "utf8");
961
+ } catch {
962
+ return { providers: {} };
963
+ }
964
+ try {
965
+ const parsed = JSON.parse(raw);
966
+ if (!isPlainObject(parsed)) return { providers: {} };
967
+ const providersField = parsed["providers"];
968
+ if (!isPlainObject(providersField)) return { providers: {} };
969
+ const providers = {};
970
+ for (const [id, block] of Object.entries(providersField)) {
971
+ if (isPlainObject(block)) {
972
+ providers[id] = block;
973
+ }
974
+ }
975
+ return { providers };
976
+ } catch {
977
+ return { providers: {} };
978
+ }
979
+ }
980
+ /** {@inheritDoc Harness.writeModelsConfig} */
981
+ async writeModelsConfig(config, scope) {
982
+ const filePath = this.modelsConfigPath(scope);
983
+ await atomicWriteJson(filePath, config);
984
+ }
985
+ /** {@inheritDoc Harness.listModels} */
986
+ async listModels(scope) {
987
+ const models = await this.readModelsConfig(scope);
988
+ const settings = await this.readSettings(scope);
989
+ const settingsObj = isPlainObject(settings) ? settings : {};
990
+ const enabledRaw = settingsObj["enabledModels"];
991
+ const enabled = Array.isArray(enabledRaw) ? enabledRaw.filter((v) => typeof v === "string") : [];
992
+ const defaultModel = typeof settingsObj["defaultModel"] === "string" ? settingsObj["defaultModel"] : null;
993
+ const defaultProvider = typeof settingsObj["defaultProvider"] === "string" ? settingsObj["defaultProvider"] : null;
994
+ const out = [];
995
+ const seen = /* @__PURE__ */ new Set();
996
+ for (const [providerId, providerBlock] of Object.entries(models.providers)) {
997
+ const modelDefs = providerBlock.models ?? [];
998
+ for (const def of modelDefs) {
999
+ const key = `${providerId}:${def.id}`;
1000
+ seen.add(key);
1001
+ const isEnabled = enabled.includes(key) || enabled.includes(`${providerId}/*`);
1002
+ const isDefault = defaultProvider === providerId && defaultModel === def.id;
1003
+ out.push({
1004
+ provider: providerId,
1005
+ id: def.id,
1006
+ name: def.name ?? null,
1007
+ enabled: isEnabled,
1008
+ isDefault,
1009
+ custom: true
1010
+ });
1011
+ }
1012
+ }
1013
+ for (const selection of enabled) {
1014
+ if (!selection.includes(":") && !selection.includes("/")) continue;
1015
+ const match = selection.match(/^([^:/]+)[:/]([^:/].*)$/);
1016
+ if (match === null) continue;
1017
+ const provider = match[1];
1018
+ const id = match[2];
1019
+ if (provider === void 0 || id === void 0) continue;
1020
+ if (id.endsWith("*")) continue;
1021
+ const key = `${provider}:${id}`;
1022
+ if (seen.has(key)) continue;
1023
+ seen.add(key);
1024
+ const isDefault = defaultProvider === provider && defaultModel === id;
1025
+ out.push({
1026
+ provider,
1027
+ id,
1028
+ name: null,
1029
+ enabled: true,
1030
+ isDefault,
1031
+ custom: false
1032
+ });
1033
+ }
1034
+ if (defaultProvider !== null && defaultModel !== null && !seen.has(`${defaultProvider}:${defaultModel}`)) {
1035
+ out.push({
1036
+ provider: defaultProvider,
1037
+ id: defaultModel,
1038
+ name: null,
1039
+ enabled: false,
1040
+ isDefault: true,
1041
+ custom: false
1042
+ });
1043
+ }
1044
+ return out;
1045
+ }
1046
+ // ── Prompts (Wave-1, T266) ──────────────────────────────────────────
1047
+ /** {@inheritDoc Harness.installPrompt} */
1048
+ async installPrompt(sourceDir, name, tier, projectDir, opts) {
1049
+ if (!existsSync4(sourceDir)) {
1050
+ throw new Error(`installPrompt: source directory does not exist: ${sourceDir}`);
1051
+ }
1052
+ const stats = await stat(sourceDir);
1053
+ if (!stats.isDirectory()) {
1054
+ throw new Error(`installPrompt: source path is not a directory: ${sourceDir}`);
1055
+ }
1056
+ if (!existsSync4(join5(sourceDir, "prompt.md"))) {
1057
+ throw new Error(`installPrompt: source directory is missing a prompt.md file: ${sourceDir}`);
1058
+ }
1059
+ const baseDir = resolveTierDir({ tier, kind: "prompts", projectDir });
1060
+ const targetPath = join5(baseDir, name);
1061
+ if (existsSync4(targetPath)) {
1062
+ if (opts?.force !== true) {
1063
+ throw new Error(
1064
+ `installPrompt: target already exists at ${targetPath} (pass --force to overwrite)`
1065
+ );
1066
+ }
1067
+ await rm3(targetPath, { recursive: true, force: true });
1068
+ }
1069
+ await mkdir3(baseDir, { recursive: true });
1070
+ await cp3(sourceDir, targetPath, { recursive: true });
1071
+ return { targetPath, tier };
1072
+ }
1073
+ /** {@inheritDoc Harness.listPrompts} */
1074
+ async listPrompts(projectDir) {
1075
+ const tiers = resolveAllTiers("prompts", projectDir);
1076
+ const out = [];
1077
+ const seenNames = /* @__PURE__ */ new Set();
1078
+ for (const { tier, dir } of tiers) {
1079
+ if (!existsSync4(dir)) continue;
1080
+ let entries;
1081
+ try {
1082
+ entries = await readdir(dir, { withFileTypes: true });
1083
+ } catch {
1084
+ continue;
1085
+ }
1086
+ for (const entry of entries) {
1087
+ if (!entry.isDirectory()) continue;
1088
+ const name = entry.name;
1089
+ const shadowed = seenNames.has(name);
1090
+ out.push({
1091
+ name,
1092
+ tier,
1093
+ path: join5(dir, name),
1094
+ shadowed
1095
+ });
1096
+ seenNames.add(name);
1097
+ }
1098
+ }
1099
+ return out;
1100
+ }
1101
+ /** {@inheritDoc Harness.removePrompt} */
1102
+ async removePrompt(name, tier, projectDir) {
1103
+ const dir = resolveTierDir({ tier, kind: "prompts", projectDir });
1104
+ const targetPath = join5(dir, name);
1105
+ if (!existsSync4(targetPath)) return false;
1106
+ await rm3(targetPath, { recursive: true, force: true });
1107
+ return true;
1108
+ }
1109
+ // ── Themes (Wave-1, T267) ───────────────────────────────────────────
1110
+ /** {@inheritDoc Harness.installTheme} */
1111
+ async installTheme(sourceFile, name, tier, projectDir, opts) {
1112
+ if (!existsSync4(sourceFile)) {
1113
+ throw new Error(`installTheme: source file does not exist: ${sourceFile}`);
1114
+ }
1115
+ const stats = await stat(sourceFile);
1116
+ if (!stats.isFile()) {
1117
+ throw new Error(`installTheme: source path is not a regular file: ${sourceFile}`);
1118
+ }
1119
+ const ext = extname(sourceFile);
1120
+ if (ext !== ".ts" && ext !== ".tsx" && ext !== ".mts" && ext !== ".json") {
1121
+ throw new Error(
1122
+ `installTheme: expected a theme file (.ts/.tsx/.mts/.json), got: ${ext || "(no extension)"}`
1123
+ );
1124
+ }
1125
+ const dir = resolveTierDir({ tier, kind: "themes", projectDir });
1126
+ const targetPath = join5(dir, `${name}${ext}`);
1127
+ if (existsSync4(targetPath) && opts?.force !== true) {
1128
+ throw new Error(
1129
+ `installTheme: target already exists at ${targetPath} (pass --force to overwrite)`
1130
+ );
1131
+ }
1132
+ const otherExts = [".ts", ".tsx", ".mts", ".json"].filter((e) => e !== ext);
1133
+ for (const otherExt of otherExts) {
1134
+ const otherPath = join5(dir, `${name}${otherExt}`);
1135
+ if (existsSync4(otherPath) && opts?.force !== true) {
1136
+ throw new Error(
1137
+ `installTheme: conflicting theme exists at ${otherPath} (pass --force to overwrite both)`
1138
+ );
1139
+ }
1140
+ if (existsSync4(otherPath) && opts?.force === true) {
1141
+ await rm3(otherPath, { force: true });
1142
+ }
1143
+ }
1144
+ await mkdir3(dir, { recursive: true });
1145
+ const contents = await readFile(sourceFile);
1146
+ await writeFile(targetPath, contents);
1147
+ return { targetPath, tier };
1148
+ }
1149
+ /** {@inheritDoc Harness.listThemes} */
1150
+ async listThemes(projectDir) {
1151
+ const tiers = resolveAllTiers("themes", projectDir);
1152
+ const out = [];
1153
+ const seenNames = /* @__PURE__ */ new Set();
1154
+ const validExts = /* @__PURE__ */ new Set([".ts", ".tsx", ".mts", ".json"]);
1155
+ for (const { tier, dir } of tiers) {
1156
+ if (!existsSync4(dir)) continue;
1157
+ let entries;
1158
+ try {
1159
+ entries = await readdir(dir, { withFileTypes: true });
1160
+ } catch {
1161
+ continue;
1162
+ }
1163
+ for (const entry of entries) {
1164
+ if (!entry.isFile()) continue;
1165
+ const fileExt = extname(entry.name);
1166
+ if (!validExts.has(fileExt)) continue;
1167
+ const name = entry.name.slice(0, -fileExt.length);
1168
+ const shadowed = seenNames.has(name);
1169
+ out.push({
1170
+ name,
1171
+ tier,
1172
+ path: join5(dir, entry.name),
1173
+ fileExt,
1174
+ shadowed
1175
+ });
1176
+ seenNames.add(name);
1177
+ }
1178
+ }
1179
+ return out;
1180
+ }
1181
+ /** {@inheritDoc Harness.removeTheme} */
1182
+ async removeTheme(name, tier, projectDir) {
1183
+ const dir = resolveTierDir({ tier, kind: "themes", projectDir });
1184
+ let removed = false;
1185
+ for (const ext of [".ts", ".tsx", ".mts", ".json"]) {
1186
+ const targetPath = join5(dir, `${name}${ext}`);
1187
+ if (existsSync4(targetPath)) {
1188
+ await rm3(targetPath, { force: true });
1189
+ removed = true;
1190
+ }
1191
+ }
1192
+ return removed;
1193
+ }
801
1194
  };
1195
+ async function readSessionHeader(filePath) {
1196
+ let handle = null;
1197
+ try {
1198
+ handle = await open(filePath, "r");
1199
+ const stats = await handle.stat();
1200
+ const capacity = Math.min(stats.size, 64 * 1024);
1201
+ if (capacity === 0) return null;
1202
+ const buffer = Buffer.alloc(capacity);
1203
+ const { bytesRead } = await handle.read(buffer, 0, capacity, 0);
1204
+ const text = buffer.subarray(0, bytesRead).toString("utf8");
1205
+ const newlineIdx = text.indexOf("\n");
1206
+ const firstLine = newlineIdx === -1 ? text : text.slice(0, newlineIdx);
1207
+ if (firstLine.trim().length === 0) return null;
1208
+ let parsed;
1209
+ try {
1210
+ parsed = JSON.parse(firstLine);
1211
+ } catch {
1212
+ return null;
1213
+ }
1214
+ if (!isPlainObject(parsed)) return null;
1215
+ const id = typeof parsed["id"] === "string" ? parsed["id"] : null;
1216
+ if (id === null) {
1217
+ const stem = basename2(filePath, ".jsonl");
1218
+ return {
1219
+ id: stem,
1220
+ version: typeof parsed["version"] === "number" ? parsed["version"] : 0,
1221
+ timestamp: typeof parsed["timestamp"] === "string" ? parsed["timestamp"] : null,
1222
+ cwd: typeof parsed["cwd"] === "string" ? parsed["cwd"] : null,
1223
+ parentSession: typeof parsed["parentSession"] === "string" ? parsed["parentSession"] : null,
1224
+ filePath,
1225
+ mtimeMs: stats.mtimeMs
1226
+ };
1227
+ }
1228
+ return {
1229
+ id,
1230
+ version: typeof parsed["version"] === "number" ? parsed["version"] : 0,
1231
+ timestamp: typeof parsed["timestamp"] === "string" ? parsed["timestamp"] : null,
1232
+ cwd: typeof parsed["cwd"] === "string" ? parsed["cwd"] : null,
1233
+ parentSession: typeof parsed["parentSession"] === "string" ? parsed["parentSession"] : null,
1234
+ filePath,
1235
+ mtimeMs: stats.mtimeMs
1236
+ };
1237
+ } catch {
1238
+ return null;
1239
+ } finally {
1240
+ if (handle !== null) {
1241
+ await handle.close().catch(() => {
1242
+ });
1243
+ }
1244
+ }
1245
+ }
802
1246
 
803
1247
  // src/core/harness/index.ts
804
1248
  function getHarnessFor(provider) {
@@ -1105,63 +1549,395 @@ async function removeYamlConfig(filePath, configKey, serverName) {
1105
1549
  if (typeof next !== "object" || next === null) return false;
1106
1550
  current = next;
1107
1551
  }
1108
- if (!(serverName in current)) return false;
1109
- delete current[serverName];
1110
- const content = yaml.dump(existing, {
1111
- indent: 2,
1112
- lineWidth: -1,
1113
- noRefs: true,
1114
- sortKeys: false
1115
- });
1116
- await writeFile4(filePath, content, "utf-8");
1117
- return true;
1118
- }
1119
-
1120
- // src/core/formats/index.ts
1121
- async function readConfig(filePath, format) {
1122
- debug(`reading config: ${filePath} (format: ${format})`);
1123
- switch (format) {
1124
- case "json":
1125
- case "jsonc":
1126
- return readJsonConfig(filePath);
1127
- case "yaml":
1128
- return readYamlConfig(filePath);
1129
- case "toml":
1130
- return readTomlConfig(filePath);
1131
- default:
1132
- throw new Error(`Unsupported config format: ${format}`);
1552
+ if (!(serverName in current)) return false;
1553
+ delete current[serverName];
1554
+ const content = yaml.dump(existing, {
1555
+ indent: 2,
1556
+ lineWidth: -1,
1557
+ noRefs: true,
1558
+ sortKeys: false
1559
+ });
1560
+ await writeFile4(filePath, content, "utf-8");
1561
+ return true;
1562
+ }
1563
+
1564
+ // src/core/formats/index.ts
1565
+ async function readConfig(filePath, format) {
1566
+ debug(`reading config: ${filePath} (format: ${format})`);
1567
+ switch (format) {
1568
+ case "json":
1569
+ case "jsonc":
1570
+ return readJsonConfig(filePath);
1571
+ case "yaml":
1572
+ return readYamlConfig(filePath);
1573
+ case "toml":
1574
+ return readTomlConfig(filePath);
1575
+ default:
1576
+ throw new Error(`Unsupported config format: ${format}`);
1577
+ }
1578
+ }
1579
+ async function writeConfig(filePath, format, key, serverName, serverConfig) {
1580
+ debug(`writing config: ${filePath} (format: ${format}, key: ${key}, server: ${serverName})`);
1581
+ switch (format) {
1582
+ case "json":
1583
+ case "jsonc":
1584
+ return writeJsonConfig(filePath, key, serverName, serverConfig);
1585
+ case "yaml":
1586
+ return writeYamlConfig(filePath, key, serverName, serverConfig);
1587
+ case "toml":
1588
+ return writeTomlConfig(filePath, key, serverName, serverConfig);
1589
+ default:
1590
+ throw new Error(`Unsupported config format: ${format}`);
1591
+ }
1592
+ }
1593
+ async function removeConfig(filePath, format, key, serverName) {
1594
+ switch (format) {
1595
+ case "json":
1596
+ case "jsonc":
1597
+ return removeJsonConfig(filePath, key, serverName);
1598
+ case "yaml":
1599
+ return removeYamlConfig(filePath, key, serverName);
1600
+ case "toml":
1601
+ return removeTomlConfig(filePath, key, serverName);
1602
+ default:
1603
+ throw new Error(`Unsupported config format: ${format}`);
1604
+ }
1605
+ }
1606
+
1607
+ // src/core/mcp/reader.ts
1608
+ import { existsSync as existsSync8 } from "fs";
1609
+ import { stat as stat2 } from "fs/promises";
1610
+ function resolveMcpConfigPath(provider, scope, projectDir) {
1611
+ if (provider.capabilities.mcp === null) return null;
1612
+ return resolveProviderConfigPath(provider, scope, projectDir);
1613
+ }
1614
+ async function listMcpServers(provider, scope, projectDir) {
1615
+ const mcp = provider.capabilities.mcp;
1616
+ if (mcp === null) return [];
1617
+ const configPath = resolveMcpConfigPath(provider, scope, projectDir);
1618
+ if (configPath === null) return [];
1619
+ if (!existsSync8(configPath)) {
1620
+ debug(`mcp.list: ${provider.id} (${scope}) \u2014 config file missing at ${configPath}`);
1621
+ return [];
1622
+ }
1623
+ let parsed;
1624
+ try {
1625
+ parsed = await readConfig(configPath, mcp.configFormat);
1626
+ } catch (err) {
1627
+ const message = err instanceof Error ? err.message : String(err);
1628
+ debug(`mcp.list: ${provider.id} parse failed at ${configPath}: ${message}`);
1629
+ return [];
1630
+ }
1631
+ const servers = getNestedValue(parsed, mcp.configKey);
1632
+ if (servers === void 0 || servers === null || typeof servers !== "object") return [];
1633
+ const out = [];
1634
+ for (const [name, raw] of Object.entries(servers)) {
1635
+ out.push({
1636
+ name,
1637
+ providerId: provider.id,
1638
+ providerName: provider.toolName,
1639
+ scope,
1640
+ configPath,
1641
+ config: raw ?? {}
1642
+ });
1643
+ }
1644
+ return out;
1645
+ }
1646
+ async function listAllMcpServers(scope, projectDir) {
1647
+ const out = /* @__PURE__ */ new Map();
1648
+ for (const provider of getAllProviders()) {
1649
+ if (provider.capabilities.mcp === null) continue;
1650
+ const entries = await listMcpServers(provider, scope, projectDir);
1651
+ out.set(provider.id, entries);
1652
+ }
1653
+ return out;
1654
+ }
1655
+ async function detectMcpInstallations(scope, projectDir) {
1656
+ const out = [];
1657
+ for (const provider of getAllProviders()) {
1658
+ const mcp = provider.capabilities.mcp;
1659
+ if (mcp === null) continue;
1660
+ const configPath = resolveMcpConfigPath(provider, scope, projectDir);
1661
+ if (configPath === null) continue;
1662
+ const exists = existsSync8(configPath);
1663
+ let serverCount = null;
1664
+ let lastModified = null;
1665
+ if (exists) {
1666
+ try {
1667
+ const stats = await stat2(configPath);
1668
+ lastModified = stats.mtime.toISOString();
1669
+ } catch {
1670
+ lastModified = null;
1671
+ }
1672
+ const entries = await listMcpServers(provider, scope, projectDir);
1673
+ serverCount = entries.length;
1674
+ }
1675
+ out.push({
1676
+ providerId: provider.id,
1677
+ providerName: provider.toolName,
1678
+ scope,
1679
+ configPath,
1680
+ exists,
1681
+ serverCount,
1682
+ lastModified
1683
+ });
1684
+ }
1685
+ return out;
1686
+ }
1687
+
1688
+ // src/core/mcp/installer.ts
1689
+ async function installMcpServer(provider, serverName, config, opts) {
1690
+ const mcp = provider.capabilities.mcp;
1691
+ if (mcp === null) {
1692
+ throw new Error(`Provider ${provider.id} does not declare an MCP capability.`);
1693
+ }
1694
+ const configPath = resolveMcpConfigPath(provider, opts.scope, opts.projectDir);
1695
+ if (configPath === null) {
1696
+ throw new Error(
1697
+ `Provider ${provider.id} has no ${opts.scope}-scoped MCP config path available.`
1698
+ );
1699
+ }
1700
+ debug(
1701
+ `mcp.install: ${provider.id} ${serverName} \u2192 ${configPath} (format=${mcp.configFormat}, key=${mcp.configKey})`
1702
+ );
1703
+ const existing = await listMcpServers(provider, opts.scope, opts.projectDir);
1704
+ const conflicted = existing.some((e) => e.name === serverName);
1705
+ if (conflicted && opts.force !== true) {
1706
+ return {
1707
+ installed: false,
1708
+ conflicted: true,
1709
+ sourcePath: configPath,
1710
+ providerId: provider.id,
1711
+ serverName
1712
+ };
1713
+ }
1714
+ await writeConfig(configPath, mcp.configFormat, mcp.configKey, serverName, config);
1715
+ return {
1716
+ installed: true,
1717
+ conflicted,
1718
+ sourcePath: configPath,
1719
+ providerId: provider.id,
1720
+ serverName
1721
+ };
1722
+ }
1723
+
1724
+ // src/core/mcp/remover.ts
1725
+ import { existsSync as existsSync9 } from "fs";
1726
+ async function removeMcpServer(provider, serverName, opts) {
1727
+ const mcp = provider.capabilities.mcp;
1728
+ if (mcp === null) {
1729
+ return {
1730
+ providerId: provider.id,
1731
+ serverName,
1732
+ sourcePath: null,
1733
+ removed: false,
1734
+ reason: "no-mcp-capability"
1735
+ };
1736
+ }
1737
+ const configPath = resolveMcpConfigPath(provider, opts.scope, opts.projectDir);
1738
+ if (configPath === null) {
1739
+ return {
1740
+ providerId: provider.id,
1741
+ serverName,
1742
+ sourcePath: null,
1743
+ removed: false,
1744
+ reason: "no-config-path"
1745
+ };
1746
+ }
1747
+ if (!existsSync9(configPath)) {
1748
+ return {
1749
+ providerId: provider.id,
1750
+ serverName,
1751
+ sourcePath: configPath,
1752
+ removed: false,
1753
+ reason: "file-missing"
1754
+ };
1755
+ }
1756
+ debug(`mcp.remove: ${provider.id} ${serverName} \u2192 ${configPath}`);
1757
+ const removed = await removeConfig(configPath, mcp.configFormat, mcp.configKey, serverName);
1758
+ return {
1759
+ providerId: provider.id,
1760
+ serverName,
1761
+ sourcePath: configPath,
1762
+ removed,
1763
+ reason: removed ? null : "entry-missing"
1764
+ };
1765
+ }
1766
+ async function removeMcpServerFromAll(serverName, opts) {
1767
+ const out = [];
1768
+ for (const provider of getAllProviders()) {
1769
+ if (provider.capabilities.mcp === null) continue;
1770
+ out.push(await removeMcpServer(provider, serverName, opts));
1771
+ }
1772
+ return out;
1773
+ }
1774
+
1775
+ // src/core/sources/parser.ts
1776
+ var GITHUB_SHORTHAND = /^([a-zA-Z0-9_.-]+)\/([a-zA-Z0-9_.-]+)(?:\/(.+))?$/;
1777
+ var GITHUB_URL = /^https?:\/\/(?:www\.)?github\.com\/([^/]+)\/([^/]+)(?:\/(?:tree|blob)\/([^/]+)(?:\/(.+))?)?/;
1778
+ var GITLAB_URL = /^https?:\/\/(?:www\.)?gitlab\.com\/([^/]+)\/([^/]+)(?:\/-\/(?:tree|blob)\/([^/]+)(?:\/(.+))?)?/;
1779
+ var HTTP_URL = /^https?:\/\//;
1780
+ var NPM_SCOPED = /^@[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/;
1781
+ var NPM_PACKAGE = /^[a-zA-Z0-9_.-]+$/;
1782
+ var LIBRARY_SKILL = /^(@[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+|[a-zA-Z0-9_.-]+):([a-zA-Z0-9_.-]+)$/;
1783
+ function inferName(source, type) {
1784
+ if (type === "library") {
1785
+ const match = source.match(LIBRARY_SKILL);
1786
+ return match?.[2] ?? source;
1787
+ }
1788
+ if (type === "remote") {
1789
+ try {
1790
+ const url = new URL(source);
1791
+ const parts = url.hostname.split(".");
1792
+ if (parts.length >= 2) {
1793
+ const fallback = parts[0] ?? source;
1794
+ const secondLevel = parts[parts.length - 2] ?? fallback;
1795
+ const brand = parts.length === 3 ? secondLevel : fallback;
1796
+ if (brand !== "www" && brand !== "api" && brand !== "mcp") {
1797
+ return brand;
1798
+ }
1799
+ return secondLevel;
1800
+ }
1801
+ return parts[0] ?? source;
1802
+ } catch {
1803
+ return source;
1804
+ }
1805
+ }
1806
+ if (type === "package") {
1807
+ let name = source.replace(/^@[^/]+\//, "");
1808
+ name = name.replace(/^mcp-server-/, "");
1809
+ name = name.replace(/^server-/, "");
1810
+ name = name.replace(/-mcp$/, "");
1811
+ name = name.replace(/-server$/, "");
1812
+ return name;
1813
+ }
1814
+ if (type === "github" || type === "gitlab") {
1815
+ const match = source.match(/\/([^/]+?)(?:\.git)?$/);
1816
+ return match?.[1] ?? source;
1817
+ }
1818
+ if (type === "local") {
1819
+ const normalized = source.replace(/\\/g, "/").replace(/\/+$/, "");
1820
+ const lastSegment = normalized.split("/").pop();
1821
+ return lastSegment ?? source;
1822
+ }
1823
+ if (type === "command") {
1824
+ const parts = source.split(/\s+/);
1825
+ const command = parts.find(
1826
+ (p) => !p.startsWith("-") && p !== "npx" && p !== "node" && p !== "python" && p !== "python3"
1827
+ );
1828
+ return command ?? parts[0] ?? source;
1829
+ }
1830
+ return source;
1831
+ }
1832
+ function parseSource(input) {
1833
+ const ghUrlMatch = input.match(GITHUB_URL);
1834
+ if (ghUrlMatch) {
1835
+ const owner = ghUrlMatch[1];
1836
+ const repo = ghUrlMatch[2];
1837
+ const path = ghUrlMatch[4];
1838
+ if (!owner || !repo) {
1839
+ return { type: "command", value: input, inferredName: inferName(input, "command") };
1840
+ }
1841
+ const inferredName = path ? path.split("/").pop() ?? repo : repo;
1842
+ return {
1843
+ type: "github",
1844
+ value: input,
1845
+ inferredName,
1846
+ owner,
1847
+ repo,
1848
+ ref: ghUrlMatch[3],
1849
+ path
1850
+ };
1851
+ }
1852
+ const glUrlMatch = input.match(GITLAB_URL);
1853
+ if (glUrlMatch) {
1854
+ const owner = glUrlMatch[1];
1855
+ const repo = glUrlMatch[2];
1856
+ const path = glUrlMatch[4];
1857
+ if (!owner || !repo) {
1858
+ return { type: "command", value: input, inferredName: inferName(input, "command") };
1859
+ }
1860
+ const inferredName = path ? path.split("/").pop() ?? repo : repo;
1861
+ return {
1862
+ type: "gitlab",
1863
+ value: input,
1864
+ inferredName,
1865
+ owner,
1866
+ repo,
1867
+ ref: glUrlMatch[3],
1868
+ path
1869
+ };
1870
+ }
1871
+ if (HTTP_URL.test(input)) {
1872
+ return {
1873
+ type: "remote",
1874
+ value: input,
1875
+ inferredName: inferName(input, "remote")
1876
+ };
1877
+ }
1878
+ if (input.startsWith("/") || input.startsWith("./") || input.startsWith("../") || input.startsWith("~")) {
1879
+ return {
1880
+ type: "local",
1881
+ value: input,
1882
+ inferredName: inferName(input, "local")
1883
+ };
1884
+ }
1885
+ const ghShorthand = input.match(GITHUB_SHORTHAND);
1886
+ if (ghShorthand && !NPM_SCOPED.test(input)) {
1887
+ const owner = ghShorthand[1];
1888
+ const repo = ghShorthand[2];
1889
+ const path = ghShorthand[3];
1890
+ if (!owner || !repo) {
1891
+ return { type: "command", value: input, inferredName: inferName(input, "command") };
1892
+ }
1893
+ const inferredName = path ? path.split("/").pop() ?? repo : repo;
1894
+ return {
1895
+ type: "github",
1896
+ value: `https://github.com/${owner}/${repo}`,
1897
+ inferredName,
1898
+ owner,
1899
+ repo,
1900
+ path
1901
+ };
1902
+ }
1903
+ const libraryMatch = input.match(LIBRARY_SKILL);
1904
+ if (libraryMatch) {
1905
+ return {
1906
+ type: "library",
1907
+ value: input,
1908
+ inferredName: inferName(input, "library"),
1909
+ owner: libraryMatch[1],
1910
+ // This will be the package name, e.g. @cleocode/skills
1911
+ repo: libraryMatch[2]
1912
+ // This will be the skill name, e.g. ct-research-agent
1913
+ };
1914
+ }
1915
+ if (NPM_SCOPED.test(input)) {
1916
+ return {
1917
+ type: "package",
1918
+ value: input,
1919
+ inferredName: inferName(input, "package")
1920
+ };
1133
1921
  }
1134
- }
1135
- async function writeConfig(filePath, format, key, serverName, serverConfig) {
1136
- debug(`writing config: ${filePath} (format: ${format}, key: ${key}, server: ${serverName})`);
1137
- switch (format) {
1138
- case "json":
1139
- case "jsonc":
1140
- return writeJsonConfig(filePath, key, serverName, serverConfig);
1141
- case "yaml":
1142
- return writeYamlConfig(filePath, key, serverName, serverConfig);
1143
- case "toml":
1144
- return writeTomlConfig(filePath, key, serverName, serverConfig);
1145
- default:
1146
- throw new Error(`Unsupported config format: ${format}`);
1922
+ if (NPM_PACKAGE.test(input) && !input.includes(" ")) {
1923
+ return {
1924
+ type: "package",
1925
+ value: input,
1926
+ inferredName: inferName(input, "package")
1927
+ };
1147
1928
  }
1929
+ return {
1930
+ type: "command",
1931
+ value: input,
1932
+ inferredName: inferName(input, "command")
1933
+ };
1148
1934
  }
1149
- async function removeConfig(filePath, format, key, serverName) {
1150
- switch (format) {
1151
- case "json":
1152
- case "jsonc":
1153
- return removeJsonConfig(filePath, key, serverName);
1154
- case "yaml":
1155
- return removeYamlConfig(filePath, key, serverName);
1156
- case "toml":
1157
- return removeTomlConfig(filePath, key, serverName);
1158
- default:
1159
- throw new Error(`Unsupported config format: ${format}`);
1160
- }
1935
+ function isMarketplaceScoped(input) {
1936
+ return /^@[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/.test(input);
1161
1937
  }
1162
1938
 
1163
1939
  // src/core/skills/audit/scanner.ts
1164
- import { existsSync as existsSync8 } from "fs";
1940
+ import { existsSync as existsSync10 } from "fs";
1165
1941
  import { readFile as readFile5 } from "fs/promises";
1166
1942
 
1167
1943
  // src/core/skills/audit/rules.ts
@@ -1534,7 +2310,7 @@ var SEVERITY_WEIGHTS = {
1534
2310
  info: 0
1535
2311
  };
1536
2312
  async function scanFile(filePath, rules) {
1537
- if (!existsSync8(filePath)) {
2313
+ if (!existsSync10(filePath)) {
1538
2314
  return { file: filePath, findings: [], score: 100, passed: true };
1539
2315
  }
1540
2316
  const content = await readFile5(filePath, "utf-8");
@@ -1568,14 +2344,14 @@ async function scanFile(filePath, rules) {
1568
2344
  }
1569
2345
  async function scanDirectory(dirPath) {
1570
2346
  const { readdir: readdir3 } = await import("fs/promises");
1571
- const { join: join8 } = await import("path");
1572
- if (!existsSync8(dirPath)) return [];
2347
+ const { join: join9 } = await import("path");
2348
+ if (!existsSync10(dirPath)) return [];
1573
2349
  const entries = await readdir3(dirPath, { withFileTypes: true });
1574
2350
  const results = [];
1575
2351
  for (const entry of entries) {
1576
2352
  if (entry.isDirectory() || entry.isSymbolicLink()) {
1577
- const skillFile = join8(dirPath, entry.name, "SKILL.md");
1578
- if (existsSync8(skillFile)) {
2353
+ const skillFile = join9(dirPath, entry.name, "SKILL.md");
2354
+ if (existsSync10(skillFile)) {
1579
2355
  results.push(await scanFile(skillFile));
1580
2356
  }
1581
2357
  }
@@ -1626,178 +2402,14 @@ function toSarif(results) {
1626
2402
  };
1627
2403
  }
1628
2404
 
1629
- // src/core/sources/parser.ts
1630
- var GITHUB_SHORTHAND = /^([a-zA-Z0-9_.-]+)\/([a-zA-Z0-9_.-]+)(?:\/(.+))?$/;
1631
- var GITHUB_URL = /^https?:\/\/(?:www\.)?github\.com\/([^/]+)\/([^/]+)(?:\/(?:tree|blob)\/([^/]+)(?:\/(.+))?)?/;
1632
- var GITLAB_URL = /^https?:\/\/(?:www\.)?gitlab\.com\/([^/]+)\/([^/]+)(?:\/-\/(?:tree|blob)\/([^/]+)(?:\/(.+))?)?/;
1633
- var HTTP_URL = /^https?:\/\//;
1634
- var NPM_SCOPED = /^@[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/;
1635
- var NPM_PACKAGE = /^[a-zA-Z0-9_.-]+$/;
1636
- var LIBRARY_SKILL = /^(@[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+|[a-zA-Z0-9_.-]+):([a-zA-Z0-9_.-]+)$/;
1637
- function inferName(source, type) {
1638
- if (type === "library") {
1639
- const match = source.match(LIBRARY_SKILL);
1640
- return match?.[2] ?? source;
1641
- }
1642
- if (type === "remote") {
1643
- try {
1644
- const url = new URL(source);
1645
- const parts = url.hostname.split(".");
1646
- if (parts.length >= 2) {
1647
- const fallback = parts[0] ?? source;
1648
- const secondLevel = parts[parts.length - 2] ?? fallback;
1649
- const brand = parts.length === 3 ? secondLevel : fallback;
1650
- if (brand !== "www" && brand !== "api" && brand !== "mcp") {
1651
- return brand;
1652
- }
1653
- return secondLevel;
1654
- }
1655
- return parts[0] ?? source;
1656
- } catch {
1657
- return source;
1658
- }
1659
- }
1660
- if (type === "package") {
1661
- let name = source.replace(/^@[^/]+\//, "");
1662
- name = name.replace(/^mcp-server-/, "");
1663
- name = name.replace(/^server-/, "");
1664
- name = name.replace(/-mcp$/, "");
1665
- name = name.replace(/-server$/, "");
1666
- return name;
1667
- }
1668
- if (type === "github" || type === "gitlab") {
1669
- const match = source.match(/\/([^/]+?)(?:\.git)?$/);
1670
- return match?.[1] ?? source;
1671
- }
1672
- if (type === "local") {
1673
- const normalized = source.replace(/\\/g, "/").replace(/\/+$/, "");
1674
- const lastSegment = normalized.split("/").pop();
1675
- return lastSegment ?? source;
1676
- }
1677
- if (type === "command") {
1678
- const parts = source.split(/\s+/);
1679
- const command = parts.find(
1680
- (p) => !p.startsWith("-") && p !== "npx" && p !== "node" && p !== "python" && p !== "python3"
1681
- );
1682
- return command ?? parts[0] ?? source;
1683
- }
1684
- return source;
1685
- }
1686
- function parseSource(input) {
1687
- const ghUrlMatch = input.match(GITHUB_URL);
1688
- if (ghUrlMatch) {
1689
- const owner = ghUrlMatch[1];
1690
- const repo = ghUrlMatch[2];
1691
- const path = ghUrlMatch[4];
1692
- if (!owner || !repo) {
1693
- return { type: "command", value: input, inferredName: inferName(input, "command") };
1694
- }
1695
- const inferredName = path ? path.split("/").pop() ?? repo : repo;
1696
- return {
1697
- type: "github",
1698
- value: input,
1699
- inferredName,
1700
- owner,
1701
- repo,
1702
- ref: ghUrlMatch[3],
1703
- path
1704
- };
1705
- }
1706
- const glUrlMatch = input.match(GITLAB_URL);
1707
- if (glUrlMatch) {
1708
- const owner = glUrlMatch[1];
1709
- const repo = glUrlMatch[2];
1710
- const path = glUrlMatch[4];
1711
- if (!owner || !repo) {
1712
- return { type: "command", value: input, inferredName: inferName(input, "command") };
1713
- }
1714
- const inferredName = path ? path.split("/").pop() ?? repo : repo;
1715
- return {
1716
- type: "gitlab",
1717
- value: input,
1718
- inferredName,
1719
- owner,
1720
- repo,
1721
- ref: glUrlMatch[3],
1722
- path
1723
- };
1724
- }
1725
- if (HTTP_URL.test(input)) {
1726
- return {
1727
- type: "remote",
1728
- value: input,
1729
- inferredName: inferName(input, "remote")
1730
- };
1731
- }
1732
- if (input.startsWith("/") || input.startsWith("./") || input.startsWith("../") || input.startsWith("~")) {
1733
- return {
1734
- type: "local",
1735
- value: input,
1736
- inferredName: inferName(input, "local")
1737
- };
1738
- }
1739
- const ghShorthand = input.match(GITHUB_SHORTHAND);
1740
- if (ghShorthand && !NPM_SCOPED.test(input)) {
1741
- const owner = ghShorthand[1];
1742
- const repo = ghShorthand[2];
1743
- const path = ghShorthand[3];
1744
- if (!owner || !repo) {
1745
- return { type: "command", value: input, inferredName: inferName(input, "command") };
1746
- }
1747
- const inferredName = path ? path.split("/").pop() ?? repo : repo;
1748
- return {
1749
- type: "github",
1750
- value: `https://github.com/${owner}/${repo}`,
1751
- inferredName,
1752
- owner,
1753
- repo,
1754
- path
1755
- };
1756
- }
1757
- const libraryMatch = input.match(LIBRARY_SKILL);
1758
- if (libraryMatch) {
1759
- return {
1760
- type: "library",
1761
- value: input,
1762
- inferredName: inferName(input, "library"),
1763
- owner: libraryMatch[1],
1764
- // This will be the package name, e.g. @cleocode/skills
1765
- repo: libraryMatch[2]
1766
- // This will be the skill name, e.g. ct-research-agent
1767
- };
1768
- }
1769
- if (NPM_SCOPED.test(input)) {
1770
- return {
1771
- type: "package",
1772
- value: input,
1773
- inferredName: inferName(input, "package")
1774
- };
1775
- }
1776
- if (NPM_PACKAGE.test(input) && !input.includes(" ")) {
1777
- return {
1778
- type: "package",
1779
- value: input,
1780
- inferredName: inferName(input, "package")
1781
- };
1782
- }
1783
- return {
1784
- type: "command",
1785
- value: input,
1786
- inferredName: inferName(input, "command")
1787
- };
1788
- }
1789
- function isMarketplaceScoped(input) {
1790
- return /^@[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/.test(input);
1791
- }
1792
-
1793
2405
  // src/core/skills/lock.ts
1794
2406
  import { execFile } from "child_process";
1795
2407
  import { promisify } from "util";
1796
2408
  import { simpleGit } from "simple-git";
1797
2409
 
1798
2410
  // src/core/lock-utils.ts
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";
2411
+ import { existsSync as existsSync11 } from "fs";
2412
+ import { mkdir as mkdir4, open as open2, readFile as readFile6, rename as rename2, rm as rm4, stat as stat3, writeFile as writeFile5 } from "fs/promises";
1801
2413
  var LOCK_GUARD_PATH = `${LOCK_FILE_PATH}.lock`;
1802
2414
  var STALE_LOCK_MS = 5e3;
1803
2415
  function sleep(ms) {
@@ -1805,7 +2417,7 @@ function sleep(ms) {
1805
2417
  }
1806
2418
  async function removeStaleLock() {
1807
2419
  try {
1808
- const info = await stat(LOCK_GUARD_PATH);
2420
+ const info = await stat3(LOCK_GUARD_PATH);
1809
2421
  if (Date.now() - info.mtimeMs > STALE_LOCK_MS) {
1810
2422
  await rm4(LOCK_GUARD_PATH, { force: true });
1811
2423
  return true;
@@ -1818,7 +2430,7 @@ async function acquireLockGuard(retries = 40, delayMs = 25) {
1818
2430
  await mkdir4(AGENTS_HOME, { recursive: true });
1819
2431
  for (let attempt = 0; attempt < retries; attempt += 1) {
1820
2432
  try {
1821
- const handle = await open(LOCK_GUARD_PATH, "wx");
2433
+ const handle = await open2(LOCK_GUARD_PATH, "wx");
1822
2434
  await handle.close();
1823
2435
  return;
1824
2436
  } catch (error) {
@@ -1844,7 +2456,7 @@ async function writeLockFileUnsafe(lock) {
1844
2456
  }
1845
2457
  async function readLockFile() {
1846
2458
  try {
1847
- if (!existsSync9(LOCK_FILE_PATH)) {
2459
+ if (!existsSync11(LOCK_FILE_PATH)) {
1848
2460
  return { version: 1, skills: {}, mcpServers: {} };
1849
2461
  }
1850
2462
  const content = await readFile6(LOCK_FILE_PATH, "utf-8");
@@ -2575,9 +3187,9 @@ async function recommendSkills2(query, criteria, options = {}) {
2575
3187
  }
2576
3188
 
2577
3189
  // src/core/skills/library-loader.ts
2578
- import { existsSync as existsSync10, readdirSync, readFileSync } from "fs";
3190
+ import { existsSync as existsSync12, readdirSync, readFileSync } from "fs";
2579
3191
  import { createRequire } from "module";
2580
- import { basename as basename2, dirname as dirname3, join as join5 } from "path";
3192
+ import { basename as basename3, dirname as dirname3, join as join6 } from "path";
2581
3193
  var require2 = createRequire(import.meta.url);
2582
3194
  function loadLibraryFromModule(root) {
2583
3195
  let mod;
@@ -2623,16 +3235,16 @@ function loadLibraryFromModule(root) {
2623
3235
  return mod;
2624
3236
  }
2625
3237
  function buildLibraryFromFiles(root) {
2626
- const catalogPath = join5(root, "skills.json");
2627
- if (!existsSync10(catalogPath)) {
3238
+ const catalogPath = join6(root, "skills.json");
3239
+ if (!existsSync12(catalogPath)) {
2628
3240
  throw new Error(`No skills.json found at ${root}`);
2629
3241
  }
2630
3242
  const catalogData = JSON.parse(readFileSync(catalogPath, "utf-8"));
2631
3243
  const entries = catalogData.skills ?? [];
2632
3244
  const version = catalogData.version ?? "0.0.0";
2633
- const manifestPath = join5(root, "skills", "manifest.json");
3245
+ const manifestPath = join6(root, "skills", "manifest.json");
2634
3246
  let manifest;
2635
- if (existsSync10(manifestPath)) {
3247
+ if (existsSync12(manifestPath)) {
2636
3248
  manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
2637
3249
  } else {
2638
3250
  manifest = {
@@ -2642,14 +3254,14 @@ function buildLibraryFromFiles(root) {
2642
3254
  skills: []
2643
3255
  };
2644
3256
  }
2645
- const profilesDir = join5(root, "profiles");
3257
+ const profilesDir = join6(root, "profiles");
2646
3258
  const profiles = /* @__PURE__ */ new Map();
2647
- if (existsSync10(profilesDir)) {
3259
+ if (existsSync12(profilesDir)) {
2648
3260
  for (const file of readdirSync(profilesDir)) {
2649
3261
  if (!file.endsWith(".json")) continue;
2650
3262
  try {
2651
3263
  const profile = JSON.parse(
2652
- readFileSync(join5(profilesDir, file), "utf-8")
3264
+ readFileSync(join6(profilesDir, file), "utf-8")
2653
3265
  );
2654
3266
  profiles.set(profile.name, profile);
2655
3267
  } catch {
@@ -2663,9 +3275,9 @@ function buildLibraryFromFiles(root) {
2663
3275
  function getSkillDir2(name) {
2664
3276
  const entry = skillMap.get(name);
2665
3277
  if (entry) {
2666
- return dirname3(join5(root, entry.path));
3278
+ return dirname3(join6(root, entry.path));
2667
3279
  }
2668
- return join5(root, "skills", name);
3280
+ return join6(root, "skills", name);
2669
3281
  }
2670
3282
  function resolveDeps(names, visited = /* @__PURE__ */ new Set()) {
2671
3283
  const result = [];
@@ -2693,8 +3305,8 @@ function buildLibraryFromFiles(root) {
2693
3305
  return resolveDeps([...new Set(skills)]);
2694
3306
  }
2695
3307
  function discoverFiles(dir, ext) {
2696
- if (!existsSync10(dir)) return [];
2697
- return readdirSync(dir).filter((f) => f.endsWith(ext)).map((f) => basename2(f, ext));
3308
+ if (!existsSync12(dir)) return [];
3309
+ return readdirSync(dir).filter((f) => f.endsWith(ext)).map((f) => basename3(f, ext));
2698
3310
  }
2699
3311
  const library = {
2700
3312
  version,
@@ -2710,14 +3322,14 @@ function buildLibraryFromFiles(root) {
2710
3322
  getSkillPath(name) {
2711
3323
  const entry = skillMap.get(name);
2712
3324
  if (entry) {
2713
- return join5(root, entry.path);
3325
+ return join6(root, entry.path);
2714
3326
  }
2715
- return join5(root, "skills", name, "SKILL.md");
3327
+ return join6(root, "skills", name, "SKILL.md");
2716
3328
  },
2717
3329
  getSkillDir: getSkillDir2,
2718
3330
  readSkillContent(name) {
2719
3331
  const skillPath = library.getSkillPath(name);
2720
- if (!existsSync10(skillPath)) {
3332
+ if (!existsSync12(skillPath)) {
2721
3333
  throw new Error(`Skill content not found: ${skillPath}`);
2722
3334
  }
2723
3335
  return readFileSync(skillPath, "utf-8");
@@ -2744,11 +3356,11 @@ function buildLibraryFromFiles(root) {
2744
3356
  return resolveProfileByName(name);
2745
3357
  },
2746
3358
  listSharedResources() {
2747
- return discoverFiles(join5(root, "skills", "_shared"), ".md");
3359
+ return discoverFiles(join6(root, "skills", "_shared"), ".md");
2748
3360
  },
2749
3361
  getSharedResourcePath(name) {
2750
- const resourcePath = join5(root, "skills", "_shared", `${name}.md`);
2751
- return existsSync10(resourcePath) ? resourcePath : void 0;
3362
+ const resourcePath = join6(root, "skills", "_shared", `${name}.md`);
3363
+ return existsSync12(resourcePath) ? resourcePath : void 0;
2752
3364
  },
2753
3365
  readSharedResource(name) {
2754
3366
  const resourcePath = library.getSharedResourcePath(name);
@@ -2756,15 +3368,15 @@ function buildLibraryFromFiles(root) {
2756
3368
  return readFileSync(resourcePath, "utf-8");
2757
3369
  },
2758
3370
  listProtocols() {
2759
- const rootProtocols = discoverFiles(join5(root, "protocols"), ".md");
3371
+ const rootProtocols = discoverFiles(join6(root, "protocols"), ".md");
2760
3372
  if (rootProtocols.length > 0) return rootProtocols;
2761
- return discoverFiles(join5(root, "skills", "protocols"), ".md");
3373
+ return discoverFiles(join6(root, "skills", "protocols"), ".md");
2762
3374
  },
2763
3375
  getProtocolPath(name) {
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;
3376
+ const rootPath = join6(root, "protocols", `${name}.md`);
3377
+ if (existsSync12(rootPath)) return rootPath;
3378
+ const skillsPath = join6(root, "skills", "protocols", `${name}.md`);
3379
+ return existsSync12(skillsPath) ? skillsPath : void 0;
2768
3380
  },
2769
3381
  readProtocol(name) {
2770
3382
  const protocolPath = library.getProtocolPath(name);
@@ -2789,8 +3401,8 @@ function buildLibraryFromFiles(root) {
2789
3401
  if (!entry.version) {
2790
3402
  issues.push({ level: "warn", field: "version", message: "Missing version" });
2791
3403
  }
2792
- const skillPath = join5(root, entry.path);
2793
- if (!existsSync10(skillPath)) {
3404
+ const skillPath = join6(root, entry.path);
3405
+ if (!existsSync12(skillPath)) {
2794
3406
  issues.push({
2795
3407
  level: "error",
2796
3408
  field: "path",
@@ -2849,15 +3461,15 @@ __export(catalog_exports, {
2849
3461
  validateAll: () => validateAll,
2850
3462
  validateSkillFrontmatter: () => validateSkillFrontmatter
2851
3463
  });
2852
- import { existsSync as existsSync11 } from "fs";
2853
- import { join as join6 } from "path";
3464
+ import { existsSync as existsSync13 } from "fs";
3465
+ import { join as join7 } from "path";
2854
3466
  var _library = null;
2855
3467
  function registerSkillLibrary(library) {
2856
3468
  _library = library;
2857
3469
  }
2858
3470
  function registerSkillLibraryFromPath(root) {
2859
- const indexPath = join6(root, "index.js");
2860
- if (existsSync11(indexPath)) {
3471
+ const indexPath = join7(root, "index.js");
3472
+ if (existsSync13(indexPath)) {
2861
3473
  _library = loadLibraryFromModule(root);
2862
3474
  return;
2863
3475
  }
@@ -2868,13 +3480,13 @@ function clearRegisteredLibrary() {
2868
3480
  }
2869
3481
  function discoverLibrary() {
2870
3482
  const envPath = process.env["CAAMP_SKILL_LIBRARY"];
2871
- if (envPath && existsSync11(envPath)) {
3483
+ if (envPath && existsSync13(envPath)) {
2872
3484
  try {
2873
- const indexPath = join6(envPath, "index.js");
2874
- if (existsSync11(indexPath)) {
3485
+ const indexPath = join7(envPath, "index.js");
3486
+ if (existsSync13(indexPath)) {
2875
3487
  return loadLibraryFromModule(envPath);
2876
3488
  }
2877
- if (existsSync11(join6(envPath, "skills.json"))) {
3489
+ if (existsSync13(join7(envPath, "skills.json"))) {
2878
3490
  return buildLibraryFromFiles(envPath);
2879
3491
  }
2880
3492
  } catch {
@@ -2981,9 +3593,9 @@ function getLibraryRoot() {
2981
3593
  }
2982
3594
 
2983
3595
  // src/core/skills/discovery.ts
2984
- import { existsSync as existsSync12 } from "fs";
3596
+ import { existsSync as existsSync14 } from "fs";
2985
3597
  import { readdir as readdir2, readFile as readFile7 } from "fs/promises";
2986
- import { join as join7 } from "path";
3598
+ import { join as join8 } from "path";
2987
3599
  import matter from "gray-matter";
2988
3600
  async function parseSkillFile(filePath) {
2989
3601
  try {
@@ -3007,8 +3619,8 @@ async function parseSkillFile(filePath) {
3007
3619
  }
3008
3620
  }
3009
3621
  async function discoverSkill(skillDir) {
3010
- const skillFile = join7(skillDir, "SKILL.md");
3011
- if (!existsSync12(skillFile)) return null;
3622
+ const skillFile = join8(skillDir, "SKILL.md");
3623
+ if (!existsSync14(skillFile)) return null;
3012
3624
  const metadata = await parseSkillFile(skillFile);
3013
3625
  if (!metadata) return null;
3014
3626
  return {
@@ -3019,12 +3631,12 @@ async function discoverSkill(skillDir) {
3019
3631
  };
3020
3632
  }
3021
3633
  async function discoverSkills(rootDir) {
3022
- if (!existsSync12(rootDir)) return [];
3634
+ if (!existsSync14(rootDir)) return [];
3023
3635
  const entries = await readdir2(rootDir, { withFileTypes: true });
3024
3636
  const skills = [];
3025
3637
  for (const entry of entries) {
3026
3638
  if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
3027
- const skillDir = join7(rootDir, entry.name);
3639
+ const skillDir = join8(rootDir, entry.name);
3028
3640
  const skill = await discoverSkill(skillDir);
3029
3641
  if (skill) {
3030
3642
  skills.push(skill);
@@ -3048,7 +3660,7 @@ async function discoverSkillsMulti(dirs) {
3048
3660
  }
3049
3661
 
3050
3662
  // src/core/skills/validator.ts
3051
- import { existsSync as existsSync13 } from "fs";
3663
+ import { existsSync as existsSync15 } from "fs";
3052
3664
  import { readFile as readFile8 } from "fs/promises";
3053
3665
  import matter2 from "gray-matter";
3054
3666
  var RESERVED_NAMES = [
@@ -3070,7 +3682,7 @@ var WARN_BODY_LINES = 500;
3070
3682
  var WARN_DESCRIPTION_LENGTH = 50;
3071
3683
  async function validateSkill(filePath) {
3072
3684
  const issues = [];
3073
- if (!existsSync13(filePath)) {
3685
+ if (!existsSync15(filePath)) {
3074
3686
  return {
3075
3687
  valid: false,
3076
3688
  issues: [{ level: "error", field: "file", message: "File does not exist" }],
@@ -3219,17 +3831,25 @@ export {
3219
3831
  writeConfig,
3220
3832
  removeConfig,
3221
3833
  readLockFile,
3834
+ resolveMcpConfigPath,
3835
+ listMcpServers,
3836
+ listAllMcpServers,
3837
+ detectMcpInstallations,
3838
+ installMcpServer,
3839
+ removeMcpServer,
3840
+ removeMcpServerFromAll,
3841
+ fetchWithTimeout,
3842
+ formatNetworkError,
3843
+ parseSource,
3844
+ isMarketplaceScoped,
3222
3845
  scanFile,
3223
3846
  scanDirectory,
3224
3847
  toSarif,
3225
- parseSource,
3226
- isMarketplaceScoped,
3227
3848
  recordSkillInstall,
3228
3849
  removeSkillFromLock,
3229
3850
  getTrackedSkills,
3230
3851
  checkSkillUpdate,
3231
3852
  checkAllSkillUpdates,
3232
- formatNetworkError,
3233
3853
  MarketplaceClient,
3234
3854
  RECOMMENDATION_ERROR_CODES,
3235
3855
  tokenizeCriteriaValue,
@@ -3258,4 +3878,4 @@ export {
3258
3878
  discoverSkillsMulti,
3259
3879
  validateSkill
3260
3880
  };
3261
- //# sourceMappingURL=chunk-43GULI6J.js.map
3881
+ //# sourceMappingURL=chunk-HEAGCHKU.js.map