@cleocode/caamp 0.4.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);
55
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);
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();
@@ -829,7 +1025,7 @@ var MarketplaceClient = class {
829
1025
  // src/core/skills/discovery.ts
830
1026
  import { readFile as readFile3, readdir } from "fs/promises";
831
1027
  import { existsSync as existsSync5 } from "fs";
832
- import { join as join5 } from "path";
1028
+ import { join as join4 } from "path";
833
1029
  import matter from "gray-matter";
834
1030
  async function parseSkillFile(filePath) {
835
1031
  try {
@@ -853,7 +1049,7 @@ async function parseSkillFile(filePath) {
853
1049
  }
854
1050
  }
855
1051
  async function discoverSkill(skillDir) {
856
- const skillFile = join5(skillDir, "SKILL.md");
1052
+ const skillFile = join4(skillDir, "SKILL.md");
857
1053
  if (!existsSync5(skillFile)) return null;
858
1054
  const metadata = await parseSkillFile(skillFile);
859
1055
  if (!metadata) return null;
@@ -870,7 +1066,7 @@ async function discoverSkills(rootDir) {
870
1066
  const skills = [];
871
1067
  for (const entry of entries) {
872
1068
  if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
873
- const skillDir = join5(rootDir, entry.name);
1069
+ const skillDir = join4(rootDir, entry.name);
874
1070
  const skill = await discoverSkill(skillDir);
875
1071
  if (skill) {
876
1072
  skills.push(skill);
@@ -1573,13 +1769,13 @@ async function scanFile(filePath, rules) {
1573
1769
  }
1574
1770
  async function scanDirectory(dirPath) {
1575
1771
  const { readdir: readdir2 } = await import("fs/promises");
1576
- const { join: join9 } = await import("path");
1772
+ const { join: join7 } = await import("path");
1577
1773
  if (!existsSync6(dirPath)) return [];
1578
1774
  const entries = await readdir2(dirPath, { withFileTypes: true });
1579
1775
  const results = [];
1580
1776
  for (const entry of entries) {
1581
1777
  if (entry.isDirectory() || entry.isSymbolicLink()) {
1582
- const skillFile = join9(dirPath, entry.name, "SKILL.md");
1778
+ const skillFile = join7(dirPath, entry.name, "SKILL.md");
1583
1779
  if (existsSync6(skillFile)) {
1584
1780
  results.push(await scanFile(skillFile));
1585
1781
  }
@@ -1794,8 +1990,8 @@ function getNestedValue(obj, keyPath) {
1794
1990
  }
1795
1991
  async function ensureDir(filePath) {
1796
1992
  const { mkdir: mkdir5 } = await import("fs/promises");
1797
- const { dirname: dirname5 } = await import("path");
1798
- await mkdir5(dirname5(filePath), { recursive: true });
1993
+ const { dirname: dirname6 } = await import("path");
1994
+ await mkdir5(dirname6(filePath), { recursive: true });
1799
1995
  }
1800
1996
 
1801
1997
  // src/core/formats/json.ts
@@ -2107,14 +2303,9 @@ function getTransform(providerId) {
2107
2303
  }
2108
2304
 
2109
2305
  // src/core/mcp/reader.ts
2110
- import { join as join6 } from "path";
2111
2306
  import { existsSync as existsSync11 } from "fs";
2112
2307
  function resolveConfigPath(provider, scope, projectDir) {
2113
- if (scope === "project") {
2114
- if (!provider.configPathProject) return null;
2115
- return join6(projectDir ?? process.cwd(), provider.configPathProject);
2116
- }
2117
- return provider.configPathGlobal;
2308
+ return resolveProviderConfigPath(provider, scope, projectDir ?? process.cwd());
2118
2309
  }
2119
2310
  async function listMcpServers(provider, scope, projectDir) {
2120
2311
  const configPath = resolveConfigPath(provider, scope, projectDir);
@@ -2237,37 +2428,39 @@ function buildServerConfig(source, transport, headers) {
2237
2428
 
2238
2429
  // src/core/mcp/lock.ts
2239
2430
  async function recordMcpInstall(serverName, source, sourceType, agents, isGlobal) {
2240
- const lock = await readLockFile();
2241
- const now = (/* @__PURE__ */ new Date()).toISOString();
2242
- const existing = lock.mcpServers[serverName];
2243
- lock.mcpServers[serverName] = {
2244
- name: serverName,
2245
- scopedName: serverName,
2246
- source,
2247
- sourceType,
2248
- installedAt: existing?.installedAt ?? now,
2249
- updatedAt: now,
2250
- agents: [.../* @__PURE__ */ new Set([...existing?.agents ?? [], ...agents])],
2251
- canonicalPath: "",
2252
- isGlobal
2253
- };
2254
- 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
+ });
2255
2446
  }
2256
2447
  async function removeMcpFromLock(serverName) {
2257
- const lock = await readLockFile();
2258
- if (!(serverName in lock.mcpServers)) return false;
2259
- delete lock.mcpServers[serverName];
2260
- await writeLockFile(lock);
2261
- 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;
2262
2455
  }
2263
2456
  async function getTrackedMcpServers() {
2264
2457
  const lock = await readLockFile();
2265
2458
  return lock.mcpServers;
2266
2459
  }
2267
2460
  async function saveLastSelectedAgents(agents) {
2268
- const lock = await readLockFile();
2269
- lock.lastSelectedAgents = agents;
2270
- await writeLockFile(lock);
2461
+ await updateLockFile((lock) => {
2462
+ lock.lastSelectedAgents = agents;
2463
+ });
2271
2464
  }
2272
2465
  async function getLastSelectedAgents() {
2273
2466
  const lock = await readLockFile();
@@ -2277,7 +2470,7 @@ async function getLastSelectedAgents() {
2277
2470
  // src/core/instructions/injector.ts
2278
2471
  import { readFile as readFile9, writeFile as writeFile6 } from "fs/promises";
2279
2472
  import { existsSync as existsSync12 } from "fs";
2280
- import { join as join7, dirname as dirname3 } from "path";
2473
+ import { join as join5, dirname as dirname4 } from "path";
2281
2474
  import { mkdir as mkdir3 } from "fs/promises";
2282
2475
  var MARKER_START = "<!-- CAAMP:START -->";
2283
2476
  var MARKER_END = "<!-- CAAMP:END -->";
@@ -2307,7 +2500,7 @@ ${MARKER_END}`;
2307
2500
  }
2308
2501
  async function inject(filePath, content) {
2309
2502
  const block = buildBlock(content);
2310
- await mkdir3(dirname3(filePath), { recursive: true });
2503
+ await mkdir3(dirname4(filePath), { recursive: true });
2311
2504
  if (!existsSync12(filePath)) {
2312
2505
  await writeFile6(filePath, block + "\n", "utf-8");
2313
2506
  return "created";
@@ -2328,8 +2521,8 @@ async function removeInjection(filePath) {
2328
2521
  if (!MARKER_PATTERN.test(content)) return false;
2329
2522
  const cleaned = content.replace(MARKER_PATTERN, "").replace(/^\n{2,}/, "\n").trim();
2330
2523
  if (!cleaned) {
2331
- const { rm: rm3 } = await import("fs/promises");
2332
- await rm3(filePath);
2524
+ const { rm: rm4 } = await import("fs/promises");
2525
+ await rm4(filePath);
2333
2526
  } else {
2334
2527
  await writeFile6(filePath, cleaned + "\n", "utf-8");
2335
2528
  }
@@ -2339,7 +2532,7 @@ async function checkAllInjections(providers, projectDir, scope, expectedContent)
2339
2532
  const results = [];
2340
2533
  const checked = /* @__PURE__ */ new Set();
2341
2534
  for (const provider of providers) {
2342
- 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);
2343
2536
  if (checked.has(filePath)) continue;
2344
2537
  checked.add(filePath);
2345
2538
  const status = await checkInjection(filePath, expectedContent);
@@ -2356,7 +2549,7 @@ async function injectAll(providers, projectDir, scope, content) {
2356
2549
  const results = /* @__PURE__ */ new Map();
2357
2550
  const injected = /* @__PURE__ */ new Set();
2358
2551
  for (const provider of providers) {
2359
- 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);
2360
2553
  if (injected.has(filePath)) continue;
2361
2554
  injected.add(filePath);
2362
2555
  const action = await inject(filePath, content);
@@ -2400,25 +2593,25 @@ import {
2400
2593
  mkdir as mkdir4,
2401
2594
  readFile as readFile10,
2402
2595
  readlink as readlink2,
2403
- rm as rm2,
2596
+ rm as rm3,
2404
2597
  symlink as symlink2,
2405
2598
  writeFile as writeFile7
2406
2599
  } from "fs/promises";
2407
- import { homedir as homedir4, tmpdir } from "os";
2408
- import { basename as basename2, dirname as dirname4, join as join8 } from "path";
2600
+ import { homedir as homedir2, tmpdir } from "os";
2601
+ import { basename as basename2, dirname as dirname5, join as join6 } from "path";
2409
2602
  var PRIORITY_ORDER = {
2410
2603
  high: 0,
2411
2604
  medium: 1,
2412
2605
  low: 2
2413
2606
  };
2414
- var CANONICAL_SKILLS_DIR = join8(homedir4(), ".agents", "skills");
2607
+ var CANONICAL_SKILLS_DIR2 = join6(homedir2(), ".agents", "skills");
2415
2608
  function selectProvidersByMinimumPriority(providers, minimumPriority = "low") {
2416
2609
  const maxRank = PRIORITY_ORDER[minimumPriority];
2417
2610
  return [...providers].filter((provider) => PRIORITY_ORDER[provider.priority] <= maxRank).sort((a, b) => PRIORITY_ORDER[a.priority] - PRIORITY_ORDER[b.priority]);
2418
2611
  }
2419
2612
  function resolveSkillLinkPath(provider, skillName, isGlobal, projectDir) {
2420
- const skillDir = isGlobal ? provider.pathSkills : join8(projectDir, provider.pathProjectSkills);
2421
- return join8(skillDir, skillName);
2613
+ const skillDir = isGlobal ? provider.pathSkills : join6(projectDir, provider.pathProjectSkills);
2614
+ return join6(skillDir, skillName);
2422
2615
  }
2423
2616
  async function snapshotConfigs(paths) {
2424
2617
  const snapshots = /* @__PURE__ */ new Map();
@@ -2435,21 +2628,21 @@ async function snapshotConfigs(paths) {
2435
2628
  async function restoreConfigSnapshots(snapshots) {
2436
2629
  for (const [path, content] of snapshots) {
2437
2630
  if (content === null) {
2438
- await rm2(path, { force: true });
2631
+ await rm3(path, { force: true });
2439
2632
  continue;
2440
2633
  }
2441
- await mkdir4(dirname4(path), { recursive: true });
2634
+ await mkdir4(dirname5(path), { recursive: true });
2442
2635
  await writeFile7(path, content, "utf-8");
2443
2636
  }
2444
2637
  }
2445
2638
  async function snapshotSkillState(providerTargets, operation, projectDir, backupRoot) {
2446
2639
  const skillName = operation.skillName;
2447
2640
  const isGlobal = operation.isGlobal ?? true;
2448
- const canonicalPath = join8(CANONICAL_SKILLS_DIR, skillName);
2641
+ const canonicalPath = join6(CANONICAL_SKILLS_DIR2, skillName);
2449
2642
  const canonicalExisted = existsSync13(canonicalPath);
2450
- const canonicalBackupPath = join8(backupRoot, "canonical", skillName);
2643
+ const canonicalBackupPath = join6(backupRoot, "canonical", skillName);
2451
2644
  if (canonicalExisted) {
2452
- await mkdir4(dirname4(canonicalBackupPath), { recursive: true });
2645
+ await mkdir4(dirname5(canonicalBackupPath), { recursive: true });
2453
2646
  await cp2(canonicalPath, canonicalBackupPath, { recursive: true });
2454
2647
  }
2455
2648
  const pathSnapshots = [];
@@ -2468,8 +2661,8 @@ async function snapshotSkillState(providerTargets, operation, projectDir, backup
2468
2661
  });
2469
2662
  continue;
2470
2663
  }
2471
- const backupPath = join8(backupRoot, "links", provider.id, `${skillName}-${basename2(linkPath)}`);
2472
- await mkdir4(dirname4(backupPath), { recursive: true });
2664
+ const backupPath = join6(backupRoot, "links", provider.id, `${skillName}-${basename2(linkPath)}`);
2665
+ await mkdir4(dirname5(backupPath), { recursive: true });
2473
2666
  if (stat.isDirectory()) {
2474
2667
  await cp2(linkPath, backupPath, { recursive: true });
2475
2668
  pathSnapshots.push({ linkPath, state: "directory", backupPath });
@@ -2489,16 +2682,16 @@ async function snapshotSkillState(providerTargets, operation, projectDir, backup
2489
2682
  }
2490
2683
  async function restoreSkillSnapshot(snapshot) {
2491
2684
  if (existsSync13(snapshot.canonicalPath)) {
2492
- await rm2(snapshot.canonicalPath, { recursive: true, force: true });
2685
+ await rm3(snapshot.canonicalPath, { recursive: true, force: true });
2493
2686
  }
2494
2687
  if (snapshot.canonicalExisted && snapshot.canonicalBackupPath && existsSync13(snapshot.canonicalBackupPath)) {
2495
- await mkdir4(dirname4(snapshot.canonicalPath), { recursive: true });
2688
+ await mkdir4(dirname5(snapshot.canonicalPath), { recursive: true });
2496
2689
  await cp2(snapshot.canonicalBackupPath, snapshot.canonicalPath, { recursive: true });
2497
2690
  }
2498
2691
  for (const pathSnapshot of snapshot.pathSnapshots) {
2499
- await rm2(pathSnapshot.linkPath, { recursive: true, force: true });
2692
+ await rm3(pathSnapshot.linkPath, { recursive: true, force: true });
2500
2693
  if (pathSnapshot.state === "missing") continue;
2501
- await mkdir4(dirname4(pathSnapshot.linkPath), { recursive: true });
2694
+ await mkdir4(dirname5(pathSnapshot.linkPath), { recursive: true });
2502
2695
  if (pathSnapshot.state === "symlink" && pathSnapshot.symlinkTarget) {
2503
2696
  const linkType = process.platform === "win32" ? "junction" : "dir";
2504
2697
  await symlink2(pathSnapshot.symlinkTarget, pathSnapshot.linkPath, linkType);
@@ -2529,7 +2722,7 @@ async function installBatchWithRollback(options) {
2529
2722
  return paths;
2530
2723
  });
2531
2724
  const configSnapshots = await snapshotConfigs(configPaths);
2532
- const backupRoot = join8(
2725
+ const backupRoot = join6(
2533
2726
  tmpdir(),
2534
2727
  `caamp-skill-backup-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
2535
2728
  );
@@ -2578,7 +2771,7 @@ async function installBatchWithRollback(options) {
2578
2771
  }
2579
2772
  skillsApplied += 1;
2580
2773
  }
2581
- await rm2(backupRoot, { recursive: true, force: true });
2774
+ await rm3(backupRoot, { recursive: true, force: true });
2582
2775
  return {
2583
2776
  success: true,
2584
2777
  providerIds: providers.map((provider) => provider.id),
@@ -2608,7 +2801,7 @@ async function installBatchWithRollback(options) {
2608
2801
  rollbackErrors.push(err instanceof Error ? err.message : String(err));
2609
2802
  }
2610
2803
  }
2611
- await rm2(backupRoot, { recursive: true, force: true });
2804
+ await rm3(backupRoot, { recursive: true, force: true });
2612
2805
  return {
2613
2806
  success: false,
2614
2807
  providerIds: providers.map((provider) => provider.id),
@@ -2720,7 +2913,7 @@ async function updateInstructionsSingleOperation(providers, content, scope = "pr
2720
2913
  };
2721
2914
  for (const [filePath, action] of actions.entries()) {
2722
2915
  const providersForFile = providers.filter((provider) => {
2723
- const expectedPath = scope === "global" ? join8(provider.pathGlobal, provider.instructFile) : join8(projectDir, provider.instructFile);
2916
+ const expectedPath = scope === "global" ? join6(provider.pathGlobal, provider.instructFile) : join6(projectDir, provider.instructFile);
2724
2917
  return expectedPath === filePath;
2725
2918
  });
2726
2919
  const fallback = groupedByFile.get(basename2(filePath)) ?? [];
@@ -2786,6 +2979,16 @@ async function configureProviderGlobalAndProject(provider, options) {
2786
2979
  }
2787
2980
 
2788
2981
  export {
2982
+ getPlatformLocations,
2983
+ getAgentsHome,
2984
+ getProjectAgentsDir,
2985
+ getCanonicalSkillsDir,
2986
+ getLockFilePath,
2987
+ resolveRegistryTemplatePath,
2988
+ resolveProviderConfigPath,
2989
+ resolvePreferredConfigScope,
2990
+ resolveProviderSkillsDir,
2991
+ buildSkillSubPathCandidates,
2789
2992
  getAllProviders,
2790
2993
  getProvider,
2791
2994
  resolveAlias,
@@ -2803,6 +3006,7 @@ export {
2803
3006
  detectAllProviders,
2804
3007
  getInstalledProviders,
2805
3008
  detectProjectProviders,
3009
+ resetDetectionCache,
2806
3010
  parseSource,
2807
3011
  isMarketplaceScoped,
2808
3012
  installSkill,
@@ -2865,4 +3069,4 @@ export {
2865
3069
  updateInstructionsSingleOperation,
2866
3070
  configureProviderGlobalAndProject
2867
3071
  };
2868
- //# sourceMappingURL=chunk-ZYINKJDE.js.map
3072
+ //# sourceMappingURL=chunk-6HQDRJLS.js.map