@iowarp/clio-coder 0.3.0
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/CHANGELOG.md +407 -0
- package/CODE_OF_CONDUCT.md +21 -0
- package/CONTRIBUTING.md +224 -0
- package/LICENSE +202 -0
- package/NOTICE +9 -0
- package/README.md +798 -0
- package/SECURITY.md +72 -0
- package/assets/clio-coder-logo-128.webp +0 -0
- package/damage-control-rules.yaml +419 -0
- package/dist/acp-UMLFVA3F.js +92 -0
- package/dist/agents-Q4MYPMUW.js +91 -0
- package/dist/auth-O6HYIJ6J.js +521 -0
- package/dist/chunk-262G75JS.js +35 -0
- package/dist/chunk-26BZQOAD.js +1281 -0
- package/dist/chunk-2J63S4SF.js +508 -0
- package/dist/chunk-3DANZDGR.js +717 -0
- package/dist/chunk-4UQA7NCT.js +29 -0
- package/dist/chunk-527KG6XR.js +497 -0
- package/dist/chunk-5LDRNKX2.js +1063 -0
- package/dist/chunk-5N2FG33Q.js +25 -0
- package/dist/chunk-67MTHP2E.js +135 -0
- package/dist/chunk-6CWDTGUC.js +20 -0
- package/dist/chunk-7BHLZB3A.js +2115 -0
- package/dist/chunk-7RBKDI66.js +348 -0
- package/dist/chunk-AMFR5YA3.js +541 -0
- package/dist/chunk-BBUH4VAA.js +1224 -0
- package/dist/chunk-BYEU76JP.js +899 -0
- package/dist/chunk-CLJ5HLUD.js +458 -0
- package/dist/chunk-D5YD55AR.js +116 -0
- package/dist/chunk-DXQNI4PC.js +61 -0
- package/dist/chunk-E3NYWENM.js +1004 -0
- package/dist/chunk-GNGDQYDU.js +34688 -0
- package/dist/chunk-GOTUR54M.js +9 -0
- package/dist/chunk-HBU5MTAM.js +41 -0
- package/dist/chunk-HMYNFFY4.js +28 -0
- package/dist/chunk-JPOWPFCU.js +1010 -0
- package/dist/chunk-JWHCJDCI.js +1215 -0
- package/dist/chunk-KBR4MZZR.js +41 -0
- package/dist/chunk-KKKPTZLM.js +93 -0
- package/dist/chunk-ME6DNWIU.js +66 -0
- package/dist/chunk-NI4DEJMC.js +88 -0
- package/dist/chunk-O4EJEDHO.js +659 -0
- package/dist/chunk-PIDUD6M2.js +31 -0
- package/dist/chunk-PS4PFJQP.js +29459 -0
- package/dist/chunk-QV47YRF4.js +48 -0
- package/dist/chunk-RQDWMVRB.js +279 -0
- package/dist/chunk-TFSSEXL6.js +136 -0
- package/dist/chunk-TKHQ4DGZ.js +8290 -0
- package/dist/chunk-TPOCL34A.js +2876 -0
- package/dist/chunk-UGYAX5YI.js +565 -0
- package/dist/chunk-UHTSULZS.js +461 -0
- package/dist/chunk-UU3R62TT.js +128 -0
- package/dist/chunk-UWIJNAOB.js +3906 -0
- package/dist/chunk-VOO7NYPP.js +914 -0
- package/dist/chunk-VPAWTYLY.js +117 -0
- package/dist/chunk-WD6AJM35.js +1216 -0
- package/dist/chunk-X3BR7HWV.js +115 -0
- package/dist/chunk-X3NE4WVW.js +120 -0
- package/dist/chunk-XNISANGE.js +1395 -0
- package/dist/chunk-XV4ZJ6ZM.js +3177 -0
- package/dist/cli/index.js +236 -0
- package/dist/clio-KIQ5SNDS.js +53 -0
- package/dist/components-JVHMUBEB.js +653 -0
- package/dist/config-ZFCDBMDC.js +372 -0
- package/dist/configure-G4E3A2PG.js +27 -0
- package/dist/context-CDXTP2MP.js +293 -0
- package/dist/context-E3KIFVXI.js +185 -0
- package/dist/context-clear-3F4PLXOS.js +102 -0
- package/dist/context-index-Q7YSYTR3.js +106 -0
- package/dist/docs-YIETIWZI.js +280 -0
- package/dist/doctor-M5HJJZOL.js +61 -0
- package/dist/domains/agents/builtins/architect.md +33 -0
- package/dist/domains/agents/builtins/coder.md +31 -0
- package/dist/domains/agents/builtins/context-bootstrap.md +38 -0
- package/dist/domains/agents/builtins/debugger.md +30 -0
- package/dist/domains/agents/builtins/documenter.md +31 -0
- package/dist/domains/agents/builtins/git-master.md +30 -0
- package/dist/domains/agents/builtins/provenance.md +30 -0
- package/dist/domains/agents/builtins/researcher.md +71 -0
- package/dist/domains/agents/builtins/scout.md +42 -0
- package/dist/domains/agents/builtins/tester.md +31 -0
- package/dist/domains/agents/builtins/verifier.md +30 -0
- package/dist/domains/agents/builtins/wiki-writer.md +41 -0
- package/dist/eval-B3KZZESM.js +2674 -0
- package/dist/evidence-V67CHM35.js +233 -0
- package/dist/evolve-YDZSUQYA.js +518 -0
- package/dist/extensions-SRG7XCAH.js +207 -0
- package/dist/fleet-CA2CRTVG.js +760 -0
- package/dist/fleet-preflight-CLIAX7YR.js +21 -0
- package/dist/init-2OZDJE2D.js +227 -0
- package/dist/memory-3PIQQAKX.js +207 -0
- package/dist/models-DY35XI7Y.js +237 -0
- package/dist/paths-5OMXW7Z4.js +57 -0
- package/dist/preload-KZVHET2B.js +11 -0
- package/dist/reset-PIFYNOS3.js +216 -0
- package/dist/run-3VSPP24F.js +735 -0
- package/dist/share-D36RQCXM.js +241 -0
- package/dist/skills-F2MRLELY.js +445 -0
- package/dist/skills-eval-E2ZTW4PL.js +932 -0
- package/dist/targets-DZMEZAH4.js +977 -0
- package/dist/trace-7NYCUI2J.js +250 -0
- package/dist/uninstall-AD3JWHBB.js +322 -0
- package/dist/upgrade-WYYBKGDY.js +301 -0
- package/dist/usage-ULIDAGFF.js +755 -0
- package/dist/version-ROZ6CZKH.js +16 -0
- package/dist/wiki-generate-PKFIX6OB.js +377 -0
- package/dist/worker/entry.js +1739 -0
- package/docs/README.md +93 -0
- package/docs/acp.md +120 -0
- package/docs/alcf-provider.md +72 -0
- package/docs/architecture.md +172 -0
- package/docs/artifact-versions.md +54 -0
- package/docs/built-in-agents.md +265 -0
- package/docs/capacity-and-scheduling.md +97 -0
- package/docs/commands-and-modes.md +554 -0
- package/docs/config-knobs-audit.md +115 -0
- package/docs/configuration-and-targets.md +812 -0
- package/docs/context-engine.md +236 -0
- package/docs/dispatch-architecture-rationale.md +126 -0
- package/docs/documentation-coverage.md +46 -0
- package/docs/documentation-guide.md +166 -0
- package/docs/environment-variables.md +105 -0
- package/docs/eval-runner.md +205 -0
- package/docs/evals-internal.md +298 -0
- package/docs/evidence-and-memory.md +243 -0
- package/docs/evolution.md +143 -0
- package/docs/exit-codes-and-output.md +74 -0
- package/docs/extensions-and-sharing.md +306 -0
- package/docs/fleet-demo-runbook.md +179 -0
- package/docs/fleet-dispatch.md +591 -0
- package/docs/glossary.md +75 -0
- package/docs/html/agents_blueprint.html +936 -0
- package/docs/html/alcf_blueprint.html +324 -0
- package/docs/html/architecture_blueprint.html +850 -0
- package/docs/html/commands_blueprint.html +794 -0
- package/docs/html/config_knobs_audit_blueprint.html +178 -0
- package/docs/html/configuration_blueprint.html +1080 -0
- package/docs/html/context_blueprint.html +603 -0
- package/docs/html/documentation_blueprint.html +832 -0
- package/docs/html/environment_blueprint.html +404 -0
- package/docs/html/eval_blueprint.html +743 -0
- package/docs/html/evals_internal_blueprint.html +190 -0
- package/docs/html/evolution_blueprint.html +674 -0
- package/docs/html/extensions_blueprint.html +2065 -0
- package/docs/html/fleet_dispatch_blueprint.html +286 -0
- package/docs/html/index.html +919 -0
- package/docs/html/lifecycle_blueprint.html +723 -0
- package/docs/html/memory_blueprint.html +699 -0
- package/docs/html/middleware_blueprint.html +664 -0
- package/docs/html/models_blueprint.html +2366 -0
- package/docs/html/observability_blueprint.html +683 -0
- package/docs/html/provider_adapter_blueprint.html +245 -0
- package/docs/html/safety_blueprint.html +1386 -0
- package/docs/html/shared.css +571 -0
- package/docs/html/shared.js +143 -0
- package/docs/html/skills_blueprint.html +671 -0
- package/docs/html/soak_blueprint.html +182 -0
- package/docs/html/tool_usage_blueprint.html +350 -0
- package/docs/html/tools_blueprint.html +2249 -0
- package/docs/html/trace_blueprint.html +235 -0
- package/docs/html/tui_design_blueprint.html +314 -0
- package/docs/html/validation_blueprint.html +961 -0
- package/docs/html/worker_dispatch_blueprint.html +231 -0
- package/docs/installation-and-lifecycle.md +308 -0
- package/docs/middleware-and-components.md +148 -0
- package/docs/model-catalog.md +189 -0
- package/docs/observability.md +233 -0
- package/docs/proactive-memory.md +452 -0
- package/docs/prompt-envelope-and-tools.md +142 -0
- package/docs/provider-adapter-cookbook.md +148 -0
- package/docs/release-cut-checklist.md +138 -0
- package/docs/safety-model.md +357 -0
- package/docs/scientific-validation.md +105 -0
- package/docs/session-lifecycle.md +156 -0
- package/docs/skills-marketplace.md +46 -0
- package/docs/tool-usage.md +527 -0
- package/docs/trace-store.md +132 -0
- package/docs/troubleshooting.md +33 -0
- package/docs/tui-design.md +239 -0
- package/docs/worker-dispatch-mechanics.md +242 -0
- package/package.json +132 -0
- package/skills/README.md +408 -0
- package/skills/git/commit-crafting/SKILL.md +79 -0
- package/skills/git/commit-crafting/evals.md +92 -0
- package/skills/git/create-pr/SKILL.md +116 -0
- package/skills/git/create-pr/evals.md +114 -0
- package/skills/git/investigate-issue/SKILL.md +139 -0
- package/skills/git/investigate-issue/evals.md +94 -0
- package/skills/git/resolve-merge-conflicts/SKILL.md +96 -0
- package/skills/git/resolve-merge-conflicts/evals.md +58 -0
- package/skills/git/review-changes/SKILL.md +103 -0
- package/skills/git/review-changes/evals.md +85 -0
- package/skills/git/worktree-create/SKILL.md +92 -0
- package/skills/git/worktree-create/evals.md +97 -0
- package/skills/git/worktree-create/references/worktree-setup.md +66 -0
- package/skills/git/worktree-merge/SKILL.md +95 -0
- package/skills/git/worktree-merge/evals.md +114 -0
- package/skills/skill-marketplace.json +261 -0
- package/skills/workflow/cut-it/SKILL.md +86 -0
- package/skills/workflow/cut-it/evals.md +42 -0
- package/src/domains/agents/builtins/architect.md +33 -0
- package/src/domains/agents/builtins/coder.md +31 -0
- package/src/domains/agents/builtins/context-bootstrap.md +38 -0
- package/src/domains/agents/builtins/debugger.md +30 -0
- package/src/domains/agents/builtins/documenter.md +31 -0
- package/src/domains/agents/builtins/git-master.md +30 -0
- package/src/domains/agents/builtins/provenance.md +30 -0
- package/src/domains/agents/builtins/researcher.md +71 -0
- package/src/domains/agents/builtins/scout.md +42 -0
- package/src/domains/agents/builtins/tester.md +31 -0
- package/src/domains/agents/builtins/verifier.md +30 -0
- package/src/domains/agents/builtins/wiki-writer.md +41 -0
- package/src/domains/agents/fleets/build-review.md +34 -0
- package/src/domains/agents/fleets/build-test.md +35 -0
- package/src/domains/agents/fleets/sdlc.md +86 -0
- package/src/domains/prompts/fragments/identity/clio-worker.md +11 -0
- package/src/domains/prompts/fragments/identity/clio.md +26 -0
- package/src/domains/prompts/fragments/operating/contract.md +64 -0
- package/src/domains/prompts/fragments/safety/auto-edit.md +14 -0
- package/src/domains/prompts/fragments/safety/full-auto.md +14 -0
- package/src/domains/prompts/fragments/safety/read-only.md +13 -0
- package/src/domains/prompts/fragments/safety/suggest.md +13 -0
- package/src/domains/prompts/fragments/wiki/page.md +75 -0
- package/src/domains/prompts/fragments/wiki/plan.md +48 -0
- package/src/domains/providers/models/cloud-models/alcf.yaml +40 -0
- package/src/domains/providers/models/local-models/clio-local-coding-targets.yaml +993 -0
|
@@ -0,0 +1,1281 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ToolNames
|
|
3
|
+
} from "./chunk-QV47YRF4.js";
|
|
4
|
+
import {
|
|
5
|
+
enabledExtensionResourceRoots
|
|
6
|
+
} from "./chunk-CLJ5HLUD.js";
|
|
7
|
+
import {
|
|
8
|
+
fsyncDirectory
|
|
9
|
+
} from "./chunk-VPAWTYLY.js";
|
|
10
|
+
import {
|
|
11
|
+
clioConfigDir
|
|
12
|
+
} from "./chunk-X3NE4WVW.js";
|
|
13
|
+
|
|
14
|
+
// src/domains/resources/skills/install.ts
|
|
15
|
+
import { execFileSync } from "node:child_process";
|
|
16
|
+
import {
|
|
17
|
+
cpSync,
|
|
18
|
+
existsSync as existsSync4,
|
|
19
|
+
mkdirSync,
|
|
20
|
+
mkdtempSync,
|
|
21
|
+
readFileSync as readFileSync3,
|
|
22
|
+
renameSync,
|
|
23
|
+
rmSync,
|
|
24
|
+
statSync as statSync2,
|
|
25
|
+
writeFileSync
|
|
26
|
+
} from "node:fs";
|
|
27
|
+
import { homedir as homedir2, tmpdir } from "node:os";
|
|
28
|
+
import path4 from "node:path";
|
|
29
|
+
|
|
30
|
+
// src/domains/resources/skills/content-hash.ts
|
|
31
|
+
import { createHash } from "node:crypto";
|
|
32
|
+
var PROVENANCE_KEYS = /* @__PURE__ */ new Set([
|
|
33
|
+
"source-url",
|
|
34
|
+
"sourceUrl",
|
|
35
|
+
"install-url",
|
|
36
|
+
"installed-at",
|
|
37
|
+
"installedAt",
|
|
38
|
+
"updated-at",
|
|
39
|
+
"updatedAt",
|
|
40
|
+
"installed-hash",
|
|
41
|
+
"installedHash",
|
|
42
|
+
"audit"
|
|
43
|
+
]);
|
|
44
|
+
function frontmatterRegion(rawText) {
|
|
45
|
+
const opening = rawText.match(/^---\r?\n/);
|
|
46
|
+
if (!opening) return null;
|
|
47
|
+
const closeRegex = /\r?\n---(?:\r?\n|$)/g;
|
|
48
|
+
closeRegex.lastIndex = opening[0].length;
|
|
49
|
+
const closing = closeRegex.exec(rawText);
|
|
50
|
+
if (!closing) return null;
|
|
51
|
+
const frontmatterText = rawText.slice(opening[0].length, closing.index);
|
|
52
|
+
return {
|
|
53
|
+
head: opening[0],
|
|
54
|
+
lines: frontmatterText.split(/\r?\n/),
|
|
55
|
+
tail: rawText.slice(closing.index)
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function stripProvenanceLines(lines) {
|
|
59
|
+
const kept = [];
|
|
60
|
+
let dropIndent = null;
|
|
61
|
+
let inClioBlock = false;
|
|
62
|
+
for (const line of lines) {
|
|
63
|
+
const indent = line.length - line.trimStart().length;
|
|
64
|
+
if (dropIndent !== null && line.trim().length > 0 && indent > dropIndent) continue;
|
|
65
|
+
dropIndent = null;
|
|
66
|
+
const keyMatch = line.match(/^([ \t]*)([A-Za-z][A-Za-z0-9-]*):/);
|
|
67
|
+
const key = keyMatch?.[2];
|
|
68
|
+
if (key !== void 0) {
|
|
69
|
+
if (indent === 0) inClioBlock = key === "clio";
|
|
70
|
+
if (PROVENANCE_KEYS.has(key) && (indent === 0 || inClioBlock)) {
|
|
71
|
+
dropIndent = indent;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
kept.push(line);
|
|
76
|
+
}
|
|
77
|
+
return kept.filter((line, index) => {
|
|
78
|
+
if (!/^clio:\s*$/.test(line)) return true;
|
|
79
|
+
for (let next = index + 1; next < kept.length; next += 1) {
|
|
80
|
+
const candidate = kept[next];
|
|
81
|
+
if (candidate.trim().length === 0) continue;
|
|
82
|
+
return /^[ \t]/.test(candidate);
|
|
83
|
+
}
|
|
84
|
+
return false;
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
function stripProvenanceFrontmatter(rawText) {
|
|
88
|
+
const region = frontmatterRegion(rawText);
|
|
89
|
+
if (!region) return rawText;
|
|
90
|
+
return `${region.head}${stripProvenanceLines(region.lines).join("\n")}${region.tail}`;
|
|
91
|
+
}
|
|
92
|
+
function normalizedSkillHash(rawText) {
|
|
93
|
+
return createHash("sha256").update(stripProvenanceFrontmatter(rawText), "utf8").digest("hex");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/domains/resources/skills/loader.ts
|
|
97
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
98
|
+
import { existsSync as existsSync3, readdirSync as readdirSync3, readFileSync as readFileSync2, realpathSync } from "node:fs";
|
|
99
|
+
import { homedir } from "node:os";
|
|
100
|
+
import path3 from "node:path";
|
|
101
|
+
|
|
102
|
+
// src/domains/resources/common-loader.ts
|
|
103
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
104
|
+
import path from "node:path";
|
|
105
|
+
import { parse as parseYaml } from "yaml";
|
|
106
|
+
function defaultScopedResourceRoots(kind, cwd) {
|
|
107
|
+
return [
|
|
108
|
+
...enabledExtensionResourceRoots(kind, cwd).map((root) => ({
|
|
109
|
+
path: root.path,
|
|
110
|
+
scope: "package",
|
|
111
|
+
source: root.source
|
|
112
|
+
})),
|
|
113
|
+
{ path: path.join(clioConfigDir(), kind), scope: "user", source: "config" },
|
|
114
|
+
{ path: path.join(cwd, ".clio-coder", kind), scope: "project", source: "project" }
|
|
115
|
+
];
|
|
116
|
+
}
|
|
117
|
+
function sourceInfoForRoot(root, filePath) {
|
|
118
|
+
return {
|
|
119
|
+
path: filePath,
|
|
120
|
+
scope: root.scope,
|
|
121
|
+
...root.source ? { source: root.source } : {}
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function readRootEntries(root, label, diagnostics) {
|
|
125
|
+
if (!existsSync(root.path)) return [];
|
|
126
|
+
let stat;
|
|
127
|
+
try {
|
|
128
|
+
stat = statSync(root.path);
|
|
129
|
+
} catch (err) {
|
|
130
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
131
|
+
diagnostics.push({
|
|
132
|
+
type: "warning",
|
|
133
|
+
message: `${label} root could not be stat'ed: ${reason}`,
|
|
134
|
+
path: root.path
|
|
135
|
+
});
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
if (!stat.isDirectory()) {
|
|
139
|
+
diagnostics.push({ type: "warning", message: `${label} root is not a directory`, path: root.path });
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
return readdirSync(root.path, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
144
|
+
} catch (err) {
|
|
145
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
146
|
+
diagnostics.push({ type: "warning", message: `${label} root could not be read: ${reason}`, path: root.path });
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function splitYamlFrontmatter(raw) {
|
|
151
|
+
const opening = raw.match(/^---\r?\n/);
|
|
152
|
+
if (!opening) return { ok: false, reason: "missing", body: raw };
|
|
153
|
+
const closeRegex = /\r?\n---(?:\r?\n|$)/g;
|
|
154
|
+
closeRegex.lastIndex = opening[0].length;
|
|
155
|
+
const closing = closeRegex.exec(raw);
|
|
156
|
+
if (!closing) return { ok: false, reason: "missing closing delimiter", body: raw };
|
|
157
|
+
const body = raw.slice(closing.index + closing[0].length);
|
|
158
|
+
const frontmatterText = raw.slice(opening[0].length, closing.index);
|
|
159
|
+
let parsed;
|
|
160
|
+
try {
|
|
161
|
+
parsed = parseYaml(frontmatterText);
|
|
162
|
+
} catch (err) {
|
|
163
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
164
|
+
return { ok: false, reason: `invalid YAML: ${reason}`, body };
|
|
165
|
+
}
|
|
166
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
167
|
+
return { ok: false, reason: "must be a YAML object", body };
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
ok: true,
|
|
171
|
+
frontmatter: parsed,
|
|
172
|
+
body
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function stringField(frontmatter, key) {
|
|
176
|
+
const value = frontmatter[key];
|
|
177
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// src/domains/resources/skills/marketplace.ts
|
|
181
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync } from "node:fs";
|
|
182
|
+
import path2 from "node:path";
|
|
183
|
+
var MARKETPLACE_UNCONFIGURED = "no local skill marketplace catalog or index configured";
|
|
184
|
+
function defaultIndexPath() {
|
|
185
|
+
try {
|
|
186
|
+
return path2.join(clioConfigDir(), "skill-marketplace.json");
|
|
187
|
+
} catch {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function isIndexSkill(value) {
|
|
192
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
193
|
+
const record = value;
|
|
194
|
+
return typeof record.name === "string" && record.name.trim().length > 0 && typeof record.description === "string" && typeof record.sourceUrl === "string" && record.sourceUrl.trim().length > 0;
|
|
195
|
+
}
|
|
196
|
+
function optionalString(value) {
|
|
197
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
198
|
+
}
|
|
199
|
+
function indexAudit(value) {
|
|
200
|
+
return value === "pass" || value === "warn" || value === "fail" || value === "unknown" ? value : void 0;
|
|
201
|
+
}
|
|
202
|
+
function indexSkills(indexPath, diagnostics) {
|
|
203
|
+
try {
|
|
204
|
+
const parsed = JSON.parse(readFileSync(indexPath, "utf8"));
|
|
205
|
+
const rawSkills = Array.isArray(parsed) ? parsed : parsed && typeof parsed === "object" && Array.isArray(parsed.skills) ? parsed.skills : [];
|
|
206
|
+
return rawSkills.filter(isIndexSkill).map((skill) => {
|
|
207
|
+
const record = skill;
|
|
208
|
+
const version = optionalString(record.version);
|
|
209
|
+
const audit = indexAudit(record.audit);
|
|
210
|
+
const category = optionalString(record.category);
|
|
211
|
+
return {
|
|
212
|
+
name: skill.name.trim(),
|
|
213
|
+
description: skill.description.trim(),
|
|
214
|
+
sourceUrl: skill.sourceUrl.trim(),
|
|
215
|
+
...version ? { version } : {},
|
|
216
|
+
...audit ? { audit } : {},
|
|
217
|
+
...category ? { category } : {},
|
|
218
|
+
origin: "index"
|
|
219
|
+
};
|
|
220
|
+
});
|
|
221
|
+
} catch (err) {
|
|
222
|
+
diagnostics.push(`skill marketplace index unreadable: ${err instanceof Error ? err.message : String(err)}`);
|
|
223
|
+
return [];
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function looksLikeSkillCatalog(dir) {
|
|
227
|
+
if (!existsSync2(dir)) return false;
|
|
228
|
+
try {
|
|
229
|
+
return readdirSync2(dir, { withFileTypes: true }).some((entry) => {
|
|
230
|
+
if (!entry.isDirectory()) return false;
|
|
231
|
+
const child = path2.join(dir, entry.name);
|
|
232
|
+
if (existsSync2(path2.join(child, "SKILL.md"))) return true;
|
|
233
|
+
try {
|
|
234
|
+
return readdirSync2(child, { withFileTypes: true }).some(
|
|
235
|
+
(inner) => inner.isDirectory() && existsSync2(path2.join(child, inner.name, "SKILL.md"))
|
|
236
|
+
);
|
|
237
|
+
} catch {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
} catch {
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
function resolveCatalogDir(options) {
|
|
246
|
+
if (options.catalogDir === null) return null;
|
|
247
|
+
if (options.catalogDir) return path2.resolve(options.catalogDir);
|
|
248
|
+
const fromEnv = process.env.CLIO_CODER_SKILL_CATALOG_DIR;
|
|
249
|
+
if (fromEnv && fromEnv.trim().length > 0) return path2.resolve(fromEnv.trim());
|
|
250
|
+
const repoCatalog = path2.join(options.cwd ?? process.cwd(), "skills");
|
|
251
|
+
return looksLikeSkillCatalog(repoCatalog) ? repoCatalog : null;
|
|
252
|
+
}
|
|
253
|
+
function catalogCategory(catalogDir, baseDir) {
|
|
254
|
+
const rel = path2.relative(catalogDir, baseDir);
|
|
255
|
+
const category = path2.dirname(rel);
|
|
256
|
+
if (rel.length === 0 || rel.startsWith("..") || category === "." || category.startsWith("..")) return void 0;
|
|
257
|
+
return category;
|
|
258
|
+
}
|
|
259
|
+
function catalogEntry(skill, catalogDir) {
|
|
260
|
+
const version = typeof skill.metadata.version === "string" ? skill.metadata.version : void 0;
|
|
261
|
+
const category = catalogCategory(catalogDir, skill.baseDir);
|
|
262
|
+
return {
|
|
263
|
+
name: skill.name,
|
|
264
|
+
description: skill.description,
|
|
265
|
+
sourceUrl: skill.baseDir,
|
|
266
|
+
...version ? { version } : {},
|
|
267
|
+
...skill.provenance?.audit ? { audit: skill.provenance.audit } : {},
|
|
268
|
+
...category ? { category } : {},
|
|
269
|
+
origin: "catalog"
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
function catalogSkills(dir, diagnostics) {
|
|
273
|
+
const list = loadSkills({ disableDiscovery: true, explicitSkillPaths: [dir] });
|
|
274
|
+
for (const diag of list.diagnostics) {
|
|
275
|
+
if (diag.type === "warning" || diag.type === "error") diagnostics.push(diag.message);
|
|
276
|
+
}
|
|
277
|
+
return list.items.map((skill) => catalogEntry(skill, dir));
|
|
278
|
+
}
|
|
279
|
+
function discoverMarketplaceSkills(options = {}) {
|
|
280
|
+
const diagnostics = [];
|
|
281
|
+
const skills = [];
|
|
282
|
+
const seen = /* @__PURE__ */ new Set();
|
|
283
|
+
const catalogDir = resolveCatalogDir(options);
|
|
284
|
+
if (catalogDir) {
|
|
285
|
+
for (const skill of catalogSkills(catalogDir, diagnostics)) {
|
|
286
|
+
if (seen.has(skill.name)) continue;
|
|
287
|
+
seen.add(skill.name);
|
|
288
|
+
skills.push(skill);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
const indexPath = options.indexPath === null ? null : options.indexPath ?? process.env.CLIO_CODER_SKILL_MARKETPLACE_INDEX ?? null;
|
|
292
|
+
const resolvedIndexPath = indexPath ?? defaultIndexPath();
|
|
293
|
+
if (options.indexPath !== null && resolvedIndexPath && existsSync2(resolvedIndexPath)) {
|
|
294
|
+
for (const skill of indexSkills(resolvedIndexPath, diagnostics)) {
|
|
295
|
+
if (seen.has(skill.name)) continue;
|
|
296
|
+
seen.add(skill.name);
|
|
297
|
+
skills.push(skill);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (skills.length === 0 && diagnostics.length === 0) {
|
|
301
|
+
diagnostics.push(MARKETPLACE_UNCONFIGURED);
|
|
302
|
+
}
|
|
303
|
+
return { status: skills.length > 0 ? "installable" : "unavailable", skills, diagnostics };
|
|
304
|
+
}
|
|
305
|
+
function getMarketplaceSkills(options = {}) {
|
|
306
|
+
return discoverMarketplaceSkills(options).skills;
|
|
307
|
+
}
|
|
308
|
+
function installSkill(input) {
|
|
309
|
+
const source = input.source.trim();
|
|
310
|
+
const spec = parseSkillSourceSpec(source);
|
|
311
|
+
if (spec?.kind === "local" && isSkillName(source) && !existsSync2(spec.path)) {
|
|
312
|
+
const skill = getMarketplaceSkills({ ...input.cwd ? { cwd: input.cwd } : {} }).find(
|
|
313
|
+
(entry) => entry.name === source
|
|
314
|
+
);
|
|
315
|
+
if (!skill) {
|
|
316
|
+
throw new Error(`"${source}" is neither an existing local path nor available in the local marketplace`);
|
|
317
|
+
}
|
|
318
|
+
return installSkillFromSource({ ...input, source: skill.sourceUrl });
|
|
319
|
+
}
|
|
320
|
+
return installSkillFromSource(input);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// src/domains/resources/skills/loader.ts
|
|
324
|
+
var MAX_NAME_LENGTH = 64;
|
|
325
|
+
var MAX_DESCRIPTION_LENGTH = 1024;
|
|
326
|
+
var MAX_SKILL_BYTES = 1024 * 1024;
|
|
327
|
+
var SKILL_ACTIVATION_BODY_CAP_BYTES = 50 * 1024;
|
|
328
|
+
var CORE_FRONTMATTER_KEYS = /* @__PURE__ */ new Set([
|
|
329
|
+
"name",
|
|
330
|
+
"description",
|
|
331
|
+
"disable-model-invocation",
|
|
332
|
+
"allowed-tools",
|
|
333
|
+
"disallowed-tools"
|
|
334
|
+
]);
|
|
335
|
+
var SKILL_PRECEDENCE = {
|
|
336
|
+
extension: 10,
|
|
337
|
+
userCompat: 20,
|
|
338
|
+
user: 30,
|
|
339
|
+
projectCompat: 40,
|
|
340
|
+
project: 50,
|
|
341
|
+
cli: 60
|
|
342
|
+
};
|
|
343
|
+
function sha256(value) {
|
|
344
|
+
return createHash2("sha256").update(value, "utf8").digest("hex");
|
|
345
|
+
}
|
|
346
|
+
function defaultPrecedenceForScope(scope) {
|
|
347
|
+
switch (scope) {
|
|
348
|
+
case "package":
|
|
349
|
+
return SKILL_PRECEDENCE.extension;
|
|
350
|
+
case "user":
|
|
351
|
+
return SKILL_PRECEDENCE.user;
|
|
352
|
+
case "project":
|
|
353
|
+
return SKILL_PRECEDENCE.project;
|
|
354
|
+
case "cli":
|
|
355
|
+
return SKILL_PRECEDENCE.cli;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
function projectCompatTrusted(input) {
|
|
359
|
+
if (input.trustProjectCompatRoots === true) return true;
|
|
360
|
+
return process.env.CLIO_CODER_TRUST_PROJECT_SKILLS === "1";
|
|
361
|
+
}
|
|
362
|
+
function defaultSkillRoots(input = {}) {
|
|
363
|
+
const cwd = input.cwd ?? process.cwd();
|
|
364
|
+
const home = input.home ?? homedir();
|
|
365
|
+
const configDir = input.configDir ?? clioConfigDirSafe();
|
|
366
|
+
const trustProject = projectCompatTrusted(input);
|
|
367
|
+
const roots = [];
|
|
368
|
+
for (const root of enabledExtensionResourceRoots("skills", cwd)) {
|
|
369
|
+
roots.push({
|
|
370
|
+
path: root.path,
|
|
371
|
+
scope: "package",
|
|
372
|
+
source: "extension",
|
|
373
|
+
origin: root.source,
|
|
374
|
+
precedence: SKILL_PRECEDENCE.extension,
|
|
375
|
+
trusted: true
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
roots.push({
|
|
379
|
+
path: path3.join(home, ".agents", "skills"),
|
|
380
|
+
scope: "user",
|
|
381
|
+
source: "agents",
|
|
382
|
+
origin: "agents-user",
|
|
383
|
+
precedence: SKILL_PRECEDENCE.userCompat,
|
|
384
|
+
trusted: true,
|
|
385
|
+
containment: home
|
|
386
|
+
});
|
|
387
|
+
roots.push({
|
|
388
|
+
path: path3.join(home, ".claude", "skills"),
|
|
389
|
+
scope: "user",
|
|
390
|
+
source: "claude",
|
|
391
|
+
origin: "claude-user",
|
|
392
|
+
precedence: SKILL_PRECEDENCE.userCompat,
|
|
393
|
+
trusted: true,
|
|
394
|
+
containment: home
|
|
395
|
+
});
|
|
396
|
+
roots.push({
|
|
397
|
+
path: path3.join(home, ".codex", "skills"),
|
|
398
|
+
scope: "user",
|
|
399
|
+
source: "codex",
|
|
400
|
+
origin: "codex-user",
|
|
401
|
+
precedence: SKILL_PRECEDENCE.userCompat,
|
|
402
|
+
trusted: true,
|
|
403
|
+
containment: home
|
|
404
|
+
});
|
|
405
|
+
roots.push({
|
|
406
|
+
path: path3.join(home, ".config", "opencode", "skills"),
|
|
407
|
+
scope: "user",
|
|
408
|
+
source: "opencode",
|
|
409
|
+
origin: "opencode-user",
|
|
410
|
+
precedence: SKILL_PRECEDENCE.userCompat,
|
|
411
|
+
trusted: true,
|
|
412
|
+
containment: home
|
|
413
|
+
});
|
|
414
|
+
roots.push({
|
|
415
|
+
path: path3.join(home, ".copilot", "skills"),
|
|
416
|
+
scope: "user",
|
|
417
|
+
source: "copilot",
|
|
418
|
+
origin: "copilot-user",
|
|
419
|
+
precedence: SKILL_PRECEDENCE.userCompat,
|
|
420
|
+
trusted: true,
|
|
421
|
+
containment: home
|
|
422
|
+
});
|
|
423
|
+
if (configDir) {
|
|
424
|
+
roots.push({
|
|
425
|
+
path: path3.join(configDir, "skills"),
|
|
426
|
+
scope: "user",
|
|
427
|
+
source: "clio",
|
|
428
|
+
origin: "config",
|
|
429
|
+
precedence: SKILL_PRECEDENCE.user,
|
|
430
|
+
trusted: true,
|
|
431
|
+
containment: home
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
roots.push({
|
|
435
|
+
path: path3.join(cwd, ".agents", "skills"),
|
|
436
|
+
scope: "project",
|
|
437
|
+
source: "agents",
|
|
438
|
+
origin: "agents-project",
|
|
439
|
+
precedence: SKILL_PRECEDENCE.projectCompat,
|
|
440
|
+
trusted: trustProject,
|
|
441
|
+
containment: cwd
|
|
442
|
+
});
|
|
443
|
+
roots.push({
|
|
444
|
+
path: path3.join(cwd, ".claude", "skills"),
|
|
445
|
+
scope: "project",
|
|
446
|
+
source: "claude",
|
|
447
|
+
origin: "claude-project",
|
|
448
|
+
precedence: SKILL_PRECEDENCE.projectCompat,
|
|
449
|
+
trusted: trustProject,
|
|
450
|
+
containment: cwd
|
|
451
|
+
});
|
|
452
|
+
roots.push({
|
|
453
|
+
path: path3.join(cwd, ".codex", "skills"),
|
|
454
|
+
scope: "project",
|
|
455
|
+
source: "codex",
|
|
456
|
+
origin: "codex-project",
|
|
457
|
+
precedence: SKILL_PRECEDENCE.projectCompat,
|
|
458
|
+
trusted: trustProject,
|
|
459
|
+
containment: cwd
|
|
460
|
+
});
|
|
461
|
+
roots.push({
|
|
462
|
+
path: path3.join(cwd, ".opencode", "skills"),
|
|
463
|
+
scope: "project",
|
|
464
|
+
source: "opencode",
|
|
465
|
+
origin: "opencode-project",
|
|
466
|
+
precedence: SKILL_PRECEDENCE.projectCompat,
|
|
467
|
+
trusted: trustProject,
|
|
468
|
+
containment: cwd
|
|
469
|
+
});
|
|
470
|
+
roots.push({
|
|
471
|
+
path: path3.join(cwd, ".github", "skills"),
|
|
472
|
+
scope: "project",
|
|
473
|
+
source: "copilot",
|
|
474
|
+
origin: "copilot-project",
|
|
475
|
+
precedence: SKILL_PRECEDENCE.projectCompat,
|
|
476
|
+
trusted: trustProject,
|
|
477
|
+
containment: cwd
|
|
478
|
+
});
|
|
479
|
+
roots.push({
|
|
480
|
+
path: path3.join(cwd, ".clio-coder", "skills"),
|
|
481
|
+
scope: "project",
|
|
482
|
+
source: "clio",
|
|
483
|
+
origin: "project",
|
|
484
|
+
precedence: SKILL_PRECEDENCE.project,
|
|
485
|
+
trusted: true,
|
|
486
|
+
containment: cwd
|
|
487
|
+
});
|
|
488
|
+
return roots;
|
|
489
|
+
}
|
|
490
|
+
function clioConfigDirSafe() {
|
|
491
|
+
try {
|
|
492
|
+
return clioConfigDir();
|
|
493
|
+
} catch {
|
|
494
|
+
return null;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
function splitSkillFrontmatter(raw) {
|
|
498
|
+
const split = splitYamlFrontmatter(raw);
|
|
499
|
+
if (!split.ok && split.reason === "missing") {
|
|
500
|
+
throw new Error("skill file is missing YAML frontmatter");
|
|
501
|
+
}
|
|
502
|
+
if (!split.ok && split.reason === "missing closing delimiter") {
|
|
503
|
+
throw new Error("skill file is missing a closing YAML frontmatter delimiter");
|
|
504
|
+
}
|
|
505
|
+
if (!split.ok && split.reason === "must be a YAML object") {
|
|
506
|
+
throw new Error("skill frontmatter must be a YAML object");
|
|
507
|
+
}
|
|
508
|
+
if (!split.ok) throw new Error(`skill frontmatter is ${split.reason}`);
|
|
509
|
+
return split;
|
|
510
|
+
}
|
|
511
|
+
function booleanField(frontmatter, key) {
|
|
512
|
+
return frontmatter[key] === true;
|
|
513
|
+
}
|
|
514
|
+
function rawToolListField(frontmatter, key) {
|
|
515
|
+
const raw = frontmatter[key];
|
|
516
|
+
const entries = typeof raw === "string" ? raw.split(",") : Array.isArray(raw) ? raw.filter((entry) => typeof entry === "string") : null;
|
|
517
|
+
if (entries === null) return void 0;
|
|
518
|
+
const tools = entries.map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
519
|
+
return tools.length > 0 ? [...new Set(tools)] : void 0;
|
|
520
|
+
}
|
|
521
|
+
var TOOL_NAMES_BY_LOWERCASE = new Map(Object.values(ToolNames).map((name) => [name.toLowerCase(), name]));
|
|
522
|
+
function resolveDeclaredTools(declared) {
|
|
523
|
+
const tools = [];
|
|
524
|
+
const unrecognized = [];
|
|
525
|
+
for (const entry of declared) {
|
|
526
|
+
const resolved = TOOL_NAMES_BY_LOWERCASE.get(entry.toLowerCase());
|
|
527
|
+
if (resolved === void 0) unrecognized.push(entry);
|
|
528
|
+
else if (!tools.includes(resolved)) tools.push(resolved);
|
|
529
|
+
}
|
|
530
|
+
return { tools, unrecognized };
|
|
531
|
+
}
|
|
532
|
+
function declaredToolSurface(frontmatter, key, filePath, diagnostics) {
|
|
533
|
+
const declared = rawToolListField(frontmatter, key);
|
|
534
|
+
if (declared === void 0) return void 0;
|
|
535
|
+
const { tools, unrecognized } = resolveDeclaredTools(declared);
|
|
536
|
+
if (unrecognized.length > 0) {
|
|
537
|
+
diagnostics.push({
|
|
538
|
+
type: "warning",
|
|
539
|
+
message: `${key} names ${unrecognized.length === 1 ? "a tool" : "tools"} Clio does not have: ${unrecognized.join(", ")}`,
|
|
540
|
+
path: filePath
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
if (tools.length === 0 && key === "allowed-tools") {
|
|
544
|
+
diagnostics.push({
|
|
545
|
+
type: "warning",
|
|
546
|
+
message: "allowed-tools names no tool Clio has, so it narrows nothing; declare Clio tool names to confine this skill",
|
|
547
|
+
path: filePath
|
|
548
|
+
});
|
|
549
|
+
return void 0;
|
|
550
|
+
}
|
|
551
|
+
return tools.length > 0 ? tools : void 0;
|
|
552
|
+
}
|
|
553
|
+
function validationSubject(filePath) {
|
|
554
|
+
return path3.basename(filePath) === "SKILL.md" ? path3.basename(path3.dirname(filePath)) : path3.basename(filePath, ".md");
|
|
555
|
+
}
|
|
556
|
+
function validateNameFormat(name) {
|
|
557
|
+
const errors = [];
|
|
558
|
+
if (name.length > MAX_NAME_LENGTH) errors.push(`name exceeds ${MAX_NAME_LENGTH} characters (${name.length})`);
|
|
559
|
+
if (!/^[a-z0-9-]+$/.test(name)) {
|
|
560
|
+
errors.push("name contains invalid characters (must be lowercase a-z, 0-9, hyphens only)");
|
|
561
|
+
}
|
|
562
|
+
if (name.startsWith("-") || name.endsWith("-")) errors.push("name must not start or end with a hyphen");
|
|
563
|
+
if (name.includes("--")) errors.push("name must not contain consecutive hyphens");
|
|
564
|
+
return errors;
|
|
565
|
+
}
|
|
566
|
+
function validateDescription(description) {
|
|
567
|
+
if (!description) return ["description is required"];
|
|
568
|
+
if (description.length > MAX_DESCRIPTION_LENGTH) {
|
|
569
|
+
return [`description exceeds ${MAX_DESCRIPTION_LENGTH} characters (${description.length})`];
|
|
570
|
+
}
|
|
571
|
+
return [];
|
|
572
|
+
}
|
|
573
|
+
function extractMetadata(frontmatter) {
|
|
574
|
+
const metadata = {};
|
|
575
|
+
for (const [key, value] of Object.entries(frontmatter)) {
|
|
576
|
+
if (CORE_FRONTMATTER_KEYS.has(key)) continue;
|
|
577
|
+
metadata[key] = value;
|
|
578
|
+
}
|
|
579
|
+
return metadata;
|
|
580
|
+
}
|
|
581
|
+
function extractProvenance(frontmatter) {
|
|
582
|
+
const clioRaw = frontmatter.clio;
|
|
583
|
+
const clio = clioRaw !== null && typeof clioRaw === "object" && !Array.isArray(clioRaw) ? clioRaw : void 0;
|
|
584
|
+
const field = (...keys) => {
|
|
585
|
+
for (const key of keys) {
|
|
586
|
+
if (clio) {
|
|
587
|
+
const nested = stringField(clio, key);
|
|
588
|
+
if (nested !== null) return nested;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
for (const key of keys) {
|
|
592
|
+
const flat = stringField(frontmatter, key);
|
|
593
|
+
if (flat !== null) return flat;
|
|
594
|
+
}
|
|
595
|
+
return null;
|
|
596
|
+
};
|
|
597
|
+
const installUrl = field("source-url", "sourceUrl", "install-url");
|
|
598
|
+
const registryId = field("registry-id", "registryId");
|
|
599
|
+
const registryUrl = field("registry-url", "registryUrl");
|
|
600
|
+
const installedAt = field("installed-at", "installedAt");
|
|
601
|
+
const updatedAt = field("updated-at", "updatedAt");
|
|
602
|
+
const installedHash = field("installed-hash", "installedHash");
|
|
603
|
+
const auditRaw = field("audit");
|
|
604
|
+
const audit = auditRaw === "pass" || auditRaw === "warn" || auditRaw === "fail" || auditRaw === "unknown" ? auditRaw : void 0;
|
|
605
|
+
if (!installUrl && !registryId && !registryUrl && !installedAt && !updatedAt && !installedHash && !audit)
|
|
606
|
+
return void 0;
|
|
607
|
+
return {
|
|
608
|
+
...installUrl ? { installUrl } : {},
|
|
609
|
+
...registryId ? { registryId } : {},
|
|
610
|
+
...registryUrl ? { registryUrl } : {},
|
|
611
|
+
...installedAt ? { installedAt } : {},
|
|
612
|
+
...updatedAt ? { updatedAt } : {},
|
|
613
|
+
...installedHash ? { installedHash } : {},
|
|
614
|
+
...audit ? { audit } : {}
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
function canonicalizePath(filePath) {
|
|
618
|
+
try {
|
|
619
|
+
return realpathSync.native(filePath);
|
|
620
|
+
} catch {
|
|
621
|
+
return path3.resolve(filePath);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
function pathIsInside(candidate, anchor) {
|
|
625
|
+
return candidate === anchor || candidate.startsWith(anchor + path3.sep);
|
|
626
|
+
}
|
|
627
|
+
function containmentAnchor(root) {
|
|
628
|
+
if (root.containment === void 0) return null;
|
|
629
|
+
const anchor = canonicalizePath(root.containment);
|
|
630
|
+
const rootReal = canonicalizePath(root.path);
|
|
631
|
+
return pathIsInside(rootReal, anchor) ? anchor : rootReal;
|
|
632
|
+
}
|
|
633
|
+
function escapesContainment(fullPath, anchor, diagnostics) {
|
|
634
|
+
if (anchor === null) return false;
|
|
635
|
+
const resolved = canonicalizePath(fullPath);
|
|
636
|
+
if (pathIsInside(resolved, anchor)) return false;
|
|
637
|
+
diagnostics.push({
|
|
638
|
+
type: "warning",
|
|
639
|
+
message: `skill path resolves to ${resolved}, outside ${anchor}; a skill root may not leave its scope through a symlink`,
|
|
640
|
+
path: fullPath
|
|
641
|
+
});
|
|
642
|
+
return true;
|
|
643
|
+
}
|
|
644
|
+
function loadSkillFile(filePath, root) {
|
|
645
|
+
const diagnostics = [];
|
|
646
|
+
let bytes;
|
|
647
|
+
try {
|
|
648
|
+
bytes = readFileSync2(filePath);
|
|
649
|
+
} catch (err) {
|
|
650
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
651
|
+
return {
|
|
652
|
+
candidate: null,
|
|
653
|
+
diagnostics: [{ type: "warning", message: `skill file could not be read: ${reason}`, path: filePath }]
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
if (bytes.byteLength > MAX_SKILL_BYTES) {
|
|
657
|
+
return {
|
|
658
|
+
candidate: null,
|
|
659
|
+
diagnostics: [
|
|
660
|
+
{
|
|
661
|
+
type: "warning",
|
|
662
|
+
message: `skill file is ${bytes.byteLength} bytes, over the ${MAX_SKILL_BYTES}-byte limit for a SKILL.md`,
|
|
663
|
+
path: filePath
|
|
664
|
+
}
|
|
665
|
+
]
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
const raw = bytes.toString("utf8");
|
|
669
|
+
if (bytes.includes(0) || !Buffer.from(raw, "utf8").equals(bytes)) {
|
|
670
|
+
return {
|
|
671
|
+
candidate: null,
|
|
672
|
+
diagnostics: [
|
|
673
|
+
{
|
|
674
|
+
type: "warning",
|
|
675
|
+
message: "skill file is not valid UTF-8 text; a SKILL.md is a document, not a binary payload",
|
|
676
|
+
path: filePath
|
|
677
|
+
}
|
|
678
|
+
]
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
if (bytes.byteLength > SKILL_ACTIVATION_BODY_CAP_BYTES) {
|
|
682
|
+
diagnostics.push({
|
|
683
|
+
type: "warning",
|
|
684
|
+
message: `skill file is ${bytes.byteLength} bytes; activation delivers at most ${SKILL_ACTIVATION_BODY_CAP_BYTES}, so the model will not be shown the rest`,
|
|
685
|
+
path: filePath
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
let parsed;
|
|
689
|
+
try {
|
|
690
|
+
parsed = splitSkillFrontmatter(raw);
|
|
691
|
+
} catch (err) {
|
|
692
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
693
|
+
return { candidate: null, diagnostics: [{ type: "warning", message: reason, path: filePath }] };
|
|
694
|
+
}
|
|
695
|
+
const pathSubject = validationSubject(filePath);
|
|
696
|
+
const frontmatterName = stringField(parsed.frontmatter, "name");
|
|
697
|
+
const name = frontmatterName ?? pathSubject;
|
|
698
|
+
const description = stringField(parsed.frontmatter, "description");
|
|
699
|
+
for (const message of validateDescription(description)) diagnostics.push({ type: "warning", message, path: filePath });
|
|
700
|
+
if (frontmatterName && frontmatterName !== pathSubject) {
|
|
701
|
+
diagnostics.push({
|
|
702
|
+
type: "warning",
|
|
703
|
+
message: `name "${frontmatterName}" differs from path subject "${pathSubject}"; using frontmatter name`,
|
|
704
|
+
path: filePath
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
for (const message of validateNameFormat(name)) diagnostics.push({ type: "warning", message, path: filePath });
|
|
708
|
+
if (!description) return { candidate: null, diagnostics };
|
|
709
|
+
const baseDir = path3.dirname(filePath);
|
|
710
|
+
const scope = root.scope;
|
|
711
|
+
const sourceInfo = {
|
|
712
|
+
path: filePath,
|
|
713
|
+
scope,
|
|
714
|
+
...root.origin ? { source: root.origin } : {}
|
|
715
|
+
};
|
|
716
|
+
const provenance = extractProvenance(parsed.frontmatter);
|
|
717
|
+
const allowedTools = declaredToolSurface(parsed.frontmatter, "allowed-tools", filePath, diagnostics);
|
|
718
|
+
const disallowedTools = declaredToolSurface(parsed.frontmatter, "disallowed-tools", filePath, diagnostics);
|
|
719
|
+
const skill = {
|
|
720
|
+
name,
|
|
721
|
+
description,
|
|
722
|
+
filePath,
|
|
723
|
+
baseDir,
|
|
724
|
+
content: parsed.body.trim(),
|
|
725
|
+
sourceInfo,
|
|
726
|
+
disableModelInvocation: booleanField(parsed.frontmatter, "disable-model-invocation"),
|
|
727
|
+
...allowedTools ? { allowedTools } : {},
|
|
728
|
+
...disallowedTools ? { disallowedTools } : {},
|
|
729
|
+
source: root.source ?? "clio",
|
|
730
|
+
scope,
|
|
731
|
+
hash: sha256(raw),
|
|
732
|
+
normalizedHash: normalizedSkillHash(raw),
|
|
733
|
+
pathSubject,
|
|
734
|
+
trusted: root.trusted ?? true,
|
|
735
|
+
precedence: root.precedence ?? defaultPrecedenceForScope(scope),
|
|
736
|
+
metadata: extractMetadata(parsed.frontmatter),
|
|
737
|
+
diagnostics,
|
|
738
|
+
...provenance ? { provenance } : {}
|
|
739
|
+
};
|
|
740
|
+
return { candidate: { skill, canonicalPath: canonicalizePath(filePath) }, diagnostics };
|
|
741
|
+
}
|
|
742
|
+
function isSkillMarkdownFile(entryName) {
|
|
743
|
+
return entryName === "SKILL.md" || entryName.endsWith(".md");
|
|
744
|
+
}
|
|
745
|
+
function collectSkills(root, dir, diagnostics, includeRootFiles, anchor = containmentAnchor(root)) {
|
|
746
|
+
if (!existsSync3(dir)) return [];
|
|
747
|
+
if (escapesContainment(dir, anchor, diagnostics)) return [];
|
|
748
|
+
let entries;
|
|
749
|
+
try {
|
|
750
|
+
entries = readdirSync3(dir, { withFileTypes: true });
|
|
751
|
+
} catch (err) {
|
|
752
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
753
|
+
diagnostics.push({ type: "warning", message: `skill root could not be read: ${reason}`, path: dir });
|
|
754
|
+
return [];
|
|
755
|
+
}
|
|
756
|
+
const skillEntry = entries.find((entry) => entry.name === "SKILL.md" && entry.isFile());
|
|
757
|
+
if (skillEntry) {
|
|
758
|
+
const skillPath = path3.join(dir, skillEntry.name);
|
|
759
|
+
if (escapesContainment(skillPath, anchor, diagnostics)) return [];
|
|
760
|
+
const loaded = loadSkillFile(skillPath, root);
|
|
761
|
+
diagnostics.push(...loaded.diagnostics);
|
|
762
|
+
return loaded.candidate ? [loaded.candidate] : [];
|
|
763
|
+
}
|
|
764
|
+
const candidates = [];
|
|
765
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
766
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
767
|
+
const fullPath = path3.join(dir, entry.name);
|
|
768
|
+
if (entry.isDirectory() || entry.isSymbolicLink() && existsSync3(path3.join(fullPath, "SKILL.md"))) {
|
|
769
|
+
candidates.push(...collectSkills(root, fullPath, diagnostics, false, anchor));
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
772
|
+
if (!includeRootFiles || !entry.isFile() && !entry.isSymbolicLink() || !isSkillMarkdownFile(entry.name)) continue;
|
|
773
|
+
if (escapesContainment(fullPath, anchor, diagnostics)) continue;
|
|
774
|
+
const loaded = loadSkillFile(fullPath, root);
|
|
775
|
+
diagnostics.push(...loaded.diagnostics);
|
|
776
|
+
if (loaded.candidate) candidates.push(loaded.candidate);
|
|
777
|
+
}
|
|
778
|
+
return candidates;
|
|
779
|
+
}
|
|
780
|
+
function loadSkillRoot(root, diagnostics) {
|
|
781
|
+
const entries = readRootEntries(root, "skill", diagnostics);
|
|
782
|
+
if (entries.length === 0) return [];
|
|
783
|
+
return collectSkills(root, root.path, diagnostics, true);
|
|
784
|
+
}
|
|
785
|
+
function explicitSkillRoot(filePath) {
|
|
786
|
+
return {
|
|
787
|
+
path: filePath,
|
|
788
|
+
scope: "cli",
|
|
789
|
+
source: "path",
|
|
790
|
+
origin: "explicit-path",
|
|
791
|
+
precedence: SKILL_PRECEDENCE.cli,
|
|
792
|
+
trusted: true
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
function loadExplicitSkillPath(inputPath, diagnostics) {
|
|
796
|
+
const resolved = path3.resolve(inputPath);
|
|
797
|
+
const root = explicitSkillRoot(resolved);
|
|
798
|
+
if (!existsSync3(resolved)) {
|
|
799
|
+
diagnostics.push({ type: "warning", message: `explicit skill path does not exist: ${resolved}`, path: resolved });
|
|
800
|
+
return [];
|
|
801
|
+
}
|
|
802
|
+
const skillFile = path3.basename(resolved) === "SKILL.md" || resolved.endsWith(".md") ? resolved : path3.join(resolved, "SKILL.md");
|
|
803
|
+
if (!existsSync3(skillFile)) {
|
|
804
|
+
const packages = collectSkills(root, resolved, diagnostics, false);
|
|
805
|
+
if (packages.length > 0) return packages;
|
|
806
|
+
diagnostics.push({
|
|
807
|
+
type: "warning",
|
|
808
|
+
message: `explicit skill path is not a SKILL.md file, skill directory, or catalog of skill packages: ${resolved}`,
|
|
809
|
+
path: resolved
|
|
810
|
+
});
|
|
811
|
+
return [];
|
|
812
|
+
}
|
|
813
|
+
const loaded = loadSkillFile(skillFile, root);
|
|
814
|
+
diagnostics.push(...loaded.diagnostics);
|
|
815
|
+
return loaded.candidate ? [loaded.candidate] : [];
|
|
816
|
+
}
|
|
817
|
+
function dedupeCanonicalSkillPaths(candidates, diagnostics) {
|
|
818
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
819
|
+
for (const candidate of candidates) {
|
|
820
|
+
const list = byPath.get(candidate.canonicalPath) ?? [];
|
|
821
|
+
list.push(candidate);
|
|
822
|
+
byPath.set(candidate.canonicalPath, list);
|
|
823
|
+
}
|
|
824
|
+
const winners = [];
|
|
825
|
+
for (const entries of byPath.values()) {
|
|
826
|
+
if (entries.length === 1) {
|
|
827
|
+
const only = entries[0];
|
|
828
|
+
if (only) winners.push(only);
|
|
829
|
+
continue;
|
|
830
|
+
}
|
|
831
|
+
const sorted = [...entries].sort((a, b) => {
|
|
832
|
+
const delta = a.skill.precedence - b.skill.precedence;
|
|
833
|
+
if (delta !== 0) return delta;
|
|
834
|
+
return a.skill.filePath.localeCompare(b.skill.filePath);
|
|
835
|
+
});
|
|
836
|
+
const winner = sorted[sorted.length - 1];
|
|
837
|
+
if (!winner) continue;
|
|
838
|
+
winners.push(winner);
|
|
839
|
+
for (const loser of sorted.slice(0, -1)) {
|
|
840
|
+
diagnostics.push({
|
|
841
|
+
type: "warning",
|
|
842
|
+
message: `${loser.skill.name} at ${loser.skill.filePath} resolves to the same canonical skill file as ${winner.skill.filePath}; using the higher-precedence entry`,
|
|
843
|
+
path: loser.skill.filePath
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
return winners;
|
|
848
|
+
}
|
|
849
|
+
function resolveSkillCollisions(candidates) {
|
|
850
|
+
const byName = /* @__PURE__ */ new Map();
|
|
851
|
+
for (const candidate of candidates) {
|
|
852
|
+
const key = candidate.skill.name.trim();
|
|
853
|
+
if (key.length === 0) continue;
|
|
854
|
+
const list = byName.get(key) ?? [];
|
|
855
|
+
list.push(candidate);
|
|
856
|
+
byName.set(key, list);
|
|
857
|
+
}
|
|
858
|
+
const winners = [];
|
|
859
|
+
const diagnostics = [];
|
|
860
|
+
for (const [name, entries] of byName.entries()) {
|
|
861
|
+
const sorted = [...entries].sort((a, b) => {
|
|
862
|
+
const delta = a.skill.precedence - b.skill.precedence;
|
|
863
|
+
if (delta !== 0) return delta;
|
|
864
|
+
return a.skill.filePath.localeCompare(b.skill.filePath);
|
|
865
|
+
});
|
|
866
|
+
const winner = sorted[sorted.length - 1];
|
|
867
|
+
if (!winner) continue;
|
|
868
|
+
winners.push(winner.skill);
|
|
869
|
+
for (const loser of sorted.slice(0, -1)) {
|
|
870
|
+
diagnostics.push({
|
|
871
|
+
type: "collision",
|
|
872
|
+
message: `${name} from ${winner.skill.source}/${winner.skill.scope} overrides ${loser.skill.source}/${loser.skill.scope}`,
|
|
873
|
+
path: loser.skill.filePath,
|
|
874
|
+
collision: {
|
|
875
|
+
name,
|
|
876
|
+
winnerPath: winner.skill.filePath,
|
|
877
|
+
loserPath: loser.skill.filePath,
|
|
878
|
+
winnerScope: winner.skill.scope,
|
|
879
|
+
loserScope: loser.skill.scope
|
|
880
|
+
}
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
return { winners, diagnostics };
|
|
885
|
+
}
|
|
886
|
+
function requiresDiagnostics(winners) {
|
|
887
|
+
const names = new Set(winners.map((skill) => skill.name));
|
|
888
|
+
const out = [];
|
|
889
|
+
for (const skill of winners) {
|
|
890
|
+
const requires = skill.metadata.requires;
|
|
891
|
+
if (!Array.isArray(requires)) continue;
|
|
892
|
+
for (const entry of requires) {
|
|
893
|
+
if (typeof entry !== "string" || !entry.startsWith("skill:")) continue;
|
|
894
|
+
const dep = entry.slice("skill:".length).trim();
|
|
895
|
+
if (dep.length === 0 || names.has(dep)) continue;
|
|
896
|
+
out.push({
|
|
897
|
+
type: "warning",
|
|
898
|
+
message: `${skill.name} requires skill "${dep}" which is not available`,
|
|
899
|
+
path: skill.filePath
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
return out;
|
|
904
|
+
}
|
|
905
|
+
function loadSkills(input = {}) {
|
|
906
|
+
const roots = input.roots ?? (input.disableDiscovery === true ? [] : defaultSkillRoots(input));
|
|
907
|
+
const diagnostics = [];
|
|
908
|
+
const candidates = [
|
|
909
|
+
...roots.flatMap((root) => loadSkillRoot(root, diagnostics)),
|
|
910
|
+
...(input.explicitSkillPaths ?? []).flatMap((skillPath) => loadExplicitSkillPath(skillPath, diagnostics))
|
|
911
|
+
];
|
|
912
|
+
const deduped = dedupeCanonicalSkillPaths(candidates, diagnostics);
|
|
913
|
+
const resolved = resolveSkillCollisions(deduped);
|
|
914
|
+
const winners = [...resolved.winners].sort((a, b) => a.name.localeCompare(b.name));
|
|
915
|
+
return {
|
|
916
|
+
items: winners,
|
|
917
|
+
diagnostics: [...diagnostics, ...resolved.diagnostics, ...requiresDiagnostics(winners)]
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
function modelVisibleSkills(skills) {
|
|
921
|
+
return skills.filter((skill) => skill.trusted && !skill.disableModelInvocation);
|
|
922
|
+
}
|
|
923
|
+
function parseSkillCommand(input) {
|
|
924
|
+
const trimmed = input.trim();
|
|
925
|
+
const prefix = trimmed.startsWith("/skill:") ? "/skill:" : trimmed.startsWith("/skills:") ? "/skills:" : trimmed.startsWith("/skill ") ? "/skill " : null;
|
|
926
|
+
if (!prefix) return null;
|
|
927
|
+
const rest = trimmed.slice(prefix.length).trim();
|
|
928
|
+
const separator = rest.search(/\s/);
|
|
929
|
+
const name = separator === -1 ? rest : rest.slice(0, separator);
|
|
930
|
+
if (name.length === 0) return null;
|
|
931
|
+
const args = separator === -1 ? "" : rest.slice(separator).trim();
|
|
932
|
+
return { name, args };
|
|
933
|
+
}
|
|
934
|
+
function expandSkillInvocationInput(input, skills, _options = {}) {
|
|
935
|
+
const command = parseSkillCommand(input);
|
|
936
|
+
if (!command) return { expanded: false, text: input, args: "", diagnostics: skills.diagnostics };
|
|
937
|
+
const skill = skills.items.find((entry) => entry.name === command.name);
|
|
938
|
+
const args = command.args;
|
|
939
|
+
if (!skill) return { expanded: false, text: input, args, diagnostics: skills.diagnostics };
|
|
940
|
+
return {
|
|
941
|
+
expanded: true,
|
|
942
|
+
text: args,
|
|
943
|
+
args,
|
|
944
|
+
skill,
|
|
945
|
+
triggeredBy: "slash-command",
|
|
946
|
+
diagnostics: skills.diagnostics
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
function parsePendingSkillRequests(input, skills, options = {}) {
|
|
950
|
+
const command = parseSkillCommand(input);
|
|
951
|
+
if (command) {
|
|
952
|
+
const name = command.name;
|
|
953
|
+
const args = command.args;
|
|
954
|
+
const installedSkill = skills.items.find((entry) => entry.name === name);
|
|
955
|
+
if (installedSkill) {
|
|
956
|
+
return {
|
|
957
|
+
text: args,
|
|
958
|
+
pendingSkillRequests: [
|
|
959
|
+
{
|
|
960
|
+
name,
|
|
961
|
+
args,
|
|
962
|
+
source: "slash-command",
|
|
963
|
+
installed: true,
|
|
964
|
+
filePath: installedSkill.filePath
|
|
965
|
+
}
|
|
966
|
+
]
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
const marketplaceSkill = getMarketplaceSkills(options.cwd ? { cwd: options.cwd } : {}).find((s) => s.name === name);
|
|
970
|
+
if (marketplaceSkill) {
|
|
971
|
+
return {
|
|
972
|
+
text: args,
|
|
973
|
+
pendingSkillRequests: [
|
|
974
|
+
{
|
|
975
|
+
name,
|
|
976
|
+
args,
|
|
977
|
+
source: "marketplace",
|
|
978
|
+
installed: false,
|
|
979
|
+
marketplaceRef: marketplaceSkill.sourceUrl
|
|
980
|
+
}
|
|
981
|
+
]
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
return {
|
|
985
|
+
text: args,
|
|
986
|
+
pendingSkillRequests: [
|
|
987
|
+
{
|
|
988
|
+
name,
|
|
989
|
+
args,
|
|
990
|
+
source: "slash-command",
|
|
991
|
+
installed: false
|
|
992
|
+
}
|
|
993
|
+
]
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
return {
|
|
997
|
+
text: input,
|
|
998
|
+
pendingSkillRequests: []
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// src/domains/resources/skills/install.ts
|
|
1003
|
+
var SKILL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
1004
|
+
var CLONE_TIMEOUT_MS = 6e4;
|
|
1005
|
+
function isSkillName(value) {
|
|
1006
|
+
return value.length > 0 && value.length <= 64 && SKILL_NAME_PATTERN.test(value);
|
|
1007
|
+
}
|
|
1008
|
+
function repoRelativePath(filePath) {
|
|
1009
|
+
if (path4.isAbsolute(filePath) || filePath.includes("\0")) return null;
|
|
1010
|
+
const segments = filePath.split("/");
|
|
1011
|
+
if (segments.some((segment) => segment === "..")) return null;
|
|
1012
|
+
const normalized = path4.posix.normalize(filePath);
|
|
1013
|
+
if (normalized.startsWith("..") || path4.posix.isAbsolute(normalized)) return null;
|
|
1014
|
+
return normalized;
|
|
1015
|
+
}
|
|
1016
|
+
function parseSkillSourceSpec(source) {
|
|
1017
|
+
const trimmed = source.trim();
|
|
1018
|
+
if (trimmed.length === 0) return null;
|
|
1019
|
+
const browser = trimmed.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/(?:blob|tree)\/([^/]+)\/(.+?)\/?$/);
|
|
1020
|
+
if (browser?.[1] && browser[2] && browser[3] && browser[4]) {
|
|
1021
|
+
const filePath = repoRelativePath(browser[4]);
|
|
1022
|
+
if (!filePath) return null;
|
|
1023
|
+
return {
|
|
1024
|
+
kind: "github",
|
|
1025
|
+
cloneUrl: `https://github.com/${browser[1]}/${browser[2]}.git`,
|
|
1026
|
+
branch: browser[3],
|
|
1027
|
+
filePath,
|
|
1028
|
+
original: trimmed
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
const raw = trimmed.match(/^https:\/\/raw\.githubusercontent\.com\/([^/]+)\/([^/]+)\/([^/]+)\/(.+?)\/?$/);
|
|
1032
|
+
if (raw?.[1] && raw[2] && raw[3] && raw[4]) {
|
|
1033
|
+
const filePath = repoRelativePath(raw[4]);
|
|
1034
|
+
if (!filePath) return null;
|
|
1035
|
+
return {
|
|
1036
|
+
kind: "github",
|
|
1037
|
+
cloneUrl: `https://github.com/${raw[1]}/${raw[2]}.git`,
|
|
1038
|
+
branch: raw[3],
|
|
1039
|
+
filePath,
|
|
1040
|
+
original: trimmed
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) return null;
|
|
1044
|
+
const expanded = trimmed.startsWith("~/") ? path4.join(homedir2(), trimmed.slice(2)) : trimmed;
|
|
1045
|
+
return { kind: "local", path: path4.resolve(expanded), original: trimmed };
|
|
1046
|
+
}
|
|
1047
|
+
function yamlQuote(value) {
|
|
1048
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
1049
|
+
}
|
|
1050
|
+
function injectProvenanceFrontmatter(rawText, fields) {
|
|
1051
|
+
const region = frontmatterRegion(rawText);
|
|
1052
|
+
if (!region) throw new Error("skill file is missing YAML frontmatter");
|
|
1053
|
+
const kept = stripProvenanceLines(region.lines);
|
|
1054
|
+
const stamps = [
|
|
1055
|
+
`source-url: ${yamlQuote(fields.sourceUrl)}`,
|
|
1056
|
+
`installed-at: ${yamlQuote(fields.installedAt)}`,
|
|
1057
|
+
...fields.updatedAt ? [`updated-at: ${yamlQuote(fields.updatedAt)}`] : [],
|
|
1058
|
+
`installed-hash: ${yamlQuote(fields.installedHash)}`,
|
|
1059
|
+
// Audit is a human decision; installs always land unreviewed.
|
|
1060
|
+
"audit: unknown"
|
|
1061
|
+
];
|
|
1062
|
+
const clioIndex = kept.findIndex((line) => /^clio:/.test(line));
|
|
1063
|
+
if (clioIndex >= 0 && !/^clio:\s*$/.test(kept[clioIndex])) {
|
|
1064
|
+
return `${region.head}${[...kept, ...stamps].join("\n")}${region.tail}`;
|
|
1065
|
+
}
|
|
1066
|
+
const nested = stamps.map((line) => ` ${line}`);
|
|
1067
|
+
if (clioIndex < 0) {
|
|
1068
|
+
return `${region.head}${[...kept, "clio:", ...nested].join("\n")}${region.tail}`;
|
|
1069
|
+
}
|
|
1070
|
+
let blockEnd = clioIndex + 1;
|
|
1071
|
+
while (blockEnd < kept.length) {
|
|
1072
|
+
const line = kept[blockEnd];
|
|
1073
|
+
if (line.trim().length > 0 && !/^[ \t]/.test(line)) break;
|
|
1074
|
+
blockEnd += 1;
|
|
1075
|
+
}
|
|
1076
|
+
return `${region.head}${[...kept.slice(0, blockEnd), ...nested, ...kept.slice(blockEnd)].join("\n")}${region.tail}`;
|
|
1077
|
+
}
|
|
1078
|
+
function resolveSkillDir(target) {
|
|
1079
|
+
if (!existsSync4(target)) throw new Error(`skill source path does not exist: ${target}`);
|
|
1080
|
+
const stat = statSync2(target);
|
|
1081
|
+
if (stat.isFile()) {
|
|
1082
|
+
if (!target.endsWith(".md")) throw new Error(`skill source must be a SKILL.md file or directory: ${target}`);
|
|
1083
|
+
return path4.dirname(target);
|
|
1084
|
+
}
|
|
1085
|
+
if (!existsSync4(path4.join(target, "SKILL.md"))) {
|
|
1086
|
+
throw new Error(`skill source directory has no SKILL.md: ${target}`);
|
|
1087
|
+
}
|
|
1088
|
+
return target;
|
|
1089
|
+
}
|
|
1090
|
+
function fetchSource(spec) {
|
|
1091
|
+
if (spec.kind === "local") {
|
|
1092
|
+
return { skillDir: resolveSkillDir(spec.path), cleanup: () => {
|
|
1093
|
+
} };
|
|
1094
|
+
}
|
|
1095
|
+
const tmp = mkdtempSync(path4.join(tmpdir(), "clio-skill-"));
|
|
1096
|
+
const cleanup = () => rmSync(tmp, { recursive: true, force: true });
|
|
1097
|
+
try {
|
|
1098
|
+
execFileSync("git", ["clone", "--depth", "1", "--branch", spec.branch, spec.cloneUrl, tmp], {
|
|
1099
|
+
stdio: "pipe",
|
|
1100
|
+
timeout: CLONE_TIMEOUT_MS
|
|
1101
|
+
});
|
|
1102
|
+
const target = path4.join(tmp, spec.filePath);
|
|
1103
|
+
if (!existsSync4(target)) {
|
|
1104
|
+
throw new Error(
|
|
1105
|
+
`${spec.cloneUrl} (branch ${spec.branch}) has no ${spec.filePath}; the source-url may name a path that branch does not carry`
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
1108
|
+
return { skillDir: resolveSkillDir(target), cleanup };
|
|
1109
|
+
} catch (err) {
|
|
1110
|
+
cleanup();
|
|
1111
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
function copySkillDir(from, to) {
|
|
1115
|
+
mkdirSync(path4.dirname(to), { recursive: true });
|
|
1116
|
+
cpSync(from, to, {
|
|
1117
|
+
recursive: true,
|
|
1118
|
+
filter: (source) => {
|
|
1119
|
+
const base = path4.basename(source);
|
|
1120
|
+
return base !== ".git" && base !== "node_modules";
|
|
1121
|
+
}
|
|
1122
|
+
});
|
|
1123
|
+
}
|
|
1124
|
+
var skillSwapSequence = 0;
|
|
1125
|
+
function swapSkillDir(stage, dest) {
|
|
1126
|
+
const suffix = `${process.pid}-${++skillSwapSequence}`;
|
|
1127
|
+
const staging = `${dest}.clio-coder-staging-${suffix}`;
|
|
1128
|
+
const backup = `${dest}.clio-coder-backup-${suffix}`;
|
|
1129
|
+
rmSync(staging, { recursive: true, force: true });
|
|
1130
|
+
try {
|
|
1131
|
+
stage(staging);
|
|
1132
|
+
} catch (err) {
|
|
1133
|
+
rmSync(staging, { recursive: true, force: true });
|
|
1134
|
+
throw err;
|
|
1135
|
+
}
|
|
1136
|
+
const hadPrevious = existsSync4(dest);
|
|
1137
|
+
if (hadPrevious) renameSync(dest, backup);
|
|
1138
|
+
try {
|
|
1139
|
+
renameSync(staging, dest);
|
|
1140
|
+
} catch (err) {
|
|
1141
|
+
if (hadPrevious) renameSync(backup, dest);
|
|
1142
|
+
rmSync(staging, { recursive: true, force: true });
|
|
1143
|
+
throw err;
|
|
1144
|
+
}
|
|
1145
|
+
if (hadPrevious) rmSync(backup, { recursive: true, force: true });
|
|
1146
|
+
fsyncDirectory(path4.dirname(dest));
|
|
1147
|
+
}
|
|
1148
|
+
function destinationRoot(scope, cwd, configDir) {
|
|
1149
|
+
return scope === "user" ? path4.join(configDir ?? clioConfigDir(), "skills") : path4.join(cwd, ".clio-coder", "skills");
|
|
1150
|
+
}
|
|
1151
|
+
function validateSourceSkill(skillDir) {
|
|
1152
|
+
const list = loadSkills({ disableDiscovery: true, explicitSkillPaths: [skillDir] });
|
|
1153
|
+
const errors = list.diagnostics.filter((diag) => diag.type === "error");
|
|
1154
|
+
if (errors.length > 0) {
|
|
1155
|
+
throw new Error(`skill source failed validation: ${errors.map((diag) => diag.message).join("; ")}`);
|
|
1156
|
+
}
|
|
1157
|
+
const skill = list.items[0];
|
|
1158
|
+
if (!skill || list.items.length !== 1) {
|
|
1159
|
+
throw new Error(`skill source did not resolve to exactly one skill: ${skillDir}`);
|
|
1160
|
+
}
|
|
1161
|
+
return { skill, warnings: list.diagnostics.map((diag) => diag.message) };
|
|
1162
|
+
}
|
|
1163
|
+
function installSkillFromSource(input) {
|
|
1164
|
+
const spec = parseSkillSourceSpec(input.source);
|
|
1165
|
+
if (!spec) throw new Error(`unsupported skill source: ${input.source}`);
|
|
1166
|
+
const cwd = input.cwd ?? process.cwd();
|
|
1167
|
+
const scope = input.scope ?? "project";
|
|
1168
|
+
const fetched = fetchSource(spec);
|
|
1169
|
+
try {
|
|
1170
|
+
const { skill, warnings } = validateSourceSkill(fetched.skillDir);
|
|
1171
|
+
const name = (input.name ?? skill.name).trim();
|
|
1172
|
+
if (!isSkillName(name)) {
|
|
1173
|
+
throw new Error(`invalid skill name "${name}": use lowercase letters, numbers, and single hyphens`);
|
|
1174
|
+
}
|
|
1175
|
+
const dest = path4.join(destinationRoot(scope, cwd, input.configDir), name);
|
|
1176
|
+
if (existsSync4(dest) && input.force !== true) {
|
|
1177
|
+
throw new Error(`skill already installed at ${dest} (use --force to overwrite)`);
|
|
1178
|
+
}
|
|
1179
|
+
const sourceRaw = readFileSync3(skill.filePath, "utf8");
|
|
1180
|
+
const installedHash = normalizedSkillHash(sourceRaw);
|
|
1181
|
+
const destFile = path4.join(dest, "SKILL.md");
|
|
1182
|
+
swapSkillDir((staging) => {
|
|
1183
|
+
copySkillDir(fetched.skillDir, staging);
|
|
1184
|
+
writeFileSync(
|
|
1185
|
+
path4.join(staging, "SKILL.md"),
|
|
1186
|
+
injectProvenanceFrontmatter(sourceRaw, {
|
|
1187
|
+
sourceUrl: spec.original,
|
|
1188
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1189
|
+
installedHash
|
|
1190
|
+
}),
|
|
1191
|
+
"utf8"
|
|
1192
|
+
);
|
|
1193
|
+
}, dest);
|
|
1194
|
+
const after = loadSkills({ cwd, ...scope === "user" && input.configDir ? { configDir: input.configDir } : {} });
|
|
1195
|
+
const installWarnings = after.diagnostics.filter((diag) => diag.path && path4.resolve(diag.path).startsWith(dest)).map((diag) => diag.message);
|
|
1196
|
+
return {
|
|
1197
|
+
name,
|
|
1198
|
+
scope,
|
|
1199
|
+
path: destFile,
|
|
1200
|
+
sourceUrl: spec.original,
|
|
1201
|
+
installedHash,
|
|
1202
|
+
warnings: [...warnings, ...installWarnings]
|
|
1203
|
+
};
|
|
1204
|
+
} finally {
|
|
1205
|
+
fetched.cleanup();
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
function managedSkills(cwd, configDir) {
|
|
1209
|
+
const list = loadSkills({ cwd, ...configDir ? { configDir } : {} });
|
|
1210
|
+
return list.items.filter((skill) => skill.source === "clio" && (skill.scope === "user" || skill.scope === "project"));
|
|
1211
|
+
}
|
|
1212
|
+
function updateOne(skill, force) {
|
|
1213
|
+
const sourceUrl = skill.provenance?.installUrl;
|
|
1214
|
+
if (!sourceUrl) return { name: skill.name, status: "no-source", detail: "no source-url provenance recorded" };
|
|
1215
|
+
const spec = parseSkillSourceSpec(sourceUrl);
|
|
1216
|
+
if (!spec) return { name: skill.name, status: "error", detail: `unsupported source-url: ${sourceUrl}` };
|
|
1217
|
+
const fetched = fetchSource(spec);
|
|
1218
|
+
try {
|
|
1219
|
+
validateSourceSkill(fetched.skillDir);
|
|
1220
|
+
const remoteFile = path4.join(fetched.skillDir, "SKILL.md");
|
|
1221
|
+
const remoteRaw = readFileSync3(remoteFile, "utf8");
|
|
1222
|
+
const remoteHash = normalizedSkillHash(remoteRaw);
|
|
1223
|
+
const localRaw = readFileSync3(skill.filePath, "utf8");
|
|
1224
|
+
const localHash = normalizedSkillHash(localRaw);
|
|
1225
|
+
const recordedHash = skill.provenance?.installedHash ?? null;
|
|
1226
|
+
if (remoteHash === localHash) return { name: skill.name, status: "up-to-date" };
|
|
1227
|
+
const locallyModified = recordedHash === null || localHash !== recordedHash;
|
|
1228
|
+
if (locallyModified && !force) {
|
|
1229
|
+
return { name: skill.name, status: "local-changes", detail: "skipped, use --force to overwrite" };
|
|
1230
|
+
}
|
|
1231
|
+
swapSkillDir((staging) => {
|
|
1232
|
+
copySkillDir(fetched.skillDir, staging);
|
|
1233
|
+
writeFileSync(
|
|
1234
|
+
path4.join(staging, "SKILL.md"),
|
|
1235
|
+
injectProvenanceFrontmatter(remoteRaw, {
|
|
1236
|
+
sourceUrl,
|
|
1237
|
+
installedAt: skill.provenance?.installedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1238
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1239
|
+
installedHash: remoteHash
|
|
1240
|
+
}),
|
|
1241
|
+
"utf8"
|
|
1242
|
+
);
|
|
1243
|
+
}, skill.baseDir);
|
|
1244
|
+
return { name: skill.name, status: "updated" };
|
|
1245
|
+
} catch (err) {
|
|
1246
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
1247
|
+
return { name: skill.name, status: "error", detail: reason };
|
|
1248
|
+
} finally {
|
|
1249
|
+
fetched.cleanup();
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
function updateSkills(input = {}) {
|
|
1253
|
+
const cwd = input.cwd ?? process.cwd();
|
|
1254
|
+
const skills = managedSkills(cwd, input.configDir);
|
|
1255
|
+
if (input.name) {
|
|
1256
|
+
const skill = skills.find((entry) => entry.name === input.name);
|
|
1257
|
+
if (!skill) return [{ name: input.name, status: "error", detail: "not found in Clio-managed skill roots" }];
|
|
1258
|
+
return [updateOne(skill, input.force === true)];
|
|
1259
|
+
}
|
|
1260
|
+
if (input.all !== true) throw new Error("updateSkills requires a name or all=true");
|
|
1261
|
+
return skills.filter((skill) => skill.provenance?.installUrl).map((skill) => updateOne(skill, input.force === true));
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
export {
|
|
1265
|
+
defaultScopedResourceRoots,
|
|
1266
|
+
sourceInfoForRoot,
|
|
1267
|
+
readRootEntries,
|
|
1268
|
+
splitYamlFrontmatter,
|
|
1269
|
+
stringField,
|
|
1270
|
+
updateSkills,
|
|
1271
|
+
MARKETPLACE_UNCONFIGURED,
|
|
1272
|
+
discoverMarketplaceSkills,
|
|
1273
|
+
getMarketplaceSkills,
|
|
1274
|
+
installSkill,
|
|
1275
|
+
loadSkills,
|
|
1276
|
+
modelVisibleSkills,
|
|
1277
|
+
parseSkillCommand,
|
|
1278
|
+
expandSkillInvocationInput,
|
|
1279
|
+
parsePendingSkillRequests
|
|
1280
|
+
};
|
|
1281
|
+
//# sourceMappingURL=chunk-26BZQOAD.js.map
|