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