@cleocode/caamp 0.3.0 → 0.4.1

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,58 +1,158 @@
1
- // src/core/registry/providers.ts
2
- import { readFileSync, existsSync } from "fs";
1
+ // src/core/paths/standard.ts
2
+ import { existsSync } from "fs";
3
3
  import { homedir } from "os";
4
- import { join, dirname } from "path";
5
- import { fileURLToPath } from "url";
6
- function findRegistryPath() {
7
- const thisDir = dirname(fileURLToPath(import.meta.url));
8
- const devPath = join(thisDir, "..", "..", "..", "providers", "registry.json");
9
- if (existsSync(devPath)) return devPath;
10
- const distPath = join(thisDir, "..", "providers", "registry.json");
11
- if (existsSync(distPath)) return distPath;
12
- let dir = thisDir;
13
- for (let i = 0; i < 5; i++) {
14
- const candidate = join(dir, "providers", "registry.json");
15
- if (existsSync(candidate)) return candidate;
16
- dir = dirname(dir);
17
- }
18
- throw new Error(`Cannot find providers/registry.json (searched from ${thisDir})`);
19
- }
20
- var _registry = null;
21
- var _providers = null;
22
- var _aliasMap = null;
23
- function getPlatformPaths() {
4
+ import { dirname, isAbsolute, join, resolve } from "path";
5
+ function getPlatformLocations() {
24
6
  const home = homedir();
25
7
  const platform = process.platform;
26
8
  if (platform === "win32") {
27
9
  const appData = process.env["APPDATA"] ?? join(home, "AppData", "Roaming");
28
10
  return {
11
+ home,
29
12
  config: appData,
30
13
  vscodeConfig: join(appData, "Code", "User"),
31
14
  zedConfig: join(appData, "Zed"),
32
- claudeDesktopConfig: join(appData, "Claude")
15
+ claudeDesktopConfig: join(appData, "Claude"),
16
+ applications: []
33
17
  };
34
- } else if (platform === "darwin") {
18
+ }
19
+ if (platform === "darwin") {
20
+ const config2 = process.env["XDG_CONFIG_HOME"] ?? join(home, ".config");
35
21
  return {
36
- config: process.env["XDG_CONFIG_HOME"] ?? join(home, ".config"),
22
+ home,
23
+ config: config2,
37
24
  vscodeConfig: join(home, "Library", "Application Support", "Code", "User"),
38
25
  zedConfig: join(home, "Library", "Application Support", "Zed"),
39
- claudeDesktopConfig: join(home, "Library", "Application Support", "Claude")
40
- };
41
- } else {
42
- const config = process.env["XDG_CONFIG_HOME"] ?? join(home, ".config");
43
- return {
44
- config,
45
- vscodeConfig: join(config, "Code", "User"),
46
- zedConfig: join(config, "zed"),
47
- claudeDesktopConfig: join(config, "Claude")
26
+ claudeDesktopConfig: join(home, "Library", "Application Support", "Claude"),
27
+ applications: ["/Applications", join(home, "Applications")]
48
28
  };
49
29
  }
30
+ const config = process.env["XDG_CONFIG_HOME"] ?? join(home, ".config");
31
+ return {
32
+ home,
33
+ config,
34
+ vscodeConfig: join(config, "Code", "User"),
35
+ zedConfig: join(config, "zed"),
36
+ claudeDesktopConfig: join(config, "Claude"),
37
+ applications: []
38
+ };
50
39
  }
51
- function resolvePath(template) {
40
+ function normalizeHomeOverride(value) {
52
41
  const home = homedir();
53
- const paths = getPlatformPaths();
54
- return template.replace(/\$HOME/g, home).replace(/\$CONFIG/g, paths.config).replace(/\$VSCODE_CONFIG/g, paths.vscodeConfig).replace(/\$ZED_CONFIG/g, paths.zedConfig).replace(/\$CLAUDE_DESKTOP_CONFIG/g, paths.claudeDesktopConfig);
42
+ const trimmed = value.trim();
43
+ if (trimmed.startsWith("~/")) {
44
+ return join(home, trimmed.slice(2));
45
+ }
46
+ if (trimmed === "~") {
47
+ return home;
48
+ }
49
+ if (isAbsolute(trimmed)) {
50
+ return resolve(trimmed);
51
+ }
52
+ return resolve(home, trimmed);
53
+ }
54
+ function getAgentsHome() {
55
+ const override = process.env["AGENTS_HOME"];
56
+ if (override && override.trim().length > 0) {
57
+ return normalizeHomeOverride(override);
58
+ }
59
+ return join(homedir(), ".agents");
60
+ }
61
+ function getProjectAgentsDir(projectRoot = process.cwd()) {
62
+ return join(projectRoot, ".agents");
63
+ }
64
+ function resolveProjectPath(relativePath, projectDir = process.cwd()) {
65
+ return join(projectDir, relativePath);
66
+ }
67
+ function getCanonicalSkillsDir() {
68
+ return join(getAgentsHome(), "skills");
69
+ }
70
+ function getLockFilePath() {
71
+ return join(getAgentsHome(), ".caamp-lock.json");
72
+ }
73
+ function resolveRegistryTemplatePath(template) {
74
+ const locations = getPlatformLocations();
75
+ return template.replace(/\$HOME/g, locations.home).replace(/\$CONFIG/g, locations.config).replace(/\$VSCODE_CONFIG/g, locations.vscodeConfig).replace(/\$ZED_CONFIG/g, locations.zedConfig).replace(/\$CLAUDE_DESKTOP_CONFIG/g, locations.claudeDesktopConfig).replace(/\$AGENTS_HOME/g, getAgentsHome());
76
+ }
77
+ function resolveProviderConfigPath(provider, scope, projectDir = process.cwd()) {
78
+ if (scope === "global") {
79
+ return provider.configPathGlobal;
80
+ }
81
+ if (!provider.configPathProject) {
82
+ return null;
83
+ }
84
+ return resolveProjectPath(provider.configPathProject, projectDir);
85
+ }
86
+ function resolvePreferredConfigScope(provider, useGlobalFlag) {
87
+ if (useGlobalFlag) {
88
+ return "global";
89
+ }
90
+ return provider.configPathProject ? "project" : "global";
91
+ }
92
+ function resolveProviderSkillsDir(provider, scope, projectDir = process.cwd()) {
93
+ if (scope === "global") {
94
+ return provider.pathSkills;
95
+ }
96
+ return resolveProjectPath(provider.pathProjectSkills, projectDir);
97
+ }
98
+ function resolveProviderProjectPath(provider, projectDir = process.cwd()) {
99
+ return resolveProjectPath(provider.pathProject, projectDir);
100
+ }
101
+ function resolveProvidersRegistryPath(startDir) {
102
+ const candidates = [
103
+ join(startDir, "..", "..", "..", "providers", "registry.json"),
104
+ join(startDir, "..", "providers", "registry.json")
105
+ ];
106
+ for (const candidate of candidates) {
107
+ if (existsSync(candidate)) {
108
+ return candidate;
109
+ }
110
+ }
111
+ let current = startDir;
112
+ for (let i = 0; i < 8; i += 1) {
113
+ const candidate = join(current, "providers", "registry.json");
114
+ if (existsSync(candidate)) {
115
+ return candidate;
116
+ }
117
+ current = dirname(current);
118
+ }
119
+ throw new Error(`Cannot find providers/registry.json (searched from ${startDir})`);
120
+ }
121
+ function normalizeSkillSubPath(path) {
122
+ if (!path) return void 0;
123
+ const normalized = path.replace(/\\/g, "/").replace(/^\/+/, "").replace(/\/SKILL\.md$/i, "").trim();
124
+ return normalized.length > 0 ? normalized : void 0;
125
+ }
126
+ function buildSkillSubPathCandidates(marketplacePath, parsedPath) {
127
+ const candidates = [];
128
+ const base = normalizeSkillSubPath(marketplacePath);
129
+ const parsed = normalizeSkillSubPath(parsedPath);
130
+ if (base) candidates.push(base);
131
+ if (parsed) candidates.push(parsed);
132
+ const knownPrefixes = [".agents", ".claude"];
133
+ for (const value of [base, parsed]) {
134
+ if (!value || !value.startsWith("skills/")) continue;
135
+ for (const prefix of knownPrefixes) {
136
+ candidates.push(`${prefix}/${value}`);
137
+ }
138
+ }
139
+ if (candidates.length === 0) {
140
+ candidates.push(void 0);
141
+ }
142
+ return Array.from(new Set(candidates));
143
+ }
144
+
145
+ // src/core/registry/providers.ts
146
+ import { readFileSync } from "fs";
147
+ import { dirname as dirname2 } from "path";
148
+ import { fileURLToPath } from "url";
149
+ function findRegistryPath() {
150
+ const thisDir = dirname2(fileURLToPath(import.meta.url));
151
+ return resolveProvidersRegistryPath(thisDir);
55
152
  }
153
+ var _registry = null;
154
+ var _providers = null;
155
+ var _aliasMap = null;
56
156
  function resolveProvider(raw) {
57
157
  return {
58
158
  id: raw.id,
@@ -60,19 +160,19 @@ function resolveProvider(raw) {
60
160
  vendor: raw.vendor,
61
161
  agentFlag: raw.agentFlag,
62
162
  aliases: raw.aliases,
63
- pathGlobal: resolvePath(raw.pathGlobal),
163
+ pathGlobal: resolveRegistryTemplatePath(raw.pathGlobal),
64
164
  pathProject: raw.pathProject,
65
165
  instructFile: raw.instructFile,
66
166
  configKey: raw.configKey,
67
167
  configFormat: raw.configFormat,
68
- configPathGlobal: resolvePath(raw.configPathGlobal),
168
+ configPathGlobal: resolveRegistryTemplatePath(raw.configPathGlobal),
69
169
  configPathProject: raw.configPathProject,
70
- pathSkills: resolvePath(raw.pathSkills),
170
+ pathSkills: resolveRegistryTemplatePath(raw.pathSkills),
71
171
  pathProjectSkills: raw.pathProjectSkills,
72
172
  detection: {
73
173
  methods: raw.detection.methods,
74
174
  binary: raw.detection.binary,
75
- directories: raw.detection.directories?.map(resolvePath),
175
+ directories: raw.detection.directories?.map(resolveRegistryTemplatePath),
76
176
  appBundle: raw.detection.appBundle,
77
177
  flatpakId: raw.detection.flatpakId
78
178
  },
@@ -163,6 +263,8 @@ function isQuiet() {
163
263
  import { existsSync as existsSync2 } from "fs";
164
264
  import { execFileSync } from "child_process";
165
265
  import { join as join2 } from "path";
266
+ var DEFAULT_DETECTION_CACHE_TTL_MS = 3e4;
267
+ var detectionCache = null;
166
268
  function checkBinary(binary) {
167
269
  try {
168
270
  const cmd = process.platform === "win32" ? "where" : "which";
@@ -177,7 +279,8 @@ function checkDirectory(dir) {
177
279
  }
178
280
  function checkAppBundle(appName) {
179
281
  if (process.platform !== "darwin") return false;
180
- return existsSync2(join2("/Applications", appName));
282
+ const applications = getPlatformLocations().applications;
283
+ return applications.some((base) => existsSync2(join2(base, appName)));
181
284
  }
182
285
  function checkFlatpak(flatpakId) {
183
286
  if (process.platform !== "linux") return false;
@@ -229,24 +332,71 @@ function detectProvider(provider) {
229
332
  projectDetected: false
230
333
  };
231
334
  }
335
+ function providerSignature(provider) {
336
+ return JSON.stringify({
337
+ id: provider.id,
338
+ methods: provider.detection.methods,
339
+ binary: provider.detection.binary,
340
+ directories: provider.detection.directories,
341
+ appBundle: provider.detection.appBundle,
342
+ flatpakId: provider.detection.flatpakId
343
+ });
344
+ }
345
+ function buildProvidersSignature(providers) {
346
+ return providers.map(providerSignature).join("|");
347
+ }
348
+ function cloneDetectionResults(results) {
349
+ return results.map((result) => ({
350
+ provider: result.provider,
351
+ installed: result.installed,
352
+ methods: [...result.methods],
353
+ projectDetected: result.projectDetected
354
+ }));
355
+ }
356
+ function getCachedResults(signature, options) {
357
+ if (!detectionCache || options.forceRefresh) return null;
358
+ if (detectionCache.signature !== signature) return null;
359
+ const ttlMs = options.ttlMs ?? DEFAULT_DETECTION_CACHE_TTL_MS;
360
+ if (ttlMs <= 0) return null;
361
+ if (Date.now() - detectionCache.createdAt > ttlMs) return null;
362
+ return cloneDetectionResults(detectionCache.results);
363
+ }
364
+ function setCachedResults(signature, results) {
365
+ detectionCache = {
366
+ createdAt: Date.now(),
367
+ signature,
368
+ results: cloneDetectionResults(results)
369
+ };
370
+ }
232
371
  function detectProjectProvider(provider, projectDir) {
233
372
  if (!provider.pathProject) return false;
234
- return existsSync2(join2(projectDir, provider.pathProject));
373
+ return existsSync2(resolveProviderProjectPath(provider, projectDir));
235
374
  }
236
- function detectAllProviders() {
375
+ function detectAllProviders(options = {}) {
237
376
  const providers = getAllProviders();
238
- return providers.map(detectProvider);
377
+ const signature = buildProvidersSignature(providers);
378
+ const cached = getCachedResults(signature, options);
379
+ if (cached) {
380
+ debug(`detection cache hit for ${providers.length} providers`);
381
+ return cached;
382
+ }
383
+ const results = providers.map(detectProvider);
384
+ setCachedResults(signature, results);
385
+ return cloneDetectionResults(results);
239
386
  }
240
- function getInstalledProviders() {
241
- return detectAllProviders().filter((r) => r.installed).map((r) => r.provider);
387
+ function getInstalledProviders(options = {}) {
388
+ return detectAllProviders(options).filter((r) => r.installed).map((r) => r.provider);
242
389
  }
243
- function detectProjectProviders(projectDir) {
244
- const results = detectAllProviders();
390
+ function detectProjectProviders(projectDir, options = {}) {
391
+ const results = detectAllProviders(options);
245
392
  return results.map((r) => ({
246
393
  ...r,
247
394
  projectDetected: detectProjectProvider(r.provider, projectDir)
248
395
  }));
249
396
  }
397
+ function resetDetectionCache() {
398
+ detectionCache = null;
399
+ }
250
400
 
251
401
  // src/core/sources/parser.ts
252
402
  var GITHUB_SHORTHAND = /^([a-zA-Z0-9_.-]+)\/([a-zA-Z0-9_.-]+)(?:\/(.+))?$/;
@@ -368,15 +518,20 @@ function isMarketplaceScoped(input) {
368
518
  // src/core/skills/installer.ts
369
519
  import { mkdir, symlink, rm, cp } from "fs/promises";
370
520
  import { existsSync as existsSync3, lstatSync } from "fs";
371
- import { homedir as homedir2 } from "os";
372
521
  import { join as join3 } from "path";
373
- var CANONICAL_DIR = join3(homedir2(), ".agents", "skills");
522
+
523
+ // src/core/paths/agents.ts
524
+ var AGENTS_HOME = getAgentsHome();
525
+ var LOCK_FILE_PATH = getLockFilePath();
526
+ var CANONICAL_SKILLS_DIR = getCanonicalSkillsDir();
527
+
528
+ // src/core/skills/installer.ts
374
529
  async function ensureCanonicalDir() {
375
- await mkdir(CANONICAL_DIR, { recursive: true });
530
+ await mkdir(CANONICAL_SKILLS_DIR, { recursive: true });
376
531
  }
377
532
  async function installToCanonical(sourcePath, skillName) {
378
533
  await ensureCanonicalDir();
379
- const targetDir = join3(CANONICAL_DIR, skillName);
534
+ const targetDir = join3(CANONICAL_SKILLS_DIR, skillName);
380
535
  if (existsSync3(targetDir)) {
381
536
  await rm(targetDir, { recursive: true });
382
537
  }
@@ -384,7 +539,11 @@ async function installToCanonical(sourcePath, skillName) {
384
539
  return targetDir;
385
540
  }
386
541
  async function linkToAgent(canonicalPath, provider, skillName, isGlobal, projectDir) {
387
- const targetSkillsDir = isGlobal ? provider.pathSkills : join3(projectDir ?? process.cwd(), provider.pathProjectSkills);
542
+ const targetSkillsDir = resolveProviderSkillsDir(
543
+ provider,
544
+ isGlobal ? "global" : "project",
545
+ projectDir
546
+ );
388
547
  if (!targetSkillsDir) {
389
548
  return { success: false, error: `Provider ${provider.id} has no skills directory` };
390
549
  }
@@ -437,7 +596,11 @@ async function removeSkill(skillName, providers, isGlobal, projectDir) {
437
596
  const removed = [];
438
597
  const errors = [];
439
598
  for (const provider of providers) {
440
- const skillsDir = isGlobal ? provider.pathSkills : join3(projectDir ?? process.cwd(), provider.pathProjectSkills);
599
+ const skillsDir = resolveProviderSkillsDir(
600
+ provider,
601
+ isGlobal ? "global" : "project",
602
+ projectDir
603
+ );
441
604
  if (!skillsDir) continue;
442
605
  const linkPath = join3(skillsDir, skillName);
443
606
  if (existsSync3(linkPath)) {
@@ -449,7 +612,7 @@ async function removeSkill(skillName, providers, isGlobal, projectDir) {
449
612
  }
450
613
  }
451
614
  }
452
- const canonicalPath = join3(CANONICAL_DIR, skillName);
615
+ const canonicalPath = join3(CANONICAL_SKILLS_DIR, skillName);
453
616
  if (existsSync3(canonicalPath)) {
454
617
  try {
455
618
  await rm(canonicalPath, { recursive: true });
@@ -460,62 +623,95 @@ async function removeSkill(skillName, providers, isGlobal, projectDir) {
460
623
  return { removed, errors };
461
624
  }
462
625
  async function listCanonicalSkills() {
463
- if (!existsSync3(CANONICAL_DIR)) return [];
626
+ if (!existsSync3(CANONICAL_SKILLS_DIR)) return [];
464
627
  const { readdir: readdir2 } = await import("fs/promises");
465
- const entries = await readdir2(CANONICAL_DIR, { withFileTypes: true });
628
+ const entries = await readdir2(CANONICAL_SKILLS_DIR, { withFileTypes: true });
466
629
  return entries.filter((e) => e.isDirectory() || e.isSymbolicLink()).map((e) => e.name);
467
630
  }
468
631
 
469
632
  // src/core/lock-utils.ts
470
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
633
+ import { open, readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, rm as rm2, rename } from "fs/promises";
471
634
  import { existsSync as existsSync4 } from "fs";
472
- import { homedir as homedir3 } from "os";
473
- import { join as join4 } from "path";
474
- var LOCK_DIR = join4(homedir3(), ".agents");
475
- var LOCK_FILE = join4(LOCK_DIR, ".caamp-lock.json");
635
+ var LOCK_GUARD_PATH = `${LOCK_FILE_PATH}.lock`;
636
+ function sleep(ms) {
637
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
638
+ }
639
+ async function acquireLockGuard(retries = 40, delayMs = 25) {
640
+ await mkdir2(AGENTS_HOME, { recursive: true });
641
+ for (let attempt = 0; attempt < retries; attempt += 1) {
642
+ try {
643
+ const handle = await open(LOCK_GUARD_PATH, "wx");
644
+ await handle.close();
645
+ return;
646
+ } catch (error) {
647
+ if (!(error instanceof Error) || !("code" in error) || error.code !== "EEXIST") {
648
+ throw error;
649
+ }
650
+ await sleep(delayMs);
651
+ }
652
+ }
653
+ throw new Error("Timed out waiting for lock file guard");
654
+ }
655
+ async function releaseLockGuard() {
656
+ await rm2(LOCK_GUARD_PATH, { force: true });
657
+ }
658
+ async function writeLockFileUnsafe(lock) {
659
+ const tmpPath = `${LOCK_FILE_PATH}.tmp-${process.pid}-${Date.now()}`;
660
+ await writeFile2(tmpPath, JSON.stringify(lock, null, 2) + "\n", "utf-8");
661
+ await rename(tmpPath, LOCK_FILE_PATH);
662
+ }
476
663
  async function readLockFile() {
477
664
  try {
478
- if (!existsSync4(LOCK_FILE)) {
665
+ if (!existsSync4(LOCK_FILE_PATH)) {
479
666
  return { version: 1, skills: {}, mcpServers: {} };
480
667
  }
481
- const content = await readFile2(LOCK_FILE, "utf-8");
668
+ const content = await readFile2(LOCK_FILE_PATH, "utf-8");
482
669
  return JSON.parse(content);
483
670
  } catch {
484
671
  return { version: 1, skills: {}, mcpServers: {} };
485
672
  }
486
673
  }
487
- async function writeLockFile(lock) {
488
- await mkdir2(LOCK_DIR, { recursive: true });
489
- await writeFile2(LOCK_FILE, JSON.stringify(lock, null, 2) + "\n", "utf-8");
674
+ async function updateLockFile(updater) {
675
+ await acquireLockGuard();
676
+ try {
677
+ const lock = await readLockFile();
678
+ await updater(lock);
679
+ await writeLockFileUnsafe(lock);
680
+ return lock;
681
+ } finally {
682
+ await releaseLockGuard();
683
+ }
490
684
  }
491
685
 
492
686
  // src/core/skills/lock.ts
493
687
  import { simpleGit } from "simple-git";
494
688
  async function recordSkillInstall(skillName, scopedName, source, sourceType, agents, canonicalPath, isGlobal, projectDir, version) {
495
- const lock = await readLockFile();
496
- const now = (/* @__PURE__ */ new Date()).toISOString();
497
- const existing = lock.skills[skillName];
498
- lock.skills[skillName] = {
499
- name: skillName,
500
- scopedName,
501
- source,
502
- sourceType,
503
- version,
504
- installedAt: existing?.installedAt ?? now,
505
- updatedAt: now,
506
- agents: [.../* @__PURE__ */ new Set([...existing?.agents ?? [], ...agents])],
507
- canonicalPath,
508
- isGlobal,
509
- projectDir
510
- };
511
- await writeLockFile(lock);
689
+ await updateLockFile((lock) => {
690
+ const now = (/* @__PURE__ */ new Date()).toISOString();
691
+ const existing = lock.skills[skillName];
692
+ lock.skills[skillName] = {
693
+ name: skillName,
694
+ scopedName,
695
+ source,
696
+ sourceType,
697
+ version,
698
+ installedAt: existing?.installedAt ?? now,
699
+ updatedAt: now,
700
+ agents: [.../* @__PURE__ */ new Set([...existing?.agents ?? [], ...agents])],
701
+ canonicalPath,
702
+ isGlobal,
703
+ projectDir
704
+ };
705
+ });
512
706
  }
513
707
  async function removeSkillFromLock(skillName) {
514
- const lock = await readLockFile();
515
- if (!(skillName in lock.skills)) return false;
516
- delete lock.skills[skillName];
517
- await writeLockFile(lock);
518
- return true;
708
+ let removed = false;
709
+ await updateLockFile((lock) => {
710
+ if (!(skillName in lock.skills)) return;
711
+ delete lock.skills[skillName];
712
+ removed = true;
713
+ });
714
+ return removed;
519
715
  }
520
716
  async function getTrackedSkills() {
521
717
  const lock = await readLockFile();
@@ -576,8 +772,66 @@ async function checkSkillUpdate(skillName) {
576
772
  };
577
773
  }
578
774
 
775
+ // src/core/network/fetch.ts
776
+ var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
777
+ var NetworkError = class extends Error {
778
+ kind;
779
+ url;
780
+ status;
781
+ constructor(message, kind, url, status) {
782
+ super(message);
783
+ this.name = "NetworkError";
784
+ this.kind = kind;
785
+ this.url = url;
786
+ this.status = status;
787
+ }
788
+ };
789
+ function isAbortError(error) {
790
+ return error instanceof Error && error.name === "AbortError";
791
+ }
792
+ async function fetchWithTimeout(url, init, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
793
+ try {
794
+ return await fetch(url, {
795
+ ...init,
796
+ signal: AbortSignal.timeout(timeoutMs)
797
+ });
798
+ } catch (error) {
799
+ if (isAbortError(error)) {
800
+ throw new NetworkError(`Request timed out after ${timeoutMs}ms`, "timeout", url);
801
+ }
802
+ throw new NetworkError("Network request failed", "network", url);
803
+ }
804
+ }
805
+ function ensureOkResponse(response, url) {
806
+ if (!response.ok) {
807
+ throw new NetworkError(`Request failed with status ${response.status}`, "http", url, response.status);
808
+ }
809
+ return response;
810
+ }
811
+ function formatNetworkError(error) {
812
+ if (error instanceof NetworkError) {
813
+ if (error.kind === "timeout") {
814
+ return "Network request timed out. Please check your connection and try again.";
815
+ }
816
+ if (error.kind === "http") {
817
+ return `Marketplace request failed with HTTP ${error.status ?? "unknown"}. Please try again shortly.`;
818
+ }
819
+ return "Network request failed. Please check your connection and try again.";
820
+ }
821
+ if (error instanceof Error) return error.message;
822
+ return String(error);
823
+ }
824
+
579
825
  // src/core/marketplace/skillsmp.ts
580
826
  var API_BASE = "https://www.agentskills.in/api/skills";
827
+ function parseScopedName(value) {
828
+ const match = value.match(/^@([^/]+)\/([^/]+)$/);
829
+ if (!match) return null;
830
+ return {
831
+ author: match[1],
832
+ name: match[2]
833
+ };
834
+ }
581
835
  function toResult(skill) {
582
836
  return {
583
837
  name: skill.name,
@@ -599,31 +853,34 @@ var SkillsMPAdapter = class {
599
853
  limit: String(limit),
600
854
  sortBy: "stars"
601
855
  });
602
- try {
603
- const response = await fetch(`${API_BASE}?${params}`);
604
- if (!response.ok) return [];
605
- const data = await response.json();
606
- return data.skills.map(toResult);
607
- } catch {
608
- return [];
609
- }
856
+ const url = `${API_BASE}?${params}`;
857
+ const response = ensureOkResponse(await fetchWithTimeout(url), url);
858
+ const data = await response.json();
859
+ return data.skills.map(toResult);
610
860
  }
611
861
  async getSkill(scopedName) {
612
- const params = new URLSearchParams({
613
- search: scopedName,
614
- limit: "1"
615
- });
616
- try {
617
- const response = await fetch(`${API_BASE}?${params}`);
618
- if (!response.ok) return null;
862
+ const parts = parseScopedName(scopedName);
863
+ const searchTerms = parts ? [parts.name, `${parts.author} ${parts.name}`, scopedName] : [scopedName];
864
+ const seen = /* @__PURE__ */ new Set();
865
+ for (const term of searchTerms) {
866
+ if (seen.has(term)) continue;
867
+ seen.add(term);
868
+ const params = new URLSearchParams({
869
+ search: term,
870
+ limit: "50",
871
+ sortBy: "stars"
872
+ });
873
+ const url = `${API_BASE}?${params}`;
874
+ const response = ensureOkResponse(await fetchWithTimeout(url), url);
619
875
  const data = await response.json();
620
876
  const match = data.skills.find(
621
877
  (s) => s.scopedName === scopedName || `@${s.author}/${s.name}` === scopedName
622
878
  );
623
- return match ? toResult(match) : null;
624
- } catch {
625
- return null;
879
+ if (match) {
880
+ return toResult(match);
881
+ }
626
882
  }
883
+ return null;
627
884
  }
628
885
  };
629
886
 
@@ -645,18 +902,14 @@ function toResult2(skill) {
645
902
  var SkillsShAdapter = class {
646
903
  name = "skills.sh";
647
904
  async search(query, limit = 20) {
648
- try {
649
- const params = new URLSearchParams({
650
- q: query,
651
- limit: String(limit)
652
- });
653
- const response = await fetch(`${API_BASE2}/search?${params}`);
654
- if (!response.ok) return [];
655
- const data = await response.json();
656
- return data.results.map(toResult2);
657
- } catch {
658
- return [];
659
- }
905
+ const params = new URLSearchParams({
906
+ q: query,
907
+ limit: String(limit)
908
+ });
909
+ const url = `${API_BASE2}/search?${params}`;
910
+ const response = ensureOkResponse(await fetchWithTimeout(url), url);
911
+ const data = await response.json();
912
+ return data.results.map(toResult2);
660
913
  }
661
914
  async getSkill(scopedName) {
662
915
  const results = await this.search(scopedName, 5);
@@ -665,6 +918,14 @@ var SkillsShAdapter = class {
665
918
  };
666
919
 
667
920
  // src/core/marketplace/client.ts
921
+ var MarketplaceUnavailableError = class extends Error {
922
+ details;
923
+ constructor(message, details) {
924
+ super(message);
925
+ this.name = "MarketplaceUnavailableError";
926
+ this.details = details;
927
+ }
928
+ };
668
929
  var MarketplaceClient = class {
669
930
  adapters;
670
931
  /**
@@ -704,11 +965,21 @@ var MarketplaceClient = class {
704
965
  * ```
705
966
  */
706
967
  async search(query, limit = 20) {
707
- const promises = this.adapters.map(
708
- (adapter) => adapter.search(query, limit).catch(() => [])
709
- );
710
- const allResults = await Promise.all(promises);
711
- const flat = allResults.flat();
968
+ const settled = await Promise.allSettled(this.adapters.map((adapter) => adapter.search(query, limit)));
969
+ const flat = [];
970
+ const failures = [];
971
+ for (const [index, result] of settled.entries()) {
972
+ const adapterName = this.adapters[index]?.name ?? "unknown";
973
+ if (result.status === "fulfilled") {
974
+ flat.push(...result.value);
975
+ } else {
976
+ const reason = result.reason instanceof Error ? result.reason.message : String(result.reason);
977
+ failures.push(`${adapterName}: ${reason}`);
978
+ }
979
+ }
980
+ if (flat.length === 0 && failures.length > 0) {
981
+ throw new MarketplaceUnavailableError("All marketplace sources failed.", failures);
982
+ }
712
983
  const seen = /* @__PURE__ */ new Map();
713
984
  for (const result of flat) {
714
985
  const existing = seen.get(result.scopedName);
@@ -734,9 +1005,18 @@ var MarketplaceClient = class {
734
1005
  * ```
735
1006
  */
736
1007
  async getSkill(scopedName) {
1008
+ const failures = [];
737
1009
  for (const adapter of this.adapters) {
738
- const result = await adapter.getSkill(scopedName).catch(() => null);
739
- if (result) return result;
1010
+ try {
1011
+ const result = await adapter.getSkill(scopedName);
1012
+ if (result) return result;
1013
+ } catch (error) {
1014
+ const reason = error instanceof Error ? error.message : String(error);
1015
+ failures.push(`${adapter.name}: ${reason}`);
1016
+ }
1017
+ }
1018
+ if (failures.length === this.adapters.length && this.adapters.length > 0) {
1019
+ throw new MarketplaceUnavailableError("All marketplace sources failed.", failures);
740
1020
  }
741
1021
  return null;
742
1022
  }
@@ -745,7 +1025,7 @@ var MarketplaceClient = class {
745
1025
  // src/core/skills/discovery.ts
746
1026
  import { readFile as readFile3, readdir } from "fs/promises";
747
1027
  import { existsSync as existsSync5 } from "fs";
748
- import { join as join5 } from "path";
1028
+ import { join as join4 } from "path";
749
1029
  import matter from "gray-matter";
750
1030
  async function parseSkillFile(filePath) {
751
1031
  try {
@@ -769,7 +1049,7 @@ async function parseSkillFile(filePath) {
769
1049
  }
770
1050
  }
771
1051
  async function discoverSkill(skillDir) {
772
- const skillFile = join5(skillDir, "SKILL.md");
1052
+ const skillFile = join4(skillDir, "SKILL.md");
773
1053
  if (!existsSync5(skillFile)) return null;
774
1054
  const metadata = await parseSkillFile(skillFile);
775
1055
  if (!metadata) return null;
@@ -786,7 +1066,7 @@ async function discoverSkills(rootDir) {
786
1066
  const skills = [];
787
1067
  for (const entry of entries) {
788
1068
  if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
789
- const skillDir = join5(rootDir, entry.name);
1069
+ const skillDir = join4(rootDir, entry.name);
790
1070
  const skill = await discoverSkill(skillDir);
791
1071
  if (skill) {
792
1072
  skills.push(skill);
@@ -809,6 +1089,273 @@ async function discoverSkillsMulti(dirs) {
809
1089
  return all;
810
1090
  }
811
1091
 
1092
+ // src/core/skills/recommendation.ts
1093
+ var RECOMMENDATION_ERROR_CODES = {
1094
+ QUERY_INVALID: "E_SKILLS_QUERY_INVALID",
1095
+ NO_MATCHES: "E_SKILLS_NO_MATCHES",
1096
+ SOURCE_UNAVAILABLE: "E_SKILLS_SOURCE_UNAVAILABLE",
1097
+ CRITERIA_CONFLICT: "E_SKILLS_CRITERIA_CONFLICT"
1098
+ };
1099
+ var DEFAULT_WEIGHTS = {
1100
+ mustHaveMatch: 10,
1101
+ preferMatch: 4,
1102
+ queryTokenMatch: 3,
1103
+ starsFactor: 2,
1104
+ metadataBoost: 2,
1105
+ modernMarkerBoost: 3,
1106
+ legacyMarkerPenalty: 3,
1107
+ excludePenalty: 25,
1108
+ missingMustHavePenalty: 20
1109
+ };
1110
+ var DEFAULT_MODERN_MARKERS = ["svelte 5", "runes", "lafs", "slsa", "drizzle", "better-auth"];
1111
+ var DEFAULT_LEGACY_MARKERS = ["svelte 3", "jquery", "bower", "legacy", "book.json", "gitbook-cli"];
1112
+ function tokenizeCriteriaValue(value) {
1113
+ return value.split(",").map((part) => part.trim().toLowerCase()).filter(Boolean);
1114
+ }
1115
+ function normalizeList(value) {
1116
+ if (value === void 0) return [];
1117
+ if (!(typeof value === "string" || Array.isArray(value))) return [];
1118
+ const source = Array.isArray(value) ? value : [value];
1119
+ const flattened = source.flatMap((item) => typeof item === "string" ? tokenizeCriteriaValue(item) : []);
1120
+ return Array.from(new Set(flattened)).sort((a, b) => a.localeCompare(b));
1121
+ }
1122
+ function hasAnyCriteriaInput(input) {
1123
+ const query = typeof input.query === "string" ? input.query.trim() : "";
1124
+ if (query.length > 0) return true;
1125
+ const lists = [input.mustHave, input.prefer, input.exclude];
1126
+ return lists.some((list) => normalizeList(list).length > 0);
1127
+ }
1128
+ function validateRecommendationCriteria(input) {
1129
+ const issues = [];
1130
+ if (input.query !== void 0 && typeof input.query !== "string") {
1131
+ issues.push({
1132
+ code: RECOMMENDATION_ERROR_CODES.QUERY_INVALID,
1133
+ field: "query",
1134
+ message: "query must be a string"
1135
+ });
1136
+ }
1137
+ if (input.mustHave !== void 0 && !(typeof input.mustHave === "string" || Array.isArray(input.mustHave))) {
1138
+ issues.push({
1139
+ code: RECOMMENDATION_ERROR_CODES.QUERY_INVALID,
1140
+ field: "mustHave",
1141
+ message: "mustHave must be a string or string[]"
1142
+ });
1143
+ }
1144
+ if (input.prefer !== void 0 && !(typeof input.prefer === "string" || Array.isArray(input.prefer))) {
1145
+ issues.push({
1146
+ code: RECOMMENDATION_ERROR_CODES.QUERY_INVALID,
1147
+ field: "prefer",
1148
+ message: "prefer must be a string or string[]"
1149
+ });
1150
+ }
1151
+ if (input.exclude !== void 0 && !(typeof input.exclude === "string" || Array.isArray(input.exclude))) {
1152
+ issues.push({
1153
+ code: RECOMMENDATION_ERROR_CODES.QUERY_INVALID,
1154
+ field: "exclude",
1155
+ message: "exclude must be a string or string[]"
1156
+ });
1157
+ }
1158
+ const mustHave = normalizeList(input.mustHave);
1159
+ const prefer = normalizeList(input.prefer);
1160
+ const exclude = normalizeList(input.exclude);
1161
+ const conflict = mustHave.some((term) => exclude.includes(term)) || prefer.some((term) => exclude.includes(term));
1162
+ if (conflict) {
1163
+ issues.push({
1164
+ code: RECOMMENDATION_ERROR_CODES.CRITERIA_CONFLICT,
1165
+ field: "exclude",
1166
+ message: "criteria terms cannot appear in both prefer/must-have and exclude"
1167
+ });
1168
+ }
1169
+ if (issues.length === 0 && !hasAnyCriteriaInput(input)) {
1170
+ issues.push({
1171
+ code: RECOMMENDATION_ERROR_CODES.QUERY_INVALID,
1172
+ field: "query",
1173
+ message: "at least one criteria value is required"
1174
+ });
1175
+ }
1176
+ return {
1177
+ valid: issues.length === 0,
1178
+ issues
1179
+ };
1180
+ }
1181
+ function normalizeRecommendationCriteria(input) {
1182
+ const query = (input.query ?? "").trim().toLowerCase();
1183
+ return {
1184
+ query,
1185
+ queryTokens: query ? Array.from(new Set(tokenizeCriteriaValue(query.replace(/\s+/g, ",")))).sort((a, b) => a.localeCompare(b)) : [],
1186
+ mustHave: normalizeList(input.mustHave),
1187
+ prefer: normalizeList(input.prefer),
1188
+ exclude: normalizeList(input.exclude)
1189
+ };
1190
+ }
1191
+ function countMatches(haystack, needles) {
1192
+ let count = 0;
1193
+ for (const needle of needles) {
1194
+ if (haystack.includes(needle)) {
1195
+ count += 1;
1196
+ }
1197
+ }
1198
+ return count;
1199
+ }
1200
+ function clampScore(value) {
1201
+ return Number(value.toFixed(6));
1202
+ }
1203
+ function buildSearchText(skill) {
1204
+ return `${skill.name} ${skill.scopedName} ${skill.description} ${skill.author}`.toLowerCase();
1205
+ }
1206
+ function scoreSkillRecommendation(skill, criteria, options = {}) {
1207
+ const weights = { ...DEFAULT_WEIGHTS, ...options.weights };
1208
+ const modernMarkers = (options.modernMarkers ?? DEFAULT_MODERN_MARKERS).map((marker) => marker.toLowerCase());
1209
+ const legacyMarkers = (options.legacyMarkers ?? DEFAULT_LEGACY_MARKERS).map((marker) => marker.toLowerCase());
1210
+ const text = buildSearchText(skill);
1211
+ const reasons = [];
1212
+ const tradeoffs = [];
1213
+ const mustHaveMatches = countMatches(text, criteria.mustHave);
1214
+ const missingMustHave = Math.max(criteria.mustHave.length - mustHaveMatches, 0);
1215
+ const preferMatches = countMatches(text, criteria.prefer);
1216
+ const queryMatches = countMatches(text, criteria.queryTokens);
1217
+ const excludeMatches = countMatches(text, criteria.exclude);
1218
+ const modernMatches = countMatches(text, modernMarkers);
1219
+ const legacyMatches = countMatches(text, legacyMarkers);
1220
+ const metadataSignal = skill.description.trim().length >= 80 ? 1 : 0;
1221
+ const starsSignal = Math.log10(skill.stars + 1);
1222
+ const sourceConfidence = skill.source === "agentskills.in" ? 1 : skill.source === "skills.sh" ? 0.8 : 0.6;
1223
+ const mustHaveScore = mustHaveMatches * weights.mustHaveMatch - missingMustHave * weights.missingMustHavePenalty;
1224
+ const preferScore = preferMatches * weights.preferMatch;
1225
+ const queryScore = queryMatches * weights.queryTokenMatch;
1226
+ const starsScore = starsSignal * weights.starsFactor;
1227
+ const metadataScore = (metadataSignal + sourceConfidence) * weights.metadataBoost;
1228
+ const modernityScore = modernMatches * weights.modernMarkerBoost - legacyMatches * weights.legacyMarkerPenalty;
1229
+ const exclusionPenalty = excludeMatches * weights.excludePenalty;
1230
+ const hasGitbookTopic = text.includes("gitbook");
1231
+ const hasGitSync = text.includes("git sync") || text.includes("git") && text.includes("sync");
1232
+ const hasApiWorkflow = text.includes("api") && (text.includes("workflow") || text.includes("sync"));
1233
+ const hasLegacyCli = text.includes("gitbook-cli") || text.includes("book.json");
1234
+ const topicScore = (hasGitbookTopic ? 3 : 0) + (hasGitSync ? 2 : 0) + (hasApiWorkflow ? 2 : 0) - (hasLegacyCli ? 4 : 0);
1235
+ const total = clampScore(
1236
+ mustHaveScore + preferScore + queryScore + starsScore + metadataScore + modernityScore + topicScore - exclusionPenalty
1237
+ );
1238
+ if (hasGitbookTopic) reasons.push({ code: "MATCH_TOPIC_GITBOOK" });
1239
+ if (hasGitSync) reasons.push({ code: "HAS_GIT_SYNC" });
1240
+ if (hasApiWorkflow) reasons.push({ code: "HAS_API_WORKFLOW" });
1241
+ if (hasLegacyCli) reasons.push({ code: "PENALTY_LEGACY_CLI" });
1242
+ if (mustHaveMatches > 0) reasons.push({ code: "MUST_HAVE_MATCH", detail: String(mustHaveMatches) });
1243
+ if (missingMustHave > 0) reasons.push({ code: "MISSING_MUST_HAVE", detail: String(missingMustHave) });
1244
+ if (preferMatches > 0) reasons.push({ code: "PREFER_MATCH", detail: String(preferMatches) });
1245
+ if (queryMatches > 0) reasons.push({ code: "QUERY_MATCH", detail: String(queryMatches) });
1246
+ if (starsSignal > 0) reasons.push({ code: "STAR_SIGNAL" });
1247
+ if (metadataSignal > 0) reasons.push({ code: "METADATA_SIGNAL" });
1248
+ if (modernMatches > 0) reasons.push({ code: "MODERN_MARKER", detail: String(modernMatches) });
1249
+ if (legacyMatches > 0) reasons.push({ code: "LEGACY_MARKER", detail: String(legacyMatches) });
1250
+ if (excludeMatches > 0) reasons.push({ code: "EXCLUDE_MATCH", detail: String(excludeMatches) });
1251
+ if (missingMustHave > 0) tradeoffs.push("Missing one or more required criteria terms.");
1252
+ if (excludeMatches > 0) tradeoffs.push("Matches one or more excluded terms.");
1253
+ if (skill.stars < 10) tradeoffs.push("Low quality signal from repository stars.");
1254
+ if (hasLegacyCli) tradeoffs.push("Contains legacy GitBook CLI markers.");
1255
+ const result = {
1256
+ skill,
1257
+ score: total,
1258
+ reasons,
1259
+ tradeoffs,
1260
+ excluded: excludeMatches > 0
1261
+ };
1262
+ if (options.includeDetails) {
1263
+ result.breakdown = {
1264
+ mustHave: clampScore(mustHaveScore),
1265
+ prefer: clampScore(preferScore),
1266
+ query: clampScore(queryScore),
1267
+ stars: clampScore(starsScore),
1268
+ metadata: clampScore(metadataScore),
1269
+ modernity: clampScore(modernityScore),
1270
+ exclusionPenalty: clampScore(exclusionPenalty),
1271
+ total
1272
+ };
1273
+ }
1274
+ return result;
1275
+ }
1276
+ function recommendSkills(skills, criteriaInput, options = {}) {
1277
+ const validation = validateRecommendationCriteria(criteriaInput);
1278
+ if (!validation.valid) {
1279
+ const first = validation.issues[0];
1280
+ const error = new Error(first?.message ?? "Invalid recommendation criteria");
1281
+ error.code = first?.code;
1282
+ error.issues = validation.issues;
1283
+ throw error;
1284
+ }
1285
+ const criteria = normalizeRecommendationCriteria(criteriaInput);
1286
+ const ranking = skills.map((skill) => scoreSkillRecommendation(skill, criteria, options)).sort((a, b) => {
1287
+ if (b.score !== a.score) return b.score - a.score;
1288
+ if (b.skill.stars !== a.skill.stars) return b.skill.stars - a.skill.stars;
1289
+ return a.skill.scopedName.localeCompare(b.skill.scopedName);
1290
+ });
1291
+ return {
1292
+ criteria,
1293
+ ranking: typeof options.top === "number" ? ranking.slice(0, Math.max(0, options.top)) : ranking
1294
+ };
1295
+ }
1296
+ var rankSkills = recommendSkills;
1297
+
1298
+ // src/core/skills/recommendation-api.ts
1299
+ function formatSkillRecommendations(result, opts) {
1300
+ const top = result.ranking;
1301
+ if (opts.mode === "human") {
1302
+ if (top.length === 0) return "No recommendations found.";
1303
+ const lines = ["Recommended skills:", ""];
1304
+ for (const [index, entry] of top.entries()) {
1305
+ const marker = index === 0 ? " (Recommended)" : "";
1306
+ lines.push(`${index + 1}) ${entry.skill.scopedName}${marker}`);
1307
+ lines.push(` why: ${entry.reasons.map((reason) => reason.code).join(", ") || "score-based match"}`);
1308
+ lines.push(` tradeoff: ${entry.tradeoffs[0] ?? "none"}`);
1309
+ }
1310
+ lines.push("");
1311
+ lines.push(`CHOOSE: ${top.map((_, index) => index + 1).join(",")}`);
1312
+ return lines.join("\n");
1313
+ }
1314
+ const options = top.map((entry, index) => ({
1315
+ rank: index + 1,
1316
+ scopedName: entry.skill.scopedName,
1317
+ score: entry.score,
1318
+ reasons: entry.reasons,
1319
+ tradeoffs: entry.tradeoffs,
1320
+ ...opts.details ? {
1321
+ description: entry.skill.description,
1322
+ source: entry.skill.source,
1323
+ evidence: entry.breakdown ?? null
1324
+ } : {}
1325
+ }));
1326
+ return {
1327
+ query: result.criteria.query,
1328
+ recommended: options[0] ?? null,
1329
+ options
1330
+ };
1331
+ }
1332
+ async function searchSkills(query, options = {}) {
1333
+ const trimmed = query.trim();
1334
+ if (!trimmed) {
1335
+ const error = new Error("query must be non-empty");
1336
+ error.code = RECOMMENDATION_ERROR_CODES.QUERY_INVALID;
1337
+ throw error;
1338
+ }
1339
+ const client = new MarketplaceClient();
1340
+ try {
1341
+ return await client.search(trimmed, options.limit ?? 20);
1342
+ } catch (error) {
1343
+ const wrapped = new Error(error instanceof Error ? error.message : String(error));
1344
+ wrapped.code = RECOMMENDATION_ERROR_CODES.SOURCE_UNAVAILABLE;
1345
+ throw wrapped;
1346
+ }
1347
+ }
1348
+ async function recommendSkills2(query, criteria, options = {}) {
1349
+ const hits = await searchSkills(query, { limit: options.limit ?? Math.max((options.top ?? 3) * 5, 20) });
1350
+ const ranked = recommendSkills(hits, { ...criteria, query }, options);
1351
+ if (ranked.ranking.length === 0) {
1352
+ const error = new Error("no matches found");
1353
+ error.code = RECOMMENDATION_ERROR_CODES.NO_MATCHES;
1354
+ throw error;
1355
+ }
1356
+ return ranked;
1357
+ }
1358
+
812
1359
  // src/core/skills/audit/scanner.ts
813
1360
  import { readFile as readFile4 } from "fs/promises";
814
1361
  import { existsSync as existsSync6 } from "fs";
@@ -1222,13 +1769,13 @@ async function scanFile(filePath, rules) {
1222
1769
  }
1223
1770
  async function scanDirectory(dirPath) {
1224
1771
  const { readdir: readdir2 } = await import("fs/promises");
1225
- const { join: join8 } = await import("path");
1772
+ const { join: join7 } = await import("path");
1226
1773
  if (!existsSync6(dirPath)) return [];
1227
1774
  const entries = await readdir2(dirPath, { withFileTypes: true });
1228
1775
  const results = [];
1229
1776
  for (const entry of entries) {
1230
1777
  if (entry.isDirectory() || entry.isSymbolicLink()) {
1231
- const skillFile = join8(dirPath, entry.name, "SKILL.md");
1778
+ const skillFile = join7(dirPath, entry.name, "SKILL.md");
1232
1779
  if (existsSync6(skillFile)) {
1233
1780
  results.push(await scanFile(skillFile));
1234
1781
  }
@@ -1442,9 +1989,9 @@ function getNestedValue(obj, keyPath) {
1442
1989
  return current;
1443
1990
  }
1444
1991
  async function ensureDir(filePath) {
1445
- const { mkdir: mkdir4 } = await import("fs/promises");
1446
- const { dirname: dirname4 } = await import("path");
1447
- await mkdir4(dirname4(filePath), { recursive: true });
1992
+ const { mkdir: mkdir5 } = await import("fs/promises");
1993
+ const { dirname: dirname6 } = await import("path");
1994
+ await mkdir5(dirname6(filePath), { recursive: true });
1448
1995
  }
1449
1996
 
1450
1997
  // src/core/formats/json.ts
@@ -1756,14 +2303,9 @@ function getTransform(providerId) {
1756
2303
  }
1757
2304
 
1758
2305
  // src/core/mcp/reader.ts
1759
- import { join as join6 } from "path";
1760
2306
  import { existsSync as existsSync11 } from "fs";
1761
2307
  function resolveConfigPath(provider, scope, projectDir) {
1762
- if (scope === "project") {
1763
- if (!provider.configPathProject) return null;
1764
- return join6(projectDir ?? process.cwd(), provider.configPathProject);
1765
- }
1766
- return provider.configPathGlobal;
2308
+ return resolveProviderConfigPath(provider, scope, projectDir ?? process.cwd());
1767
2309
  }
1768
2310
  async function listMcpServers(provider, scope, projectDir) {
1769
2311
  const configPath = resolveConfigPath(provider, scope, projectDir);
@@ -1886,37 +2428,39 @@ function buildServerConfig(source, transport, headers) {
1886
2428
 
1887
2429
  // src/core/mcp/lock.ts
1888
2430
  async function recordMcpInstall(serverName, source, sourceType, agents, isGlobal) {
1889
- const lock = await readLockFile();
1890
- const now = (/* @__PURE__ */ new Date()).toISOString();
1891
- const existing = lock.mcpServers[serverName];
1892
- lock.mcpServers[serverName] = {
1893
- name: serverName,
1894
- scopedName: serverName,
1895
- source,
1896
- sourceType,
1897
- installedAt: existing?.installedAt ?? now,
1898
- updatedAt: now,
1899
- agents: [.../* @__PURE__ */ new Set([...existing?.agents ?? [], ...agents])],
1900
- canonicalPath: "",
1901
- isGlobal
1902
- };
1903
- await writeLockFile(lock);
2431
+ await updateLockFile((lock) => {
2432
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2433
+ const existing = lock.mcpServers[serverName];
2434
+ lock.mcpServers[serverName] = {
2435
+ name: serverName,
2436
+ scopedName: serverName,
2437
+ source,
2438
+ sourceType,
2439
+ installedAt: existing?.installedAt ?? now,
2440
+ updatedAt: now,
2441
+ agents: [.../* @__PURE__ */ new Set([...existing?.agents ?? [], ...agents])],
2442
+ canonicalPath: "",
2443
+ isGlobal
2444
+ };
2445
+ });
1904
2446
  }
1905
2447
  async function removeMcpFromLock(serverName) {
1906
- const lock = await readLockFile();
1907
- if (!(serverName in lock.mcpServers)) return false;
1908
- delete lock.mcpServers[serverName];
1909
- await writeLockFile(lock);
1910
- return true;
2448
+ let removed = false;
2449
+ await updateLockFile((lock) => {
2450
+ if (!(serverName in lock.mcpServers)) return;
2451
+ delete lock.mcpServers[serverName];
2452
+ removed = true;
2453
+ });
2454
+ return removed;
1911
2455
  }
1912
2456
  async function getTrackedMcpServers() {
1913
2457
  const lock = await readLockFile();
1914
2458
  return lock.mcpServers;
1915
2459
  }
1916
2460
  async function saveLastSelectedAgents(agents) {
1917
- const lock = await readLockFile();
1918
- lock.lastSelectedAgents = agents;
1919
- await writeLockFile(lock);
2461
+ await updateLockFile((lock) => {
2462
+ lock.lastSelectedAgents = agents;
2463
+ });
1920
2464
  }
1921
2465
  async function getLastSelectedAgents() {
1922
2466
  const lock = await readLockFile();
@@ -1926,7 +2470,7 @@ async function getLastSelectedAgents() {
1926
2470
  // src/core/instructions/injector.ts
1927
2471
  import { readFile as readFile9, writeFile as writeFile6 } from "fs/promises";
1928
2472
  import { existsSync as existsSync12 } from "fs";
1929
- import { join as join7, dirname as dirname3 } from "path";
2473
+ import { join as join5, dirname as dirname4 } from "path";
1930
2474
  import { mkdir as mkdir3 } from "fs/promises";
1931
2475
  var MARKER_START = "<!-- CAAMP:START -->";
1932
2476
  var MARKER_END = "<!-- CAAMP:END -->";
@@ -1956,7 +2500,7 @@ ${MARKER_END}`;
1956
2500
  }
1957
2501
  async function inject(filePath, content) {
1958
2502
  const block = buildBlock(content);
1959
- await mkdir3(dirname3(filePath), { recursive: true });
2503
+ await mkdir3(dirname4(filePath), { recursive: true });
1960
2504
  if (!existsSync12(filePath)) {
1961
2505
  await writeFile6(filePath, block + "\n", "utf-8");
1962
2506
  return "created";
@@ -1977,8 +2521,8 @@ async function removeInjection(filePath) {
1977
2521
  if (!MARKER_PATTERN.test(content)) return false;
1978
2522
  const cleaned = content.replace(MARKER_PATTERN, "").replace(/^\n{2,}/, "\n").trim();
1979
2523
  if (!cleaned) {
1980
- const { rm: rm2 } = await import("fs/promises");
1981
- await rm2(filePath);
2524
+ const { rm: rm4 } = await import("fs/promises");
2525
+ await rm4(filePath);
1982
2526
  } else {
1983
2527
  await writeFile6(filePath, cleaned + "\n", "utf-8");
1984
2528
  }
@@ -1988,7 +2532,7 @@ async function checkAllInjections(providers, projectDir, scope, expectedContent)
1988
2532
  const results = [];
1989
2533
  const checked = /* @__PURE__ */ new Set();
1990
2534
  for (const provider of providers) {
1991
- const filePath = scope === "global" ? join7(provider.pathGlobal, provider.instructFile) : join7(projectDir, provider.instructFile);
2535
+ const filePath = scope === "global" ? join5(provider.pathGlobal, provider.instructFile) : join5(projectDir, provider.instructFile);
1992
2536
  if (checked.has(filePath)) continue;
1993
2537
  checked.add(filePath);
1994
2538
  const status = await checkInjection(filePath, expectedContent);
@@ -2005,7 +2549,7 @@ async function injectAll(providers, projectDir, scope, content) {
2005
2549
  const results = /* @__PURE__ */ new Map();
2006
2550
  const injected = /* @__PURE__ */ new Set();
2007
2551
  for (const provider of providers) {
2008
- const filePath = scope === "global" ? join7(provider.pathGlobal, provider.instructFile) : join7(projectDir, provider.instructFile);
2552
+ const filePath = scope === "global" ? join5(provider.pathGlobal, provider.instructFile) : join5(projectDir, provider.instructFile);
2009
2553
  if (injected.has(filePath)) continue;
2010
2554
  injected.add(filePath);
2011
2555
  const action = await inject(filePath, content);
@@ -2042,7 +2586,409 @@ function groupByInstructFile(providers) {
2042
2586
  return groups;
2043
2587
  }
2044
2588
 
2589
+ // src/core/advanced/orchestration.ts
2590
+ import { existsSync as existsSync13, lstatSync as lstatSync2 } from "fs";
2591
+ import {
2592
+ cp as cp2,
2593
+ mkdir as mkdir4,
2594
+ readFile as readFile10,
2595
+ readlink as readlink2,
2596
+ rm as rm3,
2597
+ symlink as symlink2,
2598
+ writeFile as writeFile7
2599
+ } from "fs/promises";
2600
+ import { homedir as homedir2, tmpdir } from "os";
2601
+ import { basename as basename2, dirname as dirname5, join as join6 } from "path";
2602
+ var PRIORITY_ORDER = {
2603
+ high: 0,
2604
+ medium: 1,
2605
+ low: 2
2606
+ };
2607
+ var CANONICAL_SKILLS_DIR2 = join6(homedir2(), ".agents", "skills");
2608
+ function selectProvidersByMinimumPriority(providers, minimumPriority = "low") {
2609
+ const maxRank = PRIORITY_ORDER[minimumPriority];
2610
+ return [...providers].filter((provider) => PRIORITY_ORDER[provider.priority] <= maxRank).sort((a, b) => PRIORITY_ORDER[a.priority] - PRIORITY_ORDER[b.priority]);
2611
+ }
2612
+ function resolveSkillLinkPath(provider, skillName, isGlobal, projectDir) {
2613
+ const skillDir = isGlobal ? provider.pathSkills : join6(projectDir, provider.pathProjectSkills);
2614
+ return join6(skillDir, skillName);
2615
+ }
2616
+ async function snapshotConfigs(paths) {
2617
+ const snapshots = /* @__PURE__ */ new Map();
2618
+ for (const path of paths) {
2619
+ if (!path || snapshots.has(path)) continue;
2620
+ if (!existsSync13(path)) {
2621
+ snapshots.set(path, null);
2622
+ continue;
2623
+ }
2624
+ snapshots.set(path, await readFile10(path, "utf-8"));
2625
+ }
2626
+ return snapshots;
2627
+ }
2628
+ async function restoreConfigSnapshots(snapshots) {
2629
+ for (const [path, content] of snapshots) {
2630
+ if (content === null) {
2631
+ await rm3(path, { force: true });
2632
+ continue;
2633
+ }
2634
+ await mkdir4(dirname5(path), { recursive: true });
2635
+ await writeFile7(path, content, "utf-8");
2636
+ }
2637
+ }
2638
+ async function snapshotSkillState(providerTargets, operation, projectDir, backupRoot) {
2639
+ const skillName = operation.skillName;
2640
+ const isGlobal = operation.isGlobal ?? true;
2641
+ const canonicalPath = join6(CANONICAL_SKILLS_DIR2, skillName);
2642
+ const canonicalExisted = existsSync13(canonicalPath);
2643
+ const canonicalBackupPath = join6(backupRoot, "canonical", skillName);
2644
+ if (canonicalExisted) {
2645
+ await mkdir4(dirname5(canonicalBackupPath), { recursive: true });
2646
+ await cp2(canonicalPath, canonicalBackupPath, { recursive: true });
2647
+ }
2648
+ const pathSnapshots = [];
2649
+ for (const provider of providerTargets) {
2650
+ const linkPath = resolveSkillLinkPath(provider, skillName, isGlobal, projectDir);
2651
+ if (!existsSync13(linkPath)) {
2652
+ pathSnapshots.push({ linkPath, state: "missing" });
2653
+ continue;
2654
+ }
2655
+ const stat = lstatSync2(linkPath);
2656
+ if (stat.isSymbolicLink()) {
2657
+ pathSnapshots.push({
2658
+ linkPath,
2659
+ state: "symlink",
2660
+ symlinkTarget: await readlink2(linkPath)
2661
+ });
2662
+ continue;
2663
+ }
2664
+ const backupPath = join6(backupRoot, "links", provider.id, `${skillName}-${basename2(linkPath)}`);
2665
+ await mkdir4(dirname5(backupPath), { recursive: true });
2666
+ if (stat.isDirectory()) {
2667
+ await cp2(linkPath, backupPath, { recursive: true });
2668
+ pathSnapshots.push({ linkPath, state: "directory", backupPath });
2669
+ continue;
2670
+ }
2671
+ await cp2(linkPath, backupPath);
2672
+ pathSnapshots.push({ linkPath, state: "file", backupPath });
2673
+ }
2674
+ return {
2675
+ skillName,
2676
+ isGlobal,
2677
+ canonicalPath,
2678
+ canonicalBackupPath: canonicalExisted ? canonicalBackupPath : void 0,
2679
+ canonicalExisted,
2680
+ pathSnapshots
2681
+ };
2682
+ }
2683
+ async function restoreSkillSnapshot(snapshot) {
2684
+ if (existsSync13(snapshot.canonicalPath)) {
2685
+ await rm3(snapshot.canonicalPath, { recursive: true, force: true });
2686
+ }
2687
+ if (snapshot.canonicalExisted && snapshot.canonicalBackupPath && existsSync13(snapshot.canonicalBackupPath)) {
2688
+ await mkdir4(dirname5(snapshot.canonicalPath), { recursive: true });
2689
+ await cp2(snapshot.canonicalBackupPath, snapshot.canonicalPath, { recursive: true });
2690
+ }
2691
+ for (const pathSnapshot of snapshot.pathSnapshots) {
2692
+ await rm3(pathSnapshot.linkPath, { recursive: true, force: true });
2693
+ if (pathSnapshot.state === "missing") continue;
2694
+ await mkdir4(dirname5(pathSnapshot.linkPath), { recursive: true });
2695
+ if (pathSnapshot.state === "symlink" && pathSnapshot.symlinkTarget) {
2696
+ const linkType = process.platform === "win32" ? "junction" : "dir";
2697
+ await symlink2(pathSnapshot.symlinkTarget, pathSnapshot.linkPath, linkType);
2698
+ continue;
2699
+ }
2700
+ if ((pathSnapshot.state === "directory" || pathSnapshot.state === "file") && pathSnapshot.backupPath) {
2701
+ if (pathSnapshot.state === "directory") {
2702
+ await cp2(pathSnapshot.backupPath, pathSnapshot.linkPath, { recursive: true });
2703
+ } else {
2704
+ await cp2(pathSnapshot.backupPath, pathSnapshot.linkPath);
2705
+ }
2706
+ }
2707
+ }
2708
+ }
2709
+ async function installBatchWithRollback(options) {
2710
+ const projectDir = options.projectDir ?? process.cwd();
2711
+ const minimumPriority = options.minimumPriority ?? "low";
2712
+ const mcpOps = options.mcp ?? [];
2713
+ const skillOps = options.skills ?? [];
2714
+ const baseProviders = options.providers ?? getInstalledProviders();
2715
+ const providers = selectProvidersByMinimumPriority(baseProviders, minimumPriority);
2716
+ const configPaths = providers.flatMap((provider) => {
2717
+ const paths = [];
2718
+ for (const operation of mcpOps) {
2719
+ const path = resolveConfigPath(provider, operation.scope ?? "project", projectDir);
2720
+ if (path) paths.push(path);
2721
+ }
2722
+ return paths;
2723
+ });
2724
+ const configSnapshots = await snapshotConfigs(configPaths);
2725
+ const backupRoot = join6(
2726
+ tmpdir(),
2727
+ `caamp-skill-backup-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
2728
+ );
2729
+ const skillSnapshots = await Promise.all(
2730
+ skillOps.map((operation) => snapshotSkillState(providers, operation, projectDir, backupRoot))
2731
+ );
2732
+ const appliedSkills = [];
2733
+ const rollbackErrors = [];
2734
+ let mcpApplied = 0;
2735
+ let skillsApplied = 0;
2736
+ let rollbackPerformed = false;
2737
+ try {
2738
+ for (const operation of mcpOps) {
2739
+ const scope = operation.scope ?? "project";
2740
+ for (const provider of providers) {
2741
+ const result = await installMcpServer(
2742
+ provider,
2743
+ operation.serverName,
2744
+ operation.config,
2745
+ scope,
2746
+ projectDir
2747
+ );
2748
+ if (!result.success) {
2749
+ throw new Error(result.error ?? `Failed MCP install for ${provider.id}`);
2750
+ }
2751
+ mcpApplied += 1;
2752
+ }
2753
+ }
2754
+ for (const operation of skillOps) {
2755
+ const isGlobal = operation.isGlobal ?? true;
2756
+ const result = await installSkill(
2757
+ operation.sourcePath,
2758
+ operation.skillName,
2759
+ providers,
2760
+ isGlobal,
2761
+ projectDir
2762
+ );
2763
+ const linkedProviders = providers.filter((provider) => result.linkedAgents.includes(provider.id));
2764
+ appliedSkills.push({
2765
+ skillName: operation.skillName,
2766
+ isGlobal,
2767
+ linkedProviders
2768
+ });
2769
+ if (result.errors.length > 0) {
2770
+ throw new Error(result.errors.join("; "));
2771
+ }
2772
+ skillsApplied += 1;
2773
+ }
2774
+ await rm3(backupRoot, { recursive: true, force: true });
2775
+ return {
2776
+ success: true,
2777
+ providerIds: providers.map((provider) => provider.id),
2778
+ mcpApplied,
2779
+ skillsApplied,
2780
+ rollbackPerformed: false,
2781
+ rollbackErrors: []
2782
+ };
2783
+ } catch (error) {
2784
+ rollbackPerformed = true;
2785
+ for (const applied of [...appliedSkills].reverse()) {
2786
+ try {
2787
+ await removeSkill(applied.skillName, applied.linkedProviders, applied.isGlobal, projectDir);
2788
+ } catch (err) {
2789
+ rollbackErrors.push(err instanceof Error ? err.message : String(err));
2790
+ }
2791
+ }
2792
+ try {
2793
+ await restoreConfigSnapshots(configSnapshots);
2794
+ } catch (err) {
2795
+ rollbackErrors.push(err instanceof Error ? err.message : String(err));
2796
+ }
2797
+ for (const snapshot of skillSnapshots) {
2798
+ try {
2799
+ await restoreSkillSnapshot(snapshot);
2800
+ } catch (err) {
2801
+ rollbackErrors.push(err instanceof Error ? err.message : String(err));
2802
+ }
2803
+ }
2804
+ await rm3(backupRoot, { recursive: true, force: true });
2805
+ return {
2806
+ success: false,
2807
+ providerIds: providers.map((provider) => provider.id),
2808
+ mcpApplied,
2809
+ skillsApplied,
2810
+ rollbackPerformed,
2811
+ rollbackErrors,
2812
+ error: error instanceof Error ? error.message : String(error)
2813
+ };
2814
+ }
2815
+ }
2816
+ function stableStringify(value) {
2817
+ if (Array.isArray(value)) {
2818
+ return `[${value.map(stableStringify).join(",")}]`;
2819
+ }
2820
+ if (value && typeof value === "object") {
2821
+ const record = value;
2822
+ const keys = Object.keys(record).sort();
2823
+ return `{${keys.map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`).join(",")}}`;
2824
+ }
2825
+ return JSON.stringify(value);
2826
+ }
2827
+ async function detectMcpConfigConflicts(providers, operations, projectDir = process.cwd()) {
2828
+ const conflicts = [];
2829
+ for (const provider of providers) {
2830
+ for (const operation of operations) {
2831
+ const scope = operation.scope ?? "project";
2832
+ if (operation.config.type && !provider.supportedTransports.includes(operation.config.type)) {
2833
+ conflicts.push({
2834
+ providerId: provider.id,
2835
+ serverName: operation.serverName,
2836
+ scope,
2837
+ code: "unsupported-transport",
2838
+ message: `${provider.id} does not support transport ${operation.config.type}`
2839
+ });
2840
+ }
2841
+ if (operation.config.headers && !provider.supportsHeaders) {
2842
+ conflicts.push({
2843
+ providerId: provider.id,
2844
+ serverName: operation.serverName,
2845
+ scope,
2846
+ code: "unsupported-headers",
2847
+ message: `${provider.id} does not support header configuration`
2848
+ });
2849
+ }
2850
+ const existingEntries = await listMcpServers(provider, scope, projectDir);
2851
+ const current = existingEntries.find((entry) => entry.name === operation.serverName);
2852
+ if (!current) continue;
2853
+ const transform = getTransform(provider.id);
2854
+ const desired = transform ? transform(operation.serverName, operation.config) : operation.config;
2855
+ if (stableStringify(current.config) !== stableStringify(desired)) {
2856
+ conflicts.push({
2857
+ providerId: provider.id,
2858
+ serverName: operation.serverName,
2859
+ scope,
2860
+ code: "existing-mismatch",
2861
+ message: `${provider.id} has existing config mismatch for ${operation.serverName}`
2862
+ });
2863
+ }
2864
+ }
2865
+ }
2866
+ return conflicts;
2867
+ }
2868
+ async function applyMcpInstallWithPolicy(providers, operations, policy = "fail", projectDir = process.cwd()) {
2869
+ const conflicts = await detectMcpConfigConflicts(providers, operations, projectDir);
2870
+ const conflictKey = (providerId, serverName, scope) => `${providerId}::${serverName}::${scope}`;
2871
+ const conflictMap = /* @__PURE__ */ new Map();
2872
+ for (const conflict of conflicts) {
2873
+ conflictMap.set(conflictKey(conflict.providerId, conflict.serverName, conflict.scope), conflict);
2874
+ }
2875
+ if (policy === "fail" && conflicts.length > 0) {
2876
+ return { conflicts, applied: [], skipped: [] };
2877
+ }
2878
+ const applied = [];
2879
+ const skipped = [];
2880
+ for (const provider of providers) {
2881
+ for (const operation of operations) {
2882
+ const scope = operation.scope ?? "project";
2883
+ const key = conflictKey(provider.id, operation.serverName, scope);
2884
+ const conflict = conflictMap.get(key);
2885
+ if (policy === "skip" && conflict) {
2886
+ skipped.push({
2887
+ providerId: provider.id,
2888
+ serverName: operation.serverName,
2889
+ scope,
2890
+ reason: conflict.code
2891
+ });
2892
+ continue;
2893
+ }
2894
+ const result = await installMcpServer(
2895
+ provider,
2896
+ operation.serverName,
2897
+ operation.config,
2898
+ scope,
2899
+ projectDir
2900
+ );
2901
+ applied.push(result);
2902
+ }
2903
+ }
2904
+ return { conflicts, applied, skipped };
2905
+ }
2906
+ async function updateInstructionsSingleOperation(providers, content, scope = "project", projectDir = process.cwd()) {
2907
+ const actions = await injectAll(providers, projectDir, scope, content);
2908
+ const groupedByFile = groupByInstructFile(providers);
2909
+ const summary = {
2910
+ scope,
2911
+ updatedFiles: actions.size,
2912
+ actions: []
2913
+ };
2914
+ for (const [filePath, action] of actions.entries()) {
2915
+ const providersForFile = providers.filter((provider) => {
2916
+ const expectedPath = scope === "global" ? join6(provider.pathGlobal, provider.instructFile) : join6(projectDir, provider.instructFile);
2917
+ return expectedPath === filePath;
2918
+ });
2919
+ const fallback = groupedByFile.get(basename2(filePath)) ?? [];
2920
+ const selected = providersForFile.length > 0 ? providersForFile : fallback;
2921
+ summary.actions.push({
2922
+ file: filePath,
2923
+ action,
2924
+ providers: selected.map((provider) => provider.id),
2925
+ configFormats: Array.from(new Set(selected.map((provider) => provider.configFormat)))
2926
+ });
2927
+ }
2928
+ return summary;
2929
+ }
2930
+ async function configureProviderGlobalAndProject(provider, options) {
2931
+ const projectDir = options.projectDir ?? process.cwd();
2932
+ const globalOps = options.globalMcp ?? [];
2933
+ const projectOps = options.projectMcp ?? [];
2934
+ const globalResults = [];
2935
+ for (const operation of globalOps) {
2936
+ globalResults.push(await installMcpServer(
2937
+ provider,
2938
+ operation.serverName,
2939
+ operation.config,
2940
+ "global",
2941
+ projectDir
2942
+ ));
2943
+ }
2944
+ const projectResults = [];
2945
+ for (const operation of projectOps) {
2946
+ projectResults.push(await installMcpServer(
2947
+ provider,
2948
+ operation.serverName,
2949
+ operation.config,
2950
+ "project",
2951
+ projectDir
2952
+ ));
2953
+ }
2954
+ const instructionResults = {};
2955
+ const instructionContent = options.instructionContent;
2956
+ if (typeof instructionContent === "string") {
2957
+ instructionResults.global = await injectAll([provider], projectDir, "global", instructionContent);
2958
+ instructionResults.project = await injectAll([provider], projectDir, "project", instructionContent);
2959
+ } else if (instructionContent) {
2960
+ if (instructionContent.global) {
2961
+ instructionResults.global = await injectAll([provider], projectDir, "global", instructionContent.global);
2962
+ }
2963
+ if (instructionContent.project) {
2964
+ instructionResults.project = await injectAll([provider], projectDir, "project", instructionContent.project);
2965
+ }
2966
+ }
2967
+ return {
2968
+ providerId: provider.id,
2969
+ configPaths: {
2970
+ global: resolveConfigPath(provider, "global", projectDir),
2971
+ project: resolveConfigPath(provider, "project", projectDir)
2972
+ },
2973
+ mcp: {
2974
+ global: globalResults,
2975
+ project: projectResults
2976
+ },
2977
+ instructions: instructionResults
2978
+ };
2979
+ }
2980
+
2045
2981
  export {
2982
+ getPlatformLocations,
2983
+ getAgentsHome,
2984
+ getProjectAgentsDir,
2985
+ getCanonicalSkillsDir,
2986
+ getLockFilePath,
2987
+ resolveRegistryTemplatePath,
2988
+ resolveProviderConfigPath,
2989
+ resolvePreferredConfigScope,
2990
+ resolveProviderSkillsDir,
2991
+ buildSkillSubPathCandidates,
2046
2992
  getAllProviders,
2047
2993
  getProvider,
2048
2994
  resolveAlias,
@@ -2060,6 +3006,7 @@ export {
2060
3006
  detectAllProviders,
2061
3007
  getInstalledProviders,
2062
3008
  detectProjectProviders,
3009
+ resetDetectionCache,
2063
3010
  parseSource,
2064
3011
  isMarketplaceScoped,
2065
3012
  installSkill,
@@ -2070,11 +3017,21 @@ export {
2070
3017
  removeSkillFromLock,
2071
3018
  getTrackedSkills,
2072
3019
  checkSkillUpdate,
3020
+ formatNetworkError,
2073
3021
  MarketplaceClient,
2074
3022
  parseSkillFile,
2075
3023
  discoverSkill,
2076
3024
  discoverSkills,
2077
3025
  discoverSkillsMulti,
3026
+ RECOMMENDATION_ERROR_CODES,
3027
+ tokenizeCriteriaValue,
3028
+ validateRecommendationCriteria,
3029
+ normalizeRecommendationCriteria,
3030
+ scoreSkillRecommendation,
3031
+ rankSkills,
3032
+ formatSkillRecommendations,
3033
+ searchSkills,
3034
+ recommendSkills2 as recommendSkills,
2078
3035
  scanFile,
2079
3036
  scanDirectory,
2080
3037
  toSarif,
@@ -2104,6 +3061,12 @@ export {
2104
3061
  checkAllInjections,
2105
3062
  injectAll,
2106
3063
  generateInjectionContent,
2107
- groupByInstructFile
3064
+ groupByInstructFile,
3065
+ selectProvidersByMinimumPriority,
3066
+ installBatchWithRollback,
3067
+ detectMcpConfigConflicts,
3068
+ applyMcpInstallWithPolicy,
3069
+ updateInstructionsSingleOperation,
3070
+ configureProviderGlobalAndProject
2108
3071
  };
2109
- //# sourceMappingURL=chunk-PCWTRJV2.js.map
3072
+ //# sourceMappingURL=chunk-6HQDRJLS.js.map