@hasna/skills 0.10.56 → 0.10.58
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/bin/index.js +697 -109
- package/bin/mcp.js +3 -2
- package/bin/migrate.js +1 -1
- package/bin/server.js +1 -1
- package/bin/worker.js +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +89 -3
- package/dist/lib/agent-integration.d.ts +2 -0
- package/dist/lib/claude-prospective-review.d.ts +140 -0
- package/dist/sdk/index.js +4 -3
- package/docs/plugin-admission.md +59 -0
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -36888,7 +36888,7 @@ var package_default;
|
|
|
36888
36888
|
var init_package = __esm(() => {
|
|
36889
36889
|
package_default = {
|
|
36890
36890
|
name: "@hasna/skills",
|
|
36891
|
-
version: "0.10.
|
|
36891
|
+
version: "0.10.58",
|
|
36892
36892
|
description: "Skills library for AI coding agents",
|
|
36893
36893
|
type: "module",
|
|
36894
36894
|
bin: {
|
|
@@ -40292,7 +40292,8 @@ var init_codex_native_compatibility = __esm(() => {
|
|
|
40292
40292
|
"codex-cli 0.159.2": ["hooks", "qualified-skill-catalog"],
|
|
40293
40293
|
"codex-cli 0.160.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"],
|
|
40294
40294
|
"codex-cli 0.160.1": ["hooks", "qualified-skill-catalog", "installed-plugin-review"],
|
|
40295
|
-
"codex-cli 0.161.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"]
|
|
40295
|
+
"codex-cli 0.161.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"],
|
|
40296
|
+
"codex-cli 0.162.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"]
|
|
40296
40297
|
};
|
|
40297
40298
|
SUPPORTED_CODEX_HOOK_VERSIONS = Object.freeze(Object.keys(MEASURED_CODEX_CAPABILITIES).filter((version) => supportsCodexNativeCapability(version, "hooks")));
|
|
40298
40299
|
});
|
|
@@ -51944,7 +51945,7 @@ async function connectCodexHookRpc(options) {
|
|
|
51944
51945
|
const deadline = setTimeout(() => {
|
|
51945
51946
|
clearTimeout(terminate);
|
|
51946
51947
|
reject2(new Error("CODEX_HOOK_TRUST_NATIVE_CLOSE_UNCONFIRMED"));
|
|
51947
|
-
},
|
|
51948
|
+
}, 1e4);
|
|
51948
51949
|
actualClose.then(() => {
|
|
51949
51950
|
clearTimeout(terminate);
|
|
51950
51951
|
clearTimeout(deadline);
|
|
@@ -116617,14 +116618,584 @@ var init_hook_diagnostics = __esm(() => {
|
|
|
116617
116618
|
]);
|
|
116618
116619
|
});
|
|
116619
116620
|
|
|
116621
|
+
// src/lib/claude-prospective-review.ts
|
|
116622
|
+
import { createHash as createHash41 } from "crypto";
|
|
116623
|
+
import { lstatSync as lstatSync37, readFileSync as readFileSync45, readdirSync as readdirSync24, realpathSync as realpathSync21 } from "fs";
|
|
116624
|
+
import { basename as basename18, dirname as dirname38, isAbsolute as isAbsolute34, join as join68, relative as relative12, resolve as resolve33, sep as sep15 } from "path";
|
|
116625
|
+
import { isDeepStrictEqual as isDeepStrictEqual18 } from "util";
|
|
116626
|
+
function refuse6(reason2) {
|
|
116627
|
+
throw new Error(`CLAUDE_CANDIDATE_REVIEW_REFUSED: ${reason2}`);
|
|
116628
|
+
}
|
|
116629
|
+
function preserveOrGenerateTimestamp(previous, field3) {
|
|
116630
|
+
return { $allowed: [previous, { $generated: "claude-utc-timestamp", field: field3 }] };
|
|
116631
|
+
}
|
|
116632
|
+
function object11(value) {
|
|
116633
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
116634
|
+
}
|
|
116635
|
+
function hasOwn2(value, key) {
|
|
116636
|
+
return object11(value) && Object.hasOwn(value, key);
|
|
116637
|
+
}
|
|
116638
|
+
function assertJsonValue(value, name, depth = 0) {
|
|
116639
|
+
if (depth > 32)
|
|
116640
|
+
refuse6(`${name} exceeds the JSON nesting bound`);
|
|
116641
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
116642
|
+
return;
|
|
116643
|
+
if (typeof value === "number") {
|
|
116644
|
+
if (!Number.isFinite(value))
|
|
116645
|
+
refuse6(`${name} contains a non-finite number`);
|
|
116646
|
+
return;
|
|
116647
|
+
}
|
|
116648
|
+
if (Array.isArray(value)) {
|
|
116649
|
+
for (const item of value)
|
|
116650
|
+
assertJsonValue(item, name, depth + 1);
|
|
116651
|
+
return;
|
|
116652
|
+
}
|
|
116653
|
+
if (object11(value)) {
|
|
116654
|
+
for (const item of Object.values(value))
|
|
116655
|
+
assertJsonValue(item, name, depth + 1);
|
|
116656
|
+
return;
|
|
116657
|
+
}
|
|
116658
|
+
refuse6(`${name} contains a non-JSON value`);
|
|
116659
|
+
}
|
|
116660
|
+
function canonicalJson3(value) {
|
|
116661
|
+
if (Array.isArray(value))
|
|
116662
|
+
return `[${value.map(canonicalJson3).join(",")}]`;
|
|
116663
|
+
if (object11(value))
|
|
116664
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson3(value[key])}`).join(",")}}`;
|
|
116665
|
+
return JSON.stringify(value);
|
|
116666
|
+
}
|
|
116667
|
+
function keys3(value, expected, name) {
|
|
116668
|
+
if (!object11(value) || Object.keys(value).sort().join("\x00") !== [...expected].sort().join("\x00"))
|
|
116669
|
+
refuse6(`${name} has an unsupported shape`);
|
|
116670
|
+
}
|
|
116671
|
+
function abs(path, name) {
|
|
116672
|
+
if (typeof path !== "string" || !path || path.length > 4096 || /[\x00-\x1f\x7f]/.test(path) || !isAbsolute34(path) || resolve33(path) !== path)
|
|
116673
|
+
refuse6(`${name} must be a normalized absolute path`);
|
|
116674
|
+
}
|
|
116675
|
+
function validUtcTimestamp(value) {
|
|
116676
|
+
return typeof value === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value) && Number.isFinite(Date.parse(value)) && new Date(Date.parse(value)).toISOString() === value;
|
|
116677
|
+
}
|
|
116678
|
+
function safeFile(path, name) {
|
|
116679
|
+
abs(path, name);
|
|
116680
|
+
for (let at2 = dirname38(path);; at2 = dirname38(at2)) {
|
|
116681
|
+
const stat4 = lstatSync37(at2, { throwIfNoEntry: false });
|
|
116682
|
+
if (!stat4?.isDirectory() || stat4.isSymbolicLink())
|
|
116683
|
+
refuse6(`${name} traverses a symlink or non-directory`);
|
|
116684
|
+
if (at2 === dirname38(at2))
|
|
116685
|
+
break;
|
|
116686
|
+
}
|
|
116687
|
+
const stat3 = lstatSync37(path, { throwIfNoEntry: false });
|
|
116688
|
+
if (!stat3?.isFile() || stat3.isSymbolicLink() || stat3.size > MAX_CANDIDATE_BYTES)
|
|
116689
|
+
refuse6(`${name} is not a bounded regular file`);
|
|
116690
|
+
return readFileSync45(path);
|
|
116691
|
+
}
|
|
116692
|
+
function canonicalDir(path, name) {
|
|
116693
|
+
abs(path, name);
|
|
116694
|
+
if (realpathSync21(path) !== path || !lstatSync37(path).isDirectory())
|
|
116695
|
+
refuse6(`${name} is not a canonical directory`);
|
|
116696
|
+
for (let at2 = path;; at2 = dirname38(at2)) {
|
|
116697
|
+
const stat3 = lstatSync37(at2);
|
|
116698
|
+
if (!stat3.isDirectory() || stat3.isSymbolicLink())
|
|
116699
|
+
refuse6(`${name} traverses a symlink`);
|
|
116700
|
+
if (at2 === dirname38(at2))
|
|
116701
|
+
break;
|
|
116702
|
+
}
|
|
116703
|
+
}
|
|
116704
|
+
function captureClaudeProspectiveCandidateClosure(root2) {
|
|
116705
|
+
canonicalDir(root2, "candidate root");
|
|
116706
|
+
const entries = [];
|
|
116707
|
+
let bytes = 0;
|
|
116708
|
+
const walk2 = (directory2) => {
|
|
116709
|
+
const beforeDirectory = lstatSync37(directory2);
|
|
116710
|
+
const names = readdirSync24(directory2).sort();
|
|
116711
|
+
if (new Set(names).size !== names.length)
|
|
116712
|
+
refuse6("candidate contains ambiguous directory entries");
|
|
116713
|
+
for (const name of names) {
|
|
116714
|
+
if (!name || name === "." || name === ".." || name.includes("/") || name.includes("\\") || /[\x00-\x1f\x7f]/.test(name))
|
|
116715
|
+
refuse6("candidate contains an unsupported path member");
|
|
116716
|
+
const path = join68(directory2, name), rel = relative12(root2, path).split(sep15).join("/");
|
|
116717
|
+
if (entries.length >= MAX_CANDIDATE_FILES)
|
|
116718
|
+
refuse6("candidate closure exceeds its member limit");
|
|
116719
|
+
const stat3 = lstatSync37(path);
|
|
116720
|
+
if (stat3.isSymbolicLink())
|
|
116721
|
+
refuse6("candidate closure contains a symlink");
|
|
116722
|
+
if (stat3.isDirectory()) {
|
|
116723
|
+
entries.push([rel, "directory", null]);
|
|
116724
|
+
walk2(path);
|
|
116725
|
+
} else if (stat3.isFile()) {
|
|
116726
|
+
bytes += stat3.size;
|
|
116727
|
+
if (bytes > MAX_CANDIDATE_BYTES)
|
|
116728
|
+
refuse6("candidate closure exceeds its byte limit");
|
|
116729
|
+
const content = safeFile(path, "candidate member"), after2 = lstatSync37(path);
|
|
116730
|
+
if (!after2.isFile() || after2.dev !== stat3.dev || after2.ino !== stat3.ino || after2.size !== stat3.size || after2.mtimeMs !== stat3.mtimeMs || after2.ctimeMs !== stat3.ctimeMs)
|
|
116731
|
+
refuse6("candidate member changed while being read");
|
|
116732
|
+
entries.push([rel, "file", sha7(content)]);
|
|
116733
|
+
} else
|
|
116734
|
+
refuse6("candidate closure contains a special file");
|
|
116735
|
+
}
|
|
116736
|
+
const afterDirectory = lstatSync37(directory2);
|
|
116737
|
+
if (!afterDirectory.isDirectory() || afterDirectory.dev !== beforeDirectory.dev || afterDirectory.ino !== beforeDirectory.ino || afterDirectory.mtimeMs !== beforeDirectory.mtimeMs || afterDirectory.ctimeMs !== beforeDirectory.ctimeMs)
|
|
116738
|
+
refuse6("candidate directory changed while being read");
|
|
116739
|
+
};
|
|
116740
|
+
walk2(root2);
|
|
116741
|
+
entries.sort((a, b) => a[0].localeCompare(b[0]));
|
|
116742
|
+
return sha7(JSON.stringify(entries));
|
|
116743
|
+
}
|
|
116744
|
+
function readWitness(path, expected, name) {
|
|
116745
|
+
abs(path, `${name} path`);
|
|
116746
|
+
const stat3 = lstatSync37(path, { throwIfNoEntry: false });
|
|
116747
|
+
if (expected === null) {
|
|
116748
|
+
if (stat3)
|
|
116749
|
+
refuse6(`${name} was absent at review but is now present`);
|
|
116750
|
+
return;
|
|
116751
|
+
}
|
|
116752
|
+
if (!SHA256.test(expected))
|
|
116753
|
+
refuse6(`${name} has an invalid SHA-256`);
|
|
116754
|
+
if (!stat3?.isFile() || stat3.isSymbolicLink() || sha7(safeFile(path, name)) !== expected)
|
|
116755
|
+
refuse6(`${name} preimage changed`);
|
|
116756
|
+
}
|
|
116757
|
+
function jsonFile(path, name) {
|
|
116758
|
+
const stat3 = lstatSync37(path, { throwIfNoEntry: false });
|
|
116759
|
+
if (!stat3)
|
|
116760
|
+
return null;
|
|
116761
|
+
let value;
|
|
116762
|
+
try {
|
|
116763
|
+
value = JSON.parse(safeFile(path, name).toString("utf8"));
|
|
116764
|
+
} catch {
|
|
116765
|
+
refuse6(`${name} is not valid JSON`);
|
|
116766
|
+
}
|
|
116767
|
+
if (!object11(value))
|
|
116768
|
+
refuse6(`${name} must be a JSON object`);
|
|
116769
|
+
return value;
|
|
116770
|
+
}
|
|
116771
|
+
function assertNoUnprojectedSkillPayload(root2, catalogPath, marketplace, plugin, manifest) {
|
|
116772
|
+
if (manifest.skills !== undefined || manifest.commands !== undefined)
|
|
116773
|
+
refuse6("candidate plugin declares native skill or command payloads; project those through the Skills plugin admission flow first");
|
|
116774
|
+
let catalog;
|
|
116775
|
+
try {
|
|
116776
|
+
catalog = JSON.parse(safeFile(catalogPath, "candidate catalog").toString("utf8"));
|
|
116777
|
+
} catch {
|
|
116778
|
+
refuse6("candidate catalog is not valid JSON");
|
|
116779
|
+
}
|
|
116780
|
+
if (!object11(catalog) || catalog.name !== marketplace || !Array.isArray(catalog.plugins))
|
|
116781
|
+
refuse6("candidate catalog identity is invalid");
|
|
116782
|
+
const entries = catalog.plugins.filter((entry) => object11(entry) && entry.name === plugin);
|
|
116783
|
+
if (entries.length !== 1 || !object11(entries[0]))
|
|
116784
|
+
refuse6("candidate catalog plugin entry is missing or ambiguous");
|
|
116785
|
+
if (entries[0].skills !== undefined || entries[0].commands !== undefined)
|
|
116786
|
+
refuse6("candidate marketplace entry declares native skill or command payloads");
|
|
116787
|
+
const walk2 = (directory2) => {
|
|
116788
|
+
for (const name of readdirSync24(directory2).sort()) {
|
|
116789
|
+
const path = join68(directory2, name), rel = relative12(root2, path).split(sep15).join("/");
|
|
116790
|
+
const stat3 = lstatSync37(path);
|
|
116791
|
+
if (stat3.isDirectory())
|
|
116792
|
+
walk2(path);
|
|
116793
|
+
else if (stat3.isFile()) {
|
|
116794
|
+
const parts = rel.toLowerCase().split("/");
|
|
116795
|
+
if (parts.includes("skills") || parts.includes("commands") || name.toLowerCase() === "skill.md")
|
|
116796
|
+
refuse6("candidate closure contains native skill or command payload paths");
|
|
116797
|
+
if (parts.includes("agents") && /\.md$/i.test(name)) {
|
|
116798
|
+
let text4;
|
|
116799
|
+
try {
|
|
116800
|
+
text4 = new TextDecoder("utf-8", { fatal: true }).decode(safeFile(path, "candidate agent file"));
|
|
116801
|
+
} catch {
|
|
116802
|
+
refuse6("candidate agent file is not strict UTF-8");
|
|
116803
|
+
}
|
|
116804
|
+
if (/^skills\s*:/m.test(text4))
|
|
116805
|
+
refuse6("candidate agent preloads native skills; project it through the Skills plugin admission flow first");
|
|
116806
|
+
}
|
|
116807
|
+
}
|
|
116808
|
+
}
|
|
116809
|
+
};
|
|
116810
|
+
walk2(root2);
|
|
116811
|
+
}
|
|
116812
|
+
function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
|
|
116813
|
+
if (!SHA256.test(requestFileSha256))
|
|
116814
|
+
refuse6("request file SHA-256 is invalid");
|
|
116815
|
+
keys3(request2, ["schema", "scope", "native", "skills", "candidate", "preimages", "delta", "review"], "request");
|
|
116816
|
+
if (request2.schema !== CLAUDE_PROSPECTIVE_REVIEW_SCHEMA)
|
|
116817
|
+
refuse6("unsupported schema");
|
|
116818
|
+
keys3(request2.scope, ["kind", "projectPath"], "scope");
|
|
116819
|
+
if (request2.scope.kind !== "user" && request2.scope.kind !== "project")
|
|
116820
|
+
refuse6("scope must be user or project");
|
|
116821
|
+
abs(request2.scope.projectPath, "project path");
|
|
116822
|
+
canonicalDir(request2.scope.projectPath, "project path");
|
|
116823
|
+
keys3(request2.native, ["home", "executable", "configRoot", "pluginRoot"], "native identity");
|
|
116824
|
+
canonicalDir(request2.native.home, "Claude home");
|
|
116825
|
+
abs(request2.native.configRoot, "Claude config root");
|
|
116826
|
+
keys3(request2.native.executable, ["path", "target", "version", "sha256"], "Claude executable identity");
|
|
116827
|
+
abs(request2.native.executable.path, "Claude executable path");
|
|
116828
|
+
abs(request2.native.executable.target, "Claude executable target");
|
|
116829
|
+
if (typeof request2.native.executable.version !== "string" || !request2.native.executable.version || request2.native.executable.version.length > 256 || /[\x00-\x1f\x7f]/.test(request2.native.executable.version) || !SHA256.test(request2.native.executable.sha256) || realpathSync21(request2.native.executable.path) !== request2.native.executable.target || hashExecutableFile(request2.native.executable.target) !== request2.native.executable.sha256)
|
|
116830
|
+
refuse6("Claude executable identity changed");
|
|
116831
|
+
keys3(request2.skills, ["dataDir", "policySha256", "discoverySha256", "discoveryInputs"], "Skills preimage");
|
|
116832
|
+
abs(request2.skills.dataDir, "Skills data directory");
|
|
116833
|
+
if (!SHA256.test(request2.skills.policySha256) || !SHA256.test(request2.skills.discoverySha256))
|
|
116834
|
+
refuse6("invalid Skills policy or discovery SHA-256");
|
|
116835
|
+
const snapshot2 = readManagedSkillPolicySnapshot(request2.skills.dataDir);
|
|
116836
|
+
if (!snapshot2 || sha7(snapshot2.text) !== request2.skills.policySha256)
|
|
116837
|
+
refuse6("Skills policy preimage changed or is absent");
|
|
116838
|
+
const aliases = snapshot2.value.bridge?.rootAliases ?? [];
|
|
116839
|
+
if (!Array.isArray(aliases))
|
|
116840
|
+
refuse6("Skills policy root aliases are invalid");
|
|
116841
|
+
try {
|
|
116842
|
+
recheckRootAliases(aliases);
|
|
116843
|
+
} catch {
|
|
116844
|
+
refuse6("Skills policy root alias changed or is invalid");
|
|
116845
|
+
}
|
|
116846
|
+
const canonical8 = (path) => canonicalAgentPath(path, aliases);
|
|
116847
|
+
const configRoot = canonical8(join68(request2.native.home, ".claude"));
|
|
116848
|
+
canonicalDir(configRoot, "Claude config root");
|
|
116849
|
+
if (request2.native.configRoot !== configRoot)
|
|
116850
|
+
refuse6("Claude config root differs from the current resolver and reviewed root alias");
|
|
116851
|
+
const pluginRoot = join68(configRoot, "plugins");
|
|
116852
|
+
if (process.env.CLAUDE_CODE_PLUGIN_CACHE_DIR)
|
|
116853
|
+
refuse6("CLAUDE_CODE_PLUGIN_CACHE_DIR overrides are unsupported by this prospective schema; bind and review the actual cache root first");
|
|
116854
|
+
if (request2.native.pluginRoot !== pluginRoot)
|
|
116855
|
+
refuse6("Claude plugin root differs from the supported standard plugins directory; Cowork and custom cache roots require a separately bound schema");
|
|
116856
|
+
const pluginRootStat = lstatSync37(pluginRoot, { throwIfNoEntry: false });
|
|
116857
|
+
if (pluginRootStat && (!pluginRootStat.isDirectory() || pluginRootStat.isSymbolicLink()))
|
|
116858
|
+
refuse6("Claude plugin root is not a regular directory");
|
|
116859
|
+
keys3(request2.candidate, ["marketplace", "root", "catalogPath", "catalogSha256", "closureSha256", "plugins", ...hasOwn2(request2.candidate, "releaseManifest") ? ["releaseManifest"] : [], ...hasOwn2(request2.candidate, "functionHooksEnv") ? ["functionHooksEnv"] : []], "candidate");
|
|
116860
|
+
const candidate2 = request2.candidate;
|
|
116861
|
+
canonicalDir(candidate2.root, "candidate root");
|
|
116862
|
+
if (typeof candidate2.marketplace !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(candidate2.marketplace))
|
|
116863
|
+
refuse6("invalid candidate marketplace");
|
|
116864
|
+
if (!Array.isArray(candidate2.plugins) || candidate2.plugins.length < 1 || candidate2.plugins.length > 32)
|
|
116865
|
+
refuse6("candidate must select between one and 32 plugins from one marketplace");
|
|
116866
|
+
if (candidate2.functionHooksEnv !== undefined && candidate2.functionHooksEnv !== "1")
|
|
116867
|
+
refuse6("function-hook environment mutation only supports the explicit value 1");
|
|
116868
|
+
abs(candidate2.catalogPath, "catalog path");
|
|
116869
|
+
if (!candidate2.catalogPath.startsWith(candidate2.root + sep15))
|
|
116870
|
+
refuse6("catalog must be inside the frozen candidate root");
|
|
116871
|
+
if (candidate2.catalogPath !== join68(candidate2.root, ".claude-plugin", "marketplace.json"))
|
|
116872
|
+
refuse6("marketplace catalog must be at the frozen root's .claude-plugin/marketplace.json path");
|
|
116873
|
+
if (!SHA256.test(candidate2.catalogSha256) || !SHA256.test(candidate2.closureSha256))
|
|
116874
|
+
refuse6("candidate digests must be lowercase SHA-256");
|
|
116875
|
+
if (captureClaudeProspectiveCandidateClosure(candidate2.root) !== candidate2.closureSha256)
|
|
116876
|
+
refuse6("complete candidate closure changed");
|
|
116877
|
+
if (sha7(safeFile(candidate2.catalogPath, "candidate catalog")) !== candidate2.catalogSha256)
|
|
116878
|
+
refuse6("candidate catalog changed");
|
|
116879
|
+
let candidateCatalog;
|
|
116880
|
+
try {
|
|
116881
|
+
candidateCatalog = JSON.parse(safeFile(candidate2.catalogPath, "candidate catalog").toString("utf8"));
|
|
116882
|
+
} catch {
|
|
116883
|
+
refuse6("candidate catalog is not valid JSON");
|
|
116884
|
+
}
|
|
116885
|
+
if (!object11(candidateCatalog) || candidateCatalog.name !== candidate2.marketplace || !Array.isArray(candidateCatalog.plugins))
|
|
116886
|
+
refuse6("candidate catalog identity is invalid");
|
|
116887
|
+
const catalogMetadata = candidateCatalog.metadata;
|
|
116888
|
+
if (catalogMetadata !== undefined && !object11(catalogMetadata))
|
|
116889
|
+
refuse6("candidate catalog metadata has an unsupported shape");
|
|
116890
|
+
if (catalogMetadata?.pluginRoot !== undefined && catalogMetadata.pluginRoot !== "." && catalogMetadata.pluginRoot !== "./")
|
|
116891
|
+
refuse6("nontrivial marketplace metadata.pluginRoot is unsupported by this local-closure schema");
|
|
116892
|
+
for (const [name, dependencies] of [["catalog", candidateCatalog.dependencies], ["catalog metadata", catalogMetadata?.dependencies]]) {
|
|
116893
|
+
if (dependencies !== undefined && (!Array.isArray(dependencies) || dependencies.length !== 0))
|
|
116894
|
+
refuse6(`${name} declares unsupported native dependencies`);
|
|
116895
|
+
}
|
|
116896
|
+
const selectedPlugins = [];
|
|
116897
|
+
const pluginNames = new Set, sanitizedCachePaths = new Set;
|
|
116898
|
+
for (const selected of candidate2.plugins) {
|
|
116899
|
+
keys3(selected, ["plugin", "manifestPath", "manifestSha256", "optionsPatch"], "selected plugin");
|
|
116900
|
+
if (typeof selected.plugin !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(selected.plugin))
|
|
116901
|
+
refuse6("invalid selected plugin name");
|
|
116902
|
+
if (pluginNames.has(selected.plugin))
|
|
116903
|
+
refuse6("candidate repeats a selected plugin");
|
|
116904
|
+
pluginNames.add(selected.plugin);
|
|
116905
|
+
if (!object11(selected.optionsPatch))
|
|
116906
|
+
refuse6("selected plugin optionsPatch must be a JSON object");
|
|
116907
|
+
assertJsonValue(selected.optionsPatch, "selected plugin optionsPatch");
|
|
116908
|
+
if (selected.optionsPatch.enabled !== true)
|
|
116909
|
+
refuse6("selected plugin optionsPatch must explicitly declare enabled: true");
|
|
116910
|
+
if (Buffer.byteLength(canonicalJson3(selected.optionsPatch), "utf8") > 16 * 1024)
|
|
116911
|
+
refuse6("selected plugin optionsPatch exceeds its byte bound");
|
|
116912
|
+
abs(selected.manifestPath, "manifest path");
|
|
116913
|
+
if (!selected.manifestPath.startsWith(candidate2.root + sep15) || basename18(dirname38(selected.manifestPath)) !== ".claude-plugin" || selected.manifestPath !== join68(dirname38(dirname38(selected.manifestPath)), ".claude-plugin", "plugin.json"))
|
|
116914
|
+
refuse6("plugin manifest must be under a plugin root inside the frozen marketplace closure");
|
|
116915
|
+
const pluginRoot2 = dirname38(dirname38(selected.manifestPath));
|
|
116916
|
+
const relativePluginRoot = relative12(candidate2.root, pluginRoot2);
|
|
116917
|
+
if (!relativePluginRoot || relativePluginRoot === "." || relativePluginRoot === ".." || relativePluginRoot.startsWith(`..${sep15}`) || isAbsolute34(relativePluginRoot))
|
|
116918
|
+
refuse6("plugin root must be a child of the frozen marketplace root");
|
|
116919
|
+
canonicalDir(pluginRoot2, "candidate plugin root");
|
|
116920
|
+
if (!SHA256.test(selected.manifestSha256))
|
|
116921
|
+
refuse6("selected plugin manifest digest must be lowercase SHA-256");
|
|
116922
|
+
if (hashClaudePluginManifest(safeFile(selected.manifestPath, "candidate plugin manifest").toString("utf8")) !== selected.manifestSha256)
|
|
116923
|
+
refuse6("candidate plugin manifest changed or is invalid");
|
|
116924
|
+
const manifest = JSON.parse(safeFile(selected.manifestPath, "candidate plugin manifest").toString("utf8"));
|
|
116925
|
+
if (!object11(manifest) || manifest.name !== selected.plugin)
|
|
116926
|
+
refuse6("candidate plugin manifest identity differs from the selected entry");
|
|
116927
|
+
const entries = candidateCatalog.plugins.filter((entry2) => object11(entry2) && entry2.name === selected.plugin);
|
|
116928
|
+
if (entries.length !== 1 || !object11(entries[0]))
|
|
116929
|
+
refuse6("candidate marketplace plugin entry is missing or ambiguous");
|
|
116930
|
+
const selectedEntry = entries[0];
|
|
116931
|
+
if (selectedEntry.skills !== undefined || selectedEntry.commands !== undefined)
|
|
116932
|
+
refuse6("candidate marketplace entry declares native skill or command payloads");
|
|
116933
|
+
if (selectedEntry.dependencies !== undefined && (!Array.isArray(selectedEntry.dependencies) || selectedEntry.dependencies.length !== 0))
|
|
116934
|
+
refuse6("selected marketplace plugin declares unsupported native dependencies");
|
|
116935
|
+
if (manifest.skills !== undefined || manifest.commands !== undefined)
|
|
116936
|
+
refuse6("candidate plugin declares native skill or command payloads; project those through the Skills plugin admission flow first");
|
|
116937
|
+
const manifestVersion = manifest.version;
|
|
116938
|
+
if (typeof manifestVersion !== "string" || !manifestVersion || typeof selectedEntry.version !== "string" || selectedEntry.version !== manifestVersion)
|
|
116939
|
+
refuse6("candidate manifest version differs from the selected marketplace entry version");
|
|
116940
|
+
const sanitizeComponent = (value) => value.replace(/[^A-Za-z0-9_-]/g, "-");
|
|
116941
|
+
const sanitizedVersion = manifestVersion === "." || manifestVersion === ".." ? "-" : manifestVersion.replace(/[^A-Za-z0-9._-]/g, "-");
|
|
116942
|
+
if (manifestVersion.length > 256 || /[\x00-\x1f\x7f/\\]/.test(manifestVersion) || !sanitizedVersion)
|
|
116943
|
+
refuse6("candidate version is not a supported native cache component");
|
|
116944
|
+
const sanitizedCachePath = [sanitizeComponent(candidate2.marketplace), sanitizeComponent(selected.plugin), sanitizedVersion].join("/");
|
|
116945
|
+
if (sanitizedCachePaths.has(sanitizedCachePath))
|
|
116946
|
+
refuse6("selected plugins collide after native cache path sanitization");
|
|
116947
|
+
sanitizedCachePaths.add(sanitizedCachePath);
|
|
116948
|
+
const sourcePath = selectedEntry.source;
|
|
116949
|
+
const sourceRelativePath = relative12(candidate2.root, pluginRoot2).split(sep15).join("/");
|
|
116950
|
+
const expectedSourcePath = sourceRelativePath === "." ? "./" : `./${sourceRelativePath}`;
|
|
116951
|
+
if (typeof sourcePath !== "string" || isAbsolute34(sourcePath) || sourcePath !== expectedSourcePath || resolve33(dirname38(dirname38(candidate2.catalogPath)), sourcePath) !== pluginRoot2)
|
|
116952
|
+
refuse6("marketplace source does not resolve to the frozen plugin closure; external source mapping is not supported by this schema");
|
|
116953
|
+
const entry = captureClaudeMarketplaceEntry(candidate2.catalogPath, candidate2.marketplace, selected.plugin);
|
|
116954
|
+
if (entry.sha256.length !== 64)
|
|
116955
|
+
refuse6("candidate marketplace entry is invalid");
|
|
116956
|
+
assertNoUnprojectedSkillPayload(candidate2.root, candidate2.catalogPath, candidate2.marketplace, selected.plugin, manifest);
|
|
116957
|
+
selectedPlugins.push({ plugin: selected.plugin, pluginId: `${selected.plugin}@${candidate2.marketplace}`, manifestSha256: selected.manifestSha256, optionsPatch: selected.optionsPatch, pluginRoot: pluginRoot2, manifest, selectedEntry });
|
|
116958
|
+
}
|
|
116959
|
+
if (candidate2.releaseManifest !== undefined) {
|
|
116960
|
+
keys3(candidate2.releaseManifest, ["path", "sha256"], "release manifest witness");
|
|
116961
|
+
abs(candidate2.releaseManifest.path, "release manifest path");
|
|
116962
|
+
if (!SHA256.test(candidate2.releaseManifest.sha256) || sha7(safeFile(candidate2.releaseManifest.path, "release manifest")) !== candidate2.releaseManifest.sha256)
|
|
116963
|
+
refuse6("complete release manifest bytes changed or lack a valid digest");
|
|
116964
|
+
}
|
|
116965
|
+
if (!request2.preimages || !object11(request2.preimages))
|
|
116966
|
+
refuse6("current Claude preimages are required");
|
|
116967
|
+
keys3(request2.preimages, ["settings", "userSettings", "projectSettings", "projectSettingsLocal", "marketplaces", "installedPlugins"], "current Claude preimages");
|
|
116968
|
+
for (const name of ["settings", "projectSettings", "projectSettingsLocal", "marketplaces", "installedPlugins"])
|
|
116969
|
+
keys3(request2.preimages[name], ["path", "sha256"], `${name} preimage`);
|
|
116970
|
+
const expectedPaths = {
|
|
116971
|
+
settings: request2.scope.kind === "project" ? canonical8(join68(request2.scope.projectPath, ".claude/settings.json")) : canonical8(join68(request2.native.home, ".claude/settings.json")),
|
|
116972
|
+
userSettings: canonical8(join68(request2.native.home, ".claude/settings.json")),
|
|
116973
|
+
projectSettings: canonical8(join68(request2.scope.projectPath, ".claude/settings.json")),
|
|
116974
|
+
projectSettingsLocal: canonical8(join68(request2.scope.projectPath, ".claude/settings.local.json")),
|
|
116975
|
+
marketplaces: canonical8(join68(request2.native.home, ".claude/plugins/known_marketplaces.json")),
|
|
116976
|
+
installedPlugins: canonical8(join68(request2.native.home, ".claude/plugins/installed_plugins.json"))
|
|
116977
|
+
};
|
|
116978
|
+
for (const name of Object.keys(expectedPaths)) {
|
|
116979
|
+
const witness = request2.preimages[name];
|
|
116980
|
+
if (witness.path !== expectedPaths[name])
|
|
116981
|
+
refuse6(`${name} preimage path is not the selected Claude path`);
|
|
116982
|
+
readWitness(witness.path, witness.sha256, name);
|
|
116983
|
+
}
|
|
116984
|
+
if (!request2.review || request2.review.pluginHooks !== "reviewed-no-skill-injection")
|
|
116985
|
+
refuse6("an explicit no-skill-injection review is required");
|
|
116986
|
+
if (request2.skills.discoveryInputs?.version !== 1 || !Array.isArray(request2.skills.discoveryInputs.agents) || request2.skills.discoveryInputs.agents.length !== 1 || request2.skills.discoveryInputs.agents[0]?.agent !== "claude" || request2.skills.discoveryInputs.agents[0]?.pluginHooks !== "reviewed-no-skill-injection")
|
|
116987
|
+
refuse6("one explicit reviewed Claude discovery binding is required");
|
|
116988
|
+
const reviewedPaths = new Set(request2.skills.discoveryInputs.agents[0].sources.map((source) => source.path));
|
|
116989
|
+
for (const path of new Set(Object.values(expectedPaths)))
|
|
116990
|
+
if (!reviewedPaths.has(path))
|
|
116991
|
+
refuse6("Skills discovery review does not bind every current user/project Claude settings and registration source");
|
|
116992
|
+
const binding = resolveAgentDiscovery({ home: request2.native.home, agent: "claude", canonical: canonical8, reviewed: request2.skills.discoveryInputs });
|
|
116993
|
+
verifyAgentDiscovery(binding);
|
|
116994
|
+
if (sha7(JSON.stringify(binding)) !== request2.skills.discoverySha256)
|
|
116995
|
+
refuse6("Skills discovery preimage differs from its reviewed binding");
|
|
116996
|
+
assertProjectDiscovery("claude", [request2.scope.projectPath], request2.native.home, canonical8, binding);
|
|
116997
|
+
const stored = snapshot2.value.bridge?.discovery?.claude;
|
|
116998
|
+
if (!stored || !isDeepStrictEqual18(stored, binding) || snapshot2.value.bridge?.home !== request2.native.home)
|
|
116999
|
+
refuse6("Skills policy does not bind the reviewed current Claude discovery");
|
|
117000
|
+
const delta = request2.delta;
|
|
117001
|
+
keys3(delta, ["settingsAfter", "knownMarketplacesAfter", "installedPluginsAfter"], "complete semantic delta");
|
|
117002
|
+
for (const [key, value] of Object.entries(delta))
|
|
117003
|
+
if (!object11(value))
|
|
117004
|
+
refuse6(`${key} must be a complete JSON object`);
|
|
117005
|
+
const settings = jsonFile(expectedPaths.settings, "Claude settings");
|
|
117006
|
+
if (settings && settings.enabledPlugins !== undefined && !object11(settings.enabledPlugins))
|
|
117007
|
+
refuse6("current settings enabledPlugins is not an object");
|
|
117008
|
+
if (settings?.pluginConfigs !== undefined && !object11(settings.pluginConfigs))
|
|
117009
|
+
refuse6("current settings pluginConfigs is not an object");
|
|
117010
|
+
const marketplaces = jsonFile(expectedPaths.marketplaces, "Claude known marketplaces");
|
|
117011
|
+
const registrations = jsonFile(expectedPaths.installedPlugins, "Claude installed plugins");
|
|
117012
|
+
if (registrations && (registrations.version !== 2 || !object11(registrations.plugins)))
|
|
117013
|
+
refuse6("current installed plugin registry has an unsupported shape");
|
|
117014
|
+
const settingsAfter = settings ? structuredClone(settings) : {};
|
|
117015
|
+
if (settingsAfter.enabledPlugins === undefined)
|
|
117016
|
+
settingsAfter.enabledPlugins = {};
|
|
117017
|
+
if (!object11(settingsAfter.enabledPlugins))
|
|
117018
|
+
refuse6("target settings enabledPlugins is not an object");
|
|
117019
|
+
if (settingsAfter.pluginConfigs === undefined)
|
|
117020
|
+
settingsAfter.pluginConfigs = {};
|
|
117021
|
+
if (!object11(settingsAfter.pluginConfigs))
|
|
117022
|
+
refuse6("target settings pluginConfigs is not an object");
|
|
117023
|
+
const targetRegistrations = new Map;
|
|
117024
|
+
const selectedPluginIds = [];
|
|
117025
|
+
const selectedOptionPaths = [];
|
|
117026
|
+
for (const selected of selectedPlugins) {
|
|
117027
|
+
const { pluginId: pluginId2, plugin, manifest, optionsPatch } = selected;
|
|
117028
|
+
if (Array.isArray(settings?.enabledPlugins?.[pluginId2]))
|
|
117029
|
+
refuse6("selected enabledPlugins value is an array; this schema does not model that native shape");
|
|
117030
|
+
selectedPluginIds.push(pluginId2);
|
|
117031
|
+
settingsAfter.enabledPlugins[pluginId2] = true;
|
|
117032
|
+
const currentConfig = settings?.pluginConfigs?.[pluginId2];
|
|
117033
|
+
if (currentConfig !== undefined && !object11(currentConfig))
|
|
117034
|
+
refuse6("selected pluginConfigs entry is malformed");
|
|
117035
|
+
const currentOptions = currentConfig?.options;
|
|
117036
|
+
if (currentOptions !== undefined && !object11(currentOptions))
|
|
117037
|
+
refuse6("selected pluginConfigs options are malformed");
|
|
117038
|
+
const nextConfig = currentConfig === undefined ? {} : structuredClone(currentConfig);
|
|
117039
|
+
nextConfig.options = { ...currentOptions ?? {}, ...optionsPatch, enabled: true };
|
|
117040
|
+
settingsAfter.pluginConfigs[pluginId2] = nextConfig;
|
|
117041
|
+
selectedOptionPaths.push(`pluginConfigs.${pluginId2}.options`);
|
|
117042
|
+
const hasCurrentPluginKey = !!registrations && Object.prototype.hasOwnProperty.call(registrations.plugins, pluginId2);
|
|
117043
|
+
const currentRows = hasCurrentPluginKey ? registrations.plugins[pluginId2] : [];
|
|
117044
|
+
if (!Array.isArray(currentRows))
|
|
117045
|
+
refuse6(`current selected plugin ${pluginId2} registrations are malformed`);
|
|
117046
|
+
for (const row2 of currentRows) {
|
|
117047
|
+
if (!object11(row2) || !["user", "project", "local"].includes(row2.scope) || typeof row2.installPath !== "string")
|
|
117048
|
+
refuse6(`current selected plugin ${pluginId2} registration row is malformed`);
|
|
117049
|
+
abs(row2.installPath, "registered plugin install path");
|
|
117050
|
+
if (row2.version !== undefined && (typeof row2.version !== "string" || !row2.version))
|
|
117051
|
+
refuse6("current selected plugin registration version is malformed");
|
|
117052
|
+
if (row2.scope === "project" || row2.scope === "local")
|
|
117053
|
+
abs(row2.projectPath, "registered plugin project path");
|
|
117054
|
+
else if (row2.projectPath !== undefined)
|
|
117055
|
+
refuse6("user plugin registration has a project path");
|
|
117056
|
+
}
|
|
117057
|
+
const isTargetScopeRow = (row2) => row2?.scope === request2.scope.kind && (request2.scope.kind === "user" ? row2.projectPath === undefined : row2.projectPath === request2.scope.projectPath);
|
|
117058
|
+
const targetIndexes = currentRows.map((row2, index) => isTargetScopeRow(row2) ? index : -1).filter((index) => index >= 0);
|
|
117059
|
+
if (targetIndexes.length > 1)
|
|
117060
|
+
refuse6(`current selected-scope registrations for ${pluginId2} are ambiguous`);
|
|
117061
|
+
const existingTarget = targetIndexes.length === 1 ? currentRows[targetIndexes[0]] : undefined;
|
|
117062
|
+
if (existingTarget !== undefined && Object.keys(existingTarget).some((key) => !["scope", "projectPath", "installPath", "version", "installedAt", "lastUpdated"].includes(key)))
|
|
117063
|
+
refuse6("current selected-scope plugin registration has unsupported fields");
|
|
117064
|
+
for (const key of ["installedAt", "lastUpdated"])
|
|
117065
|
+
if (existingTarget?.[key] !== undefined && !validUtcTimestamp(existingTarget[key]))
|
|
117066
|
+
refuse6(`current selected-scope plugin ${key} field is invalid`);
|
|
117067
|
+
const manifestVersion = manifest.version;
|
|
117068
|
+
const sanitizeComponent = (value) => value.replace(/[^A-Za-z0-9_-]/g, "-");
|
|
117069
|
+
const sanitizedVersion = manifestVersion === "." || manifestVersion === ".." ? "-" : manifestVersion.replace(/[^A-Za-z0-9._-]/g, "-");
|
|
117070
|
+
const expectedInstallPath = join68(pluginRoot, "cache", sanitizeComponent(candidate2.marketplace), sanitizeComponent(plugin), sanitizedVersion);
|
|
117071
|
+
if (expectedInstallPath.length > 4096)
|
|
117072
|
+
refuse6("candidate native cache path exceeds its bound");
|
|
117073
|
+
const targetRegistration = {
|
|
117074
|
+
scope: request2.scope.kind,
|
|
117075
|
+
...request2.scope.kind === "project" ? { projectPath: request2.scope.projectPath } : {},
|
|
117076
|
+
installPath: expectedInstallPath,
|
|
117077
|
+
version: manifestVersion,
|
|
117078
|
+
...existingTarget ? {
|
|
117079
|
+
...existingTarget.installedAt === undefined ? {} : { installedAt: existingTarget.installedAt },
|
|
117080
|
+
lastUpdated: existingTarget.lastUpdated === undefined ? GENERATED_PLUGIN_UPDATE_TIME : preserveOrGenerateTimestamp(existingTarget.lastUpdated, "installed_plugins.selected-row.lastUpdated")
|
|
117081
|
+
} : { installedAt: GENERATED_FRESH_PLUGIN_INSTALLED_AT, lastUpdated: GENERATED_PLUGIN_UPDATE_TIME }
|
|
117082
|
+
};
|
|
117083
|
+
targetRegistrations.set(pluginId2, targetRegistration);
|
|
117084
|
+
}
|
|
117085
|
+
if (candidate2.functionHooksEnv === "1") {
|
|
117086
|
+
const env3 = settingsAfter.env === undefined ? {} : settingsAfter.env;
|
|
117087
|
+
if (!object11(env3))
|
|
117088
|
+
refuse6("target settings env is not an object");
|
|
117089
|
+
settingsAfter.env = { ...env3, CLAUDE_CODE_ENABLE_FUNCTION_HOOKS: "1" };
|
|
117090
|
+
}
|
|
117091
|
+
if (!isDeepStrictEqual18(delta.settingsAfter, settingsAfter))
|
|
117092
|
+
refuse6("complete settings document differs from the exact selected plugin/options/environment operations");
|
|
117093
|
+
const marketplaceRowBefore = marketplaces?.[candidate2.marketplace];
|
|
117094
|
+
if (marketplaceRowBefore !== undefined && !object11(marketplaceRowBefore))
|
|
117095
|
+
refuse6("current marketplace row has an unsupported shape");
|
|
117096
|
+
if (marketplaceRowBefore !== undefined && Object.keys(marketplaceRowBefore).some((key) => !["source", "installLocation", "autoUpdate", "lastUpdated"].includes(key)))
|
|
117097
|
+
refuse6("current marketplace row has unsupported fields");
|
|
117098
|
+
if (marketplaceRowBefore?.autoUpdate !== undefined && typeof marketplaceRowBefore.autoUpdate !== "boolean")
|
|
117099
|
+
refuse6("current marketplace autoUpdate field is invalid");
|
|
117100
|
+
if (marketplaceRowBefore?.lastUpdated !== undefined && !validUtcTimestamp(marketplaceRowBefore.lastUpdated))
|
|
117101
|
+
refuse6("current marketplace update timestamp is invalid");
|
|
117102
|
+
const targetMarketplaceLocation = candidate2.root;
|
|
117103
|
+
const sameMarketplaceSource = isDeepStrictEqual18(marketplaceRowBefore?.source, { source: "directory", path: candidate2.root }) && marketplaceRowBefore?.installLocation === targetMarketplaceLocation;
|
|
117104
|
+
const afterMarketplace = {
|
|
117105
|
+
source: { source: "directory", path: candidate2.root },
|
|
117106
|
+
installLocation: targetMarketplaceLocation,
|
|
117107
|
+
autoUpdate: false,
|
|
117108
|
+
lastUpdated: sameMarketplaceSource && marketplaceRowBefore?.lastUpdated !== undefined ? preserveOrGenerateTimestamp(marketplaceRowBefore.lastUpdated, "known-marketplaces.lastUpdated") : GENERATED_MARKETPLACE_TIME
|
|
117109
|
+
};
|
|
117110
|
+
const marketplacesAfter = marketplaces ? structuredClone(marketplaces) : {};
|
|
117111
|
+
marketplacesAfter[candidate2.marketplace] = afterMarketplace;
|
|
117112
|
+
if (!isDeepStrictEqual18(delta.knownMarketplacesAfter, marketplacesAfter))
|
|
117113
|
+
refuse6("complete marketplace document differs from the exact selected-directory marketplace operation");
|
|
117114
|
+
const installedAfter = registrations ? structuredClone(registrations) : { version: 2, plugins: {} };
|
|
117115
|
+
if (installedAfter.plugins === undefined)
|
|
117116
|
+
installedAfter.plugins = {};
|
|
117117
|
+
if (!object11(installedAfter.plugins))
|
|
117118
|
+
refuse6("current installed plugin registry has an unsupported shape");
|
|
117119
|
+
for (const [pluginId2, targetRegistration] of targetRegistrations) {
|
|
117120
|
+
const rows = Object.prototype.hasOwnProperty.call(installedAfter.plugins, pluginId2) ? installedAfter.plugins[pluginId2] : [];
|
|
117121
|
+
if (!Array.isArray(rows))
|
|
117122
|
+
refuse6(`current selected plugin ${pluginId2} registrations are malformed`);
|
|
117123
|
+
const isTargetScopeRow = (row2) => row2?.scope === request2.scope.kind && (request2.scope.kind === "user" ? row2.projectPath === undefined : row2.projectPath === request2.scope.projectPath);
|
|
117124
|
+
const index = rows.findIndex(isTargetScopeRow);
|
|
117125
|
+
const nextRows = [...rows];
|
|
117126
|
+
if (index < 0)
|
|
117127
|
+
nextRows.push(targetRegistration);
|
|
117128
|
+
else
|
|
117129
|
+
nextRows[index] = targetRegistration;
|
|
117130
|
+
installedAfter.plugins[pluginId2] = nextRows;
|
|
117131
|
+
}
|
|
117132
|
+
if (!isDeepStrictEqual18(delta.installedPluginsAfter, installedAfter))
|
|
117133
|
+
refuse6("complete installed plugin document changes an unrelated registration or has target rows not derived from every selected candidate");
|
|
117134
|
+
const semanticDocumentsSha256 = sha7(canonicalJson3({ settings: settingsAfter, knownMarketplaces: marketplacesAfter, installedPlugins: installedAfter }));
|
|
117135
|
+
const exactPreimages = Object.keys(expectedPaths).map((name) => ({ name, path: expectedPaths[name], sha256: request2.preimages[name].sha256 }));
|
|
117136
|
+
const expectedPostStateSha256 = sha7(canonicalJson3({ paths: expectedPaths, targetSettingsPath: expectedPaths.settings, semanticDocumentsSha256, currentPreimages: exactPreimages }));
|
|
117137
|
+
const operationSha256 = sha7(canonicalJson3({
|
|
117138
|
+
scope: request2.scope,
|
|
117139
|
+
native: request2.native,
|
|
117140
|
+
candidate: request2.candidate,
|
|
117141
|
+
paths: expectedPaths,
|
|
117142
|
+
preimages: exactPreimages,
|
|
117143
|
+
skills: { dataDir: request2.skills.dataDir, policySha256: request2.skills.policySha256, discoverySha256: request2.skills.discoverySha256 },
|
|
117144
|
+
review: request2.review,
|
|
117145
|
+
semanticDocumentsSha256
|
|
117146
|
+
}));
|
|
117147
|
+
return {
|
|
117148
|
+
schema: CLAUDE_PROSPECTIVE_REVIEW_SCHEMA,
|
|
117149
|
+
accepted: true,
|
|
117150
|
+
applied: false,
|
|
117151
|
+
scope: request2.scope,
|
|
117152
|
+
pluginIds: selectedPluginIds,
|
|
117153
|
+
native: { executablePath: request2.native.executable.path, executableTarget: request2.native.executable.target, executableVersion: request2.native.executable.version, executableSha256: request2.native.executable.sha256, configRoot: request2.native.configRoot, pluginRoot: request2.native.pluginRoot },
|
|
117154
|
+
candidate: { marketplace: candidate2.marketplace, plugins: selectedPlugins.map((item) => ({ plugin: item.plugin, pluginId: item.pluginId, manifestSha256: item.manifestSha256, optionsPatchSha256: sha7(canonicalJson3(item.optionsPatch)) })), closureSha256: candidate2.closureSha256, catalogSha256: candidate2.catalogSha256, ...candidate2.releaseManifest ? { releaseManifest: candidate2.releaseManifest } : {} },
|
|
117155
|
+
targetSettingsPath: expectedPaths.settings,
|
|
117156
|
+
skillsDiscoverySha256: request2.skills.discoverySha256,
|
|
117157
|
+
requestFileSha256,
|
|
117158
|
+
semanticDocumentsSha256,
|
|
117159
|
+
operationSha256,
|
|
117160
|
+
expectedPostStateSha256,
|
|
117161
|
+
preimages: [
|
|
117162
|
+
{ name: "settings", path: expectedPaths.settings, sha256: request2.preimages.settings.sha256 },
|
|
117163
|
+
{ name: "user-settings", path: expectedPaths.userSettings, sha256: request2.preimages.userSettings.sha256 },
|
|
117164
|
+
{ name: "project-settings", path: expectedPaths.projectSettings, sha256: request2.preimages.projectSettings.sha256 },
|
|
117165
|
+
{ name: "project-settings-local", path: expectedPaths.projectSettingsLocal, sha256: request2.preimages.projectSettingsLocal.sha256 },
|
|
117166
|
+
{ name: "known-marketplaces", path: expectedPaths.marketplaces, sha256: request2.preimages.marketplaces.sha256 },
|
|
117167
|
+
{ name: "installed-plugins", path: expectedPaths.installedPlugins, sha256: request2.preimages.installedPlugins.sha256 },
|
|
117168
|
+
{ name: "skills-policy", path: join68(request2.skills.dataDir, "agent-policy.json"), sha256: request2.skills.policySha256 }
|
|
117169
|
+
],
|
|
117170
|
+
ownedDelta: { settingsEnabledPlugins: selectedPluginIds, selectedPluginOptions: selectedOptionPaths, functionHooksEnv: candidate2.functionHooksEnv ?? null, knownMarketplace: candidate2.marketplace, installedPlugins: selectedPluginIds, unrelatedGraph: "preserved-by-complete-semantic-documents" },
|
|
117171
|
+
identityPrecondition: "Harnesses must resolve the installed Claude home/config/plugin roots and executable path/version/hash plus the installed Skills data directory through their package-owned consumers, then compare every value before trusting this receipt; this validator checks supplied paths and current bytes only and does not establish actual consumer identity",
|
|
117172
|
+
reviewPrecondition: "The caller's no-skill-injection assessment is bound to the raw request hash; structural checks are not semantic hook review, and Harnesses must retain its existing review record and verify the exact candidate binding",
|
|
117173
|
+
limitation: "validation-only; native installer atomicity and crash recovery are not established"
|
|
117174
|
+
};
|
|
117175
|
+
}
|
|
117176
|
+
var CLAUDE_PROSPECTIVE_REVIEW_SCHEMA = "skills.claude-plugin-prospective-review/v1", SHA256, MAX_CANDIDATE_FILES = 16384, MAX_CANDIDATE_BYTES, sha7 = (data) => createHash41("sha256").update(data).digest("hex"), GENERATED_MARKETPLACE_TIME, GENERATED_FRESH_PLUGIN_INSTALLED_AT, GENERATED_PLUGIN_UPDATE_TIME;
|
|
117177
|
+
var init_claude_prospective_review = __esm(() => {
|
|
117178
|
+
init_agent_discovery();
|
|
117179
|
+
init_agent_integration();
|
|
117180
|
+
init_claude_marketplace_entry_witness();
|
|
117181
|
+
init_claude_plugin_manifest_witness();
|
|
117182
|
+
init_managed_policy();
|
|
117183
|
+
init_codex_native_skill_policy();
|
|
117184
|
+
SHA256 = /^[a-f0-9]{64}$/;
|
|
117185
|
+
MAX_CANDIDATE_BYTES = 256 * 1024 * 1024;
|
|
117186
|
+
GENERATED_MARKETPLACE_TIME = Object.freeze({ $generated: "claude-utc-timestamp", field: "known-marketplaces.lastUpdated" });
|
|
117187
|
+
GENERATED_FRESH_PLUGIN_INSTALLED_AT = Object.freeze({ $generated: "claude-utc-timestamp", field: "installed_plugins.fresh-row.installedAt" });
|
|
117188
|
+
GENERATED_PLUGIN_UPDATE_TIME = Object.freeze({ $generated: "claude-utc-timestamp", field: "installed_plugins.selected-row.lastUpdated" });
|
|
117189
|
+
});
|
|
117190
|
+
|
|
116620
117191
|
// src/lib/self-spawn.ts
|
|
116621
|
-
import { realpathSync as
|
|
116622
|
-
import { dirname as
|
|
117192
|
+
import { realpathSync as realpathSync22 } from "fs";
|
|
117193
|
+
import { dirname as dirname39, isAbsolute as isAbsolute35, sep as sep16 } from "path";
|
|
116623
117194
|
function selfSpawnRoot(entry) {
|
|
116624
117195
|
const index = entry.indexOf(INSTALLED_PACKAGE);
|
|
116625
|
-
const root2 = index > 0 ? entry.slice(0, index) :
|
|
117196
|
+
const root2 = index > 0 ? entry.slice(0, index) : dirname39(entry);
|
|
116626
117197
|
try {
|
|
116627
|
-
return
|
|
117198
|
+
return realpathSync22(root2);
|
|
116628
117199
|
} catch {
|
|
116629
117200
|
return root2;
|
|
116630
117201
|
}
|
|
@@ -116632,7 +117203,7 @@ function selfSpawnRoot(entry) {
|
|
|
116632
117203
|
function selfSpawnCommand(args2, options = {}) {
|
|
116633
117204
|
const runtime = options.runtime ?? process.execPath;
|
|
116634
117205
|
const entry = options.entry ?? process.argv[1];
|
|
116635
|
-
if (!runtime || !
|
|
117206
|
+
if (!runtime || !isAbsolute35(runtime) || !entry || !isAbsolute35(entry))
|
|
116636
117207
|
throw new Error("SKILLS_SELF_SPAWN_ENTRY_UNRESOLVED");
|
|
116637
117208
|
const root2 = selfSpawnRoot(entry);
|
|
116638
117209
|
const env3 = pinnedLauncherEnvironment(options.env ?? process.env);
|
|
@@ -116642,7 +117213,7 @@ function selfSpawnCommand(args2, options = {}) {
|
|
|
116642
117213
|
var INSTALLED_PACKAGE;
|
|
116643
117214
|
var init_self_spawn = __esm(() => {
|
|
116644
117215
|
init_runtime_launcher();
|
|
116645
|
-
INSTALLED_PACKAGE = `${
|
|
117216
|
+
INSTALLED_PACKAGE = `${sep16}node_modules${sep16}@hasna${sep16}skills${sep16}`;
|
|
116646
117217
|
});
|
|
116647
117218
|
|
|
116648
117219
|
// src/cli/commands/agent-integration.ts
|
|
@@ -116650,10 +117221,10 @@ var exports_agent_integration = {};
|
|
|
116650
117221
|
__export(exports_agent_integration, {
|
|
116651
117222
|
registerAgentIntegration: () => registerAgentIntegration
|
|
116652
117223
|
});
|
|
116653
|
-
import { existsSync as existsSync36, lstatSync as
|
|
116654
|
-
import { createHash as
|
|
117224
|
+
import { existsSync as existsSync36, lstatSync as lstatSync38, readFileSync as readFileSync46, readSync as readSync18, writeFileSync as writeFileSync27 } from "fs";
|
|
117225
|
+
import { createHash as createHash42 } from "crypto";
|
|
116655
117226
|
import { homedir as homedir13 } from "os";
|
|
116656
|
-
import { isAbsolute as
|
|
117227
|
+
import { isAbsolute as isAbsolute36 } from "path";
|
|
116657
117228
|
function pinnedHookProfile(input3, configuredProfile) {
|
|
116658
117229
|
try {
|
|
116659
117230
|
const parsed = parseSkillContextInput(JSON.stringify(input3));
|
|
@@ -116749,7 +117320,7 @@ function registerAgentIntegration(parent) {
|
|
|
116749
117320
|
});
|
|
116750
117321
|
hook.command("native-catalog").requiredOption("--cwd <path>", "Absolute project directory observed by native Codex skills/list").requiredOption("--output <file>", "New private file for the projected native catalog").option("--codex-command <path>", "Installed Codex executable used by the native client", "codex").option("--json", "Output bounded capture metadata as JSON", false).description("Capture a reviewed Codex native skill catalog for exact hook enrollment").action(async (options) => {
|
|
116751
117322
|
try {
|
|
116752
|
-
if (!
|
|
117323
|
+
if (!isAbsolute36(options.cwd) || !isAbsolute36(options.output))
|
|
116753
117324
|
throw new Error("CODEX_NATIVE_SKILL_CATALOG_ABSOLUTE_PATH_REQUIRED");
|
|
116754
117325
|
if (existsSync36(options.output))
|
|
116755
117326
|
throw new Error("CODEX_NATIVE_SKILL_CATALOG_OUTPUT_EXISTS");
|
|
@@ -116768,8 +117339,8 @@ function registerAgentIntegration(parent) {
|
|
|
116768
117339
|
throw new Error("CODEX_NATIVE_SKILL_CATALOG_OUTPUT_EXISTS");
|
|
116769
117340
|
throw error61;
|
|
116770
117341
|
}
|
|
116771
|
-
const written =
|
|
116772
|
-
if (!written.isFile() || written.isSymbolicLink() || (written.mode & 63) !== 0 ||
|
|
117342
|
+
const written = lstatSync38(options.output);
|
|
117343
|
+
if (!written.isFile() || written.isSymbolicLink() || (written.mode & 63) !== 0 || readFileSync46(options.output, "utf8") !== bytes)
|
|
116773
117344
|
throw new Error("CODEX_NATIVE_SKILL_CATALOG_READBACK_FAILED");
|
|
116774
117345
|
const receipt = {
|
|
116775
117346
|
version: catalog.version,
|
|
@@ -116777,7 +117348,7 @@ function registerAgentIntegration(parent) {
|
|
|
116777
117348
|
skillCount: catalog.skills.length,
|
|
116778
117349
|
output: options.output,
|
|
116779
117350
|
bytes: Buffer.byteLength(bytes),
|
|
116780
|
-
sha256:
|
|
117351
|
+
sha256: createHash42("sha256").update(bytes).digest("hex")
|
|
116781
117352
|
};
|
|
116782
117353
|
await writeCliOutput(options.json ? JSON.stringify(receipt) : `Captured ${receipt.skillCount} native Codex skill(s) in ${receipt.output}.`);
|
|
116783
117354
|
} catch (error61) {
|
|
@@ -116800,6 +117371,22 @@ function registerAgentIntegration(parent) {
|
|
|
116800
117371
|
throw new Error("Unsupported witness kind; select sumi-settings-v1, claude-settings-v4, claude-settings-v3, claude-settings-v2, codex-settings-v4, codex-settings-v3, codex-settings-v2, codex-settings-v1, claude-marketplace-registry-v2 or claude-marketplace-entry-v1");
|
|
116801
117372
|
await writeCliOutput(JSON.stringify(capture2(options.path), null, 2));
|
|
116802
117373
|
});
|
|
117374
|
+
hook.command("review-claude-candidate").requiredOption("--request <file>", "Frozen skills.claude-plugin-prospective-review/v1 JSON request").option("--json", "Return the validation-only receipt as JSON", false).description("Validate a prospective Claude plugin candidate and current preimages without changing native state; Harnesses must compare native and Skills paths against installed package-owned resolvers before trusting the receipt").action(async (options) => {
|
|
117375
|
+
try {
|
|
117376
|
+
if (!isAbsolute36(options.request))
|
|
117377
|
+
throw new Error("CLAUDE_CANDIDATE_REVIEW_REFUSED: --request must be an absolute path");
|
|
117378
|
+
const stat3 = lstatSync38(options.request);
|
|
117379
|
+
if (!stat3.isFile() || stat3.isSymbolicLink() || stat3.size > 1024 * 1024)
|
|
117380
|
+
throw new Error("CLAUDE_CANDIDATE_REVIEW_REFUSED: request must be a regular file no larger than 1 MiB");
|
|
117381
|
+
const requestBytes = readFileSync46(options.request);
|
|
117382
|
+
const request2 = JSON.parse(requestBytes.toString("utf8"));
|
|
117383
|
+
const receipt = reviewClaudeProspectiveCandidate(request2, createHash42("sha256").update(requestBytes).digest("hex"));
|
|
117384
|
+
await writeCliOutput(options.json ? JSON.stringify(receipt) : `Claude plugin candidate review accepted for ${receipt.pluginIds.length} selected plugin(s); native state was not changed.`);
|
|
117385
|
+
} catch (error61) {
|
|
117386
|
+
console.error(error61 instanceof Error && error61.message.startsWith("CLAUDE_CANDIDATE_REVIEW_REFUSED:") ? error61.message : "CLAUDE_CANDIDATE_REVIEW_REFUSED: request or current preimage validation failed");
|
|
117387
|
+
process.exitCode = 1;
|
|
117388
|
+
}
|
|
117389
|
+
});
|
|
116803
117390
|
hook.command("rebind-settings").requiredOption("--agent <agent>", "claude, codex or sumi").requiredOption("--reviewed-preimage <path>", "Exact preserved legacy settings.json, config.toml or sumi.json").requiredOption("--expected-policy-sha256 <sha256>", "Exact current managed policy bytes").requiredOption("--expected-settings-sha256 <sha256>", "Exact current native settings bytes").option("--codex-witness-version <version>", "Explicit target: 2 (legacy default) 3 (service tier and model-advertised effort), or 4 (native availability UI counts)").option("--claude-witness-version <version>", "Explicit target: 3 (default) or 4 (also permits a built-in top-level theme)").option("--apply", "Apply the explicit semantic witness upgrade with preservation and readback", false).option("--json", "Return the metadata-only plan or receipt", false).description("Explicitly migrate a legacy native settings witness after proving only known preferences changed").action(async (options) => {
|
|
116804
117391
|
try {
|
|
116805
117392
|
if (!["claude", "codex", "sumi"].includes(options.agent))
|
|
@@ -116835,8 +117422,8 @@ function registerAgentIntegration(parent) {
|
|
|
116835
117422
|
});
|
|
116836
117423
|
hook.command("install").option("--agent <agent>", `Agent to configure: ${INTEGRATION_AGENTS.join(", ")}, all`, "all").option("--command <path>", "Skills executable used by the hook (preserves existing binding; new agents use skills)").option("--selection-profile <id>", "Selection profile (preserves existing binding; new agents use default)").option("--include-vendor", "Retained for compatibility; vendor system skills are always inventoried and disabled", false).option("--discovery-inputs <file>", "Advanced reviewed active plugin roots and source hashes for unsupported registrations").option("--codex-skill-denials <file>", "Explicit reviewed remote skill denials: array of exact name, path and document sha256; requires fresh catalog and discovery inputs").option("--codex-native-catalog <file>", "Reviewed supported Codex skill and installed-plugin receipt for exact qualified-name disables").option("--reviewed-cache-alias <path>", "Exact skill-containing vendor cache alias reviewed for this hook plan").option("--allow-root-aliases", "Allow home .claude/.codex aliases to existing directories within this home", false).option("--apply", "Apply the plan, preserving prior configuration in private backups", false).option("--json", "Output a receipt as JSON", false).description("Plan or install one Skills CLI bridge plus native prompt hooks").action(async (options) => {
|
|
116837
117424
|
try {
|
|
116838
|
-
const discoveryInputs = options.discoveryInputs ? JSON.parse(
|
|
116839
|
-
const plan = planAgentIntegration({ projectDir: process.cwd(), agents: agents(options.agent), command: options.command, profileId: options.selectionProfile, includeVendor: options.includeVendor, discoveryInputs, allowRootAliases: options.allowRootAliases, reviewedCacheAlias: options.reviewedCacheAlias, codexSkillDenials: options.codexSkillDenials ? JSON.parse(
|
|
117425
|
+
const discoveryInputs = options.discoveryInputs ? JSON.parse(readFileSync46(options.discoveryInputs, "utf8")) : undefined;
|
|
117426
|
+
const plan = planAgentIntegration({ projectDir: process.cwd(), agents: agents(options.agent), command: options.command, profileId: options.selectionProfile, includeVendor: options.includeVendor, discoveryInputs, allowRootAliases: options.allowRootAliases, reviewedCacheAlias: options.reviewedCacheAlias, codexSkillDenials: options.codexSkillDenials ? JSON.parse(readFileSync46(options.codexSkillDenials, "utf8")) : undefined, codexNativeCatalog: options.codexNativeCatalog ? JSON.parse(readFileSync46(options.codexNativeCatalog, "utf8")) : undefined });
|
|
116840
117427
|
const result2 = options.apply ? applyAgentIntegration(plan) : { changed: [], backups: [] };
|
|
116841
117428
|
const receipt = { codexPluginSkillReview: plan.codexPluginSkillReview, codexPluginSkills: plan.changes.filter((change) => change.path.endsWith("agent-policy.json")).map((change) => JSON.parse(change.after).bridge?.codexPluginSkills ?? []).flat(), applied: options.apply, planned: plan.changes.map((change) => change.path), ...result2, rootAliases: plan.rootAliases ?? [], discovery: plan.discoveryAfter, nativeSkills: plan.nativeSkills.map((entry) => ({ agent: entry.agent, path: entry.path, managed: entry.managed, vendor: entry.vendor, system: entry.system === true, bridge: entry.bridge === true })), requiresNativeRetirement: plan.nativeSkills.some((entry) => !entry.bridge && !entry.system) };
|
|
116842
117429
|
if (options.json)
|
|
@@ -116895,7 +117482,7 @@ function registerAgentIntegration(parent) {
|
|
|
116895
117482
|
try {
|
|
116896
117483
|
if (agents(options.agent).length !== 1)
|
|
116897
117484
|
throw new Error("A hook invocation requires one agent");
|
|
116898
|
-
const inputBytes =
|
|
117485
|
+
const inputBytes = readFileSync46(0), inputText = inputBytes.toString("utf8");
|
|
116899
117486
|
if (inputText.length > 1024 * 1024)
|
|
116900
117487
|
throw new Error("Hook input is too large");
|
|
116901
117488
|
let input3 = JSON.parse(inputText);
|
|
@@ -116908,12 +117495,12 @@ function registerAgentIntegration(parent) {
|
|
|
116908
117495
|
input3.hook_event_name = event;
|
|
116909
117496
|
const projects = [process.cwd()];
|
|
116910
117497
|
if (input3.cwd !== undefined) {
|
|
116911
|
-
if (typeof input3.cwd !== "string" || !
|
|
117498
|
+
if (typeof input3.cwd !== "string" || !isAbsolute36(input3.cwd) || input3.cwd.includes("\x00"))
|
|
116912
117499
|
throw new Error("Invalid native hook working directory");
|
|
116913
117500
|
projects.push(input3.cwd);
|
|
116914
117501
|
}
|
|
116915
117502
|
if (options.agent === "cursor") {
|
|
116916
|
-
if (!Array.isArray(input3.workspace_roots) || input3.workspace_roots.length > 32 || input3.workspace_roots.some((path) => typeof path !== "string" || !
|
|
117503
|
+
if (!Array.isArray(input3.workspace_roots) || input3.workspace_roots.length > 32 || input3.workspace_roots.some((path) => typeof path !== "string" || !isAbsolute36(path) || path.includes("\x00")))
|
|
116917
117504
|
throw new Error("Invalid Cursor workspace roots");
|
|
116918
117505
|
projects.push(...input3.workspace_roots);
|
|
116919
117506
|
input3.cwd ??= input3.workspace_roots[0] ?? process.cwd();
|
|
@@ -117011,7 +117598,7 @@ function registerAgentIntegration(parent) {
|
|
|
117011
117598
|
try {
|
|
117012
117599
|
if (options.agent && !INTEGRATION_AGENTS.includes(options.agent))
|
|
117013
117600
|
throw new Error(`Unsupported migration agent: ${options.agent}`);
|
|
117014
|
-
const discoveryInputs = options.discoveryInputs ? JSON.parse(
|
|
117601
|
+
const discoveryInputs = options.discoveryInputs ? JSON.parse(readFileSync46(options.discoveryInputs, "utf8")) : undefined;
|
|
117015
117602
|
if (options.targetManifest && (options.includeUnmanaged || options.includeVendor))
|
|
117016
117603
|
throw new Error("--target-manifest cannot be combined with broad native migration selectors");
|
|
117017
117604
|
const targetManifest = options.targetManifest ? readNativeMigrationTargetManifest(options.targetManifest) : undefined;
|
|
@@ -117049,6 +117636,7 @@ var init_agent_integration2 = __esm(() => {
|
|
|
117049
117636
|
init_codex_settings_witness();
|
|
117050
117637
|
init_claude_marketplace_registry();
|
|
117051
117638
|
init_claude_marketplace_entry_witness();
|
|
117639
|
+
init_claude_prospective_review();
|
|
117052
117640
|
init_codex_native_skill_catalog();
|
|
117053
117641
|
init_self_spawn();
|
|
117054
117642
|
RECOVERABLE_CONTEXT_CACHE_ERRORS = new Set(["CACHED_PROFILE_EXPIRED", "CACHED_PROFILE_MISSING", "CACHED_BUNDLE_MISSING"]);
|
|
@@ -117324,28 +117912,28 @@ var init_grants = __esm(() => {
|
|
|
117324
117912
|
});
|
|
117325
117913
|
|
|
117326
117914
|
// src/lib/station-hydrate.ts
|
|
117327
|
-
import { createHash as
|
|
117915
|
+
import { createHash as createHash43 } from "crypto";
|
|
117328
117916
|
import {
|
|
117329
117917
|
copyFileSync as copyFileSync3,
|
|
117330
117918
|
mkdirSync as mkdirSync29,
|
|
117331
|
-
readdirSync as
|
|
117332
|
-
readFileSync as
|
|
117919
|
+
readdirSync as readdirSync25,
|
|
117920
|
+
readFileSync as readFileSync47,
|
|
117333
117921
|
statSync as statSync23,
|
|
117334
117922
|
writeFileSync as writeFileSync30
|
|
117335
117923
|
} from "fs";
|
|
117336
|
-
import { dirname as
|
|
117924
|
+
import { dirname as dirname40, join as join69, resolve as resolve34, sep as sep17 } from "path";
|
|
117337
117925
|
function fail6(code2, message, detail = []) {
|
|
117338
117926
|
throw new StationSnapshotError(code2, message, detail);
|
|
117339
117927
|
}
|
|
117340
117928
|
function snapshotRootFor(repoRoot, stationId) {
|
|
117341
|
-
return
|
|
117929
|
+
return join69(repoRoot, "resources", stationId, "skills");
|
|
117342
117930
|
}
|
|
117343
117931
|
function readSnapshotManifest(repoRoot, stationId) {
|
|
117344
117932
|
const snapshotRoot = snapshotRootFor(repoRoot, stationId);
|
|
117345
|
-
const manifestPath =
|
|
117933
|
+
const manifestPath = join69(snapshotRoot, "sync-manifest.json");
|
|
117346
117934
|
let manifest;
|
|
117347
117935
|
try {
|
|
117348
|
-
manifest = JSON.parse(
|
|
117936
|
+
manifest = JSON.parse(readFileSync47(manifestPath, "utf8"));
|
|
117349
117937
|
} catch (error61) {
|
|
117350
117938
|
fail6("MANIFEST_UNREADABLE", `cannot read snapshot manifest: ${manifestPath}: ${error61.message}`);
|
|
117351
117939
|
}
|
|
@@ -117369,10 +117957,10 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
117369
117957
|
const hashMismatches = [];
|
|
117370
117958
|
const skippedByRule = [];
|
|
117371
117959
|
for (const agent of SYNC_AGENTS) {
|
|
117372
|
-
const agentRoot =
|
|
117960
|
+
const agentRoot = join69(snapshotRoot, "agent-homes", agent);
|
|
117373
117961
|
let identEntries;
|
|
117374
117962
|
try {
|
|
117375
|
-
identEntries =
|
|
117963
|
+
identEntries = readdirSync25(agentRoot, { withFileTypes: true });
|
|
117376
117964
|
} catch {
|
|
117377
117965
|
continue;
|
|
117378
117966
|
}
|
|
@@ -117380,10 +117968,10 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
117380
117968
|
if (!identEntry.isDirectory() || identEntry.name.startsWith(".")) {
|
|
117381
117969
|
continue;
|
|
117382
117970
|
}
|
|
117383
|
-
const identRoot =
|
|
117971
|
+
const identRoot = join69(agentRoot, identEntry.name);
|
|
117384
117972
|
const entries = walkEntries(identRoot);
|
|
117385
117973
|
for (const entry of entries) {
|
|
117386
|
-
const relativeParts = [identEntry.name, ...entry.relativePath.split(
|
|
117974
|
+
const relativeParts = [identEntry.name, ...entry.relativePath.split(sep17)];
|
|
117387
117975
|
if (entry.kind === "symlink") {
|
|
117388
117976
|
symlinks.push({ ident: identEntry.name, agent, relativePath: entry.relativePath });
|
|
117389
117977
|
continue;
|
|
@@ -117407,8 +117995,8 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
117407
117995
|
});
|
|
117408
117996
|
continue;
|
|
117409
117997
|
}
|
|
117410
|
-
const withinIdent = relativeParts.slice(1).join(
|
|
117411
|
-
const homeRelative = relativeParts.join(
|
|
117998
|
+
const withinIdent = relativeParts.slice(1).join(sep17);
|
|
117999
|
+
const homeRelative = relativeParts.join(sep17);
|
|
117412
118000
|
if (REFUSED_SCANNER_FLAGGED.has(homeRelative)) {
|
|
117413
118001
|
skippedByRule.push({
|
|
117414
118002
|
ident: identEntry.name,
|
|
@@ -117481,7 +118069,7 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
117481
118069
|
for (const copy of copies) {
|
|
117482
118070
|
let isStub = false;
|
|
117483
118071
|
try {
|
|
117484
|
-
isStub = isPointerSkillMd(
|
|
118072
|
+
isStub = isPointerSkillMd(readFileSync47(copy.fullPath, "utf8"));
|
|
117485
118073
|
} catch {
|
|
117486
118074
|
isStub = false;
|
|
117487
118075
|
}
|
|
@@ -117527,12 +118115,12 @@ function skillSha256(skill) {
|
|
|
117527
118115
|
return sha256File(skill.files[0].winner.fullPath);
|
|
117528
118116
|
}
|
|
117529
118117
|
const joined = skill.files.map((file2) => sha256File(file2.winner.fullPath));
|
|
117530
|
-
return
|
|
118118
|
+
return createHash43("sha256").update(joined.sort().join(`
|
|
117531
118119
|
`)).digest("hex");
|
|
117532
118120
|
}
|
|
117533
118121
|
function writeStationHydration(options) {
|
|
117534
|
-
const repoRoot =
|
|
117535
|
-
const cacheRoot =
|
|
118122
|
+
const repoRoot = resolve34(options.repoRoot ?? process.cwd());
|
|
118123
|
+
const cacheRoot = resolve34(options.cacheRoot ?? resolveCorpusRoot());
|
|
117536
118124
|
const plan = planStationHydration(options.stationId, repoRoot);
|
|
117537
118125
|
const resultSkills = plan.winners.map((skill) => ({
|
|
117538
118126
|
ident: skill.ident,
|
|
@@ -117565,7 +118153,7 @@ function writeStationHydration(options) {
|
|
|
117565
118153
|
const toWrite = [];
|
|
117566
118154
|
for (const skill of plan.winners) {
|
|
117567
118155
|
for (const file2 of skill.files) {
|
|
117568
|
-
const destination =
|
|
118156
|
+
const destination = join69(cacheRoot, skill.ident, file2.withinIdent);
|
|
117569
118157
|
const digest4 = sha256File(file2.winner.fullPath);
|
|
117570
118158
|
let existingDigest = null;
|
|
117571
118159
|
try {
|
|
@@ -117586,7 +118174,7 @@ function writeStationHydration(options) {
|
|
|
117586
118174
|
}
|
|
117587
118175
|
let written = 0;
|
|
117588
118176
|
for (const entry of toWrite) {
|
|
117589
|
-
mkdirSync29(
|
|
118177
|
+
mkdirSync29(dirname40(entry.destination), { recursive: true });
|
|
117590
118178
|
copyFileSync3(entry.fullPath, entry.destination);
|
|
117591
118179
|
written += 1;
|
|
117592
118180
|
}
|
|
@@ -117607,8 +118195,8 @@ function writeStationHydration(options) {
|
|
|
117607
118195
|
},
|
|
117608
118196
|
skills: resultSkills
|
|
117609
118197
|
};
|
|
117610
|
-
const hydrationManifestPath =
|
|
117611
|
-
mkdirSync29(
|
|
118198
|
+
const hydrationManifestPath = join69(dirname40(cacheRoot), `hydration-${options.stationId}.json`);
|
|
118199
|
+
mkdirSync29(dirname40(hydrationManifestPath), { recursive: true });
|
|
117612
118200
|
writeFileSync30(hydrationManifestPath, `${JSON.stringify(hydration, null, 2)}
|
|
117613
118201
|
`);
|
|
117614
118202
|
return {
|
|
@@ -117684,15 +118272,15 @@ var init_hydrate = __esm(() => {
|
|
|
117684
118272
|
});
|
|
117685
118273
|
|
|
117686
118274
|
// src/lib/prepare-skill.ts
|
|
117687
|
-
import { createHash as
|
|
117688
|
-
import { chmodSync as chmodSync5, existsSync as existsSync37, lstatSync as
|
|
118275
|
+
import { createHash as createHash44, randomUUID as randomUUID13 } from "crypto";
|
|
118276
|
+
import { chmodSync as chmodSync5, existsSync as existsSync37, lstatSync as lstatSync39, mkdirSync as mkdirSync30, mkdtempSync as mkdtempSync6, readFileSync as readFileSync48, readdirSync as readdirSync26, renameSync as renameSync15, rmSync as rmSync9, writeFileSync as writeFileSync31 } from "fs";
|
|
117689
118277
|
import { tmpdir as tmpdir2 } from "os";
|
|
117690
|
-
import { dirname as
|
|
118278
|
+
import { dirname as dirname41, isAbsolute as isAbsolute37, join as join70, relative as relative13, resolve as resolve35, sep as sep18 } from "path";
|
|
117691
118279
|
function prepareSkill(name, options) {
|
|
117692
118280
|
const normalized = normalizePortableSkillName(name);
|
|
117693
118281
|
if (!new RegExp(SEMVER_PATTERN).test(options.version))
|
|
117694
118282
|
throw new Error("--version must be a semantic version, for example 0.2.0.");
|
|
117695
|
-
const path =
|
|
118283
|
+
const path = join70(getPortableSkillsRootReadOnly(options), normalized);
|
|
117696
118284
|
assertPortableAuthoringPath(path, options);
|
|
117697
118285
|
const before2 = snapshot2(path);
|
|
117698
118286
|
const manifestFile = before2.files.find((file2) => file2.path === "skill.json");
|
|
@@ -117727,16 +118315,16 @@ function prepareSkill(name, options) {
|
|
|
117727
118315
|
throw new Error("skill.json provenance must be an object.");
|
|
117728
118316
|
const provenance = manifest.provenance;
|
|
117729
118317
|
const candidate2 = { ...manifest, version: options.version, kind, provenance: { ...provenance } };
|
|
117730
|
-
const staging = mkdtempSync6(
|
|
118318
|
+
const staging = mkdtempSync6(join70(tmpdir2(), "skills-prepare-"));
|
|
117731
118319
|
try {
|
|
117732
118320
|
for (const directory2 of before2.directories)
|
|
117733
|
-
mkdirSync30(
|
|
118321
|
+
mkdirSync30(join70(staging, directory2), { recursive: true });
|
|
117734
118322
|
for (const file2 of before2.files) {
|
|
117735
|
-
const destination =
|
|
117736
|
-
mkdirSync30(
|
|
118323
|
+
const destination = join70(staging, file2.path);
|
|
118324
|
+
mkdirSync30(dirname41(destination), { recursive: true });
|
|
117737
118325
|
writeFileSync31(destination, file2.bytes, { mode: file2.mode });
|
|
117738
118326
|
}
|
|
117739
|
-
const stagedManifest =
|
|
118327
|
+
const stagedManifest = join70(staging, "skill.json");
|
|
117740
118328
|
writeFileSync31(stagedManifest, `${JSON.stringify(candidate2, null, 2)}
|
|
117741
118329
|
`);
|
|
117742
118330
|
const contentHash = computeContentHash(staging);
|
|
@@ -117759,11 +118347,11 @@ ${validation.issues.map((issue2) => `${issue2.code}: ${issue2.message}`).join(`
|
|
|
117759
118347
|
if (snapshot2(path).identity !== before2.identity)
|
|
117760
118348
|
throw new Error("Skill changed during preparation; review the draft and run prepare again.");
|
|
117761
118349
|
if (changed3 && !options.dryRun) {
|
|
117762
|
-
const temporary =
|
|
118350
|
+
const temporary = join70(path, `.skill-prepare-${randomUUID13()}.tmp`);
|
|
117763
118351
|
try {
|
|
117764
118352
|
writeFileSync31(temporary, candidateBytes, { flag: "wx", mode: manifestFile.mode });
|
|
117765
118353
|
chmodSync5(temporary, manifestFile.mode);
|
|
117766
|
-
renameSync15(temporary,
|
|
118354
|
+
renameSync15(temporary, join70(path, "skill.json"));
|
|
117767
118355
|
} finally {
|
|
117768
118356
|
rmSync9(temporary, { force: true });
|
|
117769
118357
|
}
|
|
@@ -117793,8 +118381,8 @@ function validateRuntimeEntrypoint(candidate2, root2) {
|
|
|
117793
118381
|
const entry = candidate2.runtime.entrypoint;
|
|
117794
118382
|
if (typeof entry !== "string")
|
|
117795
118383
|
return;
|
|
117796
|
-
const target =
|
|
117797
|
-
if (
|
|
118384
|
+
const target = resolve35(root2, entry), rel = relative13(root2, target);
|
|
118385
|
+
if (isAbsolute37(entry) || /^[a-zA-Z]:/.test(entry) || entry.includes("\\") || !rel || rel === ".." || rel.startsWith(`..${sep18}`) || isAbsolute37(rel) || !existsSync37(target) || !lstatSync39(target).isFile()) {
|
|
117798
118386
|
throw new Error("Executable runtime.entrypoint must name an existing regular file inside the skill directory.");
|
|
117799
118387
|
}
|
|
117800
118388
|
}
|
|
@@ -117879,20 +118467,20 @@ function compareVersions(left, right) {
|
|
|
117879
118467
|
return 0;
|
|
117880
118468
|
}
|
|
117881
118469
|
function snapshot2(root2) {
|
|
117882
|
-
const rootStat =
|
|
118470
|
+
const rootStat = lstatSync39(root2);
|
|
117883
118471
|
if (rootStat.isSymbolicLink() || !rootStat.isDirectory())
|
|
117884
118472
|
throw new Error("Skill root must be a real directory, not a symlink.");
|
|
117885
118473
|
const files = [], directories = [];
|
|
117886
|
-
const hash6 =
|
|
118474
|
+
const hash6 = createHash44("sha256").update(`${rootStat.dev}:${rootStat.ino}\x00`);
|
|
117887
118475
|
let count = 0, bytes = 0;
|
|
117888
|
-
const walk2 = (
|
|
117889
|
-
for (const name of
|
|
117890
|
-
if (IGNORED.has(name.toLowerCase()) || !
|
|
118476
|
+
const walk2 = (relative14) => {
|
|
118477
|
+
for (const name of readdirSync26(join70(root2, relative14)).sort()) {
|
|
118478
|
+
if (IGNORED.has(name.toLowerCase()) || !relative14 && ROOT_IGNORED.has(name.toLowerCase()))
|
|
117891
118479
|
continue;
|
|
117892
|
-
const path =
|
|
118480
|
+
const path = relative14 ? `${relative14}/${name}` : name;
|
|
117893
118481
|
if (++count > 20000)
|
|
117894
118482
|
throw new Error("Skill exceeds the preparation entry limit (20000).");
|
|
117895
|
-
const absolute =
|
|
118483
|
+
const absolute = join70(root2, path), stat3 = lstatSync39(absolute);
|
|
117896
118484
|
if (stat3.isSymbolicLink())
|
|
117897
118485
|
throw new Error(`Skill contains a symlink: ${path}. No files were changed.`);
|
|
117898
118486
|
if (stat3.isDirectory() && name === "node_modules")
|
|
@@ -117905,7 +118493,7 @@ function snapshot2(root2) {
|
|
|
117905
118493
|
bytes += stat3.size;
|
|
117906
118494
|
if (bytes > 50000000)
|
|
117907
118495
|
throw new Error("Skill exceeds the preparation size limit (50000000 bytes).");
|
|
117908
|
-
const content =
|
|
118496
|
+
const content = readFileSync48(absolute);
|
|
117909
118497
|
if (content.length !== stat3.size)
|
|
117910
118498
|
throw new Error("Skill changed during preparation.");
|
|
117911
118499
|
files.push({ path, bytes: content, mode: stat3.mode & 511 });
|
|
@@ -118308,7 +118896,7 @@ var init_schedule = __esm(() => {
|
|
|
118308
118896
|
|
|
118309
118897
|
// src/lib/registry-sync.ts
|
|
118310
118898
|
import { mkdirSync as mkdirSync31, writeFileSync as writeFileSync32 } from "fs";
|
|
118311
|
-
import { dirname as
|
|
118899
|
+
import { dirname as dirname42, relative as relative14 } from "path";
|
|
118312
118900
|
function createRegistrySyncArtifact(options = {}) {
|
|
118313
118901
|
const profile = options.profile ?? "all";
|
|
118314
118902
|
const includeDocs = options.includeDocs ?? true;
|
|
@@ -118320,7 +118908,7 @@ function createRegistrySyncArtifact(options = {}) {
|
|
|
118320
118908
|
const registry2 = [...loadRegistryProfile(profile)].sort((a, b) => a.name.localeCompare(b.name));
|
|
118321
118909
|
const skills = registry2.map((skill) => {
|
|
118322
118910
|
const skillPath = getSkillPath(skill.name);
|
|
118323
|
-
const directory2 =
|
|
118911
|
+
const directory2 = relative14(process.cwd(), skillPath) || skillPath;
|
|
118324
118912
|
const validation = includeValidation ? validateSkillDirectory(skill.name, skillPath, skill) : undefined;
|
|
118325
118913
|
const docs = includeDocs ? buildDocs(skill.name) : undefined;
|
|
118326
118914
|
return {
|
|
@@ -118365,7 +118953,7 @@ function createRegistrySyncArtifact(options = {}) {
|
|
|
118365
118953
|
};
|
|
118366
118954
|
}
|
|
118367
118955
|
function writeRegistrySyncArtifact(path, artifact) {
|
|
118368
|
-
mkdirSync31(
|
|
118956
|
+
mkdirSync31(dirname42(path), { recursive: true });
|
|
118369
118957
|
writeFileSync32(path, `${JSON.stringify(artifact, null, 2)}
|
|
118370
118958
|
`);
|
|
118371
118959
|
}
|
|
@@ -118385,8 +118973,8 @@ var init_registry_sync = __esm(() => {
|
|
|
118385
118973
|
});
|
|
118386
118974
|
|
|
118387
118975
|
// src/lib/review-export.ts
|
|
118388
|
-
import { constants as constants25, closeSync as closeSync27, existsSync as existsSync38, fstatSync as fstatSync27, fsyncSync as fsyncSync9, lstatSync as
|
|
118389
|
-
import { basename as
|
|
118976
|
+
import { constants as constants25, closeSync as closeSync27, existsSync as existsSync38, fstatSync as fstatSync27, fsyncSync as fsyncSync9, lstatSync as lstatSync40, openSync as openSync27, realpathSync as realpathSync23, statSync as statSync24, writeFileSync as writeFileSync33 } from "fs";
|
|
118977
|
+
import { basename as basename19, dirname as dirname43, isAbsolute as isAbsolute38, join as join71, parse as parse13, resolve as resolve36, sep as sep19 } from "path";
|
|
118390
118978
|
function exactVersion(spec) {
|
|
118391
118979
|
const at2 = spec.indexOf("@");
|
|
118392
118980
|
const slug2 = spec.slice(0, at2);
|
|
@@ -118397,23 +118985,23 @@ function exactVersion(spec) {
|
|
|
118397
118985
|
return { slug: slug2, version: version2 };
|
|
118398
118986
|
}
|
|
118399
118987
|
function assertPrivateDestination(output2) {
|
|
118400
|
-
if (!
|
|
118988
|
+
if (!isAbsolute38(output2) || resolve36(output2) !== output2 || !basename19(output2).endsWith(".tar.gz")) {
|
|
118401
118989
|
throw new Error("Review output must be an absolute, normalized .tar.gz path.");
|
|
118402
118990
|
}
|
|
118403
|
-
const parent =
|
|
118404
|
-
if (
|
|
118991
|
+
const parent = dirname43(output2);
|
|
118992
|
+
if (realpathSync23(parent) !== parent || lstatSync40(parent).isSymbolicLink()) {
|
|
118405
118993
|
throw new Error("Review output parent must be a real directory, not a symlink.");
|
|
118406
118994
|
}
|
|
118407
118995
|
const parentStat = statSync24(parent);
|
|
118408
118996
|
if (!parentStat.isDirectory() || (parentStat.mode & 63) !== 0 || process.getuid && parentStat.uid !== process.getuid()) {
|
|
118409
118997
|
throw new Error("Review output parent must be an owner-only directory (mode 0700).");
|
|
118410
118998
|
}
|
|
118411
|
-
const segments = parent.toLowerCase().split(
|
|
118999
|
+
const segments = parent.toLowerCase().split(sep19);
|
|
118412
119000
|
if (segments.some((segment) => NATIVE_CONTAINERS.has(segment)) || segments.some((segment, index) => segment === ".config" && segments[index + 1] === "opencode") || segments.some((segment, index) => segment === ".hasna" && segments[index + 1] === "skills")) {
|
|
118413
119001
|
throw new Error("Review output cannot be placed in agent-native discovery or Skills installation paths.");
|
|
118414
119002
|
}
|
|
118415
|
-
for (let current = parent;; current =
|
|
118416
|
-
if (existsSync38(
|
|
119003
|
+
for (let current = parent;; current = dirname43(current)) {
|
|
119004
|
+
if (existsSync38(join71(current, ".git"))) {
|
|
118417
119005
|
throw new Error("Review output cannot be placed inside a Git worktree.");
|
|
118418
119006
|
}
|
|
118419
119007
|
if (current === parse13(current).root)
|
|
@@ -118489,12 +119077,12 @@ function registerRegistry(parent) {
|
|
|
118489
119077
|
async function writeJson2(value, space) {
|
|
118490
119078
|
const text4 = `${JSON.stringify(value, null, space)}
|
|
118491
119079
|
`;
|
|
118492
|
-
await new Promise((
|
|
119080
|
+
await new Promise((resolve37, reject2) => {
|
|
118493
119081
|
process.stdout.write(text4, (error61) => {
|
|
118494
119082
|
if (error61)
|
|
118495
119083
|
reject2(error61);
|
|
118496
119084
|
else
|
|
118497
|
-
|
|
119085
|
+
resolve37();
|
|
118498
119086
|
});
|
|
118499
119087
|
});
|
|
118500
119088
|
}
|
|
@@ -118698,9 +119286,9 @@ __export(exports_publish, {
|
|
|
118698
119286
|
PushSkillError: () => PushSkillError
|
|
118699
119287
|
});
|
|
118700
119288
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
118701
|
-
import { existsSync as existsSync39, readFileSync as
|
|
119289
|
+
import { existsSync as existsSync39, readFileSync as readFileSync49 } from "fs";
|
|
118702
119290
|
import { hostname as hostname5 } from "os";
|
|
118703
|
-
import { join as
|
|
119291
|
+
import { join as join72 } from "path";
|
|
118704
119292
|
function registerPublish(parent) {
|
|
118705
119293
|
parent.command("push").argument("<name>", "Name of a skill in the local corpus (~/.hasna/skills/installed or the migrated ~/.hasna/skills/skills)").option("--version <version>", "Override the version recorded on the instance").option("--force-new-version", "If name@version already exists with different content, publish as the next patch version", false).option("--dry-run", "Pack and validate without uploading", false).option("--json", "Output result as JSON", false).description("Publish a local skill to the configured skills instance").action(async (name, options) => {
|
|
118706
119294
|
try {
|
|
@@ -118752,8 +119340,8 @@ async function pushSkill(name, options = {}) {
|
|
|
118752
119340
|
const packed = packSkillBundle(skill.path, { maxUnpackedBytes: MAX_UNPACKED_BYTES });
|
|
118753
119341
|
await verifyPackedCanonicalHash(skill.name, packed);
|
|
118754
119342
|
const versionManifest = buildVersionManifest(skill.path, packed);
|
|
118755
|
-
const skillMdPath =
|
|
118756
|
-
const skillMd = existsSync39(skillMdPath) ?
|
|
119343
|
+
const skillMdPath = join72(skill.path, "SKILL.md");
|
|
119344
|
+
const skillMd = existsSync39(skillMdPath) ? readFileSync49(skillMdPath, "utf-8") : undefined;
|
|
118757
119345
|
const base3 = {
|
|
118758
119346
|
slug: skill.name,
|
|
118759
119347
|
path: skill.path,
|
|
@@ -118991,7 +119579,7 @@ var exports_private_publications = {};
|
|
|
118991
119579
|
__export(exports_private_publications, {
|
|
118992
119580
|
registerPrivatePublications: () => registerPrivatePublications
|
|
118993
119581
|
});
|
|
118994
|
-
import { resolve as
|
|
119582
|
+
import { resolve as resolve37 } from "path";
|
|
118995
119583
|
function verifyOptions(options, action) {
|
|
118996
119584
|
if (options.userId === undefined !== (options.membershipId === undefined) || options.userId !== undefined && (!publicationUuid(options.userId) || !publicationUuid(options.membershipId)))
|
|
118997
119585
|
throw new PrivatePublicationError("PUBLICATION_CONTEXT_REQUIRED", "Provide both observed --user-id and --membership-id UUIDs.");
|
|
@@ -119036,9 +119624,9 @@ function registerPrivatePublications(parent) {
|
|
|
119036
119624
|
if (code2 === null)
|
|
119037
119625
|
return;
|
|
119038
119626
|
const context2 = options.userId ? { userId: options.userId, membershipId: options.membershipId } : undefined;
|
|
119039
|
-
const client2 = await privatePublicationSession(options.email, code2, context2), directory2 =
|
|
119627
|
+
const client2 = await privatePublicationSession(options.email, code2, context2), directory2 = resolve37(options.recoveryDir);
|
|
119040
119628
|
if (action === "publish")
|
|
119041
|
-
await preparePrivatePublication(client2,
|
|
119629
|
+
await preparePrivatePublication(client2, resolve37(args2[0]), directory2, { skillId: options.skillId, expectedCurrentVersionId: options.expectedCurrentVersion ?? null, idempotencyKey: options.idempotencyKey });
|
|
119042
119630
|
const result2 = action === "publish" || action === "resume" ? await continuePrivatePublication(client2, directory2, { confirm: true, waitMs: Number(options.waitSeconds) * 1000 }) : await inspectPrivatePublication(client2, directory2, action === "cancel");
|
|
119043
119631
|
print2(result2, options.json);
|
|
119044
119632
|
} catch (error61) {
|
|
@@ -119140,7 +119728,7 @@ __export(exports_auth, {
|
|
|
119140
119728
|
import { createInterface as createInterface3 } from "readline";
|
|
119141
119729
|
function prompt(question) {
|
|
119142
119730
|
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
119143
|
-
return new Promise((
|
|
119731
|
+
return new Promise((resolve38) => {
|
|
119144
119732
|
let settled = false;
|
|
119145
119733
|
const finish = (answer) => {
|
|
119146
119734
|
if (settled)
|
|
@@ -119149,7 +119737,7 @@ function prompt(question) {
|
|
|
119149
119737
|
rl.close();
|
|
119150
119738
|
if (answer === null)
|
|
119151
119739
|
process.exitCode = 130;
|
|
119152
|
-
|
|
119740
|
+
resolve38(answer);
|
|
119153
119741
|
};
|
|
119154
119742
|
rl.once("SIGINT", () => finish(null));
|
|
119155
119743
|
rl.once("close", () => finish(null));
|
|
@@ -119327,7 +119915,7 @@ Skipped. Run it any time: skills setup agents`));
|
|
|
119327
119915
|
function promptHidden(question) {
|
|
119328
119916
|
const stdin = process.stdin;
|
|
119329
119917
|
process.stdout.write(question);
|
|
119330
|
-
return new Promise((
|
|
119918
|
+
return new Promise((resolve38) => {
|
|
119331
119919
|
let value = "";
|
|
119332
119920
|
const done = (answer) => {
|
|
119333
119921
|
stdin.off("data", onData);
|
|
@@ -119337,7 +119925,7 @@ function promptHidden(question) {
|
|
|
119337
119925
|
`);
|
|
119338
119926
|
if (answer === null)
|
|
119339
119927
|
process.exitCode = 130;
|
|
119340
|
-
|
|
119928
|
+
resolve38(answer);
|
|
119341
119929
|
};
|
|
119342
119930
|
const onData = (chunk2) => {
|
|
119343
119931
|
for (const character of chunk2.toString()) {
|
|
@@ -119729,8 +120317,8 @@ function registerAccountCommands(parent) {
|
|
|
119729
120317
|
}
|
|
119730
120318
|
function registerAuth(parent) {
|
|
119731
120319
|
const auth = parent.command("auth").description("Manage account authentication");
|
|
119732
|
-
const
|
|
119733
|
-
|
|
120320
|
+
const keys4 = auth.command("keys").description("Manage API keys on the configured instance");
|
|
120321
|
+
keys4.command("list").option("--json", "Output as JSON", false).requiredOption("--email <email>", "Account email for fresh reauthentication").requiredOption("--code <code>", "Fresh OTP requested through auth signup/login").action(async (options) => {
|
|
119734
120322
|
try {
|
|
119735
120323
|
const target = await captureProfileWorkspace("List API keys");
|
|
119736
120324
|
target.unchanged();
|
|
@@ -119739,7 +120327,7 @@ function registerAuth(parent) {
|
|
|
119739
120327
|
writeCommandError(error61, "Failed to list API keys", options.json);
|
|
119740
120328
|
}
|
|
119741
120329
|
});
|
|
119742
|
-
|
|
120330
|
+
keys4.command("create").argument("<name>").option("--scope <scope>", "Limit key scope (repeatable)", (value, all) => [...all, value], []).option("--json", "Output the newly created key as JSON", false).requiredOption("--email <email>", "Account email for fresh reauthentication").requiredOption("--code <code>", "Fresh OTP requested through auth signup/login").description("Create a key; the returned secret is shown once and must be stored securely").action(async (name, options) => {
|
|
119743
120331
|
try {
|
|
119744
120332
|
const target = await captureProfileWorkspace("Create API key");
|
|
119745
120333
|
const client2 = new RemoteSkillsAuthClient(target.origin);
|
|
@@ -119750,7 +120338,7 @@ function registerAuth(parent) {
|
|
|
119750
120338
|
writeCommandError(error61, "Failed to create API key", options.json);
|
|
119751
120339
|
}
|
|
119752
120340
|
});
|
|
119753
|
-
|
|
120341
|
+
keys4.command("revoke").argument("<key-id>").option("--json", "Output as JSON", false).requiredOption("--email <email>", "Account email for fresh reauthentication").requiredOption("--code <code>", "Fresh OTP requested through auth signup/login").action(async (id3, options) => {
|
|
119754
120342
|
try {
|
|
119755
120343
|
const target = await captureProfileWorkspace("Revoke API key");
|
|
119756
120344
|
target.unchanged();
|
|
@@ -119759,7 +120347,7 @@ function registerAuth(parent) {
|
|
|
119759
120347
|
writeCommandError(error61, "Failed to revoke API key", options.json);
|
|
119760
120348
|
}
|
|
119761
120349
|
});
|
|
119762
|
-
|
|
120350
|
+
keys4.command("add-publish-scope").argument("<key-id>").requiredOption("--expected-scopes <csv>", "Current comma-separated scopes; stale values are refused").option("--json", "Output as JSON", false).description("Add skills:publish to one existing key without minting or rotating it").action(async (id3, options) => {
|
|
119763
120351
|
try {
|
|
119764
120352
|
const connection = await resolveSkillsConnection();
|
|
119765
120353
|
if (!connection)
|
|
@@ -119893,7 +120481,7 @@ function promptInvitationProof(kind) {
|
|
|
119893
120481
|
const label = kind === "token" ? "invitation token" : "recovery code";
|
|
119894
120482
|
const character = kind === "token" ? /^[A-Za-z0-9_-]$/ : /^\d$/;
|
|
119895
120483
|
const { stdin, stderr: output2 } = process, wasRaw = stdin.isRaw, wasFlowing = stdin.readableFlowing;
|
|
119896
|
-
return new Promise((
|
|
120484
|
+
return new Promise((resolve38) => {
|
|
119897
120485
|
let value = "", settled = false, overflow = false;
|
|
119898
120486
|
const finish = (answer) => {
|
|
119899
120487
|
if (settled)
|
|
@@ -119910,7 +120498,7 @@ function promptInvitationProof(kind) {
|
|
|
119910
120498
|
`);
|
|
119911
120499
|
if (answer === null)
|
|
119912
120500
|
process.exitCode = 130;
|
|
119913
|
-
|
|
120501
|
+
resolve38(answer);
|
|
119914
120502
|
};
|
|
119915
120503
|
const cancel = () => finish(null);
|
|
119916
120504
|
const keypress = (text4, key) => {
|
|
@@ -120482,8 +121070,8 @@ var init_storage2 = __esm(() => {
|
|
|
120482
121070
|
});
|
|
120483
121071
|
|
|
120484
121072
|
// src/lib/registry-reconcile.ts
|
|
120485
|
-
import { existsSync as existsSync40, readFileSync as
|
|
120486
|
-
import { join as
|
|
121073
|
+
import { existsSync as existsSync40, readFileSync as readFileSync50, statSync as statSync25, writeFileSync as writeFileSync34 } from "fs";
|
|
121074
|
+
import { join as join73 } from "path";
|
|
120487
121075
|
function isDirectory2(path) {
|
|
120488
121076
|
try {
|
|
120489
121077
|
return statSync25(path).isDirectory();
|
|
@@ -120494,15 +121082,15 @@ function isDirectory2(path) {
|
|
|
120494
121082
|
function migrationNeeded(options) {
|
|
120495
121083
|
if (options.rootDir)
|
|
120496
121084
|
return false;
|
|
120497
|
-
const appDir = options.homeDir ?
|
|
120498
|
-
return !(isOwnerLayoutMigrated(appDir) && isDirectory2(
|
|
121085
|
+
const appDir = options.homeDir ? join73(options.homeDir, ".hasna", "skills") : getDataDir();
|
|
121086
|
+
return !(isOwnerLayoutMigrated(appDir) && isDirectory2(join73(appDir, SKILLS_CACHE_DIRNAME)));
|
|
120499
121087
|
}
|
|
120500
121088
|
function readBaseline(skillDir) {
|
|
120501
|
-
const markerPath =
|
|
121089
|
+
const markerPath = join73(skillDir, PULL_MARKER_FILE);
|
|
120502
121090
|
if (!existsSync40(markerPath))
|
|
120503
121091
|
return;
|
|
120504
121092
|
try {
|
|
120505
|
-
const marker = JSON.parse(
|
|
121093
|
+
const marker = JSON.parse(readFileSync50(markerPath, "utf-8"));
|
|
120506
121094
|
if (!isSkillsOwnershipMarker(marker))
|
|
120507
121095
|
return;
|
|
120508
121096
|
return {
|
|
@@ -120514,11 +121102,11 @@ function readBaseline(skillDir) {
|
|
|
120514
121102
|
}
|
|
120515
121103
|
}
|
|
120516
121104
|
function readCursor(root2) {
|
|
120517
|
-
const path =
|
|
121105
|
+
const path = join73(root2, SYNC_CURSOR_FILE);
|
|
120518
121106
|
if (!existsSync40(path))
|
|
120519
121107
|
return { runCount: 0 };
|
|
120520
121108
|
try {
|
|
120521
|
-
const cursor2 = JSON.parse(
|
|
121109
|
+
const cursor2 = JSON.parse(readFileSync50(path, "utf-8"));
|
|
120522
121110
|
return { runCount: typeof cursor2.runCount === "number" ? cursor2.runCount : 0 };
|
|
120523
121111
|
} catch {
|
|
120524
121112
|
return { runCount: 0 };
|
|
@@ -120527,12 +121115,12 @@ function readCursor(root2) {
|
|
|
120527
121115
|
function resolveCorpusRootReadOnly(options) {
|
|
120528
121116
|
if (options.rootDir)
|
|
120529
121117
|
return { root: options.rootDir, migrationPending: false };
|
|
120530
|
-
const appDir = options.homeDir ?
|
|
120531
|
-
const cache3 =
|
|
121118
|
+
const appDir = options.homeDir ? join73(options.homeDir, ".hasna", "skills") : getDataDirReadOnly();
|
|
121119
|
+
const cache3 = join73(appDir, SKILLS_CACHE_DIRNAME);
|
|
120532
121120
|
if (isOwnerLayoutMigrated(appDir) && isDirectory2(cache3)) {
|
|
120533
121121
|
return { root: cache3, migrationPending: false };
|
|
120534
121122
|
}
|
|
120535
|
-
return { root:
|
|
121123
|
+
return { root: join73(appDir, INSTALLED_SKILLS_DIRNAME), migrationPending: true };
|
|
120536
121124
|
}
|
|
120537
121125
|
function remoteRowToSkill(record12) {
|
|
120538
121126
|
const slug2 = typeof record12.slug === "string" ? record12.slug : typeof record12.name === "string" ? record12.name : undefined;
|
|
@@ -120666,7 +121254,7 @@ async function reconcileRegistry(options = {}) {
|
|
|
120666
121254
|
for (const slug2 of allSlugs) {
|
|
120667
121255
|
const local = locals.get(slug2);
|
|
120668
121256
|
const remote = remotes.get(slug2);
|
|
120669
|
-
const baseline = local ? readBaseline(
|
|
121257
|
+
const baseline = local ? readBaseline(join73(root2, slug2)) : undefined;
|
|
120670
121258
|
const { state, reason: reason2 } = classifySkill(local, remote, baseline);
|
|
120671
121259
|
let { action, reason: actionReason } = resolveAction(state, direction, conflict);
|
|
120672
121260
|
if (state === "remote-only" && isDigestless(remote)) {
|
|
@@ -120725,7 +121313,7 @@ async function reconcileRegistry(options = {}) {
|
|
|
120725
121313
|
try {
|
|
120726
121314
|
await pushSkill(slug2, { rootDir: root2, client: client2 });
|
|
120727
121315
|
const pushed = locals.get(slug2);
|
|
120728
|
-
writePullMarker(
|
|
121316
|
+
writePullMarker(join73(root2, slug2), {
|
|
120729
121317
|
skill: slug2,
|
|
120730
121318
|
...pushed?.version ? { version: pushed.version } : {},
|
|
120731
121319
|
...pushed?.sha256 ? { contentHash: pushed.sha256 } : {},
|
|
@@ -120797,7 +121385,7 @@ async function reconcileRegistry(options = {}) {
|
|
|
120797
121385
|
runCount: readCursor(root2).runCount + 1,
|
|
120798
121386
|
summary
|
|
120799
121387
|
};
|
|
120800
|
-
writeFileSync34(
|
|
121388
|
+
writeFileSync34(join73(root2, SYNC_CURSOR_FILE), `${JSON.stringify(cursor2, null, 2)}
|
|
120801
121389
|
`);
|
|
120802
121390
|
return {
|
|
120803
121391
|
corpusRoot: root2,
|