@hasna/skills 0.10.91 → 0.10.93
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/THIRD_PARTY_NOTICES +1 -1
- package/bin/index.js +1051 -725
- package/bin/mcp.js +333 -46
- package/bin/migrate.js +1 -1
- package/bin/server.js +1 -1
- package/bin/worker.js +1 -1
- package/dist/cli/commands/portable-skills.d.ts +1 -0
- package/dist/index.js +381 -67
- package/dist/lib/agent-discovery.d.ts +27 -1
- package/dist/lib/authoring-path.d.ts +5 -0
- package/dist/lib/linux-access-acl.d.ts +55 -0
- package/dist/runtime-prerequisites.js +1 -1
- package/dist/sdk/index.js +671 -357
- 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.93",
|
|
36892
36892
|
description: "Skills library for AI coding agents",
|
|
36893
36893
|
type: "module",
|
|
36894
36894
|
bin: {
|
|
@@ -59021,6 +59021,82 @@ var init_runtime_launcher = __esm(() => {
|
|
|
59021
59021
|
PINNED_LAUNCHER_MAX_BYTES = 16 * 1024;
|
|
59022
59022
|
});
|
|
59023
59023
|
|
|
59024
|
+
// src/lib/linux-access-acl.ts
|
|
59025
|
+
function queryAttribute(probe2, errno, fd, attribute) {
|
|
59026
|
+
for (let attempt2 = 0;attempt2 < LINUX_ACL_QUERY_ATTEMPTS; attempt2++) {
|
|
59027
|
+
errno.set?.(0);
|
|
59028
|
+
const result2 = probe2.fgetxattr(fd, attribute);
|
|
59029
|
+
const code = errno.get();
|
|
59030
|
+
if (result2 !== -1 && result2 !== -1n)
|
|
59031
|
+
return "present";
|
|
59032
|
+
if (code === LINUX_ENODATA)
|
|
59033
|
+
return "absent";
|
|
59034
|
+
if (code === LINUX_EOPNOTSUPP)
|
|
59035
|
+
return "unsupported";
|
|
59036
|
+
}
|
|
59037
|
+
return "unverified";
|
|
59038
|
+
}
|
|
59039
|
+
function linuxAccessAclAbsent(probe2, fd, creationParent) {
|
|
59040
|
+
if (probe2.arch !== "x64" && probe2.arch !== "arm64")
|
|
59041
|
+
return false;
|
|
59042
|
+
const errno = probe2.errno;
|
|
59043
|
+
if (!errno)
|
|
59044
|
+
return false;
|
|
59045
|
+
let unsupported = false;
|
|
59046
|
+
for (const attribute of ["system.posix_acl_access", ...creationParent ? ["system.posix_acl_default"] : []]) {
|
|
59047
|
+
const state = queryAttribute(probe2, errno, fd, attribute);
|
|
59048
|
+
if (state === "present" || state === "unverified")
|
|
59049
|
+
return false;
|
|
59050
|
+
if (state === "unsupported")
|
|
59051
|
+
unsupported = true;
|
|
59052
|
+
}
|
|
59053
|
+
if (!unsupported)
|
|
59054
|
+
return true;
|
|
59055
|
+
const type = probe2.filesystemType(fd);
|
|
59056
|
+
return type !== null && LINUX_ACL_FREE_WHEN_UNSUPPORTED.has(type);
|
|
59057
|
+
}
|
|
59058
|
+
function withGlibcAclProbe(use) {
|
|
59059
|
+
try {
|
|
59060
|
+
const { dlopen, ptr, read: read2, FFIType } = __require("bun:ffi");
|
|
59061
|
+
const library = dlopen("libc.so.6", {
|
|
59062
|
+
fgetxattr: { args: ["i32", "ptr", "ptr", "u64"], returns: FFIType.i64_fast },
|
|
59063
|
+
fstatfs: { args: ["i32", "ptr"], returns: "i32" },
|
|
59064
|
+
__errno_location: { args: [], returns: "ptr" }
|
|
59065
|
+
});
|
|
59066
|
+
try {
|
|
59067
|
+
const location = library.symbols.__errno_location();
|
|
59068
|
+
const names = new Map;
|
|
59069
|
+
return use({
|
|
59070
|
+
arch: process.arch,
|
|
59071
|
+
errno: location ? { get: () => read2.i32(location) } : null,
|
|
59072
|
+
fgetxattr: (fd, attribute) => {
|
|
59073
|
+
let name = names.get(attribute);
|
|
59074
|
+
if (!name)
|
|
59075
|
+
names.set(attribute, name = Buffer.from(attribute + "\x00"));
|
|
59076
|
+
return library.symbols.fgetxattr(fd, ptr(name), null, 0);
|
|
59077
|
+
},
|
|
59078
|
+
filesystemType: (fd) => {
|
|
59079
|
+
const statfs = new BigUint64Array(32);
|
|
59080
|
+
return library.symbols.fstatfs(fd, ptr(statfs)) === 0 ? statfs[0] : null;
|
|
59081
|
+
}
|
|
59082
|
+
});
|
|
59083
|
+
} finally {
|
|
59084
|
+
library.close();
|
|
59085
|
+
}
|
|
59086
|
+
} catch {
|
|
59087
|
+
return false;
|
|
59088
|
+
}
|
|
59089
|
+
}
|
|
59090
|
+
var LINUX_ENODATA = 61, LINUX_EOPNOTSUPP = 95, LINUX_ACL_QUERY_ATTEMPTS = 4, LINUX_ACL_FREE_WHEN_UNSUPPORTED;
|
|
59091
|
+
var init_linux_access_acl = __esm(() => {
|
|
59092
|
+
LINUX_ACL_FREE_WHEN_UNSUPPORTED = new Set([
|
|
59093
|
+
0xef53n,
|
|
59094
|
+
0x58465342n,
|
|
59095
|
+
0x9123683en,
|
|
59096
|
+
0x01021994n
|
|
59097
|
+
]);
|
|
59098
|
+
});
|
|
59099
|
+
|
|
59024
59100
|
// src/lib/retained-review-artifacts.ts
|
|
59025
59101
|
import { createHash as createHash29 } from "crypto";
|
|
59026
59102
|
import { constants as constants20, closeSync as closeSync18, fstatSync as fstatSync18, fsyncSync as fsyncSync6, lstatSync as lstatSync30, mkdirSync as mkdirSync9, openSync as openSync18, readSync as readSync14, writeSync as writeSync4 } from "fs";
|
|
@@ -60069,28 +60145,7 @@ function hookProjectionRefusal(reason2) {
|
|
|
60069
60145
|
throw new Error(`CLAUDE_MANAGED_HOOK_${reason2}`);
|
|
60070
60146
|
}
|
|
60071
60147
|
function hasNoLinuxAccessAcl(fd, creationParent) {
|
|
60072
|
-
|
|
60073
|
-
const { dlopen, read: read2, ptr } = __require("bun:ffi");
|
|
60074
|
-
const library = dlopen("libc.so.6", {
|
|
60075
|
-
fgetxattr: { args: ["i32", "ptr", "ptr", "u64"], returns: "i64" },
|
|
60076
|
-
__errno_location: { args: [], returns: "ptr" }
|
|
60077
|
-
});
|
|
60078
|
-
try {
|
|
60079
|
-
const errno = library.symbols.__errno_location();
|
|
60080
|
-
if (!errno)
|
|
60081
|
-
return false;
|
|
60082
|
-
for (const attribute of ["system.posix_acl_access", ...creationParent ? ["system.posix_acl_default"] : []]) {
|
|
60083
|
-
const name = Buffer.from(attribute + "\x00");
|
|
60084
|
-
if (Number(library.symbols.fgetxattr(fd, ptr(name), null, 0)) !== -1 || read2.i32(errno) !== 61)
|
|
60085
|
-
return false;
|
|
60086
|
-
}
|
|
60087
|
-
return true;
|
|
60088
|
-
} finally {
|
|
60089
|
-
library.close();
|
|
60090
|
-
}
|
|
60091
|
-
} catch {
|
|
60092
|
-
return false;
|
|
60093
|
-
}
|
|
60148
|
+
return withGlibcAclProbe((probe2) => linuxAccessAclAbsent(probe2, fd, creationParent));
|
|
60094
60149
|
}
|
|
60095
60150
|
function assertNativePathAcl(path, observed, options = {}) {
|
|
60096
60151
|
if (process.platform !== "darwin" && process.platform !== "linux")
|
|
@@ -60938,7 +60993,7 @@ function planCodexPluginNameReconcile(request) {
|
|
|
60938
60993
|
pathDenies: [...pathDenies].sort(),
|
|
60939
60994
|
...discovery ? { discovery } : {}
|
|
60940
60995
|
};
|
|
60941
|
-
const plan = planAgentIntegration({ home, dataDir: dataDir3, projectDir, agents: ["codex"], codexNativeCatalog: catalog, codexReconcile: input });
|
|
60996
|
+
const plan = planAgentIntegration({ home, dataDir: dataDir3, projectDir, agents: ["codex"], codexNativeCatalog: catalog, codexReconcile: input, allowRootAliases: aliases.length > 0 });
|
|
60942
60997
|
assertCodexReconcileScope(plan, { configPath, policyPath, prior, names, remoteIdentities, discovery: discovery ?? savedDiscovery });
|
|
60943
60998
|
const configChange = plan.changes.find((change) => resolve25(change.path) === configPath);
|
|
60944
60999
|
const plannedRules = configChange ? Bun.TOML.parse(configChange.after).skills?.config ?? [] : [];
|
|
@@ -61227,7 +61282,7 @@ async function applyCodexPluginReconcileUnlocked(plan, options, assertCurrent, a
|
|
|
61227
61282
|
const uncertain = (reason2) => new Error(`CODEX_RECONCILE_NATIVE_WRITE_UNCERTAIN: ${reason2}; the policy was not written (private backup: ${backupRoot}); config.toml ${codexConfigStructureEqual(currentText(configChange.path), configChange.after) ? "holds" : "may still receive"} this run's deny rows: ${rows}. Run the reconcile again: it finishes the policy once Codex reports those skills disabled`);
|
|
61228
61283
|
let result2;
|
|
61229
61284
|
try {
|
|
61230
|
-
result2 = await rpc.request("config/batchWrite", { edits: [{ keyPath: "skills.config", value: codexSkillRows(configChange.after) ?? [], mergeStrategy: "replace" }], filePath:
|
|
61285
|
+
result2 = await rpc.request("config/batchWrite", { edits: [{ keyPath: "skills.config", value: codexSkillRows(configChange.after) ?? [], mergeStrategy: "replace" }], filePath: nativeConfigPath, expectedVersion, reloadUserConfig: true });
|
|
61231
61286
|
} catch (error) {
|
|
61232
61287
|
const refused = error instanceof Error && error.message.startsWith("CODEX_HOOK_TRUST_NATIVE_RPC_REFUSED");
|
|
61233
61288
|
if (refused && !codexConfigStructureEqual(currentText(configChange.path), configChange.after))
|
|
@@ -61238,7 +61293,7 @@ async function applyCodexPluginReconcileUnlocked(plan, options, assertCurrent, a
|
|
|
61238
61293
|
nativeVersion = result2.version;
|
|
61239
61294
|
if (!nativeVersion)
|
|
61240
61295
|
throw uncertain("Codex answered the config write without a valid config version");
|
|
61241
|
-
if (result2?.status !== "ok" || result2.filePath !==
|
|
61296
|
+
if (result2?.status !== "ok" || result2.filePath !== nativeConfigPath)
|
|
61242
61297
|
throw new Error("CODEX_RECONCILE_NATIVE_WRITE_OVERRIDDEN: Codex did not confirm a plain user-layer write of the deny rows (another configuration layer may override them)");
|
|
61243
61298
|
if (!codexConfigStructureEqual(currentText(configChange.path), configChange.after))
|
|
61244
61299
|
throw new Error("CODEX_RECONCILE_NATIVE_READBACK_FAILED: config.toml after Codex's write differs from the planned configuration");
|
|
@@ -61289,7 +61344,7 @@ async function applyCodexPluginReconcileUnlocked(plan, options, assertCurrent, a
|
|
|
61289
61344
|
if (layers?.length !== 1 || layers[0].version !== nativeVersion)
|
|
61290
61345
|
incomplete = "config.toml changed after this run's write, so its deny rows were left in place";
|
|
61291
61346
|
else {
|
|
61292
|
-
const restored = await rpc.request("config/batchWrite", { edits: [{ keyPath: "skills.config", value: codexSkillRows(configChange.before), mergeStrategy: "replace" }], filePath:
|
|
61347
|
+
const restored = await rpc.request("config/batchWrite", { edits: [{ keyPath: "skills.config", value: codexSkillRows(configChange.before), mergeStrategy: "replace" }], filePath: nativeConfigPath, expectedVersion: nativeVersion, reloadUserConfig: true });
|
|
61293
61348
|
if (restored?.status !== "ok" || !codexConfigStructureEqual(currentText(configChange.path), configChange.before))
|
|
61294
61349
|
incomplete = "Codex did not restore config.toml to its preimage";
|
|
61295
61350
|
}
|
|
@@ -62051,6 +62106,7 @@ var init_agent_integration = __esm(() => {
|
|
|
62051
62106
|
init_claude_settings_witness();
|
|
62052
62107
|
init_codex_hook_trust_files();
|
|
62053
62108
|
init_runtime_launcher();
|
|
62109
|
+
init_linux_access_acl();
|
|
62054
62110
|
init_retained_review_artifacts();
|
|
62055
62111
|
HOOK_EVENTS = ["UserPromptSubmit", "SessionStart", "SubagentStart"];
|
|
62056
62112
|
ROOTS = NATIVE_SKILL_ROOTS;
|
|
@@ -65872,9 +65928,94 @@ function nativeDiscoveryAliasTargets(roots, home) {
|
|
|
65872
65928
|
}
|
|
65873
65929
|
return [...targets];
|
|
65874
65930
|
}
|
|
65931
|
+
function hooksAreManagedBridge(agent, hooks, managed) {
|
|
65932
|
+
const table = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
65933
|
+
const exactKeys4 = (value, keys3) => Object.keys(value).sort().join("\x00") === [...keys3].sort().join("\x00");
|
|
65934
|
+
if (!table(hooks) || typeof managed.command !== "string" || !isAbsolute30(managed.command))
|
|
65935
|
+
return false;
|
|
65936
|
+
const events3 = agent === "gemini" ? ["BeforeAgent", "SessionStart", "BeforeTool"] : ["beforeSubmitPrompt", "sessionStart"];
|
|
65937
|
+
for (const [event, entries] of Object.entries(hooks)) {
|
|
65938
|
+
if (!events3.includes(event) || !Array.isArray(entries) || entries.length !== 1)
|
|
65939
|
+
return false;
|
|
65940
|
+
let expected;
|
|
65941
|
+
try {
|
|
65942
|
+
expected = renderAgentHookCommand(managed.command, agent, managed.profile, event);
|
|
65943
|
+
} catch {
|
|
65944
|
+
return false;
|
|
65945
|
+
}
|
|
65946
|
+
const entry = entries[0];
|
|
65947
|
+
if (!table(entry))
|
|
65948
|
+
return false;
|
|
65949
|
+
if (agent === "cursor") {
|
|
65950
|
+
if (!exactKeys4(entry, ["command", "timeout"]) || entry.timeout !== 15 || entry.command !== expected)
|
|
65951
|
+
return false;
|
|
65952
|
+
continue;
|
|
65953
|
+
}
|
|
65954
|
+
if (!exactKeys4(entry, event === "BeforeTool" ? ["hooks", "matcher"] : ["hooks"]) || event === "BeforeTool" && entry.matcher !== "activate_skill" || !Array.isArray(entry.hooks) || entry.hooks.length !== 1)
|
|
65955
|
+
return false;
|
|
65956
|
+
const hook = entry.hooks[0];
|
|
65957
|
+
if (!table(hook) || !exactKeys4(hook, ["command", "timeout", "type"]) || hook.type !== "command" || hook.timeout !== 15000 || hook.command !== expected)
|
|
65958
|
+
return false;
|
|
65959
|
+
}
|
|
65960
|
+
return true;
|
|
65961
|
+
}
|
|
65962
|
+
function sumiPluginLoadsOnlyBuiltins(text3) {
|
|
65963
|
+
for (const match of text3.matchAll(/\b(?:import|require)\s*\(\s*(.?)/g)) {
|
|
65964
|
+
const rest2 = text3.slice(match.index + match[0].length - match[1].length);
|
|
65965
|
+
if (!/^(['"])node:[A-Za-z0-9_/.-]+\1/.test(rest2))
|
|
65966
|
+
return false;
|
|
65967
|
+
}
|
|
65968
|
+
for (const match of text3.matchAll(/\b(?:from|import)\s*(['"])([^'"]*)\1/g))
|
|
65969
|
+
if (!match[2].startsWith("node:"))
|
|
65970
|
+
return false;
|
|
65971
|
+
return true;
|
|
65972
|
+
}
|
|
65973
|
+
function codexRootFreeControls(config) {
|
|
65974
|
+
const table = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date));
|
|
65975
|
+
const adapted = [];
|
|
65976
|
+
const servers = config.mcp_servers;
|
|
65977
|
+
if (table(servers) && Object.keys(servers).length <= AGENT_POLICY_LIMITS.discoveryRoots && Object.entries(servers).every(([name, row]) => name !== "codex_apps" && name !== "_codex_apps" && isCodexLocalStdioMcpServer(name, row)))
|
|
65978
|
+
adapted.push("mcp_servers");
|
|
65979
|
+
const agents = config.agents;
|
|
65980
|
+
if (table(agents) && Object.keys(agents).length <= AGENT_POLICY_LIMITS.fields && Object.values(agents).every((value) => typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)))
|
|
65981
|
+
adapted.push("agents");
|
|
65982
|
+
return adapted;
|
|
65983
|
+
}
|
|
65984
|
+
function codexPluginHookHandlers(value) {
|
|
65985
|
+
const table = (item) => Boolean(item && typeof item === "object" && !Array.isArray(item));
|
|
65986
|
+
const identifier6 = (item) => typeof item === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(item);
|
|
65987
|
+
const text3 = (item) => typeof item === "string" && item.length <= AGENT_POLICY_LIMITS.pathCharacters && !item.includes("\x00");
|
|
65988
|
+
if (!table(value) || !Object.keys(value).every((key2) => key2 === "hooks" || key2 === "description") || value.description !== undefined && !text3(value.description) || !table(value.hooks) || Object.keys(value.hooks).length > 32)
|
|
65989
|
+
return null;
|
|
65990
|
+
const result2 = { commands: 0, servers: new Set };
|
|
65991
|
+
for (const [event, groups] of Object.entries(value.hooks)) {
|
|
65992
|
+
if (!CODEX_HOOK_EVENTS.has(event) || !Array.isArray(groups) || groups.length > 32)
|
|
65993
|
+
return null;
|
|
65994
|
+
for (const group of groups) {
|
|
65995
|
+
if (!table(group) || !Object.keys(group).every((key2) => key2 === "hooks" || key2 === "matcher") || group.matcher !== undefined && !text3(group.matcher) || !Array.isArray(group.hooks) || group.hooks.length > 32)
|
|
65996
|
+
return null;
|
|
65997
|
+
for (const handler of group.hooks) {
|
|
65998
|
+
if (!table(handler) || handler.timeout !== undefined && !(Number.isSafeInteger(handler.timeout) && handler.timeout >= 0) || handler.statusMessage !== undefined && !text3(handler.statusMessage))
|
|
65999
|
+
return null;
|
|
66000
|
+
if (handler.type === "command") {
|
|
66001
|
+
if (!text3(handler.command) || !handler.command || !Object.keys(handler).every((key2) => ["type", "command", "commandWindows", "command_windows", "timeout", "async", "statusMessage", "additionalContextLimit"].includes(key2)))
|
|
66002
|
+
return null;
|
|
66003
|
+
result2.commands++;
|
|
66004
|
+
} else if (handler.type === "mcp_tool") {
|
|
66005
|
+
if (!identifier6(handler.server) || !identifier6(handler.tool) || handler.input !== undefined && !table(handler.input) || !Object.keys(handler).every((key2) => ["type", "server", "tool", "input", "timeout", "statusMessage"].includes(key2)))
|
|
66006
|
+
return null;
|
|
66007
|
+
result2.servers.add(handler.server);
|
|
66008
|
+
} else
|
|
66009
|
+
return null;
|
|
66010
|
+
}
|
|
66011
|
+
}
|
|
66012
|
+
}
|
|
66013
|
+
return result2;
|
|
66014
|
+
}
|
|
65875
66015
|
function resolveDiscovery(options, exclusion) {
|
|
65876
66016
|
const canonical9 = options.canonical ?? resolve29, home = resolve29(options.home), agent = options.agent;
|
|
65877
66017
|
const sources = [], roots = new Set, builtinNames = [];
|
|
66018
|
+
const sumiLocalPluginTexts = new Map;
|
|
65878
66019
|
const budget3 = discoveryByteBudget();
|
|
65879
66020
|
let remainingEntries = AGENT_POLICY_LIMITS.discoveryDirectoryEntries;
|
|
65880
66021
|
const entries = (path) => {
|
|
@@ -65911,8 +66052,8 @@ function resolveDiscovery(options, exclusion) {
|
|
|
65911
66052
|
const configPath = exclusion?.nativeHome === undefined ? agentDiscoveryConfigPath(home, agent) : join46(nativeHome, agent === "codex" ? "config.toml" : "settings.json");
|
|
65912
66053
|
const configText = witness(configPath, agent === "hermes" ? "yaml" : agent === "codex" ? "toml" : "json", agent === "hermes" ? ["skills", "plugins", "hooks"] : agent === "claude" ? ["enabledPlugins", "extraKnownMarketplaces"] : agent === "codex" ? [...CODEX_DISCOVERY_PROJECTION_FIELDS] : agent === "gemini" ? ["skills", "extensions", "security"] : agent === "opencode" ? ["plugin", "skills"] : agent === "sumi" ? [...SUMI_DISCOVERY_PROJECTION_FIELDS] : ["version"]);
|
|
65913
66054
|
const config = configText === null ? {} : agent === "hermes" ? parseHermesConfig(configText) : parseConfig(configText, configPath, agent === "codex");
|
|
65914
|
-
const unresolved = (detail) => {
|
|
65915
|
-
throw new
|
|
66055
|
+
const unresolved = (detail, source) => {
|
|
66056
|
+
throw new NativeDiscoveryUnresolved(agent, detail, source);
|
|
65916
66057
|
};
|
|
65917
66058
|
let opaqueClosureVerified = false;
|
|
65918
66059
|
const verifyOpaqueClosure = (declarations) => {
|
|
@@ -65926,6 +66067,17 @@ function resolveDiscovery(options, exclusion) {
|
|
|
65926
66067
|
verifyDiscovery(retained, undefined, undefined, undefined, exclusion?.narrowed, canonical9(configPath));
|
|
65927
66068
|
opaqueClosureVerified = true;
|
|
65928
66069
|
};
|
|
66070
|
+
const reviewRemedy = (source, check) => {
|
|
66071
|
+
try {
|
|
66072
|
+
check();
|
|
66073
|
+
} catch (error) {
|
|
66074
|
+
if (error instanceof NativeDiscoveryUnresolved) {
|
|
66075
|
+
error.reviewable = true;
|
|
66076
|
+
error.source ??= source;
|
|
66077
|
+
}
|
|
66078
|
+
throw error;
|
|
66079
|
+
}
|
|
66080
|
+
};
|
|
65929
66081
|
if (exclusion) {
|
|
65930
66082
|
if (configText !== null && agent !== "codex" && agent !== "hermes")
|
|
65931
66083
|
parseNativeJsonObject(configText);
|
|
@@ -65951,12 +66103,38 @@ function resolveDiscovery(options, exclusion) {
|
|
|
65951
66103
|
const controls = agent === "claude" ? claudeDiscoveryControlKeys(configText ?? "{}") : agent === "codex" ? codexDiscoveryControlKeys(config) : Object.keys(config).filter((key2) => /skill|plugin|hook|marketplace|extension|instruction/i.test(key2));
|
|
65952
66104
|
const fieldKey = (key2) => /^[A-Za-z_$][A-Za-z0-9_$.-]{0,22}$/.test(key2) && !/(?:sk|rk|pk)_(?:live|test)_|whsec_/i.test(key2) ? JSON.stringify(key2) : "[withheld field key]";
|
|
65953
66105
|
const unsupportedFields = (names, label) => unresolved(`unknown discovery controls have no root-exclusion adapter for ${label}${names.slice(0, 8).map(fieldKey).join(", ")}${names.length > 8 ? ", additional keys withheld" : ""} in ${quotedPath(canonical9(configPath))}`);
|
|
65954
|
-
const
|
|
66106
|
+
const adapted = agent === "codex" ? codexRootFreeControls(config) : agent === "opencode" && Array.isArray(config.instructions) && config.instructions.length === 0 ? ["instructions"] : [];
|
|
66107
|
+
const unsupportedControls = controls.filter((key2) => !supported[agent].includes(key2) && !adapted.includes(key2));
|
|
65955
66108
|
if (unsupportedControls.length)
|
|
65956
66109
|
unsupportedFields(unsupportedControls, "");
|
|
65957
66110
|
if (config.hooks !== undefined && !object6(config.hooks))
|
|
65958
66111
|
unresolved("hooks must be an object");
|
|
65959
|
-
const
|
|
66112
|
+
const managedSupervisorOnly = () => {
|
|
66113
|
+
const supervisor = exclusion.managedHook?.supervisor;
|
|
66114
|
+
if (agent !== "hermes" || !supervisor || typeof supervisor.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(supervisor.sha256) || !object6(config.hooks))
|
|
66115
|
+
return false;
|
|
66116
|
+
let expected;
|
|
66117
|
+
try {
|
|
66118
|
+
expected = hermesHookDefinitions(supervisor);
|
|
66119
|
+
} catch {
|
|
66120
|
+
return false;
|
|
66121
|
+
}
|
|
66122
|
+
for (const [event, entries2] of Object.entries(config.hooks)) {
|
|
66123
|
+
const want = expected[event];
|
|
66124
|
+
if (!want || !Array.isArray(entries2) || entries2.length !== 1 || !object6(entries2[0]))
|
|
66125
|
+
return false;
|
|
66126
|
+
const entry = entries2[0];
|
|
66127
|
+
if (Object.keys(entry).sort().join("\x00") !== Object.keys(want).sort().join("\x00") || Object.keys(want).some((key2) => entry[key2] !== want[key2]))
|
|
66128
|
+
return false;
|
|
66129
|
+
}
|
|
66130
|
+
try {
|
|
66131
|
+
return hashRawDiscoveryFile(canonical9(supervisor.path), budget3) === supervisor.sha256;
|
|
66132
|
+
} catch {
|
|
66133
|
+
return false;
|
|
66134
|
+
}
|
|
66135
|
+
};
|
|
66136
|
+
const managedBridgeOnly = (agent === "gemini" || agent === "cursor") && exclusion.managedHook !== undefined && hooksAreManagedBridge(agent, config.hooks, exclusion.managedHook) || managedSupervisorOnly();
|
|
66137
|
+
const hasHooks = controls.includes("hooks") && config.hooks !== undefined && Object.keys(config.hooks).length > 0 && !managedBridgeOnly;
|
|
65960
66138
|
let hasPluginConfigs = false, hasFunctionHookEnvironment = false;
|
|
65961
66139
|
const verifyCurrentOpaqueSettings = (label) => {
|
|
65962
66140
|
const retained = exclusion.retained;
|
|
@@ -65971,7 +66149,7 @@ function resolveDiscovery(options, exclusion) {
|
|
|
65971
66149
|
unresolved(`non-enumerable ${label} require a current settings review`);
|
|
65972
66150
|
};
|
|
65973
66151
|
if (hasHooks)
|
|
65974
|
-
verifyCurrentOpaqueSettings("hooks");
|
|
66152
|
+
reviewRemedy(canonical9(configPath), () => verifyCurrentOpaqueSettings("hooks"));
|
|
65975
66153
|
if (agent === "claude") {
|
|
65976
66154
|
if (config.env !== undefined) {
|
|
65977
66155
|
if (!object6(config.env) || Object.keys(config.env).length > 512)
|
|
@@ -65984,7 +66162,7 @@ function resolveDiscovery(options, exclusion) {
|
|
|
65984
66162
|
if (hasFunctionHookEnvironment) {
|
|
65985
66163
|
if (!["0", "1"].includes(config.env.CLAUDE_CODE_ENABLE_FUNCTION_HOOKS))
|
|
65986
66164
|
unresolved("malformed Claude env.CLAUDE_CODE_ENABLE_FUNCTION_HOOKS flag");
|
|
65987
|
-
verifyCurrentOpaqueSettings("function-hook environment controls");
|
|
66165
|
+
reviewRemedy(canonical9(configPath), () => verifyCurrentOpaqueSettings("function-hook environment controls"));
|
|
65988
66166
|
}
|
|
65989
66167
|
}
|
|
65990
66168
|
if (config.pluginConfigs !== undefined) {
|
|
@@ -66011,7 +66189,7 @@ function resolveDiscovery(options, exclusion) {
|
|
|
66011
66189
|
}
|
|
66012
66190
|
hasPluginConfigs = Object.keys(config.pluginConfigs).length > 0;
|
|
66013
66191
|
if (hasPluginConfigs)
|
|
66014
|
-
verifyCurrentOpaqueSettings("plugin configurations");
|
|
66192
|
+
reviewRemedy(canonical9(configPath), () => verifyCurrentOpaqueSettings("plugin configurations"));
|
|
66015
66193
|
}
|
|
66016
66194
|
if (config.enabledPlugins !== undefined && !object6(config.enabledPlugins))
|
|
66017
66195
|
unresolved("enabledPlugins must be an object");
|
|
@@ -66061,7 +66239,9 @@ function resolveDiscovery(options, exclusion) {
|
|
|
66061
66239
|
unresolved("malformed Sumi plugins");
|
|
66062
66240
|
}
|
|
66063
66241
|
if (agent === "hermes" && config.skills !== undefined) {
|
|
66064
|
-
keys3(config.skills, ["external_dirs"], "Hermes skills");
|
|
66242
|
+
keys3(config.skills, ["external_dirs", "creation_nudge_interval"], "Hermes skills");
|
|
66243
|
+
if (config.skills.creation_nudge_interval !== undefined && !(Number.isSafeInteger(config.skills.creation_nudge_interval) && config.skills.creation_nudge_interval >= 0))
|
|
66244
|
+
unresolved("malformed Hermes skills creation_nudge_interval");
|
|
66065
66245
|
if (config.skills.external_dirs !== undefined)
|
|
66066
66246
|
paths(typeof config.skills.external_dirs === "string" ? [config.skills.external_dirs] : config.skills.external_dirs, "Hermes external directories");
|
|
66067
66247
|
}
|
|
@@ -66081,10 +66261,21 @@ function resolveDiscovery(options, exclusion) {
|
|
|
66081
66261
|
unresolved(`malformed Codex ${key2}`);
|
|
66082
66262
|
for (const value of Object.values(config.plugins ?? {}))
|
|
66083
66263
|
keys3(value, ["enabled"], "Codex plugin registration");
|
|
66084
|
-
for (const value of Object.values(config.marketplaces ?? {}))
|
|
66085
|
-
keys3(value, ["source_type", "source"], "Codex marketplace registration");
|
|
66264
|
+
for (const value of Object.values(config.marketplaces ?? {})) {
|
|
66265
|
+
keys3(value, ["source_type", "source", "last_updated"], "Codex marketplace registration");
|
|
66266
|
+
if (value.last_updated !== undefined && (typeof value.last_updated !== "string" || !CODEX_MARKETPLACE_TIMESTAMP.test(value.last_updated)))
|
|
66267
|
+
unresolved("malformed Codex marketplace refresh time");
|
|
66268
|
+
}
|
|
66086
66269
|
if (config.skills !== undefined)
|
|
66087
|
-
keys3(config.skills, ["config", "bundled"], "Codex skills");
|
|
66270
|
+
keys3(config.skills, ["config", "bundled", "native_allowed_paths"], "Codex skills");
|
|
66271
|
+
if (config.skills?.native_allowed_paths !== undefined) {
|
|
66272
|
+
for (const value of paths(config.skills.native_allowed_paths, "Codex native allowed skill paths")) {
|
|
66273
|
+
if (!isAbsolute30(value))
|
|
66274
|
+
unresolved("relative Codex native allowed skill path");
|
|
66275
|
+
const path = canonical9(value);
|
|
66276
|
+
roots.add(path.endsWith(`${sep14}SKILL.md`) ? dirname29(path) : path);
|
|
66277
|
+
}
|
|
66278
|
+
}
|
|
66088
66279
|
if (config.skills?.bundled !== undefined) {
|
|
66089
66280
|
keys3(config.skills.bundled, ["enabled"], "Codex bundled skills");
|
|
66090
66281
|
if (typeof config.skills.bundled.enabled !== "boolean")
|
|
@@ -66110,7 +66301,7 @@ function resolveDiscovery(options, exclusion) {
|
|
|
66110
66301
|
if (Object.keys(config.enabledPlugins ?? config.plugins ?? config.extensions ?? {}).length > 512)
|
|
66111
66302
|
unresolved("unbounded native registrations");
|
|
66112
66303
|
if (hasHooks || hasPluginConfigs || hasFunctionHookEnvironment)
|
|
66113
|
-
verifyOpaqueClosure([]);
|
|
66304
|
+
reviewRemedy(canonical9(configPath), () => verifyOpaqueClosure([]));
|
|
66114
66305
|
}
|
|
66115
66306
|
if (options.reviewed && (options.reviewed.version !== 1 || !Array.isArray(options.reviewed.agents) || options.reviewed.agents.some((item) => !item || typeof item !== "object") || new Set(options.reviewed.agents.map((item) => item.agent)).size !== options.reviewed.agents.length))
|
|
66116
66307
|
throw new Error("Invalid --discovery-inputs version or agents");
|
|
@@ -66142,7 +66333,9 @@ function resolveDiscovery(options, exclusion) {
|
|
|
66142
66333
|
const target = join46(path, name);
|
|
66143
66334
|
if (!lstatSync35(target).isFile())
|
|
66144
66335
|
unresolved("local plugin packages need a dedicated discovery adapter");
|
|
66145
|
-
witness(target);
|
|
66336
|
+
const text3 = witness(target);
|
|
66337
|
+
if (exclusion && text3 !== null)
|
|
66338
|
+
sumiLocalPluginTexts.set(canonical9(target), text3);
|
|
66146
66339
|
}
|
|
66147
66340
|
}
|
|
66148
66341
|
for (const root2 of roots) {
|
|
@@ -66276,38 +66469,126 @@ function resolveDiscovery(options, exclusion) {
|
|
|
66276
66469
|
const unique = combined.filter((source, index) => combined.findIndex((candidate2) => isDeepStrictEqual15(candidate2, source)) === index);
|
|
66277
66470
|
return { ...supplied, roots: [...new Set([...roots, ...supplied.roots])].sort(), sources: unique, ...agent === "gemini" ? { builtinNames } : {} };
|
|
66278
66471
|
}
|
|
66279
|
-
|
|
66472
|
+
const reviewedManifestAbsence = (root2, manifestPath) => {
|
|
66473
|
+
const retained = exclusion?.retained;
|
|
66474
|
+
if (agent !== "claude" || retained?.method !== "reviewed" || ![join46(root2, "skills"), root2].some((path) => retained.roots.includes(path)))
|
|
66475
|
+
return false;
|
|
66476
|
+
const saved = retained.sources.filter((source) => source.path === canonical9(manifestPath) && source.hashMode === "path-bytes" && source.sha256 !== null && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined);
|
|
66477
|
+
return saved.length > 0 && saved.every((source) => projected(source, undefined, budget3) === source.sha256);
|
|
66478
|
+
};
|
|
66479
|
+
const reviewedInstalledCopy = (root2, path, identity6) => {
|
|
66480
|
+
const retained = exclusion?.retained;
|
|
66481
|
+
if (agent !== "codex" || retained?.method !== "reviewed")
|
|
66482
|
+
return false;
|
|
66483
|
+
const inside = relative9(root2, canonical9(path));
|
|
66484
|
+
if (!inside || inside === ".." || inside.startsWith(`..${sep14}`) || isAbsolute30(inside))
|
|
66485
|
+
return false;
|
|
66486
|
+
const parent = canonical9(join46(nativeHome, "plugins/cache", identity6.marketplace, identity6.name));
|
|
66487
|
+
const current = hashRawDiscoveryFile(canonical9(path), budget3);
|
|
66488
|
+
if (current === null)
|
|
66489
|
+
return false;
|
|
66490
|
+
return retained.sources.some((source) => {
|
|
66491
|
+
if (source.sha256 !== current || source.format !== undefined || source.fields !== undefined || ![undefined, "bytes"].includes(source.hashMode) || source.managedPlugins !== undefined || source.reviewArtifact !== undefined || !source.path.startsWith(parent + sep14))
|
|
66492
|
+
return false;
|
|
66493
|
+
const [version, ...rest2] = source.path.slice(parent.length + 1).split(sep14);
|
|
66494
|
+
return Boolean(version) && version !== "." && version !== ".." && rest2.join(sep14) === inside;
|
|
66495
|
+
});
|
|
66496
|
+
};
|
|
66497
|
+
const verifyCodexPluginHookClosure = (root2, identity6, marketplaceSource, servers, declarations) => {
|
|
66498
|
+
const retained = exclusion.retained;
|
|
66499
|
+
if (retained.codexSemanticCache || retained.codexInstallationInputs || retained.codexRetiredMaterializations)
|
|
66500
|
+
return verifyOpaqueClosure(declarations);
|
|
66501
|
+
const within2 = (base2, path) => path === base2 || path.startsWith(base2 + sep14);
|
|
66502
|
+
const executables = [];
|
|
66503
|
+
for (const server of servers) {
|
|
66504
|
+
const row = config.mcp_servers?.[server];
|
|
66505
|
+
if (server === "codex_apps" || server === "_codex_apps" || !isCodexLocalStdioMcpServer(server, row))
|
|
66506
|
+
unresolved("plugin hook MCP server is not a configured local stdio server");
|
|
66507
|
+
for (const value of [row.command, ...row.args ?? []])
|
|
66508
|
+
for (const candidate2 of [value, value.slice(value.indexOf("=") + 1)]) {
|
|
66509
|
+
if (!isAbsolute30(candidate2) || /[\0$]/.test(candidate2) || executables.length >= AGENT_POLICY_LIMITS.discoveryRoots)
|
|
66510
|
+
continue;
|
|
66511
|
+
const path = canonical9(candidate2);
|
|
66512
|
+
if (lstatSync35(path, { throwIfNoEntry: false })?.isFile())
|
|
66513
|
+
executables.push(path);
|
|
66514
|
+
}
|
|
66515
|
+
}
|
|
66516
|
+
const cacheRoot = canonical9(join46(nativeHome, "plugins/cache"));
|
|
66517
|
+
const ownCache = canonical9(join46(nativeHome, "plugins/cache", identity6.marketplace, identity6.name));
|
|
66518
|
+
const ownCatalog = canonical9(join46(marketplaceSource, ".agents/plugins/marketplace.json"));
|
|
66519
|
+
const account = canonical9(home);
|
|
66520
|
+
const marketplaceRoots = Object.values(config.marketplaces ?? {}).filter((market) => market?.source_type === "local" && typeof market.source === "string" && isAbsolute30(market.source)).map((market) => canonical9(market.source)).filter((path) => !within2(path, account));
|
|
66521
|
+
const otherPlugin = (path) => !within2(root2, path) && !within2(ownCache, path) && path !== ownCatalog && !executables.includes(path) && (within2(cacheRoot, path) || marketplaceRoots.some((base2) => within2(base2, path)));
|
|
66522
|
+
const sources2 = retained.sources.filter((source) => source.path !== canonical9(configPath) && !otherPlugin(source.path));
|
|
66523
|
+
if (sources2.some((source) => source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || source.reviewArtifact !== undefined || ![undefined, "bytes", "path-bytes", "executable-v1"].includes(source.hashMode)))
|
|
66524
|
+
return verifyOpaqueClosure(declarations);
|
|
66525
|
+
const declared = declarations.map((path) => relative9(root2, path));
|
|
66526
|
+
const declaration = (path) => declarations.includes(path) || path.startsWith(ownCache + sep14) && declared.includes(path.slice(ownCache.length + 1).split(sep14).slice(1).join(sep14));
|
|
66527
|
+
if (!sources2.some((source) => !declaration(source.path) && source.sha256 !== null))
|
|
66528
|
+
unresolved("opaque executable dependency closure is unavailable");
|
|
66529
|
+
for (const source of sources2)
|
|
66530
|
+
if (projected(source, undefined, budget3) !== source.sha256) {
|
|
66531
|
+
throw new NativeDiscoveryWitnessChange("input", source.path, `Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
|
|
66532
|
+
}
|
|
66533
|
+
verifyDiscoveryDirectories((retained.directories ?? []).filter((directory2) => !otherPlugin(directory2.path)));
|
|
66534
|
+
};
|
|
66535
|
+
const readCodexHookDeclaration = (text3, path) => {
|
|
66536
|
+
if (text3 === null)
|
|
66537
|
+
return null;
|
|
66538
|
+
try {
|
|
66539
|
+
return codexPluginHookHandlers(parseConfig(text3, path));
|
|
66540
|
+
} catch {
|
|
66541
|
+
return null;
|
|
66542
|
+
}
|
|
66543
|
+
};
|
|
66544
|
+
function plugin(root2, codex) {
|
|
66280
66545
|
root2 = canonical9(root2);
|
|
66281
66546
|
safe6(root2);
|
|
66282
66547
|
if (exclusion)
|
|
66283
66548
|
roots.add(root2);
|
|
66284
66549
|
const manifestPath = join46(root2, agent === "claude" ? ".claude-plugin/plugin.json" : ".codex-plugin/plugin.json");
|
|
66285
66550
|
const raw = witness(manifestPath, undefined, undefined, agent === "claude" ? "claude-plugin-manifest-v1" : undefined);
|
|
66286
|
-
if (raw === null)
|
|
66551
|
+
if (raw === null && !reviewedManifestAbsence(root2, manifestPath))
|
|
66287
66552
|
unresolved("plugin manifest missing");
|
|
66288
|
-
const manifest = parseConfig(raw, manifestPath);
|
|
66553
|
+
const manifest = raw === null ? {} : parseConfig(raw, manifestPath);
|
|
66289
66554
|
if (exclusion && Object.keys(manifest).some((key2) => /skill|plugin|hook|marketplace|extension|instruction/i.test(key2) && !["skills", "hooks"].includes(key2)))
|
|
66290
66555
|
unresolved("unknown plugin discovery controls");
|
|
66291
66556
|
const hooks = witness(join46(root2, "hooks/hooks.json"));
|
|
66292
|
-
if (manifest.hooks !== undefined || hooks !== null)
|
|
66293
|
-
|
|
66294
|
-
|
|
66295
|
-
|
|
66296
|
-
|
|
66297
|
-
|
|
66298
|
-
|
|
66299
|
-
|
|
66300
|
-
|
|
66301
|
-
|
|
66302
|
-
|
|
66303
|
-
|
|
66304
|
-
|
|
66305
|
-
|
|
66306
|
-
|
|
66307
|
-
|
|
66308
|
-
|
|
66309
|
-
|
|
66310
|
-
|
|
66557
|
+
if (manifest.hooks !== undefined || hooks !== null)
|
|
66558
|
+
reviewRemedy(canonical9(hooks !== null ? join46(root2, "hooks/hooks.json") : manifestPath), () => {
|
|
66559
|
+
if (!exclusion || exclusion.retained?.method !== "reviewed")
|
|
66560
|
+
unresolved("plugin hooks require a separate no-skill-injection review");
|
|
66561
|
+
const required = [manifestPath, ...hooks !== null ? [join46(root2, "hooks/hooks.json")] : []];
|
|
66562
|
+
const scoped2 = agent === "codex" && codex !== undefined ? [] : undefined;
|
|
66563
|
+
if (manifest.hooks !== undefined) {
|
|
66564
|
+
if (scoped2 && manifest.hooks && typeof manifest.hooks === "object" && !Array.isArray(manifest.hooks)) {
|
|
66565
|
+
const inline = codexPluginHookHandlers(manifest.hooks);
|
|
66566
|
+
if (!inline)
|
|
66567
|
+
unresolved("unsupported inline plugin hooks");
|
|
66568
|
+
scoped2.push(inline);
|
|
66569
|
+
} else {
|
|
66570
|
+
if (typeof manifest.hooks !== "string")
|
|
66571
|
+
unresolved("unsupported plugin hooks target");
|
|
66572
|
+
const hook = resolve29(root2, manifest.hooks);
|
|
66573
|
+
if (!hook.startsWith(root2 + sep14))
|
|
66574
|
+
unresolved("plugin hooks target escapes its root");
|
|
66575
|
+
required.push(hook);
|
|
66576
|
+
if (scoped2)
|
|
66577
|
+
scoped2.push(readCodexHookDeclaration(witness(hook), hook));
|
|
66578
|
+
}
|
|
66579
|
+
}
|
|
66580
|
+
if (scoped2 && hooks !== null)
|
|
66581
|
+
scoped2.push(readCodexHookDeclaration(hooks, join46(root2, "hooks/hooks.json")));
|
|
66582
|
+
for (const path of required) {
|
|
66583
|
+
const saved = exclusion.retained.sources.filter((source) => source.path === canonical9(path) && source.sha256 !== null && source.format === undefined && source.fields === undefined);
|
|
66584
|
+
if (saved.length ? saved.some((source) => projected(source, undefined, budget3) !== source.sha256) : !(scoped2 && reviewedInstalledCopy(root2, path, codex)))
|
|
66585
|
+
unresolved("plugin hooks require current independently reviewed sources", canonical9(path));
|
|
66586
|
+
}
|
|
66587
|
+
if (scoped2 && scoped2.every((item) => item !== null && item.commands === 0)) {
|
|
66588
|
+
verifyCodexPluginHookClosure(root2, codex, codex.source, new Set(scoped2.flatMap((item) => [...item.servers])), required.map((path) => canonical9(path)));
|
|
66589
|
+
} else
|
|
66590
|
+
verifyOpaqueClosure(required.map((path) => canonical9(path)));
|
|
66591
|
+
});
|
|
66311
66592
|
const commands = join46(root2, "commands");
|
|
66312
66593
|
safe6(commands);
|
|
66313
66594
|
if (agent === "claude" && (manifest.commands !== undefined || lstatSync35(commands, { throwIfNoEntry: false })?.isDirectory() && entries(commands).length))
|
|
@@ -66362,7 +66643,7 @@ function resolveDiscovery(options, exclusion) {
|
|
|
66362
66643
|
const catalog = parseConfig(catalogText, join46(market.source, ".agents/plugins/marketplace.json")), matches2 = Array.isArray(catalog.plugins) ? catalog.plugins.filter((item) => item.name === name) : [];
|
|
66363
66644
|
if (matches2.length !== 1 || matches2[0]?.source?.source !== "local" || typeof matches2[0]?.source?.path !== "string")
|
|
66364
66645
|
unresolved("plugin catalog source is missing or ambiguous");
|
|
66365
|
-
plugin(resolve29(market.source, matches2[0].source.path));
|
|
66646
|
+
plugin(resolve29(market.source, matches2[0].source.path), exclusion ? { marketplace, name, source: market.source } : undefined);
|
|
66366
66647
|
}
|
|
66367
66648
|
} else if (agent === "opencode") {
|
|
66368
66649
|
if (witness(join46(home, ".config/opencode/opencode.jsonc")) !== null || witness(join46(home, ".config/opencode/config.json")) !== null)
|
|
@@ -66379,7 +66660,11 @@ function resolveDiscovery(options, exclusion) {
|
|
|
66379
66660
|
if (lstatSync35(localPlugins, { throwIfNoEntry: false }) && entries(localPlugins).some((name) => name !== "skills-cli.js"))
|
|
66380
66661
|
unresolved("additional local plugin hooks require review");
|
|
66381
66662
|
} else if (agent === "sumi") {
|
|
66382
|
-
|
|
66663
|
+
const pinnedLocalPlugin = (source) => {
|
|
66664
|
+
const retained = exclusion?.retained, text3 = sumiLocalPluginTexts.get(source.path);
|
|
66665
|
+
return retained?.method === "reviewed" && text3 !== undefined && sumiPluginLoadsOnlyBuiltins(text3) && retained.sources.some((saved) => saved.path === source.path && saved.sha256 === source.sha256 && saved.sha256 !== null && [undefined, "bytes"].includes(saved.hashMode) && saved.format === undefined && saved.fields === undefined && saved.managedPlugins === undefined && saved.reviewArtifact === undefined);
|
|
66666
|
+
};
|
|
66667
|
+
if ((config.plugin?.length ?? 0) > 0 || (config.plugins?.length ?? 0) > 0 || sources.some((source) => source.path !== canonical9(configPath) && source.path !== canonical9(join46(sumiConfigDirectory(home), "sumi.jsonc")) && source.sha256 !== null && !pinnedLocalPlugin(source)))
|
|
66383
66668
|
unresolved("external or foreign local plugins require review");
|
|
66384
66669
|
} else if (agent === "hermes") {
|
|
66385
66670
|
for (const directory2 of [join46(home, ".hermes/plugins"), join46(home, ".hermes/hermes-agent")]) {
|
|
@@ -66413,17 +66698,18 @@ function resolveDiscovery(options, exclusion) {
|
|
|
66413
66698
|
unresolved("native discovery roots exceed their bound");
|
|
66414
66699
|
return { agent, roots: [...roots].sort(), sources, method: "automatic", ...agent === "gemini" ? { builtinNames } : {}, ...agent === "hermes" ? { directories: captureDiscoveryDirectories([join46(home, ".hermes/plugins"), join46(home, ".hermes/hermes-agent")].map((path) => canonical9(path))) } : {} };
|
|
66415
66700
|
}
|
|
66416
|
-
var DISCOVERY_RUNTIME_COMMANDS, DISCOVERY_ROOT_UNRESOLVED = "DISCOVERY_ROOT_UNRESOLVED", quotedPath = (path) => JSON.stringify(path).replace(/[^\x20-\x7e]/g, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`), realpathOrNull = (path) => {
|
|
66701
|
+
var DISCOVERY_RUNTIME_COMMANDS, DISCOVERY_ROOT_UNRESOLVED = "DISCOVERY_ROOT_UNRESOLVED", NativeDiscoveryUnresolved, quotedPath = (path) => JSON.stringify(path).replace(/[^\x20-\x7e]/g, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`), realpathOrNull = (path) => {
|
|
66417
66702
|
try {
|
|
66418
66703
|
return realpathSync12(path);
|
|
66419
66704
|
} catch {
|
|
66420
66705
|
return null;
|
|
66421
66706
|
}
|
|
66422
|
-
}, digest2 = (text3) => createHash33("sha256").update(text3).digest("hex"), CLAUDE_CONTROL_WITNESS_MODES, CLAUDE_CONTROL_FILES, ADOPTABLE_SETTINGS_MATCHES, CLAUDE_PROJECT_DISCOVERY_KEYS;
|
|
66707
|
+
}, digest2 = (text3) => createHash33("sha256").update(text3).digest("hex"), CLAUDE_CONTROL_WITNESS_MODES, CLAUDE_CONTROL_FILES, ADOPTABLE_SETTINGS_MATCHES, CLAUDE_PROJECT_DISCOVERY_KEYS, CODEX_MARKETPLACE_TIMESTAMP, CODEX_HOOK_EVENTS;
|
|
66423
66708
|
var init_agent_discovery = __esm(() => {
|
|
66424
66709
|
init_sumi_settings_witness();
|
|
66425
66710
|
init_agent_policy_limits();
|
|
66426
66711
|
init_agent_hermes();
|
|
66712
|
+
init_agent_adapters();
|
|
66427
66713
|
init_agent_discovery_directories();
|
|
66428
66714
|
init_agent_discovery_bytes();
|
|
66429
66715
|
init_plugin_projection_store();
|
|
@@ -66449,10 +66735,24 @@ var init_agent_discovery = __esm(() => {
|
|
|
66449
66735
|
init_codex_semantic_cache_witness();
|
|
66450
66736
|
init_agent_discovery_directories();
|
|
66451
66737
|
DISCOVERY_RUNTIME_COMMANDS = Object.freeze({ gemini: "gemini" });
|
|
66738
|
+
NativeDiscoveryUnresolved = class NativeDiscoveryUnresolved extends Error {
|
|
66739
|
+
agent;
|
|
66740
|
+
detail;
|
|
66741
|
+
source;
|
|
66742
|
+
reviewable = false;
|
|
66743
|
+
constructor(agent, detail, source) {
|
|
66744
|
+
super(`Native discovery is unresolved (${agent}: ${detail}); provide a reviewed --discovery-inputs file`);
|
|
66745
|
+
this.agent = agent;
|
|
66746
|
+
this.detail = detail;
|
|
66747
|
+
this.source = source;
|
|
66748
|
+
}
|
|
66749
|
+
};
|
|
66452
66750
|
CLAUDE_CONTROL_WITNESS_MODES = new Set(["claude-settings-v1", "claude-settings-v2", "claude-settings-v3", "claude-settings-v4", "claude-settings-v5", "claude-marketplace-registry", "claude-marketplace-registry-v2", "claude-marketplace-entry-v1", "claude-plugin-registry"]);
|
|
66453
66751
|
CLAUDE_CONTROL_FILES = new Set(["settings.json", "settings.local.json", "installed_plugins.json", "known_marketplaces.json"]);
|
|
66454
66752
|
ADOPTABLE_SETTINGS_MATCHES = Object.freeze(["exact", "screen-reader-receipt"]);
|
|
66455
66753
|
CLAUDE_PROJECT_DISCOVERY_KEYS = Object.freeze(["enabledPlugins", "extraKnownMarketplaces", "additionalMarketplaces", "skillOverrides"]);
|
|
66754
|
+
CODEX_MARKETPLACE_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
66755
|
+
CODEX_HOOK_EVENTS = new Set(["PreToolUse", "PermissionRequest", "PostToolUse", "PreCompact", "PostCompact", "SessionStart", "SessionEnd", "UserPromptSubmit", "SubagentStart", "SubagentStop", "Stop", "Interrupt"]);
|
|
66456
66756
|
});
|
|
66457
66757
|
|
|
66458
66758
|
// src/lib/authoring-path.ts
|
|
@@ -66485,6 +66785,11 @@ function physicalPath(path) {
|
|
|
66485
66785
|
}
|
|
66486
66786
|
return resolve30(realpathSync13(cursor), ...suffix);
|
|
66487
66787
|
}
|
|
66788
|
+
function portableAuthoringDiscoveryRefusal(error, destination) {
|
|
66789
|
+
const head2 = `Native discovery is unresolved (${error.agent}: ${error.detail}).${error.source ? ` Checked source: ${error.source}.` : ""}`;
|
|
66790
|
+
const review = `skills hook install --agent ${error.agent} --discovery-inputs <reviewed-file> --apply`;
|
|
66791
|
+
return error.reviewable ? `${head2} Portable Skills authoring stays refused: an unreviewed ${error.agent} hook or plugin option could load skills written to ${destination}. This command takes no --discovery-inputs option; review the current ${error.agent} configuration with ${review}, then retry.` : `${head2} Portable Skills authoring stays refused: Skills cannot list every skill root this ${error.agent} configuration registers, so it cannot rule out ${destination}. This command takes no --discovery-inputs option; correct that ${error.agent} configuration entry, then retry. Where a reviewed discovery binding covers it, record one with ${review}.`;
|
|
66792
|
+
}
|
|
66488
66793
|
function contains2(root2, path) {
|
|
66489
66794
|
const boundary = root2.endsWith(sep15) ? root2 : root2 + sep15;
|
|
66490
66795
|
return path === root2 || path.startsWith(boundary);
|
|
@@ -66530,7 +66835,10 @@ function configuredRoots(home, explicitHome) {
|
|
|
66530
66835
|
if (recorded && (!executable2 || executable2.target !== recorded.target || onPath && onPath.target !== recorded.target) || !executable2 && (binding.builtinNames?.length || binding.agent === "gemini" && binding.sources.some((source) => basename19(source.path) === "package.json"))) {
|
|
66531
66836
|
throw new Error("Native discovery runtime target is unresolved or changed");
|
|
66532
66837
|
}
|
|
66533
|
-
|
|
66838
|
+
const managedCommand = current.commands?.[agent], managedProfile = current.profiles?.[agent];
|
|
66839
|
+
const supervisor = current.supervisors?.[agent];
|
|
66840
|
+
const managedHook = (binding.agent === "gemini" || binding.agent === "cursor") && typeof managedCommand === "string" && typeof managedProfile === "string" ? { command: managedCommand, profile: managedProfile } : binding.agent === "hermes" && typeof managedCommand === "string" && typeof managedProfile === "string" && supervisor && typeof supervisor === "object" && typeof supervisor.path === "string" && typeof supervisor.runtime === "string" && typeof supervisor.sha256 === "string" ? { command: managedCommand, profile: managedProfile, supervisor: { path: supervisor.path, runtime: supervisor.runtime, sha256: supervisor.sha256 } } : undefined;
|
|
66841
|
+
roots.push(...resolveAgentDiscoveryExclusionRoots({ home, agent: binding.agent, canonical: physicalPath, retained: binding, executable: executable2, narrowed, ...managedHook ? { managedHook } : {} }));
|
|
66534
66842
|
}
|
|
66535
66843
|
};
|
|
66536
66844
|
if (homePolicy)
|
|
@@ -66582,11 +66890,18 @@ function assertPortableAuthoringPath(path, options = {}) {
|
|
|
66582
66890
|
if (native) {
|
|
66583
66891
|
throw new Error(`Refusing portable Skills authoring below native discovery root '${native}': ${lexical}. Set HASNA_SKILLS_DIR to a private authoring corpus outside native roots.`);
|
|
66584
66892
|
}
|
|
66585
|
-
const roots =
|
|
66586
|
-
|
|
66587
|
-
|
|
66588
|
-
|
|
66589
|
-
|
|
66893
|
+
const roots = [];
|
|
66894
|
+
try {
|
|
66895
|
+
roots.push(...configuredRoots(home, options.homeDir !== undefined));
|
|
66896
|
+
for (const value of selected) {
|
|
66897
|
+
if (physicalPath(value.path) === physicalPath(join47(home, `.${value.agent}`)))
|
|
66898
|
+
continue;
|
|
66899
|
+
roots.push(...resolveAgentDiscoveryExclusionRoots({ home, agent: value.agent, nativeHome: value.path, canonical: physicalPath, executable: null }));
|
|
66900
|
+
}
|
|
66901
|
+
} catch (error) {
|
|
66902
|
+
if (error instanceof NativeDiscoveryUnresolved)
|
|
66903
|
+
throw new Error(portableAuthoringDiscoveryRefusal(error, lexical));
|
|
66904
|
+
throw error;
|
|
66590
66905
|
}
|
|
66591
66906
|
for (const root2 of roots) {
|
|
66592
66907
|
if (candidates.some((candidate2) => [resolve30(root2), physicalPath(root2)].some((nativeRoot) => contains2(nativeRoot, candidate2) || contains2(candidate2, nativeRoot)))) {
|
|
@@ -122238,10 +122553,403 @@ var init_completion = __esm(() => {
|
|
|
122238
122553
|
categoryNames = CATEGORIES.map((c) => c);
|
|
122239
122554
|
});
|
|
122240
122555
|
|
|
122556
|
+
// src/lib/prepare-skill.ts
|
|
122557
|
+
import { createHash as createHash47, randomUUID as randomUUID15 } from "crypto";
|
|
122558
|
+
import { chmodSync as chmodSync5, existsSync as existsSync35, lstatSync as lstatSync47, mkdirSync as mkdirSync30, mkdtempSync as mkdtempSync7, readFileSync as readFileSync45, readdirSync as readdirSync27, renameSync as renameSync18, rmSync as rmSync10, writeFileSync as writeFileSync28 } from "fs";
|
|
122559
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
122560
|
+
import { dirname as dirname47, isAbsolute as isAbsolute43, join as join73, relative as relative15, resolve as resolve39, sep as sep21 } from "path";
|
|
122561
|
+
function prepareSkill(name, options) {
|
|
122562
|
+
const normalized2 = normalizePortableSkillName(name);
|
|
122563
|
+
if (!new RegExp(SEMVER_PATTERN).test(options.version))
|
|
122564
|
+
throw new Error("--version must be a semantic version, for example 0.2.0.");
|
|
122565
|
+
const path = join73(getPortableSkillsRootReadOnly(options), normalized2);
|
|
122566
|
+
assertPortableAuthoringPath(path, options);
|
|
122567
|
+
const before2 = snapshot2(path);
|
|
122568
|
+
const manifestFile = before2.files.find((file2) => file2.path === "skill.json");
|
|
122569
|
+
if (!manifestFile)
|
|
122570
|
+
throw new Error(`Skill '${normalized2}' needs skill.json. Import the source with skills port first.`);
|
|
122571
|
+
let manifest;
|
|
122572
|
+
try {
|
|
122573
|
+
const parsed = JSON.parse(manifestFile.bytes.toString("utf8"));
|
|
122574
|
+
if (!isRecord9(parsed))
|
|
122575
|
+
throw new Error("not an object");
|
|
122576
|
+
manifest = parsed;
|
|
122577
|
+
} catch {
|
|
122578
|
+
throw new Error("skill.json must contain a JSON object; no files were changed.");
|
|
122579
|
+
}
|
|
122580
|
+
const previousVersion = manifest.version;
|
|
122581
|
+
if (typeof previousVersion !== "string" || !new RegExp(SEMVER_PATTERN).test(previousVersion)) {
|
|
122582
|
+
throw new Error("skill.json must declare its current semantic version before preparation.");
|
|
122583
|
+
}
|
|
122584
|
+
if (manifest.kind !== undefined && manifest.kind !== "instruction" && manifest.kind !== "executable") {
|
|
122585
|
+
throw new Error("skill.json has an invalid kind; correct it before preparation.");
|
|
122586
|
+
}
|
|
122587
|
+
const kind = options.kind ?? manifest.kind;
|
|
122588
|
+
if (kind !== "instruction" && kind !== "executable") {
|
|
122589
|
+
throw new Error("This legacy manifest has no explicit kind. Pass --kind instruction or --kind executable; helper scripts alone do not determine a skill's kind.");
|
|
122590
|
+
}
|
|
122591
|
+
const document3 = before2.files.find((file2) => file2.path === "SKILL.md");
|
|
122592
|
+
const documentKind = document3 ? parseSkillFrontmatter(document3.bytes.toString("utf8"))?.kind : undefined;
|
|
122593
|
+
if (documentKind !== undefined && documentKind !== kind) {
|
|
122594
|
+
throw new Error("SKILL.md kind conflicts with the candidate skill.json kind. Review and align both declarations (or remove the optional frontmatter kind), then prepare again; no files were changed.");
|
|
122595
|
+
}
|
|
122596
|
+
if (manifest.provenance !== undefined && !isRecord9(manifest.provenance))
|
|
122597
|
+
throw new Error("skill.json provenance must be an object.");
|
|
122598
|
+
const provenance = manifest.provenance;
|
|
122599
|
+
const candidate2 = { ...manifest, version: options.version, kind, provenance: { ...provenance } };
|
|
122600
|
+
const staging = mkdtempSync7(join73(tmpdir2(), "skills-prepare-"));
|
|
122601
|
+
try {
|
|
122602
|
+
for (const directory3 of before2.directories)
|
|
122603
|
+
mkdirSync30(join73(staging, directory3), { recursive: true });
|
|
122604
|
+
for (const file2 of before2.files) {
|
|
122605
|
+
const destination = join73(staging, file2.path);
|
|
122606
|
+
mkdirSync30(dirname47(destination), { recursive: true });
|
|
122607
|
+
writeFileSync28(destination, file2.bytes, { mode: file2.mode });
|
|
122608
|
+
}
|
|
122609
|
+
const stagedManifest = join73(staging, "skill.json");
|
|
122610
|
+
writeFileSync28(stagedManifest, `${JSON.stringify(candidate2, null, 2)}
|
|
122611
|
+
`);
|
|
122612
|
+
const contentHash = computeContentHash(staging);
|
|
122613
|
+
const changed3 = options.version !== previousVersion || kind !== manifest.kind || contentHash !== provenance?.content_hash;
|
|
122614
|
+
if (changed3 && compareVersions(options.version, previousVersion) <= 0) {
|
|
122615
|
+
throw new Error(`Changed content requires a version greater than ${previousVersion}; pass --version <new-semver>.`);
|
|
122616
|
+
}
|
|
122617
|
+
candidate2.provenance.content_hash = contentHash;
|
|
122618
|
+
const candidateBytes = `${JSON.stringify(candidate2, null, 2)}
|
|
122619
|
+
`;
|
|
122620
|
+
writeFileSync28(stagedManifest, candidateBytes);
|
|
122621
|
+
validateRawCandidate(candidate2);
|
|
122622
|
+
validateRuntimeEntrypoint(candidate2, staging);
|
|
122623
|
+
const validation = validatePortableSkillDirectory(normalized2, staging);
|
|
122624
|
+
if (!validation.valid)
|
|
122625
|
+
throw new Error(`Skill '${normalized2}' cannot be prepared:
|
|
122626
|
+
${validation.issues.map((issue2) => `${issue2.code}: ${issue2.message}`).join(`
|
|
122627
|
+
`)}`);
|
|
122628
|
+
assertPackedCanonicalCoverage(staging);
|
|
122629
|
+
if (snapshot2(path).identity !== before2.identity)
|
|
122630
|
+
throw new Error("Skill changed during preparation; review the draft and run prepare again.");
|
|
122631
|
+
if (changed3 && !options.dryRun) {
|
|
122632
|
+
const temporary = join73(path, `.skill-prepare-${randomUUID15()}.tmp`);
|
|
122633
|
+
try {
|
|
122634
|
+
writeFileSync28(temporary, candidateBytes, { flag: "wx", mode: manifestFile.mode });
|
|
122635
|
+
chmodSync5(temporary, manifestFile.mode);
|
|
122636
|
+
renameSync18(temporary, join73(path, "skill.json"));
|
|
122637
|
+
} finally {
|
|
122638
|
+
rmSync10(temporary, { force: true });
|
|
122639
|
+
}
|
|
122640
|
+
}
|
|
122641
|
+
return { name: normalized2, path, previousVersion, version: options.version, kind, contentHash, changed: changed3, written: changed3 && !options.dryRun, warnings: validation.warnings };
|
|
122642
|
+
} finally {
|
|
122643
|
+
rmSync10(staging, { recursive: true, force: true });
|
|
122644
|
+
}
|
|
122645
|
+
}
|
|
122646
|
+
function assertPackedCanonicalCoverage(root2) {
|
|
122647
|
+
const canonicalPaths = new Set(collectBundleFiles(root2).map((file2) => file2.rel));
|
|
122648
|
+
const packedPaths = new Set(collectSkillBundleEntries(root2).map((entry) => entry.path));
|
|
122649
|
+
const omitted = [...canonicalPaths].filter((path) => !packedPaths.has(path)).sort();
|
|
122650
|
+
if (omitted.length === 0)
|
|
122651
|
+
return;
|
|
122652
|
+
const rendered = omitted.slice(0, MAX_PACK_PARITY_PATHS).map(escapedRelativePath).join(", ");
|
|
122653
|
+
const suffix = omitted.length > MAX_PACK_PARITY_PATHS ? ` (+${omitted.length - MAX_PACK_PARITY_PATHS} more)` : "";
|
|
122654
|
+
throw new Error(`Skill cannot be prepared because packSkillBundle excludes canonical source path(s): ${rendered}${suffix}. ` + "Rename or remove the excluded source before preparing; no files were changed.");
|
|
122655
|
+
}
|
|
122656
|
+
function escapedRelativePath(path) {
|
|
122657
|
+
const bounded2 = path.length > MAX_PACK_PARITY_PATH_CHARS - 8 ? `${path.slice(0, MAX_PACK_PARITY_PATH_CHARS - 8)}...` : path;
|
|
122658
|
+
return JSON.stringify(bounded2);
|
|
122659
|
+
}
|
|
122660
|
+
function validateRuntimeEntrypoint(candidate2, root2) {
|
|
122661
|
+
if (candidate2.kind !== "executable" || !isRecord9(candidate2.runtime))
|
|
122662
|
+
return;
|
|
122663
|
+
const entry = candidate2.runtime.entrypoint;
|
|
122664
|
+
if (typeof entry !== "string")
|
|
122665
|
+
return;
|
|
122666
|
+
const target = resolve39(root2, entry), rel = relative15(root2, target);
|
|
122667
|
+
if (isAbsolute43(entry) || /^[a-zA-Z]:/.test(entry) || entry.includes("\\") || !rel || rel === ".." || rel.startsWith(`..${sep21}`) || isAbsolute43(rel) || !existsSync35(target) || !lstatSync47(target).isFile()) {
|
|
122668
|
+
throw new Error("Executable runtime.entrypoint must name an existing regular file inside the skill directory.");
|
|
122669
|
+
}
|
|
122670
|
+
}
|
|
122671
|
+
function isRecord9(value) {
|
|
122672
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
122673
|
+
}
|
|
122674
|
+
function validateRawCandidate(candidate2) {
|
|
122675
|
+
for (const field3 of ["standard", "name", "description", "version"]) {
|
|
122676
|
+
if (typeof candidate2[field3] !== "string" || !candidate2[field3].trim())
|
|
122677
|
+
throw new Error(`skill.json ${field3} is required.`);
|
|
122678
|
+
}
|
|
122679
|
+
for (const field3 of ["displayName", "category"]) {
|
|
122680
|
+
if (candidate2[field3] !== undefined && (typeof candidate2[field3] !== "string" || !candidate2[field3].trim()))
|
|
122681
|
+
throw new Error(`Invalid skill.json ${field3}.`);
|
|
122682
|
+
}
|
|
122683
|
+
if (candidate2.tags !== undefined && (!Array.isArray(candidate2.tags) || candidate2.tags.some((tag) => typeof tag !== "string" || !tag.trim())))
|
|
122684
|
+
throw new Error("Invalid skill.json tags.");
|
|
122685
|
+
const issues = validatePortableManifestContract(candidate2, { strict: true });
|
|
122686
|
+
if (issues.length)
|
|
122687
|
+
throw new Error(`Invalid skill.json: ${issues.map((issue2) => issue2.code).join(", ")}`);
|
|
122688
|
+
validateRuntimeEntrypointCompatibility(candidate2);
|
|
122689
|
+
for (const field3 of ["inputs", "commands"]) {
|
|
122690
|
+
const items = candidate2[field3];
|
|
122691
|
+
if (items === undefined)
|
|
122692
|
+
continue;
|
|
122693
|
+
if (!Array.isArray(items))
|
|
122694
|
+
throw new Error(`skill.json ${field3} must be an array.`);
|
|
122695
|
+
for (const item of items) {
|
|
122696
|
+
if (!isRecord9(item) || typeof item.name !== "string" || !item.name.trim())
|
|
122697
|
+
throw new Error(`Invalid skill.json ${field3} name.`);
|
|
122698
|
+
if (field3 === "inputs" && (typeof item.type !== "string" || !item.type.trim() || item.required !== undefined && typeof item.required !== "boolean"))
|
|
122699
|
+
throw new Error("Invalid skill.json input type or required flag.");
|
|
122700
|
+
if (field3 === "commands") {
|
|
122701
|
+
if (!/^[a-z0-9][a-z0-9._-]*$/.test(item.name))
|
|
122702
|
+
throw new Error("Invalid skill.json command name.");
|
|
122703
|
+
for (const target of ["entry", "command"])
|
|
122704
|
+
if (item[target] !== undefined && (typeof item[target] !== "string" || !item[target].trim()))
|
|
122705
|
+
throw new Error(`Invalid skill.json command ${target}.`);
|
|
122706
|
+
if (!item.entry && !item.command)
|
|
122707
|
+
throw new Error("A skill.json command needs entry or command.");
|
|
122708
|
+
if (item.args !== undefined && (!Array.isArray(item.args) || item.args.some((arg) => typeof arg !== "string")))
|
|
122709
|
+
throw new Error("Invalid skill.json command args.");
|
|
122710
|
+
}
|
|
122711
|
+
}
|
|
122712
|
+
}
|
|
122713
|
+
}
|
|
122714
|
+
function validateRuntimeEntrypointCompatibility(candidate2) {
|
|
122715
|
+
if (candidate2.kind !== "executable" || !isRecord9(candidate2.runtime))
|
|
122716
|
+
return;
|
|
122717
|
+
const runtime = candidate2.runtime.runtime;
|
|
122718
|
+
const entrypoint = candidate2.runtime.entrypoint;
|
|
122719
|
+
if (typeof runtime !== "string" || typeof entrypoint !== "string")
|
|
122720
|
+
return;
|
|
122721
|
+
const extension = entrypoint.toLowerCase().split(".").pop();
|
|
122722
|
+
if (extension === "py" && runtime !== "python3") {
|
|
122723
|
+
throw new Error("Invalid skill.json: runtime.entrypoint with .py extension requires runtime python3.");
|
|
122724
|
+
}
|
|
122725
|
+
}
|
|
122726
|
+
function compareVersions(left, right) {
|
|
122727
|
+
const parts = (value) => value.split("+")[0].split(/-(.*)/s).slice(0, 2);
|
|
122728
|
+
const [a, ap] = parts(left), [b, bp] = parts(right);
|
|
122729
|
+
const ac = a.split(".").map(BigInt), bc = b.split(".").map(BigInt);
|
|
122730
|
+
for (let i = 0;i < 3; i++)
|
|
122731
|
+
if (ac[i] !== bc[i])
|
|
122732
|
+
return ac[i] > bc[i] ? 1 : -1;
|
|
122733
|
+
if (ap === undefined || bp === undefined)
|
|
122734
|
+
return ap === bp ? 0 : ap === undefined ? 1 : -1;
|
|
122735
|
+
const aa = ap.split("."), bb = bp.split(".");
|
|
122736
|
+
for (let i = 0;i < Math.max(aa.length, bb.length); i++) {
|
|
122737
|
+
const x = aa[i], y = bb[i];
|
|
122738
|
+
if (x === undefined || y === undefined)
|
|
122739
|
+
return x === y ? 0 : x === undefined ? -1 : 1;
|
|
122740
|
+
if (x === y)
|
|
122741
|
+
continue;
|
|
122742
|
+
const xn = /^\d+$/.test(x), yn = /^\d+$/.test(y);
|
|
122743
|
+
if (xn && yn)
|
|
122744
|
+
return BigInt(x) > BigInt(y) ? 1 : -1;
|
|
122745
|
+
if (xn !== yn)
|
|
122746
|
+
return xn ? -1 : 1;
|
|
122747
|
+
return x > y ? 1 : -1;
|
|
122748
|
+
}
|
|
122749
|
+
return 0;
|
|
122750
|
+
}
|
|
122751
|
+
function snapshot2(root2) {
|
|
122752
|
+
const rootStat = lstatSync47(root2);
|
|
122753
|
+
if (rootStat.isSymbolicLink() || !rootStat.isDirectory())
|
|
122754
|
+
throw new Error("Skill root must be a real directory, not a symlink.");
|
|
122755
|
+
const files = [], directories = [];
|
|
122756
|
+
const hash7 = createHash47("sha256").update(`${rootStat.dev}:${rootStat.ino}\x00`);
|
|
122757
|
+
let count = 0, bytes = 0;
|
|
122758
|
+
const walk2 = (relative16) => {
|
|
122759
|
+
for (const name of readdirSync27(join73(root2, relative16)).sort()) {
|
|
122760
|
+
if (IGNORED.has(name.toLowerCase()) || !relative16 && ROOT_IGNORED.has(name.toLowerCase()))
|
|
122761
|
+
continue;
|
|
122762
|
+
const path = relative16 ? `${relative16}/${name}` : name;
|
|
122763
|
+
if (++count > 20000)
|
|
122764
|
+
throw new Error("Skill exceeds the preparation entry limit (20000).");
|
|
122765
|
+
const absolute = join73(root2, path), stat3 = lstatSync47(absolute);
|
|
122766
|
+
if (stat3.isSymbolicLink())
|
|
122767
|
+
throw new Error(`Skill contains a symlink: ${path}. No files were changed.`);
|
|
122768
|
+
if (stat3.isDirectory() && name === "node_modules")
|
|
122769
|
+
continue;
|
|
122770
|
+
hash7.update(JSON.stringify([path, stat3.dev, stat3.ino, stat3.mode]));
|
|
122771
|
+
if (stat3.isDirectory()) {
|
|
122772
|
+
directories.push(path);
|
|
122773
|
+
walk2(path);
|
|
122774
|
+
} else if (stat3.isFile()) {
|
|
122775
|
+
bytes += stat3.size;
|
|
122776
|
+
if (bytes > 50000000)
|
|
122777
|
+
throw new Error("Skill exceeds the preparation size limit (50000000 bytes).");
|
|
122778
|
+
const content = readFileSync45(absolute);
|
|
122779
|
+
if (content.length !== stat3.size)
|
|
122780
|
+
throw new Error("Skill changed during preparation.");
|
|
122781
|
+
files.push({ path, bytes: content, mode: stat3.mode & 511 });
|
|
122782
|
+
hash7.update(`\x00${content.length}\x00`).update(content);
|
|
122783
|
+
} else
|
|
122784
|
+
throw new Error(`Unsupported skill entry: ${path}.`);
|
|
122785
|
+
hash7.update("\x00");
|
|
122786
|
+
}
|
|
122787
|
+
};
|
|
122788
|
+
walk2("");
|
|
122789
|
+
return { files, directories, identity: hash7.digest("hex") };
|
|
122790
|
+
}
|
|
122791
|
+
var MAX_PACK_PARITY_PATHS = 8, MAX_PACK_PARITY_PATH_CHARS = 120, IGNORED, ROOT_IGNORED;
|
|
122792
|
+
var init_prepare_skill = __esm(() => {
|
|
122793
|
+
init_portable_skills();
|
|
122794
|
+
init_skill_contract();
|
|
122795
|
+
init_skill_hash();
|
|
122796
|
+
init_skill_bundle();
|
|
122797
|
+
init_skill_validation();
|
|
122798
|
+
init_authoring_path();
|
|
122799
|
+
IGNORED = new Set([".git", ".ds_store", ".system"]);
|
|
122800
|
+
ROOT_IGNORED = new Set(["dist", "build", ".turbo", ".skills-dependency-preparation"]);
|
|
122801
|
+
});
|
|
122802
|
+
|
|
122803
|
+
// src/cli/commands/portable-skills.ts
|
|
122804
|
+
var exports_portable_skills = {};
|
|
122805
|
+
__export(exports_portable_skills, {
|
|
122806
|
+
registerPortableSkillCommands: () => registerPortableSkillCommands,
|
|
122807
|
+
AUTHORING_NATIVE_HOOKS_HELP: () => AUTHORING_NATIVE_HOOKS_HELP
|
|
122808
|
+
});
|
|
122809
|
+
function registerPortableSkillCommands(parent) {
|
|
122810
|
+
parent.command("prepare").argument("<name>", "Name of a skill in the local corpus").requiredOption("--version <version>", "Reviewed semantic version for this draft").option("--kind <kind>", "Explicit instruction or executable kind; required for legacy kind-less manifests").option("--dry-run", "Validate the candidate and report its hash without changing the draft", false).option("--json", "Output result as JSON", false).description("Validate a reviewed draft and update its manifest version and content hash").addHelpText("after", AUTHORING_NATIVE_HOOKS_HELP).action(async (name, options) => {
|
|
122811
|
+
try {
|
|
122812
|
+
const result2 = prepareSkill(name, { ...options, kind: options.kind === undefined ? undefined : parseSkillKind2(options.kind) });
|
|
122813
|
+
if (result2.written)
|
|
122814
|
+
clearRegistryCache();
|
|
122815
|
+
if (options.json)
|
|
122816
|
+
await writeCliOutput(JSON.stringify(result2, null, 2));
|
|
122817
|
+
else {
|
|
122818
|
+
console.log(source_default.green(`${result2.written ? "Prepared" : options.dryRun ? "Previewed" : "Already prepared"} ${result2.name}@${result2.version} (${result2.kind})`));
|
|
122819
|
+
console.log(source_default.dim(` Content hash: ${result2.contentHash}`));
|
|
122820
|
+
console.log(source_default.dim(` Manifest: ${result2.path}/skill.json`));
|
|
122821
|
+
if (result2.written)
|
|
122822
|
+
console.log(source_default.dim(` Publish: skills push ${result2.name}`));
|
|
122823
|
+
}
|
|
122824
|
+
} catch (error61) {
|
|
122825
|
+
if (options.json)
|
|
122826
|
+
await writeCliOutput(JSON.stringify({ error: error61.message }, null, 2));
|
|
122827
|
+
else
|
|
122828
|
+
console.error(source_default.red(error61.message));
|
|
122829
|
+
process.exitCode = 1;
|
|
122830
|
+
}
|
|
122831
|
+
});
|
|
122832
|
+
parent.command("new").alias("scaffold").argument("<name>", "Portable skill name").option("--description <description>", "Short description of what the skill does").option("--kind <kind>", "Skill class: executable (default) or instruction (prose only)", "executable").option("-o, --overwrite", "Replace an existing portable skill folder", false).option("--json", "Output result as JSON", false).description("Scaffold a portable skill under ~/.hasna/skills/installed/<name>").addHelpText("after", AUTHORING_NATIVE_HOOKS_HELP).action((name, options) => {
|
|
122833
|
+
try {
|
|
122834
|
+
const kind = parseSkillKind2(options.kind);
|
|
122835
|
+
const result2 = scaffoldPortableSkill(name, {
|
|
122836
|
+
description: options.description,
|
|
122837
|
+
overwrite: options.overwrite,
|
|
122838
|
+
kind
|
|
122839
|
+
});
|
|
122840
|
+
clearRegistryCache();
|
|
122841
|
+
if (options.json) {
|
|
122842
|
+
console.log(JSON.stringify({ ...result2, manifest: result2.manifest }, null, 2));
|
|
122843
|
+
return;
|
|
122844
|
+
}
|
|
122845
|
+
console.log(source_default.green(`\u2713 Created ${kind} skill '${result2.name}'`));
|
|
122846
|
+
console.log(source_default.dim(` Path: ${result2.path}`));
|
|
122847
|
+
if (kind === "instruction") {
|
|
122848
|
+
console.log(source_default.dim(` Edit the prose in: ${result2.path}/SKILL.md`));
|
|
122849
|
+
console.log(source_default.dim(` Instruction skills are consumed by agents, not run with 'skills run'.`));
|
|
122850
|
+
} else {
|
|
122851
|
+
console.log(source_default.dim(` Agent instructions: ${result2.path}/AGENTS.md`));
|
|
122852
|
+
console.log(source_default.dim(` Validate: skills validate ${result2.name}`));
|
|
122853
|
+
console.log(source_default.dim(` Run: skills run ${result2.name} --help`));
|
|
122854
|
+
}
|
|
122855
|
+
} catch (error61) {
|
|
122856
|
+
writePortableError(error61, options.json);
|
|
122857
|
+
}
|
|
122858
|
+
});
|
|
122859
|
+
parent.command("port").alias("add").argument("<path>", "Existing skill folder (or parent directory with --all) to import").option("--name <name>", "Override the imported skill name").option("--all", "Import every skill subfolder of <path> (bulk import)", false).option("-o, --overwrite", "Replace an existing portable skill folder", false).option("--allow-shadow", "Allow an imported name that shadows a bundled official skill", false).option("--json", "Output result as JSON", false).description("Import an existing skill folder into ~/.hasna/skills/installed/<name>").addHelpText("after", AUTHORING_NATIVE_HOOKS_HELP).action((path, options) => {
|
|
122860
|
+
if (options.all) {
|
|
122861
|
+
handleBulkPort(path, options);
|
|
122862
|
+
return;
|
|
122863
|
+
}
|
|
122864
|
+
try {
|
|
122865
|
+
const result2 = portPortableSkill(path, {
|
|
122866
|
+
name: options.name,
|
|
122867
|
+
overwrite: options.overwrite,
|
|
122868
|
+
allowShadow: options.allowShadow
|
|
122869
|
+
});
|
|
122870
|
+
clearRegistryCache();
|
|
122871
|
+
const validation = validatePortableSkillDirectory(result2.name, result2.path);
|
|
122872
|
+
const payload = { ...result2, valid: validation.valid, issues: validation.issues, warnings: validation.warnings };
|
|
122873
|
+
if (!validation.valid)
|
|
122874
|
+
process.exitCode = 1;
|
|
122875
|
+
if (options.json) {
|
|
122876
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
122877
|
+
return;
|
|
122878
|
+
}
|
|
122879
|
+
console.log(validation.valid ? source_default.green(`\u2713 Ported portable skill '${result2.name}'`) : source_default.yellow(`Validation failed for imported files '${result2.name}'; correct the folder before using it.`));
|
|
122880
|
+
console.log(source_default.dim(` Path: ${result2.path}`));
|
|
122881
|
+
console.log(source_default.dim(` Valid: ${validation.valid ? "yes" : "no"}`));
|
|
122882
|
+
if (!validation.valid) {
|
|
122883
|
+
for (const issue2 of validation.issues)
|
|
122884
|
+
console.log(source_default.red(` \u2022 ${issue2.message}`));
|
|
122885
|
+
}
|
|
122886
|
+
} catch (error61) {
|
|
122887
|
+
writePortableError(error61, options.json);
|
|
122888
|
+
}
|
|
122889
|
+
});
|
|
122890
|
+
}
|
|
122891
|
+
function handleBulkPort(path, options) {
|
|
122892
|
+
if (options.name) {
|
|
122893
|
+
writePortableError(new Error("--name cannot be used with --all"), options.json);
|
|
122894
|
+
return;
|
|
122895
|
+
}
|
|
122896
|
+
try {
|
|
122897
|
+
const summary = portPortableSkillDirectory(path, { overwrite: options.overwrite, allowShadow: options.allowShadow });
|
|
122898
|
+
clearRegistryCache();
|
|
122899
|
+
if (options.json) {
|
|
122900
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
122901
|
+
if (summary.failed > 0)
|
|
122902
|
+
process.exitCode = 1;
|
|
122903
|
+
return;
|
|
122904
|
+
}
|
|
122905
|
+
console.log(source_default.green(`\u2713 Imported ${summary.succeeded}/${summary.total} skill(s) from ${summary.root}`));
|
|
122906
|
+
for (const entry of summary.imported)
|
|
122907
|
+
console.log(source_default.dim(` \u2022 ${entry.name} \u2192 ${entry.path}`));
|
|
122908
|
+
if (summary.skipped.length > 0) {
|
|
122909
|
+
console.log(source_default.yellow(` Skipped ${summary.skipped.length}:`));
|
|
122910
|
+
for (const entry of summary.skipped)
|
|
122911
|
+
console.log(source_default.yellow(` \u2022 ${entry.sourcePath}: ${entry.reason}`));
|
|
122912
|
+
process.exitCode = 1;
|
|
122913
|
+
}
|
|
122914
|
+
} catch (error61) {
|
|
122915
|
+
writePortableError(error61, options.json);
|
|
122916
|
+
}
|
|
122917
|
+
}
|
|
122918
|
+
function parseSkillKind2(value) {
|
|
122919
|
+
if (value === undefined || value === "executable")
|
|
122920
|
+
return "executable";
|
|
122921
|
+
if (value === "instruction")
|
|
122922
|
+
return "instruction";
|
|
122923
|
+
throw new Error(`Invalid --kind '${value}'. Use 'executable' or 'instruction'.`);
|
|
122924
|
+
}
|
|
122925
|
+
function writePortableError(error61, json2) {
|
|
122926
|
+
const message = error61.message;
|
|
122927
|
+
if (json2)
|
|
122928
|
+
console.log(JSON.stringify({ error: message }, null, 2));
|
|
122929
|
+
else
|
|
122930
|
+
console.error(source_default.red(message));
|
|
122931
|
+
process.exitCode = 1;
|
|
122932
|
+
}
|
|
122933
|
+
var AUTHORING_NATIVE_HOOKS_HELP = `
|
|
122934
|
+
Native hooks:
|
|
122935
|
+
Authoring refuses while another agent's native hooks, plugin hooks or plugin
|
|
122936
|
+
options are not covered by a current discovery review, because they could
|
|
122937
|
+
load skills from the authoring corpus. The refusal names the agent and the
|
|
122938
|
+
file it checked; review that agent's configuration with
|
|
122939
|
+
skills hook install --agent <agent> --discovery-inputs <reviewed-file> --apply
|
|
122940
|
+
and retry. Authoring commands themselves take no --discovery-inputs option.
|
|
122941
|
+
`;
|
|
122942
|
+
var init_portable_skills2 = __esm(() => {
|
|
122943
|
+
init_source();
|
|
122944
|
+
init_registry();
|
|
122945
|
+
init_prepare_skill();
|
|
122946
|
+
init_portable_skills();
|
|
122947
|
+
});
|
|
122948
|
+
|
|
122241
122949
|
// src/lib/portable-snapshot-filter.ts
|
|
122242
|
-
import { readdirSync as
|
|
122950
|
+
import { readdirSync as readdirSync28, statSync as statSync23 } from "fs";
|
|
122243
122951
|
import { homedir as homedir12 } from "os";
|
|
122244
|
-
import { join as
|
|
122952
|
+
import { join as join74, sep as sep22 } from "path";
|
|
122245
122953
|
function isExcludedSkillFileName(fileName) {
|
|
122246
122954
|
if (EXCLUDE_FILE_NAMES.has(fileName)) {
|
|
122247
122955
|
return true;
|
|
@@ -122264,27 +122972,27 @@ function isPortableWithinSkill(relativeParts) {
|
|
|
122264
122972
|
function homePathFor(definition, homesRoot) {
|
|
122265
122973
|
const home = homesRoot ?? homedir12();
|
|
122266
122974
|
if (definition.subClass === "skills" || definition.subClass === "custom") {
|
|
122267
|
-
return
|
|
122975
|
+
return join74(skillsDataRootForHome(home), definition.name);
|
|
122268
122976
|
}
|
|
122269
122977
|
if (definition.agent === "opencode") {
|
|
122270
|
-
return
|
|
122978
|
+
return join74(home, ".config", "opencode", "skills");
|
|
122271
122979
|
}
|
|
122272
|
-
return
|
|
122980
|
+
return join74(home, `.${definition.agent}`, "skills");
|
|
122273
122981
|
}
|
|
122274
122982
|
function destinationFor(definition, stationId, relativePath) {
|
|
122275
|
-
const category = definition.subClass === "agent-homes" ?
|
|
122276
|
-
return
|
|
122983
|
+
const category = definition.subClass === "agent-homes" ? join74("agent-homes", definition.agent ?? "") : definition.name;
|
|
122984
|
+
return join74("resources", stationId, "skills", category, ...relativePath.split(sep22));
|
|
122277
122985
|
}
|
|
122278
122986
|
function walkEntries(absoluteRoot) {
|
|
122279
122987
|
let entries;
|
|
122280
122988
|
try {
|
|
122281
|
-
entries =
|
|
122989
|
+
entries = readdirSync28(absoluteRoot, { withFileTypes: true });
|
|
122282
122990
|
} catch {
|
|
122283
122991
|
return [];
|
|
122284
122992
|
}
|
|
122285
122993
|
const output2 = [];
|
|
122286
122994
|
for (const entry of entries) {
|
|
122287
|
-
const childFull =
|
|
122995
|
+
const childFull = join74(absoluteRoot, entry.name);
|
|
122288
122996
|
if (entry.isSymbolicLink()) {
|
|
122289
122997
|
output2.push({ kind: "symlink", relativePath: entry.name, fullPath: childFull });
|
|
122290
122998
|
continue;
|
|
@@ -122295,7 +123003,7 @@ function walkEntries(absoluteRoot) {
|
|
|
122295
123003
|
}
|
|
122296
123004
|
const nested = walkEntries(childFull);
|
|
122297
123005
|
for (const item of nested) {
|
|
122298
|
-
output2.push({ ...item, relativePath:
|
|
123006
|
+
output2.push({ ...item, relativePath: join74(entry.name, item.relativePath) });
|
|
122299
123007
|
}
|
|
122300
123008
|
continue;
|
|
122301
123009
|
}
|
|
@@ -122388,22 +123096,22 @@ var init_portable_snapshot_filter = __esm(() => {
|
|
|
122388
123096
|
});
|
|
122389
123097
|
|
|
122390
123098
|
// src/lib/station-snapshot.ts
|
|
122391
|
-
import { createHash as
|
|
123099
|
+
import { createHash as createHash48 } from "crypto";
|
|
122392
123100
|
import {
|
|
122393
123101
|
copyFileSync as copyFileSync2,
|
|
122394
|
-
mkdirSync as
|
|
122395
|
-
readFileSync as
|
|
123102
|
+
mkdirSync as mkdirSync31,
|
|
123103
|
+
readFileSync as readFileSync46,
|
|
122396
123104
|
statSync as statSync24,
|
|
122397
|
-
writeFileSync as
|
|
123105
|
+
writeFileSync as writeFileSync29
|
|
122398
123106
|
} from "fs";
|
|
122399
|
-
import { dirname as
|
|
123107
|
+
import { dirname as dirname48, isAbsolute as isAbsolute44, relative as relative16, resolve as resolve40, sep as sep23 } from "path";
|
|
122400
123108
|
function validateStationId(stationId) {
|
|
122401
123109
|
if (!/^[a-z0-9-]+$/.test(stationId)) {
|
|
122402
123110
|
throw new StationSnapshotError("INVALID_STATION", `station id must be a slug, got: ${stationId}`);
|
|
122403
123111
|
}
|
|
122404
123112
|
}
|
|
122405
123113
|
function sha256File(filePath) {
|
|
122406
|
-
return
|
|
123114
|
+
return createHash48("sha256").update(readFileSync46(filePath)).digest("hex");
|
|
122407
123115
|
}
|
|
122408
123116
|
function scanHome(definition, homesRoot) {
|
|
122409
123117
|
const homePath = homePathFor(definition, homesRoot);
|
|
@@ -122411,7 +123119,7 @@ function scanHome(definition, homesRoot) {
|
|
|
122411
123119
|
const portable = [];
|
|
122412
123120
|
const skipped = [];
|
|
122413
123121
|
for (const entry of entries) {
|
|
122414
|
-
const relativeParts = entry.relativePath.split(
|
|
123122
|
+
const relativeParts = entry.relativePath.split(sep23);
|
|
122415
123123
|
const fileName = relativeParts[relativeParts.length - 1];
|
|
122416
123124
|
if (entry.kind === "symlink") {
|
|
122417
123125
|
skipped.push({ relativePath: entry.relativePath, reason: "symlink" });
|
|
@@ -122474,7 +123182,7 @@ function humanHomes(scanned) {
|
|
|
122474
123182
|
}));
|
|
122475
123183
|
}
|
|
122476
123184
|
function writeStationSnapshot(options) {
|
|
122477
|
-
const repoRoot =
|
|
123185
|
+
const repoRoot = resolve40(options.repoRoot ?? process.cwd());
|
|
122478
123186
|
const { scanned, plans, totalBytes } = planStationSnapshot(options);
|
|
122479
123187
|
const manifestFiles = plans.map((plan) => ({
|
|
122480
123188
|
relativePath: plan.source.relativePath,
|
|
@@ -122500,9 +123208,9 @@ function writeStationSnapshot(options) {
|
|
|
122500
123208
|
const conflicts = [];
|
|
122501
123209
|
const untouched = [];
|
|
122502
123210
|
for (const plan of plans) {
|
|
122503
|
-
const destination =
|
|
122504
|
-
const destinationRelative =
|
|
122505
|
-
if (destinationRelative.startsWith("..") || destinationRelative.startsWith(
|
|
123211
|
+
const destination = resolve40(repoRoot, plan.destination);
|
|
123212
|
+
const destinationRelative = relative16(repoRoot, destination);
|
|
123213
|
+
if (destinationRelative.startsWith("..") || destinationRelative.startsWith(sep23) || isAbsolute44(destinationRelative)) {
|
|
122506
123214
|
throw new StationSnapshotError("DESTINATION_ESCAPE", `destination escapes repo root: ${plan.destination}`);
|
|
122507
123215
|
}
|
|
122508
123216
|
let existingDigest = null;
|
|
@@ -122523,8 +123231,8 @@ function writeStationSnapshot(options) {
|
|
|
122523
123231
|
}
|
|
122524
123232
|
let written = 0;
|
|
122525
123233
|
for (const plan of untouched) {
|
|
122526
|
-
const destination =
|
|
122527
|
-
|
|
123234
|
+
const destination = resolve40(repoRoot, plan.destination);
|
|
123235
|
+
mkdirSync31(dirname48(destination), { recursive: true });
|
|
122528
123236
|
copyFileSync2(plan.source.fullPath, destination);
|
|
122529
123237
|
written += 1;
|
|
122530
123238
|
}
|
|
@@ -122542,9 +123250,9 @@ function writeStationSnapshot(options) {
|
|
|
122542
123250
|
},
|
|
122543
123251
|
files: manifestFiles
|
|
122544
123252
|
};
|
|
122545
|
-
const manifestPath =
|
|
122546
|
-
|
|
122547
|
-
|
|
123253
|
+
const manifestPath = resolve40(repoRoot, "resources", options.stationId, "skills", "sync-manifest.json");
|
|
123254
|
+
mkdirSync31(dirname48(manifestPath), { recursive: true });
|
|
123255
|
+
writeFileSync29(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
122548
123256
|
`);
|
|
122549
123257
|
return {
|
|
122550
123258
|
...base3,
|
|
@@ -122575,8 +123283,8 @@ var exports_create_sync_config = {};
|
|
|
122575
123283
|
__export(exports_create_sync_config, {
|
|
122576
123284
|
registerCreateSync: () => registerCreateSync
|
|
122577
123285
|
});
|
|
122578
|
-
import { existsSync as
|
|
122579
|
-
import { join as
|
|
123286
|
+
import { existsSync as existsSync36 } from "fs";
|
|
123287
|
+
import { join as join75 } from "path";
|
|
122580
123288
|
function registerCreateSync(parent) {
|
|
122581
123289
|
const configCmd = parent.command("config").description("Manage skills configuration");
|
|
122582
123290
|
configCmd.command("show", { isDefault: true }).option("--json", "Output as JSON", false).description("Show current merged configuration").action((options) => {
|
|
@@ -122656,15 +123364,15 @@ function registerCreateSync(parent) {
|
|
|
122656
123364
|
const pp = getConfigPath("project");
|
|
122657
123365
|
if (options.json) {
|
|
122658
123366
|
console.log(JSON.stringify({
|
|
122659
|
-
global: { path: gp, exists:
|
|
122660
|
-
project: { path: pp, exists:
|
|
123367
|
+
global: { path: gp, exists: existsSync36(gp) },
|
|
123368
|
+
project: { path: pp, exists: existsSync36(pp) }
|
|
122661
123369
|
}, null, 2));
|
|
122662
123370
|
return;
|
|
122663
123371
|
}
|
|
122664
|
-
console.log(`${source_default.cyan("global")}: ${gp}${
|
|
122665
|
-
console.log(`${source_default.cyan("project")}: ${pp}${
|
|
123372
|
+
console.log(`${source_default.cyan("global")}: ${gp}${existsSync36(gp) ? source_default.green(" (exists)") : source_default.dim(" (not found)")}`);
|
|
123373
|
+
console.log(`${source_default.cyan("project")}: ${pp}${existsSync36(pp) ? source_default.green(" (exists)") : source_default.dim(" (not found)")}`);
|
|
122666
123374
|
});
|
|
122667
|
-
parent.command("create").argument("<name>", "Skill name (e.g. my-tool)").option("--category <category>", "Skill category", "Development Tools").option("--description <description>", "Short description of what the skill does").option("--kind <kind>", "Skill class: executable or instruction", "executable").option("--tags <tags>", "Comma-separated tags (e.g. api,testing,automation)").option("--global", "Deprecated; custom skills are always global", false).option("--json", "Output result as JSON", false).description("Scaffold a new custom skill directory").action((name, options) => handleCreate(name, options));
|
|
123375
|
+
parent.command("create").argument("<name>", "Skill name (e.g. my-tool)").option("--category <category>", "Skill category", "Development Tools").option("--description <description>", "Short description of what the skill does").option("--kind <kind>", "Skill class: executable or instruction", "executable").option("--tags <tags>", "Comma-separated tags (e.g. api,testing,automation)").option("--global", "Deprecated; custom skills are always global", false).option("--json", "Output result as JSON", false).description("Scaffold a new custom skill directory").addHelpText("after", AUTHORING_NATIVE_HOOKS_HELP).action((name, options) => handleCreate(name, options));
|
|
122668
123376
|
parent.command("sync").alias("render").option("--selection-profile <id>", "Consume the exact versions selected by an API profile").option("--profile <id>", "Selection profile (alias for --selection-profile; use before sync for credential profile)").option("--project", "Record exact selected versions in this project's lockfile", false).option("--no-station-report", "Verify and activate the selected profile without reporting station state").argument("[names...]", "Skills to sync (default: every skill in this machine's corpus)").option("--for <agent>", `Target one agent (${SYNC_AGENTS.join(", ")}, or all)`, "all").option("--all", "Sync every corpus skill (the default)", false).option("--source <path>", "Canonical corpus source: a directory of skill folders, or a package root with skills/ (overrides $SKILLS_SOURCE)").option("--dry-run", "Show what would be written without touching any agent folder", false).option("--force", "Adopt an unmanaged skill that already has SKILL.md; other unmarked directories are never overwritten", false).option("--check", "Home drift census (missing-from-home / stray-in-home / diverged); exits non-zero on drift, writes nothing", false).option("--adopt", "Unmarked-home adoption mode: hash unmarked home skills against the corpus; exact matches are marked (dry-run by default)", false).option("--prune", "Prune mode: list (or with --apply, remove) marked home skill dirs that have no canonical corpus entry", false).option("--apply", "Write adoption markers / conflicts ledger, or perform prune removals", false).option("--json", "Output as JSON", false).option("--station <id>", "Per-station snapshot mode: snapshot the installed skill homes into resources/<station>/skills with a v3 sync-manifest (dry-run by default; --populate writes)").option("--populate", "Write the per-station snapshot (station mode; the default is dry-run)", false).option("--repo-root <path>", "Station snapshot destination repo root (default: cwd)").option("--homes-root <dir>", "Build the station snapshot from a staged mirror of the skill homes instead of this machine's $HOME").description("Sync an API selection profile into the Skills cache; native migration flags remain available for older installations").action((names, options) => handleSync(names, options));
|
|
122669
123377
|
}
|
|
122670
123378
|
function handleCreate(name, options) {
|
|
@@ -122686,7 +123394,7 @@ function handleCreate(name, options) {
|
|
|
122686
123394
|
console.log(source_default.green(`\u2713 Created custom skill '${result2.name}' at ${result2.path}`));
|
|
122687
123395
|
console.log(source_default.dim(` Category: ${result2.manifest.category}`));
|
|
122688
123396
|
console.log(source_default.dim(` Tags: ${result2.manifest.tags?.join(", ")}`));
|
|
122689
|
-
console.log(` ${source_default.cyan("Edit:")} ${
|
|
123397
|
+
console.log(` ${source_default.cyan("Edit:")} ${join75(result2.path, options.kind === "instruction" ? "SKILL.md" : "src/index.ts")}`);
|
|
122690
123398
|
console.log(` ${source_default.cyan("Prepare:")} skills prepare ${result2.name} --version <new-semver>`);
|
|
122691
123399
|
}
|
|
122692
123400
|
} catch (error61) {
|
|
@@ -122965,6 +123673,7 @@ var init_create_sync_config = __esm(() => {
|
|
|
122965
123673
|
init_config();
|
|
122966
123674
|
init_auth_store();
|
|
122967
123675
|
init_portable_skills();
|
|
123676
|
+
init_portable_skills2();
|
|
122968
123677
|
init_registry();
|
|
122969
123678
|
init_profile_sync();
|
|
122970
123679
|
init_managed_policy();
|
|
@@ -122975,9 +123684,9 @@ var init_create_sync_config = __esm(() => {
|
|
|
122975
123684
|
});
|
|
122976
123685
|
|
|
122977
123686
|
// src/lib/claude-hook-disable.ts
|
|
122978
|
-
import { createHash as
|
|
122979
|
-
import { constants as constants32, closeSync as closeSync32, fchownSync as fchownSync2, fchmodSync as fchmodSync4, fsyncSync as fsyncSync11, lstatSync as
|
|
122980
|
-
import { basename as basename25, dirname as
|
|
123687
|
+
import { createHash as createHash49, randomUUID as randomUUID16 } from "crypto";
|
|
123688
|
+
import { constants as constants32, closeSync as closeSync32, fchownSync as fchownSync2, fchmodSync as fchmodSync4, fsyncSync as fsyncSync11, lstatSync as lstatSync48, mkdirSync as mkdirSync32, openSync as openSync32, realpathSync as realpathSync21 } from "fs";
|
|
123689
|
+
import { basename as basename25, dirname as dirname49, isAbsolute as isAbsolute45, join as join76, resolve as resolve41 } from "path";
|
|
122981
123690
|
import { KernelLock as KernelLock2 } from "@hasna/contracts/kernel-lock";
|
|
122982
123691
|
function fail6(reason2) {
|
|
122983
123692
|
throw new Error(`CLAUDE_HOOK_DISABLE_${reason2}`);
|
|
@@ -122990,26 +123699,26 @@ function serialize(value) {
|
|
|
122990
123699
|
return value.kind === "string" ? JSON.stringify(value.value) : value.value;
|
|
122991
123700
|
}
|
|
122992
123701
|
function path(value) {
|
|
122993
|
-
if (!
|
|
123702
|
+
if (!isAbsolute45(value) || resolve41(value) !== value || /[\x00-\x1f\x7f]/.test(value))
|
|
122994
123703
|
fail6("PATH_INVALID");
|
|
122995
123704
|
}
|
|
122996
123705
|
function parents3(file2) {
|
|
122997
123706
|
safeParents(file2);
|
|
122998
123707
|
const observed = [];
|
|
122999
|
-
for (let directory3 =
|
|
123000
|
-
const stat3 =
|
|
123001
|
-
assertNativePathAcl(directory3, stat3, { allowDenyOnly: true, creationParent: directory3 ===
|
|
123708
|
+
for (let directory3 = dirname49(file2);; directory3 = dirname49(directory3)) {
|
|
123709
|
+
const stat3 = lstatSync48(directory3, { bigint: true });
|
|
123710
|
+
assertNativePathAcl(directory3, stat3, { allowDenyOnly: true, creationParent: directory3 === dirname49(file2) });
|
|
123002
123711
|
observed.push([directory3, identity6(stat3)]);
|
|
123003
|
-
if (directory3 ===
|
|
123712
|
+
if (directory3 === dirname49(directory3))
|
|
123004
123713
|
break;
|
|
123005
123714
|
}
|
|
123006
123715
|
return () => {
|
|
123007
123716
|
safeParents(file2);
|
|
123008
123717
|
for (const [directory3, expected] of observed) {
|
|
123009
|
-
const stat3 =
|
|
123718
|
+
const stat3 = lstatSync48(directory3, { bigint: true });
|
|
123010
123719
|
if (identity6(stat3) !== expected)
|
|
123011
123720
|
fail6("PARENT_CHANGED");
|
|
123012
|
-
assertNativePathAcl(directory3, stat3, { allowDenyOnly: true, creationParent: directory3 ===
|
|
123721
|
+
assertNativePathAcl(directory3, stat3, { allowDenyOnly: true, creationParent: directory3 === dirname49(file2) });
|
|
123013
123722
|
}
|
|
123014
123723
|
};
|
|
123015
123724
|
}
|
|
@@ -123017,24 +123726,24 @@ function executable2(command, expectedSha256) {
|
|
|
123017
123726
|
path(command);
|
|
123018
123727
|
if (!digest6(expectedSha256))
|
|
123019
123728
|
fail6("COMMAND_DIGEST_INVALID");
|
|
123020
|
-
const parentCheck = parents3(command), alias =
|
|
123729
|
+
const parentCheck = parents3(command), alias = lstatSync48(command, { bigint: true });
|
|
123021
123730
|
const resolved = resolveLauncherCommand(command);
|
|
123022
123731
|
if (alias.isSymbolicLink() && directAliasTarget(command) !== resolved.physical)
|
|
123023
123732
|
fail6("COMMAND_UNVERIFIED");
|
|
123024
123733
|
const commandFile = snapshot(resolved.physical, false, { readOnlyPackage: true });
|
|
123025
123734
|
if (commandFile.sha256 !== expectedSha256)
|
|
123026
123735
|
fail6("COMMAND_CHANGED");
|
|
123027
|
-
const entry = resolved.entry, packageRoot =
|
|
123028
|
-
if (entry !==
|
|
123736
|
+
const entry = resolved.entry, packageRoot = dirname49(dirname49(entry));
|
|
123737
|
+
if (entry !== join76(packageRoot, "bin", "index.js") || realpathSync21(entry) !== entry)
|
|
123029
123738
|
fail6("COMMAND_UNVERIFIED");
|
|
123030
123739
|
const entryFile = snapshot(entry, false, { readOnlyPackage: true });
|
|
123031
|
-
const manifest = snapshot(
|
|
123740
|
+
const manifest = snapshot(join76(packageRoot, "package.json"), false, { readOnlyPackage: true });
|
|
123032
123741
|
const parsed = parseNativeJsonObject(manifest.text);
|
|
123033
123742
|
if (string6(field3(parsed, "name")) !== "@hasna/skills" || string6(field3(field3(parsed, "bin"), "skills")) !== "bin/index.js")
|
|
123034
123743
|
fail6("COMMAND_UNVERIFIED");
|
|
123035
123744
|
return () => {
|
|
123036
123745
|
parentCheck();
|
|
123037
|
-
const current =
|
|
123746
|
+
const current = lstatSync48(command, { bigint: true });
|
|
123038
123747
|
if (movedIdentity(current) !== movedIdentity(alias) || current.ctimeNs !== alias.ctimeNs || alias.isSymbolicLink() && directAliasTarget(command) !== resolved.physical)
|
|
123039
123748
|
fail6("COMMAND_CHANGED");
|
|
123040
123749
|
unchanged2(commandFile);
|
|
@@ -123087,13 +123796,13 @@ function disableClaudeHooks(options) {
|
|
|
123087
123796
|
removed: [],
|
|
123088
123797
|
settingsWitness: "unchanged"
|
|
123089
123798
|
};
|
|
123090
|
-
const exists =
|
|
123799
|
+
const exists = lstatSync48(options.settings, { throwIfNoEntry: false });
|
|
123091
123800
|
if (!exists) {
|
|
123092
123801
|
if (options.expectedSettingsSha256 !== "absent" || options.expectedMode !== "absent")
|
|
123093
123802
|
fail6("TARGET_CHANGED");
|
|
123094
123803
|
checkCommand();
|
|
123095
123804
|
checkParents();
|
|
123096
|
-
if (
|
|
123805
|
+
if (lstatSync48(options.settings, { throwIfNoEntry: false }))
|
|
123097
123806
|
fail6("TARGET_CHANGED");
|
|
123098
123807
|
return base3;
|
|
123099
123808
|
}
|
|
@@ -123156,7 +123865,7 @@ function disableClaudeHooks(options) {
|
|
|
123156
123865
|
if (options.preimageDir === options.settings || options.preimageDir.startsWith(options.settings + "/"))
|
|
123157
123866
|
fail6("PREIMAGE_INVALID");
|
|
123158
123867
|
const checkPreimageParents = parents3(options.preimageDir);
|
|
123159
|
-
const lock = new KernelLock2(
|
|
123868
|
+
const lock = new KernelLock2(dirname49(options.settings), "skills-claude-hook-disable", { mode: "exclusive" });
|
|
123160
123869
|
let exchanged = false, preimage, displaced, receipt;
|
|
123161
123870
|
try {
|
|
123162
123871
|
if (!lock.trySync(1000))
|
|
@@ -123166,15 +123875,15 @@ function disableClaudeHooks(options) {
|
|
|
123166
123875
|
checkParents();
|
|
123167
123876
|
checkPreimageParents();
|
|
123168
123877
|
unchanged2(before2);
|
|
123169
|
-
|
|
123170
|
-
if ((
|
|
123878
|
+
mkdirSync32(options.preimageDir, { mode: 448 });
|
|
123879
|
+
if ((lstatSync48(options.preimageDir).mode & 511) !== 448)
|
|
123171
123880
|
fail6("PREIMAGE_INVALID");
|
|
123172
|
-
preimage =
|
|
123881
|
+
preimage = join76(options.preimageDir, "settings.preimage.json");
|
|
123173
123882
|
save(preimage, before2.bytes);
|
|
123174
123883
|
const preserved = snapshot(preimage, true), checkPreservationRoot = parents3(preimage);
|
|
123175
123884
|
if (preserved.sha256 !== before2.sha256 || !preserved.bytes.equals(before2.bytes))
|
|
123176
123885
|
fail6("PRESERVATION_FAILED");
|
|
123177
|
-
displaced =
|
|
123886
|
+
displaced = join76(dirname49(options.settings), `.settings.skills-disable-${randomUUID16()}.json`);
|
|
123178
123887
|
save(displaced, after2);
|
|
123179
123888
|
const fd = openSync32(displaced, constants32.O_RDONLY | constants32.O_NOFOLLOW);
|
|
123180
123889
|
try {
|
|
@@ -123188,7 +123897,7 @@ function disableClaudeHooks(options) {
|
|
|
123188
123897
|
assertNativePathAcl(displaced, staged.stat);
|
|
123189
123898
|
if (staged.sha256 !== result2.plannedAfterSha256)
|
|
123190
123899
|
fail6("STAGING_CHANGED");
|
|
123191
|
-
receipt =
|
|
123900
|
+
receipt = join76(options.preimageDir, "receipt.json");
|
|
123192
123901
|
const preparationBytes = `${JSON.stringify({
|
|
123193
123902
|
...result2,
|
|
123194
123903
|
state: "prepared",
|
|
@@ -123224,14 +123933,14 @@ function disableClaudeHooks(options) {
|
|
|
123224
123933
|
unchanged2(preserved);
|
|
123225
123934
|
if (movedIdentity(displacedOriginal.stat) !== movedIdentity(before2.stat) || displacedOriginal.sha256 !== before2.sha256 || movedIdentity(current.stat) !== movedIdentity(staged.stat) || current.sha256 !== staged.sha256)
|
|
123226
123935
|
fail6("POST_WRITE_CHANGED");
|
|
123227
|
-
const directory3 = openSync32(
|
|
123936
|
+
const directory3 = openSync32(dirname49(options.settings), constants32.O_RDONLY | constants32.O_DIRECTORY);
|
|
123228
123937
|
try {
|
|
123229
123938
|
fsyncSync11(directory3);
|
|
123230
123939
|
} finally {
|
|
123231
123940
|
closeSync32(directory3);
|
|
123232
123941
|
}
|
|
123233
123942
|
const applied = { ...result2, state: "applied", mutationOccurred: true, afterSha256: staged.sha256, preimage, displaced, receipt };
|
|
123234
|
-
const completed =
|
|
123943
|
+
const completed = join76(options.preimageDir, "completed.json");
|
|
123235
123944
|
const completionBytes = `${JSON.stringify({ ...applied, receipt: completed })}
|
|
123236
123945
|
`;
|
|
123237
123946
|
save(completed, completionBytes);
|
|
@@ -123263,7 +123972,7 @@ function disableClaudeHooks(options) {
|
|
|
123263
123972
|
}
|
|
123264
123973
|
}
|
|
123265
123974
|
}
|
|
123266
|
-
var hash7 = (text5) =>
|
|
123975
|
+
var hash7 = (text5) => createHash49("sha256").update(text5).digest("hex"), digest6 = (value) => /^[a-f0-9]{64}$/.test(value), events3, object12 = (value) => value?.kind === "object" ? value : undefined, field3 = (value, key2) => object12(value)?.entries.find(([name]) => name === key2)?.[1], string6 = (value) => value?.kind === "string" ? value.value : undefined, identity6 = (stat3) => `${stat3.dev}:${stat3.ino}:${stat3.uid}:${stat3.gid}:${stat3.mode}`, movedIdentity = (stat3) => `${identity6(stat3)}:${stat3.nlink}:${stat3.size}:${stat3.mtimeNs}`;
|
|
123267
123976
|
var init_claude_hook_disable = __esm(() => {
|
|
123268
123977
|
init_agent_adapters();
|
|
123269
123978
|
init_claude_settings_witness();
|
|
@@ -123274,8 +123983,8 @@ var init_claude_hook_disable = __esm(() => {
|
|
|
123274
123983
|
});
|
|
123275
123984
|
|
|
123276
123985
|
// src/lib/agent-install-selection.ts
|
|
123277
|
-
import { lstatSync as
|
|
123278
|
-
import { dirname as
|
|
123986
|
+
import { lstatSync as lstatSync49 } from "fs";
|
|
123987
|
+
import { dirname as dirname50, join as join77, resolve as resolve42 } from "path";
|
|
123279
123988
|
import { homedir as homedir13 } from "os";
|
|
123280
123989
|
function detectedIntegrationAgents(options = {}) {
|
|
123281
123990
|
const home = options.home ?? homedir13(), env3 = options.env ?? process.env;
|
|
@@ -123288,7 +123997,7 @@ function detectedIntegrationAgents(options = {}) {
|
|
|
123288
123997
|
throw new Error("Invalid existing bridge agent inventory");
|
|
123289
123998
|
const present2 = (path2) => {
|
|
123290
123999
|
try {
|
|
123291
|
-
return
|
|
124000
|
+
return lstatSync49(path2, { throwIfNoEntry: false }) !== undefined;
|
|
123292
124001
|
} catch {
|
|
123293
124002
|
throw new Error("Agent presence cannot be inspected; review native configuration before hook installation");
|
|
123294
124003
|
}
|
|
@@ -123298,13 +124007,13 @@ function detectedIntegrationAgents(options = {}) {
|
|
|
123298
124007
|
return true;
|
|
123299
124008
|
if (agent === "sumi") {
|
|
123300
124009
|
const xdg = env3.XDG_CONFIG_HOME;
|
|
123301
|
-
const xdgSumi = xdg?.trim() ?
|
|
123302
|
-
return ["SUMI_HOME", "SUMI_CONFIG_DIR", "SUMI_CONFIG", "SUMI_CONFIG_CONTENT"].some((key2) => env3[key2] !== undefined) || Bun.which("sumi-paths", { PATH: env3.PATH ?? "" }) !== null || NATIVE_SKILL_ROOTS.some(([provider, root2]) => provider === "sumi" && present2(
|
|
124010
|
+
const xdgSumi = xdg?.trim() ? resolve42(options.cwd ?? process.cwd(), xdg === "~" ? home : xdg.startsWith("~/") || xdg.startsWith("~\\") ? resolve42(home, xdg.slice(2)) : xdg, "sumi") : null;
|
|
124011
|
+
return ["SUMI_HOME", "SUMI_CONFIG_DIR", "SUMI_CONFIG", "SUMI_CONFIG_CONTENT"].some((key2) => env3[key2] !== undefined) || Bun.which("sumi-paths", { PATH: env3.PATH ?? "" }) !== null || NATIVE_SKILL_ROOTS.some(([provider, root2]) => provider === "sumi" && present2(join77(home, dirname50(root2)))) || present2(join77(home, ".config", "sumi")) || xdgSumi !== null && present2(xdgSumi);
|
|
123303
124012
|
}
|
|
123304
124013
|
if (agent === "hermes" && env3.HERMES_HOME !== undefined)
|
|
123305
124014
|
return true;
|
|
123306
124015
|
const adapter = AGENT_ADAPTERS[agent];
|
|
123307
|
-
return present2(
|
|
124016
|
+
return present2(join77(home, dirname50(adapter.config))) || NATIVE_SKILL_ROOTS.some(([provider, root2]) => provider === agent && dirname50(root2) === `.${agent}` && present2(join77(home, dirname50(root2))));
|
|
123308
124017
|
});
|
|
123309
124018
|
if (!selected.length && !options.allowEmpty)
|
|
123310
124019
|
throw new Error(`No configured or detected agents; select --agent <agent> explicitly (${INTEGRATION_AGENTS.join(", ")}).`);
|
|
@@ -123318,11 +124027,11 @@ var init_agent_install_selection = __esm(() => {
|
|
|
123318
124027
|
});
|
|
123319
124028
|
|
|
123320
124029
|
// src/lib/codex-hook-trust-identity.ts
|
|
123321
|
-
import { lstatSync as
|
|
123322
|
-
import { dirname as
|
|
124030
|
+
import { lstatSync as lstatSync50, realpathSync as realpathSync22 } from "fs";
|
|
124031
|
+
import { dirname as dirname51, isAbsolute as isAbsolute46, join as join78, resolve as resolve43 } from "path";
|
|
123323
124032
|
import { isDeepStrictEqual as isDeepStrictEqual20 } from "util";
|
|
123324
124033
|
function commandPhysical(command) {
|
|
123325
|
-
need8(typeof command === "string" &&
|
|
124034
|
+
need8(typeof command === "string" && isAbsolute46(command) && !command.includes("\x00") && resolve43(command) === command, COMMAND_NOT_CANONICAL);
|
|
123326
124035
|
const physical = realpathSync22(command);
|
|
123327
124036
|
let spelled;
|
|
123328
124037
|
try {
|
|
@@ -123333,7 +124042,7 @@ function commandPhysical(command) {
|
|
|
123333
124042
|
safeParents(spelled);
|
|
123334
124043
|
let direct = false;
|
|
123335
124044
|
try {
|
|
123336
|
-
const leaf =
|
|
124045
|
+
const leaf = lstatSync50(spelled);
|
|
123337
124046
|
direct = leaf.isSymbolicLink() ? trustedAncestorOwner(leaf.uid) && directAliasTarget(spelled) === physical : spelled === physical;
|
|
123338
124047
|
} catch {}
|
|
123339
124048
|
need8(direct, COMMAND_ALIAS_UNSAFE);
|
|
@@ -123345,7 +124054,7 @@ function assertPinnedRuntime(runtime) {
|
|
|
123345
124054
|
} catch {
|
|
123346
124055
|
need8(false, "SKILLS_LAUNCHER_UNVERIFIED");
|
|
123347
124056
|
}
|
|
123348
|
-
need8(
|
|
124057
|
+
need8(resolve43(runtime) === runtime, "SKILLS_LAUNCHER_UNVERIFIED");
|
|
123349
124058
|
safeParents(runtime);
|
|
123350
124059
|
}
|
|
123351
124060
|
function skillsCommandTarget(command) {
|
|
@@ -123377,12 +124086,12 @@ function targetUnchanged(command, target) {
|
|
|
123377
124086
|
assertPinnedRuntime(target.runtime);
|
|
123378
124087
|
}
|
|
123379
124088
|
function inspectRecordedSkillsCli(command, expected) {
|
|
123380
|
-
need8(
|
|
124089
|
+
need8(isAbsolute46(command) && !command.includes("\x00"), "RECONCILE_RECORDED_COMMAND_REQUIRED");
|
|
123381
124090
|
const target = skillsCommandTarget(command), resolved = target.entry;
|
|
123382
124091
|
const cli = snapshot(resolved, false, { readOnlyPackage: true });
|
|
123383
|
-
const manifest = snapshot(
|
|
124092
|
+
const manifest = snapshot(join78(dirname51(dirname51(resolved)), "package.json"), false, { readOnlyPackage: true });
|
|
123384
124093
|
const pkg = JSON.parse(manifest.text);
|
|
123385
|
-
need8(pkg.name === "@hasna/skills" && /^\d+\.\d+\.\d+$/.test(pkg.version) && pkg.bin?.skills === "bin/index.js" && resolved ===
|
|
124094
|
+
need8(pkg.name === "@hasna/skills" && /^\d+\.\d+\.\d+$/.test(pkg.version) && pkg.bin?.skills === "bin/index.js" && resolved === join78(dirname51(manifest.file), "bin/index.js"), "SKILLS_PACKAGE_MISMATCH");
|
|
123386
124095
|
need8((cli.stat.mode & 0o100n) !== 0n, "SKILLS_COMMAND_NOT_EXECUTABLE");
|
|
123387
124096
|
const receipt = { path: resolved, version: pkg.version, sha256: cli.sha256, manifestSha256: manifest.sha256 };
|
|
123388
124097
|
need8(isDeepStrictEqual20(receipt, expected), "RECONCILE_SKILLS_BINDING_CHANGED");
|
|
@@ -123396,16 +124105,16 @@ function bindSkillsCli(command, reviewed) {
|
|
|
123396
124105
|
const normal = Bun.which("skills", { PATH: process.env.PATH });
|
|
123397
124106
|
need8(normal, "SKILLS_COMMAND_UNAVAILABLE");
|
|
123398
124107
|
const normalTarget = skillsCommandTarget(normal), resolved = normalTarget.entry;
|
|
123399
|
-
const bound =
|
|
124108
|
+
const bound = isAbsolute46(command) ? command : command === "skills" ? normal : undefined;
|
|
123400
124109
|
need8(bound, "SKILLS_COMMAND_MISMATCH");
|
|
123401
124110
|
const boundTarget = skillsCommandTarget(bound);
|
|
123402
124111
|
need8(boundTarget.entry === resolved, "SKILLS_COMMAND_MISMATCH");
|
|
123403
124112
|
const entrypoint = reviewed?.path ?? process.argv[1];
|
|
123404
124113
|
need8(entrypoint && realpathSync22(entrypoint) === resolved, "SKILLS_ENTRYPOINT_MISMATCH");
|
|
123405
124114
|
const cli = snapshot(resolved, false, { readOnlyPackage: true });
|
|
123406
|
-
const manifest = snapshot(
|
|
124115
|
+
const manifest = snapshot(join78(dirname51(dirname51(resolved)), "package.json"), false, { readOnlyPackage: true });
|
|
123407
124116
|
const pkg = JSON.parse(manifest.text);
|
|
123408
|
-
need8(pkg.name === "@hasna/skills" && /^\d+\.\d+\.\d+$/.test(pkg.version) && pkg.bin?.skills === "bin/index.js" && resolved ===
|
|
124117
|
+
need8(pkg.name === "@hasna/skills" && /^\d+\.\d+\.\d+$/.test(pkg.version) && pkg.bin?.skills === "bin/index.js" && resolved === join78(dirname51(manifest.file), "bin/index.js"), "SKILLS_PACKAGE_MISMATCH");
|
|
123409
124118
|
if (reviewed)
|
|
123410
124119
|
need8(/^[a-f0-9]{64}$/.test(reviewed.sha256) && reviewed.sha256 === cli.sha256 && reviewed.version === pkg.version, "SKILLS_RELEASE_MISMATCH");
|
|
123411
124120
|
need8((cli.stat.mode & 0o100n) !== 0n, "SKILLS_COMMAND_NOT_EXECUTABLE");
|
|
@@ -123424,7 +124133,7 @@ function captureSkillsCliProducerIdentity(command) {
|
|
|
123424
124133
|
const bound = bindSkillsCli(command);
|
|
123425
124134
|
const target = skillsCommandTarget(command);
|
|
123426
124135
|
const cli = snapshot(target.entry, false, { readOnlyPackage: true });
|
|
123427
|
-
const manifest = snapshot(
|
|
124136
|
+
const manifest = snapshot(join78(dirname51(dirname51(target.entry)), "package.json"), false, { readOnlyPackage: true });
|
|
123428
124137
|
const pkg = JSON.parse(manifest.text);
|
|
123429
124138
|
need8(pkg.name === "@hasna/skills" && pkg.version === bound.receipt.version && target.entry === bound.receipt.path, "SKILLS_PACKAGE_MISMATCH");
|
|
123430
124139
|
const launcher = target.launcher ?? cli;
|
|
@@ -123478,23 +124187,23 @@ var init_codex_hook_policy_recovery = __esm(() => {
|
|
|
123478
124187
|
});
|
|
123479
124188
|
|
|
123480
124189
|
// src/lib/agent-codex-trust.ts
|
|
123481
|
-
import { lstatSync as
|
|
123482
|
-
import { createHash as
|
|
124190
|
+
import { lstatSync as lstatSync51, mkdirSync as mkdirSync33, readdirSync as readdirSync29, readFileSync as readFileSync47, realpathSync as realpathSync23, statSync as statSync25 } from "fs";
|
|
124191
|
+
import { createHash as createHash50, randomUUID as randomUUID17 } from "crypto";
|
|
123483
124192
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
123484
|
-
import { dirname as
|
|
124193
|
+
import { dirname as dirname52, isAbsolute as isAbsolute47, join as join79, resolve as resolve44 } from "path";
|
|
123485
124194
|
import { homedir as homedir14 } from "os";
|
|
123486
124195
|
import { isDeepStrictEqual as isDeepStrictEqual22 } from "util";
|
|
123487
124196
|
function verifyReleaseArtifact(proof, skillsCli) {
|
|
123488
|
-
need8(
|
|
124197
|
+
need8(isAbsolute47(proof.packageTarPath) && !proof.packageTarPath.includes("\x00"), "RECONCILE_RELEASE_PROVENANCE_PATH");
|
|
123489
124198
|
const tarPath = realpathSync23(proof.packageTarPath);
|
|
123490
124199
|
need8(statSync25(tarPath).isFile(), "RECONCILE_RELEASE_PROVENANCE_PATH");
|
|
123491
|
-
const tarBytes =
|
|
123492
|
-
need8(tarBytes.byteLength === proof.packageTarBytes &&
|
|
123493
|
-
const packageRoot =
|
|
123494
|
-
const manifest =
|
|
123495
|
-
need8(
|
|
123496
|
-
need8(
|
|
123497
|
-
const members2 = [["package/package.json", manifest], ["package/bin/index.js",
|
|
124200
|
+
const tarBytes = readFileSync47(tarPath);
|
|
124201
|
+
need8(tarBytes.byteLength === proof.packageTarBytes && createHash50("sha256").update(tarBytes).digest("hex") === proof.packageTarSha256, "RECONCILE_RELEASE_ARTIFACT_CHANGED");
|
|
124202
|
+
const packageRoot = dirname52(dirname52(realpathSync23(skillsCli.receipt.path)));
|
|
124203
|
+
const manifest = readFileSync47(join79(packageRoot, "package.json"));
|
|
124204
|
+
need8(createHash50("sha256").update(manifest).digest("hex") === skillsCli.receipt.manifestSha256 && skillsCli.receipt.manifestSha256 === proof.manifestSha256, "RECONCILE_RELEASE_MANIFEST_CHANGED");
|
|
124205
|
+
need8(createHash50("sha256").update(readFileSync47(skillsCli.receipt.path)).digest("hex") === proof.executableSha256 && skillsCli.receipt.sha256 === proof.executableSha256, "RECONCILE_RELEASE_EXECUTABLE_CHANGED");
|
|
124206
|
+
const members2 = [["package/package.json", manifest], ["package/bin/index.js", readFileSync47(skillsCli.receipt.path)]];
|
|
123498
124207
|
for (const [member, expected] of members2) {
|
|
123499
124208
|
let actual;
|
|
123500
124209
|
try {
|
|
@@ -123508,26 +124217,26 @@ function verifyReleaseArtifact(proof, skillsCli) {
|
|
|
123508
124217
|
async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc) {
|
|
123509
124218
|
let rpc;
|
|
123510
124219
|
try {
|
|
123511
|
-
const home =
|
|
123512
|
-
const parent =
|
|
124220
|
+
const home = resolve44(options.home ?? homedir14()), dataDir3 = resolve44(options.dataDir ?? getDataDirReadOnly());
|
|
124221
|
+
const parent = resolve44(join79(dataDir3, "native-hook-trust")), journal = resolve44(options.journal);
|
|
123513
124222
|
need8(journal.startsWith(parent + "/") && /^[a-f0-9-]{36}$/.test(journal.slice(parent.length + 1)), "RECONCILE_JOURNAL_PATH");
|
|
123514
|
-
const dir =
|
|
124223
|
+
const dir = lstatSync51(journal, { throwIfNoEntry: false });
|
|
123515
124224
|
need8(!!dir && dir.isDirectory() && !dir.isSymbolicLink() && dir.uid === process.getuid() && (dir.mode & 511) === 448, "UNSAFE_JOURNAL");
|
|
123516
|
-
need8(!
|
|
123517
|
-
need8(
|
|
123518
|
-
const stoppedFile = snapshot(
|
|
123519
|
-
const intentFile = snapshot(
|
|
123520
|
-
const configBefore = snapshot(
|
|
124225
|
+
need8(!lstatSync51(join79(journal, "receipt.json"), { throwIfNoEntry: false }), "RECONCILE_ALREADY_COMPLETE");
|
|
124226
|
+
need8(lstatSync51(join79(journal, "stopped.json"), { throwIfNoEntry: false })?.isFile(), "RECONCILE_JOURNAL_INCOMPLETE");
|
|
124227
|
+
const stoppedFile = snapshot(join79(journal, "stopped.json"), true);
|
|
124228
|
+
const intentFile = snapshot(join79(journal, "intent.json"), true), intent = JSON.parse(intentFile.text);
|
|
124229
|
+
const configBefore = snapshot(join79(journal, "config.before.toml"), true), hooksBefore = snapshot(join79(journal, "hooks.before.json"), true), policyBefore = snapshot(join79(journal, "policy.before.json"), true);
|
|
123521
124230
|
const legacyIntent = intent.home === undefined && intent.configSha256 === undefined && intent.configVersion === undefined && intent.declarations === undefined && intent.admitted === undefined && intent.nativeHooks === undefined;
|
|
123522
124231
|
need8(intent.version === 1 && /^[a-f0-9]{64}$/.test(intent.planDigest) && typeof intent.configPath === "string" && /^[a-f0-9]{64}$/.test(intent.policySha256) && /^[a-f0-9]{64}$/.test(intent.hooksSha256) && Array.isArray(intent.hooks) && intent.hooks.length > 0 && intent.hooks.length <= 100 && (legacyIntent || typeof intent.home === "string" && intent.home === home && /^[a-f0-9]{64}$/.test(intent.configSha256) && typeof intent.configVersion === "string" && Array.isArray(intent.declarations) && Array.isArray(intent.admitted) && Array.isArray(intent.nativeHooks)), "RECONCILE_INTENT_INVALID");
|
|
123523
|
-
const declarations = intent.declarations ?? intent.hooks.map((hook) => ({ event: hook.event, key: `${
|
|
124232
|
+
const declarations = intent.declarations ?? intent.hooks.map((hook) => ({ event: hook.event, key: `${join79(home, ".codex/hooks.json")}${hook.key.slice(hook.key.indexOf(".codex/hooks.json") + ".codex/hooks.json".length)}`, command: hook.command }));
|
|
123524
124233
|
const admitted = intent.admitted ?? intent.hooks;
|
|
123525
124234
|
const configSha256 = intent.configSha256 ?? configBefore.sha256, configVersion = intent.configVersion ?? intent.expectedVersion;
|
|
123526
124235
|
const digestInput = { version: 1, home: intent.home ?? home, configPath: intent.configPath, ...intent.nativeConfigPath !== undefined ? { nativeConfigPath: intent.nativeConfigPath } : {}, configSha256, configVersion, policySha256: intent.policySha256, hooksSha256: intent.hooksSha256, skillsCli: intent.skillsCli, nativeVersion: intent.nativeVersion, declarations, admitted };
|
|
123527
124236
|
need8(makePlanDigest(digestInput) === intent.planDigest, "RECONCILE_PLAN_CHANGED");
|
|
123528
124237
|
need8(isDeepStrictEqual22(intent.hooks, admitted.filter((hook) => !hook.enabled || hook.trustStatus !== "trusted")), "RECONCILE_INTENT_HOOKS_CHANGED");
|
|
123529
124238
|
need8(configBefore.sha256 === (intent.beforeSha256 ?? intent.configSha256) && hooksBefore.sha256 === intent.hooksSha256 && policyBefore.sha256 === intent.policySha256, "RECONCILE_JOURNAL_CHANGED");
|
|
123530
|
-
const policyCurrent = snapshot(
|
|
124239
|
+
const policyCurrent = snapshot(join79(dataDir3, "agent-policy.json"), true);
|
|
123531
124240
|
const { claudeDiscoveryRecovery: claudeRecovery, nativeHookAdditionsRecovery: additionsRecovery } = options;
|
|
123532
124241
|
const recovery = claudeRecovery ?? additionsRecovery;
|
|
123533
124242
|
need8(!recovery || !options.supersedeBinding, "RECONCILE_RECOVERY_MODE_CONFLICT");
|
|
@@ -123544,17 +124253,17 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
123544
124253
|
if (supersededBinding || recovery) {
|
|
123545
124254
|
need8(Array.isArray(intent.nativeHooks) && intent.nativeHooks.length > 0, "RECONCILE_NATIVE_INVENTORY_REQUIRED");
|
|
123546
124255
|
const proof = options.releaseProof;
|
|
123547
|
-
need8(!!proof &&
|
|
124256
|
+
need8(!!proof && isAbsolute47(proof.packageTarPath) && /^[a-f0-9]{64}$/.test(proof.packageTarSha256) && Number.isInteger(proof.packageTarBytes) && proof.packageTarBytes > 0 && /^[a-f0-9]{64}$/.test(proof.manifestSha256) && /^[a-f0-9]{64}$/.test(proof.executableSha256), "RECONCILE_RELEASE_PROVENANCE_REQUIRED");
|
|
123548
124257
|
}
|
|
123549
124258
|
const currentConfig = snapshot(intent.configPath, true);
|
|
123550
124259
|
const policy = parseManagedSkillPolicy(policyCurrent.text), binding = policy.bridge;
|
|
123551
|
-
const alias = binding.rootAliases?.find((item) => item.agent === "codex"), physicalRoot = alias?.target ??
|
|
123552
|
-
need8(
|
|
124260
|
+
const alias = binding.rootAliases?.find((item) => item.agent === "codex"), physicalRoot = alias?.target ?? join79(home, ".codex"), hooksPath = join79(physicalRoot, "hooks.json");
|
|
124261
|
+
need8(resolve44(intent.configPath) === resolve44(join79(physicalRoot, "config.toml")), "RECONCILE_CONFIG_PATH_CHANGED");
|
|
123553
124262
|
const nativeConfigPath = intent.nativeConfigPath ?? intent.configPath;
|
|
123554
|
-
need8([
|
|
123555
|
-
const codexHome = nativeConfigPath !== intent.configPath ? undefined :
|
|
124263
|
+
need8([join79(home, ".codex/config.toml"), join79(physicalRoot, "config.toml")].includes(nativeConfigPath), "RECONCILE_NATIVE_CONFIG_PATH_CHANGED");
|
|
124264
|
+
const codexHome = nativeConfigPath !== intent.configPath ? undefined : dirname52(nativeConfigPath);
|
|
123556
124265
|
const hooksCurrent = snapshot(hooksPath);
|
|
123557
|
-
const additions = additionsRecovery ? reviewNativeHookAdditions({ path: additionsRecovery.path, intent: intentFile, policy: policyCurrent, configBefore, configCurrent: currentConfig, hooksBefore, hooksCurrent, nativeHooksPath:
|
|
124266
|
+
const additions = additionsRecovery ? reviewNativeHookAdditions({ path: additionsRecovery.path, intent: intentFile, policy: policyCurrent, configBefore, configCurrent: currentConfig, hooksBefore, hooksCurrent, nativeHooksPath: join79(dirname52(nativeConfigPath), "hooks.json"), journalHooks: intent.hooks, recordedNativeHooks: intent.nativeHooks }) : undefined;
|
|
123558
124267
|
const managedHooks = [...intent.hooks, ...additions?.hooks ?? []];
|
|
123559
124268
|
const managedKeys = managedHooks.map((h) => h.key);
|
|
123560
124269
|
const witnessSame = codexTrustReconcileWitness(configBefore.text, managedKeys) === codexTrustReconcileWitness(currentConfig.text, managedKeys);
|
|
@@ -123618,7 +124327,7 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
123618
124327
|
need8(isDeepStrictEqual22(actual, expected), "RECONCILE_UNRELATED_HOOK_CHANGED");
|
|
123619
124328
|
}
|
|
123620
124329
|
for (const wanted of intent.hooks) {
|
|
123621
|
-
const matches2 = nativeHooks.filter((h2) => h2.key === wanted.key || h2.key === wanted.key.replace(
|
|
124330
|
+
const matches2 = nativeHooks.filter((h2) => h2.key === wanted.key || h2.key === wanted.key.replace(join79(home, ".codex/hooks.json"), hooksPath));
|
|
123622
124331
|
need8(matches2.length === 1, "RECONCILE_NATIVE_IDENTITY_CHANGED");
|
|
123623
124332
|
const h = matches2[0];
|
|
123624
124333
|
const trusted = h.trustStatus === "trusted";
|
|
@@ -123667,7 +124376,7 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
123667
124376
|
const receipt = { version: 1, status: preview2 ? "recovery-review-required" : supersededBinding ? "superseded" : "reconciled", reconciled: !preview2, supersededBinding, automaticRollback: false, journal, planDigest: intent.planDigest, nativeVersion: rpc.version, nativeStateVerified: !nativeTrustPending, nativeTrustPending, bindingRefreshRequired: !!configDiscoveryRecovery, nativeExecutionVerified: false, configSha256: currentConfig.sha256, hooksSha256: hooksCurrent.sha256, policySha256: policyBefore.sha256, policyBeforeSha256: policyBefore.sha256, policyCurrentSha256: policyCurrent.sha256, previousSkillsCli: intent.skillsCli, skillsCli: skillsCli.receipt, releaseProof: options.releaseProof, nativeInventoryScope: recordedNativeHooks ? "complete" : "declared-managed-hooks-only", unrelatedNativeHooksPreserved: !!recordedNativeHooks, unrelatedSettingsAndCommentsPreserved: true, ...recoveryPlan ? { recoveryPlan, recoveryPlanDigest } : {}, ...codexAdmissionReceipt(rpc.admission) };
|
|
123668
124377
|
if (preview2)
|
|
123669
124378
|
return receipt;
|
|
123670
|
-
save(
|
|
124379
|
+
save(join79(journal, "receipt.json"), json2(receipt));
|
|
123671
124380
|
return receipt;
|
|
123672
124381
|
} catch (error61) {
|
|
123673
124382
|
const message = error61 instanceof Error && error61.message.startsWith("CODEX_HOOK_TRUST_") ? error61.message : "CODEX_HOOK_TRUST_RECONCILE_REFUSED";
|
|
@@ -123677,21 +124386,21 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
123677
124386
|
}
|
|
123678
124387
|
}
|
|
123679
124388
|
async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRpc) {
|
|
123680
|
-
const home =
|
|
123681
|
-
return withCodexCorpusWriteAsync([
|
|
124389
|
+
const home = resolve44(options.home ?? homedir14());
|
|
124390
|
+
return withCodexCorpusWriteAsync([join79(home, ".codex")], (_assertCurrent, admission) => enrollCodexNativeHooksUnlocked(options, connect, admission), options);
|
|
123682
124391
|
}
|
|
123683
124392
|
async function enrollCodexNativeHooksUnlocked(options, connect, admission) {
|
|
123684
124393
|
let rpc, journal;
|
|
123685
124394
|
try {
|
|
123686
|
-
const home =
|
|
124395
|
+
const home = resolve44(options.home ?? homedir14()), dataDir3 = resolve44(options.dataDir ?? getDataDirReadOnly());
|
|
123687
124396
|
assertManagedAgentBridge("codex", { home, dataDir: dataDir3, projectDir: home });
|
|
123688
|
-
const policyFile = snapshot(
|
|
124397
|
+
const policyFile = snapshot(join79(dataDir3, "agent-policy.json"), true), policy = parseManagedSkillPolicy(policyFile.text);
|
|
123689
124398
|
const binding = policy.bridge, alias = binding.rootAliases?.find((item) => item.agent === "codex");
|
|
123690
124399
|
const skillsCli = bindSkillsCli(binding.commands.codex, options.reviewedSkillsCli);
|
|
123691
|
-
const physicalRoot = alias?.target ??
|
|
123692
|
-
need8(options.codexHome === undefined ||
|
|
124400
|
+
const physicalRoot = alias?.target ?? join79(home, ".codex"), hooksPath = join79(physicalRoot, "hooks.json"), configPath = join79(physicalRoot, "config.toml");
|
|
124401
|
+
need8(options.codexHome === undefined || isAbsolute47(options.codexHome) && [join79(home, ".codex"), physicalRoot].includes(options.codexHome), "NATIVE_HOME_NOT_ADMITTED");
|
|
123693
124402
|
const codexHome = options.codexHome === undefined ? undefined : physicalRoot;
|
|
123694
|
-
const nativeConfigPath =
|
|
124403
|
+
const nativeConfigPath = join79(codexHome ?? join79(home, ".codex"), "config.toml");
|
|
123695
124404
|
const hooksFile = snapshot(hooksPath), configFile = snapshot(configPath, true), original = structure3(configFile.text);
|
|
123696
124405
|
const declarations = JSON.parse(hooksFile.text).hooks, expected = [];
|
|
123697
124406
|
for (const [event, [nativeEvent, label]] of Object.entries(eventNames)) {
|
|
@@ -123700,25 +124409,25 @@ async function enrollCodexNativeHooksUnlocked(options, connect, admission) {
|
|
|
123700
124409
|
need8(Array.isArray(groups), "DECLARATION_CHANGED");
|
|
123701
124410
|
const matches2 = groups.flatMap((group, gi) => (group.hooks ?? []).flatMap((entry, hi) => entry.command === command ? [{ group, entry, gi, hi }] : []));
|
|
123702
124411
|
need8(matches2.length === 1 && isDeepStrictEqual22(matches2[0].group, { hooks: [{ type: "command", command, timeout: 15 }] }), "DECLARATION_CHANGED");
|
|
123703
|
-
expected.push({ event: nativeEvent, key: `${
|
|
124412
|
+
expected.push({ event: nativeEvent, key: `${join79(home, ".codex/hooks.json")}:${label}:${matches2[0].gi}:${matches2[0].hi}`, command });
|
|
123704
124413
|
}
|
|
123705
124414
|
codexTrustTextWitness(configFile.text, expected.map((h) => h.key));
|
|
123706
124415
|
rpc = await connect({ command: options.codexCommand ?? "codex", home, codexHome });
|
|
123707
124416
|
need8(SUPPORTED_CODEX_HOOK_VERSIONS.includes(rpc.version), "NATIVE_UNSUPPORTED_VERSION");
|
|
123708
124417
|
const list2 = async () => {
|
|
123709
124418
|
const response = await rpc.request("hooks/list", { cwds: [home] });
|
|
123710
|
-
const nativeRoots = [...new Set([physicalRoot,
|
|
123711
|
-
const coexistenceWarnings = nativeRoots.flatMap((hooksRoot) => nativeRoots.map((configRoot) => `loading hooks from both ${
|
|
124419
|
+
const nativeRoots = [...new Set([physicalRoot, join79(home, ".codex")])];
|
|
124420
|
+
const coexistenceWarnings = nativeRoots.flatMap((hooksRoot) => nativeRoots.map((configRoot) => `loading hooks from both ${join79(hooksRoot, "hooks.json")} and ${join79(configRoot, "config.toml")}; prefer a single representation for this layer`));
|
|
123712
124421
|
const entry = response?.data?.[0];
|
|
123713
124422
|
need8(Array.isArray(response?.data) && response.data.length === 1 && entry.cwd === home && Array.isArray(entry.hooks) && entry.hooks.length <= 1e4 && Array.isArray(entry.errors) && entry.errors.length === 0 && Array.isArray(entry.warnings) && entry.warnings.length <= 1 && entry.warnings.every((warning) => typeof warning === "string" && coexistenceWarnings.includes(warning)), "NATIVE_DISCOVERY_REFUSED");
|
|
123714
124423
|
return response.data[0].hooks;
|
|
123715
124424
|
};
|
|
123716
124425
|
const admit = (hooks) => expected.map((wanted) => {
|
|
123717
|
-
const canonicalKey = wanted.key.replace(
|
|
124426
|
+
const canonicalKey = wanted.key.replace(join79(home, ".codex/hooks.json"), hooksPath);
|
|
123718
124427
|
const matches2 = hooks.filter((h2) => h2.key === wanted.key || h2.key === canonicalKey);
|
|
123719
124428
|
need8(matches2.length === 1, "AMBIGUOUS_IDENTITY");
|
|
123720
124429
|
const h = matches2[0];
|
|
123721
|
-
need8(h.eventName === wanted.event && h.handlerType === "command" && h.command === wanted.command && h.matcher === null && h.timeoutSec === 15 && h.async === false && h.statusMessage === null && h.additionalContextLimit === null && [hooksPath,
|
|
124430
|
+
need8(h.eventName === wanted.event && h.handlerType === "command" && h.command === wanted.command && h.matcher === null && h.timeoutSec === 15 && h.async === false && h.statusMessage === null && h.additionalContextLimit === null && [hooksPath, join79(home, ".codex/hooks.json")].includes(h.sourcePath) && h.source === "user" && h.pluginId === null && h.isManaged === false && /^sha256:[a-f0-9]{64}$/.test(h.currentHash) && typeof h.enabled === "boolean", "NATIVE_IDENTITY_CHANGED");
|
|
123722
124431
|
need8(hooks.filter((other) => other.command === wanted.command).length === 1, "AMBIGUOUS_IDENTITY");
|
|
123723
124432
|
need8(["trusted", "untrusted", "modified"].includes(h.trustStatus), "UNKNOWN_TRUST_STATUS");
|
|
123724
124433
|
return { key: h.key, event: wanted.event, command: wanted.command, handlerType: h.handlerType, timeoutSec: h.timeoutSec, sourcePath: h.sourcePath, currentHash: h.currentHash, enabled: h.enabled, trustStatus: h.trustStatus };
|
|
@@ -123744,27 +124453,27 @@ async function enrollCodexNativeHooksUnlocked(options, connect, admission) {
|
|
|
123744
124453
|
const baseReceipt = { planDigest, skillsCli: skillsCli.receipt, nativeVersion: rpc.version, nativeConfigPath, ownedNativeProcessId: rpc.processId, agent: "codex", applied: false, planned, nativeEligible: planned.length === 0, bindingRefreshRequired: false, transport: "owned-stdio-process", existingSessionsReloaded: false, nativeExecutionVerified: false, ...codexAdmissionReceipt(admission) };
|
|
123745
124454
|
if (options.apply)
|
|
123746
124455
|
need8(options.reviewedPlanDigest === planDigest, "PLAN_CHANGED: run the dry-run again and pass its reviewed --plan-digest with --apply");
|
|
123747
|
-
const parent =
|
|
123748
|
-
const st =
|
|
124456
|
+
const parent = join79(dataDir3, "native-hook-trust");
|
|
124457
|
+
const st = lstatSync51(parent, { throwIfNoEntry: false });
|
|
123749
124458
|
if (st) {
|
|
123750
124459
|
need8(st.isDirectory() && !st.isSymbolicLink() && st.uid === process.getuid() && (st.mode & 511) === 448, "UNSAFE_JOURNAL");
|
|
123751
|
-
const priorJournals =
|
|
124460
|
+
const priorJournals = readdirSync29(parent);
|
|
123752
124461
|
need8(priorJournals.length < 1000, "JOURNAL_BOUND");
|
|
123753
124462
|
for (const name of priorJournals) {
|
|
123754
124463
|
need8(/^[a-f0-9-]{36}$/.test(name), "UNSAFE_JOURNAL");
|
|
123755
|
-
need8(
|
|
124464
|
+
need8(lstatSync51(join79(parent, name, "receipt.json"), { throwIfNoEntry: false })?.isFile(), "RECONCILE_REQUIRED: inspect the incomplete private journal before applying again");
|
|
123756
124465
|
}
|
|
123757
124466
|
}
|
|
123758
124467
|
if (!options.apply || !planned.length)
|
|
123759
124468
|
return baseReceipt;
|
|
123760
124469
|
if (!st)
|
|
123761
|
-
|
|
123762
|
-
journal =
|
|
123763
|
-
|
|
123764
|
-
save(
|
|
123765
|
-
save(
|
|
123766
|
-
save(
|
|
123767
|
-
save(
|
|
124470
|
+
mkdirSync33(parent, { mode: 448 });
|
|
124471
|
+
journal = join79(parent, randomUUID17());
|
|
124472
|
+
mkdirSync33(journal, { mode: 448 });
|
|
124473
|
+
save(join79(journal, "config.before.toml"), configFile.bytes);
|
|
124474
|
+
save(join79(journal, "hooks.before.json"), hooksFile.bytes);
|
|
124475
|
+
save(join79(journal, "policy.before.json"), policyFile.bytes);
|
|
124476
|
+
save(join79(journal, "intent.json"), json2({ ...digestInput, planDigest, hooks: planned, nativeHooks: discovered.map(safeNativeHook), ...codexAdmissionReceipt(admission) }));
|
|
123768
124477
|
need8(isDeepStrictEqual22(admit(await list2()), admitted), "NATIVE_DISCOVERY_CHANGED");
|
|
123769
124478
|
checkInputs();
|
|
123770
124479
|
let result2;
|
|
@@ -123780,7 +124489,7 @@ async function enrollCodexNativeHooksUnlocked(options, connect, admission) {
|
|
|
123780
124489
|
for (const h of planned)
|
|
123781
124490
|
expectedConfig.hooks.state[h.key] = { ...expectedConfig.hooks.state[h.key], enabled: true, trusted_hash: h.currentHash };
|
|
123782
124491
|
need8(isDeepStrictEqual22(structure3(after2.text), expectedConfig) && codexTrustTextWitness(after2.text, keys4) === preservationText, "PRESERVATION_FAILED");
|
|
123783
|
-
const savedConfig = snapshot(
|
|
124492
|
+
const savedConfig = snapshot(join79(journal, "config.before.toml"), true);
|
|
123784
124493
|
need8(savedConfig.sha256 === configFile.sha256, "JOURNAL_CHANGED");
|
|
123785
124494
|
const discoveryTransition = binding.discovery?.codex ? createCodexHookDiscoveryRecovery(binding.discovery.codex, savedConfig, after2, planned) : undefined;
|
|
123786
124495
|
unchanged2(policyFile);
|
|
@@ -123796,13 +124505,13 @@ async function enrollCodexNativeHooksUnlocked(options, connect, admission) {
|
|
|
123796
124505
|
skillsCli.recheck();
|
|
123797
124506
|
assertManagedAgentBridge("codex", { home, dataDir: dataDir3, projectDir: home, codexDiscoveryRecovery: discoveryTransition });
|
|
123798
124507
|
const receipt = { ...baseReceipt, applied: true, nativeEligible: true, bindingRefreshRequired: !!discoveryTransition, ...discoveryTransition ? { discoveryTransition } : {}, nativeConfigVersion: result2.version, journal, beforeSha256: configFile.sha256, afterSha256: after2.sha256, unrelatedSettingsAndCommentsPreserved: true };
|
|
123799
|
-
save(
|
|
124508
|
+
save(join79(journal, "receipt.json"), json2(receipt));
|
|
123800
124509
|
return receipt;
|
|
123801
124510
|
} catch (error61) {
|
|
123802
124511
|
const message = error61 instanceof Error && error61.message.startsWith("CODEX_HOOK_TRUST_") ? error61.message : "CODEX_HOOK_TRUST_REFUSED: verify managed bridge installation and native Codex support";
|
|
123803
124512
|
if (journal) {
|
|
123804
124513
|
try {
|
|
123805
|
-
save(
|
|
124514
|
+
save(join79(journal, "stopped.json"), json2({ error: message.split(":")[0], automaticRollback: false, reconcileBeforeRetry: true, ...codexAdmissionReceipt(admission) }));
|
|
123806
124515
|
} catch {}
|
|
123807
124516
|
}
|
|
123808
124517
|
throw new Error(message);
|
|
@@ -123811,7 +124520,7 @@ async function enrollCodexNativeHooksUnlocked(options, connect, admission) {
|
|
|
123811
124520
|
}
|
|
123812
124521
|
}
|
|
123813
124522
|
var json2 = (value) => JSON.stringify(value, null, 2) + `
|
|
123814
|
-
`, structure3 = (text5) => JSON.parse(JSON.stringify(Bun.TOML.parse(text5))), eventNames, makePlanDigest = (value) =>
|
|
124523
|
+
`, structure3 = (text5) => JSON.parse(JSON.stringify(Bun.TOML.parse(text5))), eventNames, makePlanDigest = (value) => createHash50("sha256").update(json2(value)).digest("hex"), safeNativeHook = (hook) => ({ key: hook.key, eventName: hook.eventName, handlerType: hook.handlerType, command: hook.command, matcher: hook.matcher, timeoutSec: hook.timeoutSec, async: hook.async, statusMessage: hook.statusMessage, additionalContextLimit: hook.additionalContextLimit, sourcePath: hook.sourcePath, source: hook.source, pluginId: hook.pluginId, isManaged: hook.isManaged, currentHash: hook.currentHash, enabled: hook.enabled, trustStatus: hook.trustStatus }), withoutCodexCommand = (value) => {
|
|
123815
124524
|
const copy = structuredClone(value);
|
|
123816
124525
|
if (copy?.bridge?.commands && Object.hasOwn(copy.bridge.commands, "codex"))
|
|
123817
124526
|
copy.bridge.commands.codex = "<reviewed-codex-binding>";
|
|
@@ -123835,9 +124544,9 @@ var init_agent_codex_trust = __esm(() => {
|
|
|
123835
124544
|
});
|
|
123836
124545
|
|
|
123837
124546
|
// src/lib/codex-native-trust.ts
|
|
123838
|
-
import { createHash as
|
|
123839
|
-
import { readFileSync as
|
|
123840
|
-
import { join as
|
|
124547
|
+
import { createHash as createHash51 } from "crypto";
|
|
124548
|
+
import { readFileSync as readFileSync48 } from "fs";
|
|
124549
|
+
import { join as join80, resolve as resolve45 } from "path";
|
|
123841
124550
|
function refuse7(detail) {
|
|
123842
124551
|
throw new Error(`NATIVE_SKILL_POLICY_TRUST_REFUSED: ${detail}`);
|
|
123843
124552
|
}
|
|
@@ -123856,15 +124565,15 @@ function planCodexNativeTrust(options) {
|
|
|
123856
124565
|
refuse7("digests must be unique");
|
|
123857
124566
|
if (typeof expectedPolicySha256 !== "string" || !HEX_DIGEST2.test(expectedPolicySha256))
|
|
123858
124567
|
refuse7("--expected-policy-sha256 must be lowercase SHA-256 hex");
|
|
123859
|
-
const dataDir3 =
|
|
124568
|
+
const dataDir3 = resolve45(options.dataDir ?? getDataDirReadOnly()), policyPath = join80(dataDir3, "agent-policy.json");
|
|
123860
124569
|
assertOperatorTrustRoot(dataDir3);
|
|
123861
|
-
const
|
|
123862
|
-
if (!
|
|
124570
|
+
const snapshot3 = readManagedSkillPolicySnapshot(dataDir3);
|
|
124571
|
+
if (!snapshot3)
|
|
123863
124572
|
refuse7("no managed policy; run skills hook install first");
|
|
123864
|
-
const policySha256Before = sha7(
|
|
124573
|
+
const policySha256Before = sha7(snapshot3.text);
|
|
123865
124574
|
if (policySha256Before !== expectedPolicySha256)
|
|
123866
124575
|
refuse7("the managed policy bytes differ from --expected-policy-sha256; read the current file and pass its SHA-256");
|
|
123867
|
-
const policy =
|
|
124576
|
+
const policy = snapshot3.value;
|
|
123868
124577
|
if (!policy.bridge || typeof policy.bridge !== "object" || Array.isArray(policy.bridge))
|
|
123869
124578
|
refuse7("the managed policy has no bridge binding; run skills hook install first");
|
|
123870
124579
|
const current = parseCodexNativePolicyTrust(policy.bridge.codexNativePolicy);
|
|
@@ -123873,7 +124582,7 @@ function planCodexNativeTrust(options) {
|
|
|
123873
124582
|
const next = { ...policy, bridge: { ...policy.bridge, codexNativePolicy: { executableDigests } } };
|
|
123874
124583
|
assertAgentPolicyCollections(next);
|
|
123875
124584
|
const after2 = serializeManagedSkillPolicy(next);
|
|
123876
|
-
const plan = { dataDir: dataDir3, profileId: typeof policy.profileId === "string" ? policy.profileId : "default", changes: after2 ===
|
|
124585
|
+
const plan = { dataDir: dataDir3, profileId: typeof policy.profileId === "string" ? policy.profileId : "default", changes: after2 === snapshot3.text ? [] : [{ path: policyPath, before: snapshot3.text, after: after2 }], nativeSkills: [], observedPolicy: { path: policyPath, before: snapshot3.text } };
|
|
123877
124586
|
return { schema: CODEX_NATIVE_TRUST_PLAN_SCHEMA, dataDir: dataDir3, policyPath, platform: platform2, digestsBefore: [...current.executableDigests[platform2] ?? []], digestsAfter: [...digests], policySha256Before, policySha256After: sha7(after2), plan };
|
|
123878
124587
|
}
|
|
123879
124588
|
function previewCodexNativeTrust(plan) {
|
|
@@ -123894,7 +124603,7 @@ function applyCodexNativeTrust(plan, expectedPolicySha256) {
|
|
|
123894
124603
|
const backupPath = result2.backups[0];
|
|
123895
124604
|
if (result2.backups.length !== 1 || typeof backupPath !== "string")
|
|
123896
124605
|
throw new Error("NATIVE_SKILL_POLICY_TRUST_WRITE_UNVERIFIED: the pre-change backup was not recorded");
|
|
123897
|
-
const backupSha256 = sha7(
|
|
124606
|
+
const backupSha256 = sha7(readFileSync48(backupPath));
|
|
123898
124607
|
if (backupSha256 !== plan.policySha256Before)
|
|
123899
124608
|
throw new Error(`NATIVE_SKILL_POLICY_TRUST_WRITE_UNVERIFIED: the backup at ${backupPath} does not read back as the pre-change policy`);
|
|
123900
124609
|
receipt.backup = { path: backupPath, sha256: backupSha256, verified: true };
|
|
@@ -123908,7 +124617,7 @@ function applyCodexNativeTrust(plan, expectedPolicySha256) {
|
|
|
123908
124617
|
receipt.readback = { policySha256: sha7(readback.text), digests, verified: true };
|
|
123909
124618
|
return receipt;
|
|
123910
124619
|
}
|
|
123911
|
-
var CODEX_NATIVE_TRUST_PLAN_SCHEMA = "skills.codex-native-trust-plan/v1", CODEX_NATIVE_TRUST_RECEIPT_SCHEMA = "skills.codex-native-trust-receipt/v1", PLATFORM_KEY2, HEX_DIGEST2, MAX_DIGESTS = 16, sha7 = (data) =>
|
|
124620
|
+
var CODEX_NATIVE_TRUST_PLAN_SCHEMA = "skills.codex-native-trust-plan/v1", CODEX_NATIVE_TRUST_RECEIPT_SCHEMA = "skills.codex-native-trust-receipt/v1", PLATFORM_KEY2, HEX_DIGEST2, MAX_DIGESTS = 16, sha7 = (data) => createHash51("sha256").update(data).digest("hex");
|
|
123912
124621
|
var init_codex_native_trust = __esm(() => {
|
|
123913
124622
|
init_config();
|
|
123914
124623
|
init_managed_policy();
|
|
@@ -123920,9 +124629,9 @@ var init_codex_native_trust = __esm(() => {
|
|
|
123920
124629
|
});
|
|
123921
124630
|
|
|
123922
124631
|
// src/lib/claude-prospective-review.ts
|
|
123923
|
-
import { createHash as
|
|
123924
|
-
import { lstatSync as
|
|
123925
|
-
import { basename as basename26, dirname as
|
|
124632
|
+
import { createHash as createHash52 } from "crypto";
|
|
124633
|
+
import { lstatSync as lstatSync52, readFileSync as readFileSync49, readdirSync as readdirSync30, realpathSync as realpathSync24 } from "fs";
|
|
124634
|
+
import { basename as basename26, dirname as dirname53, isAbsolute as isAbsolute48, join as join81, relative as relative17, resolve as resolve46, sep as sep24 } from "path";
|
|
123926
124635
|
import { isDeepStrictEqual as isDeepStrictEqual23 } from "util";
|
|
123927
124636
|
function hashClaudeDiscoveryBinding(binding) {
|
|
123928
124637
|
return sha8(JSON.stringify(binding));
|
|
@@ -123973,7 +124682,7 @@ function keys4(value, expected, name) {
|
|
|
123973
124682
|
refuse8(`${name} has an unsupported shape`);
|
|
123974
124683
|
}
|
|
123975
124684
|
function abs(path2, name) {
|
|
123976
|
-
if (typeof path2 !== "string" || !path2 || path2.length > 4096 || /[\x00-\x1f\x7f]/.test(path2) || !
|
|
124685
|
+
if (typeof path2 !== "string" || !path2 || path2.length > 4096 || /[\x00-\x1f\x7f]/.test(path2) || !isAbsolute48(path2) || resolve46(path2) !== path2)
|
|
123977
124686
|
refuse8(`${name} must be a normalized absolute path`);
|
|
123978
124687
|
}
|
|
123979
124688
|
function validUtcTimestamp(value) {
|
|
@@ -123981,27 +124690,27 @@ function validUtcTimestamp(value) {
|
|
|
123981
124690
|
}
|
|
123982
124691
|
function safeFile(path2, name) {
|
|
123983
124692
|
abs(path2, name);
|
|
123984
|
-
for (let at2 =
|
|
123985
|
-
const stat4 =
|
|
124693
|
+
for (let at2 = dirname53(path2);; at2 = dirname53(at2)) {
|
|
124694
|
+
const stat4 = lstatSync52(at2, { throwIfNoEntry: false });
|
|
123986
124695
|
if (!stat4?.isDirectory() || stat4.isSymbolicLink())
|
|
123987
124696
|
refuse8(`${name} traverses a symlink or non-directory`);
|
|
123988
|
-
if (at2 ===
|
|
124697
|
+
if (at2 === dirname53(at2))
|
|
123989
124698
|
break;
|
|
123990
124699
|
}
|
|
123991
|
-
const stat3 =
|
|
124700
|
+
const stat3 = lstatSync52(path2, { throwIfNoEntry: false });
|
|
123992
124701
|
if (!stat3?.isFile() || stat3.isSymbolicLink() || stat3.size > MAX_CANDIDATE_BYTES)
|
|
123993
124702
|
refuse8(`${name} is not a bounded regular file`);
|
|
123994
|
-
return
|
|
124703
|
+
return readFileSync49(path2);
|
|
123995
124704
|
}
|
|
123996
124705
|
function canonicalDir(path2, name) {
|
|
123997
124706
|
abs(path2, name);
|
|
123998
|
-
if (realpathSync24(path2) !== path2 || !
|
|
124707
|
+
if (realpathSync24(path2) !== path2 || !lstatSync52(path2).isDirectory())
|
|
123999
124708
|
refuse8(`${name} is not a canonical directory`);
|
|
124000
|
-
for (let at2 = path2;; at2 =
|
|
124001
|
-
const stat3 =
|
|
124709
|
+
for (let at2 = path2;; at2 = dirname53(at2)) {
|
|
124710
|
+
const stat3 = lstatSync52(at2);
|
|
124002
124711
|
if (!stat3.isDirectory() || stat3.isSymbolicLink())
|
|
124003
124712
|
refuse8(`${name} traverses a symlink`);
|
|
124004
|
-
if (at2 ===
|
|
124713
|
+
if (at2 === dirname53(at2))
|
|
124005
124714
|
break;
|
|
124006
124715
|
}
|
|
124007
124716
|
}
|
|
@@ -124010,17 +124719,17 @@ function captureClaudeProspectiveCandidateClosure(root2) {
|
|
|
124010
124719
|
const entries = [];
|
|
124011
124720
|
let bytes = 0;
|
|
124012
124721
|
const walk2 = (directory3) => {
|
|
124013
|
-
const beforeDirectory =
|
|
124014
|
-
const names =
|
|
124722
|
+
const beforeDirectory = lstatSync52(directory3);
|
|
124723
|
+
const names = readdirSync30(directory3).sort();
|
|
124015
124724
|
if (new Set(names).size !== names.length)
|
|
124016
124725
|
refuse8("candidate contains ambiguous directory entries");
|
|
124017
124726
|
for (const name of names) {
|
|
124018
124727
|
if (!name || name === "." || name === ".." || name.includes("/") || name.includes("\\") || /[\x00-\x1f\x7f]/.test(name))
|
|
124019
124728
|
refuse8("candidate contains an unsupported path member");
|
|
124020
|
-
const path2 =
|
|
124729
|
+
const path2 = join81(directory3, name), rel = relative17(root2, path2).split(sep24).join("/");
|
|
124021
124730
|
if (entries.length >= MAX_CANDIDATE_FILES)
|
|
124022
124731
|
refuse8("candidate closure exceeds its member limit");
|
|
124023
|
-
const stat3 =
|
|
124732
|
+
const stat3 = lstatSync52(path2);
|
|
124024
124733
|
if (stat3.isSymbolicLink())
|
|
124025
124734
|
refuse8("candidate closure contains a symlink");
|
|
124026
124735
|
if (stat3.isDirectory()) {
|
|
@@ -124030,14 +124739,14 @@ function captureClaudeProspectiveCandidateClosure(root2) {
|
|
|
124030
124739
|
bytes += stat3.size;
|
|
124031
124740
|
if (bytes > MAX_CANDIDATE_BYTES)
|
|
124032
124741
|
refuse8("candidate closure exceeds its byte limit");
|
|
124033
|
-
const content = safeFile(path2, "candidate member"), after2 =
|
|
124742
|
+
const content = safeFile(path2, "candidate member"), after2 = lstatSync52(path2);
|
|
124034
124743
|
if (!after2.isFile() || after2.dev !== stat3.dev || after2.ino !== stat3.ino || after2.size !== stat3.size || after2.mtimeMs !== stat3.mtimeMs || after2.ctimeMs !== stat3.ctimeMs)
|
|
124035
124744
|
refuse8("candidate member changed while being read");
|
|
124036
124745
|
entries.push([rel, "file", sha8(content)]);
|
|
124037
124746
|
} else
|
|
124038
124747
|
refuse8("candidate closure contains a special file");
|
|
124039
124748
|
}
|
|
124040
|
-
const afterDirectory =
|
|
124749
|
+
const afterDirectory = lstatSync52(directory3);
|
|
124041
124750
|
if (!afterDirectory.isDirectory() || afterDirectory.dev !== beforeDirectory.dev || afterDirectory.ino !== beforeDirectory.ino || afterDirectory.mtimeMs !== beforeDirectory.mtimeMs || afterDirectory.ctimeMs !== beforeDirectory.ctimeMs)
|
|
124042
124751
|
refuse8("candidate directory changed while being read");
|
|
124043
124752
|
};
|
|
@@ -124048,9 +124757,9 @@ function captureClaudeProspectiveCandidateClosure(root2) {
|
|
|
124048
124757
|
function captureClaudeInstallerCandidate(root2, policyPath) {
|
|
124049
124758
|
const closureSha256 = captureClaudeProspectiveCandidateClosure(root2);
|
|
124050
124759
|
const dataDir3 = getDataDirReadOnly();
|
|
124051
|
-
if (policyPath !==
|
|
124760
|
+
if (policyPath !== join81(dataDir3, "agent-policy.json"))
|
|
124052
124761
|
refuse8("Skills policy path differs from the package-owned resolver");
|
|
124053
|
-
const catalogPath =
|
|
124762
|
+
const catalogPath = join81(root2, ".claude-plugin/marketplace.json");
|
|
124054
124763
|
const catalog = jsonFile(catalogPath, "candidate catalog");
|
|
124055
124764
|
if (!catalog || !Array.isArray(catalog.plugins))
|
|
124056
124765
|
refuse8("candidate catalog is invalid");
|
|
@@ -124061,13 +124770,13 @@ function captureClaudeInstallerCandidate(root2, policyPath) {
|
|
|
124061
124770
|
if (names.has(row2.name))
|
|
124062
124771
|
refuse8("candidate catalog repeats a plugin name");
|
|
124063
124772
|
names.add(row2.name);
|
|
124064
|
-
if (typeof row2.source !== "string" ||
|
|
124773
|
+
if (typeof row2.source !== "string" || isAbsolute48(row2.source) || /[\\\x00-\x1f\x7f]/.test(row2.source))
|
|
124065
124774
|
refuse8("candidate catalog plugin source is invalid");
|
|
124066
|
-
const pluginRoot =
|
|
124067
|
-
const relativeRoot =
|
|
124068
|
-
if (!relativeRoot || relativeRoot === "." || relativeRoot === ".." || relativeRoot.startsWith(`..${
|
|
124775
|
+
const pluginRoot = resolve46(root2, row2.source);
|
|
124776
|
+
const relativeRoot = relative17(root2, pluginRoot);
|
|
124777
|
+
if (!relativeRoot || relativeRoot === "." || relativeRoot === ".." || relativeRoot.startsWith(`..${sep24}`) || isAbsolute48(relativeRoot))
|
|
124069
124778
|
refuse8("candidate plugin source escapes the candidate root");
|
|
124070
|
-
const path2 =
|
|
124779
|
+
const path2 = join81(pluginRoot, ".claude-plugin/plugin.json");
|
|
124071
124780
|
return { plugin: row2.name, ...captureClaudePluginManifestFile(path2) };
|
|
124072
124781
|
});
|
|
124073
124782
|
const policy = readManagedSkillPolicySnapshot(dataDir3);
|
|
@@ -124084,7 +124793,7 @@ function captureClaudeInstallerCandidate(root2, policyPath) {
|
|
|
124084
124793
|
}
|
|
124085
124794
|
function readWitness(path2, expected, name) {
|
|
124086
124795
|
abs(path2, `${name} path`);
|
|
124087
|
-
const stat3 =
|
|
124796
|
+
const stat3 = lstatSync52(path2, { throwIfNoEntry: false });
|
|
124088
124797
|
if (expected === null) {
|
|
124089
124798
|
if (stat3)
|
|
124090
124799
|
refuse8(`${name} was absent at review but is now present`);
|
|
@@ -124136,7 +124845,7 @@ function unreviewedProjectSettingsRefusal(path2, expected, name) {
|
|
|
124136
124845
|
return null;
|
|
124137
124846
|
}
|
|
124138
124847
|
function jsonFile(path2, name) {
|
|
124139
|
-
const stat3 =
|
|
124848
|
+
const stat3 = lstatSync52(path2, { throwIfNoEntry: false });
|
|
124140
124849
|
if (!stat3)
|
|
124141
124850
|
return null;
|
|
124142
124851
|
let value;
|
|
@@ -124166,9 +124875,9 @@ function assertNoUnprojectedSkillPayload(root2, catalogPath, marketplace, plugin
|
|
|
124166
124875
|
if (entries[0].skills !== undefined || entries[0].commands !== undefined)
|
|
124167
124876
|
refuse8("candidate marketplace entry declares native skill or command payloads");
|
|
124168
124877
|
const walk2 = (directory3) => {
|
|
124169
|
-
for (const name of
|
|
124170
|
-
const path2 =
|
|
124171
|
-
const stat3 =
|
|
124878
|
+
for (const name of readdirSync30(directory3).sort()) {
|
|
124879
|
+
const path2 = join81(directory3, name), rel = relative17(root2, path2).split(sep24).join("/");
|
|
124880
|
+
const stat3 = lstatSync52(path2);
|
|
124172
124881
|
if (stat3.isDirectory())
|
|
124173
124882
|
walk2(path2);
|
|
124174
124883
|
else if (stat3.isFile()) {
|
|
@@ -124213,10 +124922,10 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
|
|
|
124213
124922
|
abs(request2.skills.dataDir, "Skills data directory");
|
|
124214
124923
|
if (!SHA2562.test(request2.skills.policySha256) || !SHA2562.test(request2.skills.discoverySha256))
|
|
124215
124924
|
refuse8("invalid Skills policy or discovery SHA-256");
|
|
124216
|
-
const
|
|
124217
|
-
if (!
|
|
124925
|
+
const snapshot3 = readManagedSkillPolicySnapshot(request2.skills.dataDir);
|
|
124926
|
+
if (!snapshot3 || sha8(snapshot3.text) !== request2.skills.policySha256)
|
|
124218
124927
|
refuse8("Skills policy preimage changed or is absent");
|
|
124219
|
-
const aliases =
|
|
124928
|
+
const aliases = snapshot3.value.bridge?.rootAliases ?? [];
|
|
124220
124929
|
if (!Array.isArray(aliases))
|
|
124221
124930
|
refuse8("Skills policy root aliases are invalid");
|
|
124222
124931
|
try {
|
|
@@ -124225,16 +124934,16 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
|
|
|
124225
124934
|
refuse8("Skills policy root alias changed or is invalid");
|
|
124226
124935
|
}
|
|
124227
124936
|
const canonical9 = (path2) => canonicalAgentPath(path2, aliases);
|
|
124228
|
-
const configRoot = canonical9(
|
|
124937
|
+
const configRoot = canonical9(join81(request2.native.home, ".claude"));
|
|
124229
124938
|
canonicalDir(configRoot, "Claude config root");
|
|
124230
124939
|
if (request2.native.configRoot !== configRoot)
|
|
124231
124940
|
refuse8("Claude config root differs from the current resolver and reviewed root alias");
|
|
124232
|
-
const pluginRoot =
|
|
124941
|
+
const pluginRoot = join81(configRoot, "plugins");
|
|
124233
124942
|
if (process.env.CLAUDE_CODE_PLUGIN_CACHE_DIR)
|
|
124234
124943
|
refuse8("CLAUDE_CODE_PLUGIN_CACHE_DIR overrides are unsupported by this prospective schema; bind and review the actual cache root first");
|
|
124235
124944
|
if (request2.native.pluginRoot !== pluginRoot)
|
|
124236
124945
|
refuse8("Claude plugin root differs from the supported standard plugins directory; Cowork and custom cache roots require a separately bound schema");
|
|
124237
|
-
const pluginRootStat =
|
|
124946
|
+
const pluginRootStat = lstatSync52(pluginRoot, { throwIfNoEntry: false });
|
|
124238
124947
|
if (pluginRootStat && (!pluginRootStat.isDirectory() || pluginRootStat.isSymbolicLink()))
|
|
124239
124948
|
refuse8("Claude plugin root is not a regular directory");
|
|
124240
124949
|
keys4(request2.candidate, ["marketplace", "root", "catalogPath", "catalogSha256", "closureSha256", "plugins", ...hasOwn2(request2.candidate, "releaseManifest") ? ["releaseManifest"] : [], ...hasOwn2(request2.candidate, "functionHooksEnv") ? ["functionHooksEnv"] : [], ...hasOwn2(request2.candidate, "settingsMarketplaceSource") ? ["settingsMarketplaceSource"] : []], "candidate");
|
|
@@ -124252,9 +124961,9 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
|
|
|
124252
124961
|
if (hasOwn2(candidate2, "settingsMarketplaceSource") && (candidate2.settingsMarketplaceSource !== "directory" || request2.scope.kind !== "user" || !measuredSettingsWriter))
|
|
124253
124962
|
refuse8("settings marketplace source operation requires directory source and a measured Claude user-scope writer");
|
|
124254
124963
|
abs(candidate2.catalogPath, "catalog path");
|
|
124255
|
-
if (!candidate2.catalogPath.startsWith(candidate2.root +
|
|
124964
|
+
if (!candidate2.catalogPath.startsWith(candidate2.root + sep24))
|
|
124256
124965
|
refuse8("catalog must be inside the frozen candidate root");
|
|
124257
|
-
if (candidate2.catalogPath !==
|
|
124966
|
+
if (candidate2.catalogPath !== join81(candidate2.root, ".claude-plugin", "marketplace.json"))
|
|
124258
124967
|
refuse8("marketplace catalog must be at the frozen root's .claude-plugin/marketplace.json path");
|
|
124259
124968
|
if (!SHA2562.test(candidate2.catalogSha256) || !SHA2562.test(candidate2.closureSha256))
|
|
124260
124969
|
refuse8("candidate digests must be lowercase SHA-256");
|
|
@@ -124296,11 +125005,11 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
|
|
|
124296
125005
|
if (Buffer.byteLength(canonicalJson3(selected.optionsPatch), "utf8") > 16 * 1024)
|
|
124297
125006
|
refuse8("selected plugin optionsPatch exceeds its byte bound");
|
|
124298
125007
|
abs(selected.manifestPath, "manifest path");
|
|
124299
|
-
if (!selected.manifestPath.startsWith(candidate2.root +
|
|
125008
|
+
if (!selected.manifestPath.startsWith(candidate2.root + sep24) || basename26(dirname53(selected.manifestPath)) !== ".claude-plugin" || selected.manifestPath !== join81(dirname53(dirname53(selected.manifestPath)), ".claude-plugin", "plugin.json"))
|
|
124300
125009
|
refuse8("plugin manifest must be under a plugin root inside the frozen marketplace closure");
|
|
124301
|
-
const pluginRoot2 =
|
|
124302
|
-
const relativePluginRoot =
|
|
124303
|
-
if (!relativePluginRoot || relativePluginRoot === "." || relativePluginRoot === ".." || relativePluginRoot.startsWith(`..${
|
|
125010
|
+
const pluginRoot2 = dirname53(dirname53(selected.manifestPath));
|
|
125011
|
+
const relativePluginRoot = relative17(candidate2.root, pluginRoot2);
|
|
125012
|
+
if (!relativePluginRoot || relativePluginRoot === "." || relativePluginRoot === ".." || relativePluginRoot.startsWith(`..${sep24}`) || isAbsolute48(relativePluginRoot))
|
|
124304
125013
|
refuse8("plugin root must be a child of the frozen marketplace root");
|
|
124305
125014
|
canonicalDir(pluginRoot2, "candidate plugin root");
|
|
124306
125015
|
if (!SHA2562.test(selected.manifestSha256))
|
|
@@ -124332,9 +125041,9 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
|
|
|
124332
125041
|
refuse8("selected plugins collide after native cache path sanitization");
|
|
124333
125042
|
sanitizedCachePaths.add(sanitizedCachePath);
|
|
124334
125043
|
const sourcePath = selectedEntry.source;
|
|
124335
|
-
const sourceRelativePath =
|
|
125044
|
+
const sourceRelativePath = relative17(candidate2.root, pluginRoot2).split(sep24).join("/");
|
|
124336
125045
|
const expectedSourcePath = sourceRelativePath === "." ? "./" : `./${sourceRelativePath}`;
|
|
124337
|
-
if (typeof sourcePath !== "string" ||
|
|
125046
|
+
if (typeof sourcePath !== "string" || isAbsolute48(sourcePath) || sourcePath !== expectedSourcePath || resolve46(dirname53(dirname53(candidate2.catalogPath)), sourcePath) !== pluginRoot2)
|
|
124338
125047
|
refuse8("marketplace source does not resolve to the frozen plugin closure; external source mapping is not supported by this schema");
|
|
124339
125048
|
const entry = captureClaudeMarketplaceEntry(candidate2.catalogPath, candidate2.marketplace, selected.plugin);
|
|
124340
125049
|
if (entry.sha256.length !== 64)
|
|
@@ -124354,12 +125063,12 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
|
|
|
124354
125063
|
for (const name of ["settings", "projectSettings", "projectSettingsLocal", "marketplaces", "installedPlugins"])
|
|
124355
125064
|
keys4(request2.preimages[name], ["path", "sha256"], `${name} preimage`);
|
|
124356
125065
|
const expectedPaths = {
|
|
124357
|
-
settings: request2.scope.kind === "project" ? canonical9(
|
|
124358
|
-
userSettings: canonical9(
|
|
124359
|
-
projectSettings: canonical9(
|
|
124360
|
-
projectSettingsLocal: canonical9(
|
|
124361
|
-
marketplaces: canonical9(
|
|
124362
|
-
installedPlugins: canonical9(
|
|
125066
|
+
settings: request2.scope.kind === "project" ? canonical9(join81(request2.scope.projectPath, ".claude/settings.json")) : canonical9(join81(request2.native.home, ".claude/settings.json")),
|
|
125067
|
+
userSettings: canonical9(join81(request2.native.home, ".claude/settings.json")),
|
|
125068
|
+
projectSettings: canonical9(join81(request2.scope.projectPath, ".claude/settings.json")),
|
|
125069
|
+
projectSettingsLocal: canonical9(join81(request2.scope.projectPath, ".claude/settings.local.json")),
|
|
125070
|
+
marketplaces: canonical9(join81(request2.native.home, ".claude/plugins/known_marketplaces.json")),
|
|
125071
|
+
installedPlugins: canonical9(join81(request2.native.home, ".claude/plugins/installed_plugins.json"))
|
|
124363
125072
|
};
|
|
124364
125073
|
for (const name of Object.keys(expectedPaths)) {
|
|
124365
125074
|
const witness = request2.preimages[name];
|
|
@@ -124394,8 +125103,8 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
|
|
|
124394
125103
|
if (hashClaudeDiscoveryBinding(binding) !== request2.skills.discoverySha256)
|
|
124395
125104
|
refuse8("Skills discovery preimage differs from its reviewed binding");
|
|
124396
125105
|
assertProjectDiscovery("claude", [request2.scope.projectPath], request2.native.home, canonical9, binding);
|
|
124397
|
-
const stored =
|
|
124398
|
-
if (!stored || !isDeepStrictEqual23(stored, binding) ||
|
|
125106
|
+
const stored = snapshot3.value.bridge?.discovery?.claude;
|
|
125107
|
+
if (!stored || !isDeepStrictEqual23(stored, binding) || snapshot3.value.bridge?.home !== request2.native.home)
|
|
124399
125108
|
refuse8("Skills policy does not bind the reviewed current Claude discovery");
|
|
124400
125109
|
assertExemptUnchanged();
|
|
124401
125110
|
const delta = request2.delta;
|
|
@@ -124492,7 +125201,7 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
|
|
|
124492
125201
|
const manifestVersion = manifest.version;
|
|
124493
125202
|
const sanitizeComponent = (value) => value.replace(/[^A-Za-z0-9_-]/g, "-");
|
|
124494
125203
|
const sanitizedVersion = manifestVersion === "." || manifestVersion === ".." ? "-" : manifestVersion.replace(/[^A-Za-z0-9._-]/g, "-");
|
|
124495
|
-
const expectedInstallPath =
|
|
125204
|
+
const expectedInstallPath = join81(pluginRoot, "cache", sanitizeComponent(candidate2.marketplace), sanitizeComponent(plugin), sanitizedVersion);
|
|
124496
125205
|
if (expectedInstallPath.length > 4096)
|
|
124497
125206
|
refuse8("candidate native cache path exceeds its bound");
|
|
124498
125207
|
const targetRegistration = {
|
|
@@ -124631,7 +125340,7 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
|
|
|
124631
125340
|
{ name: "project-settings-local", path: expectedPaths.projectSettingsLocal, sha256: request2.preimages.projectSettingsLocal.sha256 },
|
|
124632
125341
|
{ name: "known-marketplaces", path: expectedPaths.marketplaces, sha256: request2.preimages.marketplaces.sha256 },
|
|
124633
125342
|
{ name: "installed-plugins", path: expectedPaths.installedPlugins, sha256: request2.preimages.installedPlugins.sha256 },
|
|
124634
|
-
{ name: "skills-policy", path:
|
|
125343
|
+
{ name: "skills-policy", path: join81(request2.skills.dataDir, "agent-policy.json"), sha256: request2.skills.policySha256 }
|
|
124635
125344
|
],
|
|
124636
125345
|
ownedDelta: { settingsEnabledPlugins: selectedPluginIds, selectedPluginOptions: selectedOptionPaths, functionHooksEnv: candidate2.functionHooksEnv ?? null, knownMarketplace: candidate2.marketplace, installedPlugins: selectedPluginIds, unrelatedGraph: "preserved-by-complete-semantic-documents" },
|
|
124637
125346
|
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",
|
|
@@ -124639,7 +125348,7 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
|
|
|
124639
125348
|
limitation: "validation-only; native installer atomicity and crash recovery are not established"
|
|
124640
125349
|
};
|
|
124641
125350
|
}
|
|
124642
|
-
var CLAUDE_PROSPECTIVE_REVIEW_SCHEMA = "skills.claude-plugin-prospective-review/v1", SHA2562, MAX_CANDIDATE_FILES = 16384, MAX_CANDIDATE_BYTES, sha8 = (data) =>
|
|
125351
|
+
var CLAUDE_PROSPECTIVE_REVIEW_SCHEMA = "skills.claude-plugin-prospective-review/v1", SHA2562, MAX_CANDIDATE_FILES = 16384, MAX_CANDIDATE_BYTES, sha8 = (data) => createHash52("sha256").update(data).digest("hex"), GENERATED_MARKETPLACE_TIME, GENERATED_FRESH_PLUGIN_INSTALLED_AT, GENERATED_PLUGIN_UPDATE_TIME, EXEMPT_PROJECT_SETTINGS, EXEMPT_PROJECT_PERMISSIONS;
|
|
124643
125352
|
var init_claude_prospective_review = __esm(() => {
|
|
124644
125353
|
init_agent_discovery();
|
|
124645
125354
|
init_agent_integration();
|
|
@@ -124673,7 +125382,7 @@ var init_claude_prospective_review = __esm(() => {
|
|
|
124673
125382
|
|
|
124674
125383
|
// src/lib/codex-plugin-reconcile.ts
|
|
124675
125384
|
import { realpathSync as realpathSync25 } from "fs";
|
|
124676
|
-
import { join as
|
|
125385
|
+
import { join as join82, resolve as resolve47 } from "path";
|
|
124677
125386
|
function boundedMessage(text5) {
|
|
124678
125387
|
const line = text5.replace(/[\u0000-\u001f\u007f-\u009f\u061c\u200e\u200f\u2028-\u202e\u2066-\u2069]/g, " ").trim();
|
|
124679
125388
|
return line.length > 800 ? `${line.slice(0, 797)}...` : line;
|
|
@@ -124707,20 +125416,20 @@ async function runCodexPluginReconcile(options) {
|
|
|
124707
125416
|
const mode = options.apply ? "apply" : "preview";
|
|
124708
125417
|
const base3 = emptyResult(mode);
|
|
124709
125418
|
try {
|
|
124710
|
-
const home =
|
|
125419
|
+
const home = resolve47(options.home), projectDir = resolve47(options.projectDir), dataDir3 = options.dataDir ?? getDataDirReadOnly();
|
|
124711
125420
|
if (options.codexHome !== undefined) {
|
|
124712
125421
|
const canonical9 = (path2) => {
|
|
124713
125422
|
try {
|
|
124714
125423
|
return realpathSync25(path2);
|
|
124715
125424
|
} catch {
|
|
124716
|
-
return
|
|
125425
|
+
return resolve47(path2);
|
|
124717
125426
|
}
|
|
124718
125427
|
};
|
|
124719
|
-
if (canonical9(options.codexHome) !== canonical9(
|
|
125428
|
+
if (canonical9(options.codexHome) !== canonical9(join82(home, ".codex")))
|
|
124720
125429
|
throw new CodexPluginReconcileRefusal("CODEX_RECONCILE_CODEX_HOME_UNSUPPORTED", "CODEX_HOME names a Codex home other than the one the Skills binding reviews");
|
|
124721
125430
|
}
|
|
124722
125431
|
const capture2 = options.captureCatalog ?? captureCodexNativeSkillCatalog;
|
|
124723
|
-
const remoteReceipts = captureCodexRemoteReceiptSnapshot(
|
|
125432
|
+
const remoteReceipts = captureCodexRemoteReceiptSnapshot(join82(home, ".codex/plugins/cache"));
|
|
124724
125433
|
const captured = await capture2({ command: options.codexCommand ?? "codex", home, ...options.codexHome === undefined ? {} : { codexHome: options.codexHome }, cwd: projectDir });
|
|
124725
125434
|
const catalog = captured && typeof captured === "object" ? { ...captured, remoteReceipts } : captured;
|
|
124726
125435
|
base3.codexVersion = typeof catalog?.version === "string" ? catalog.version : null;
|
|
@@ -124745,7 +125454,7 @@ async function runCodexPluginReconcile(options) {
|
|
|
124745
125454
|
if (!options.apply)
|
|
124746
125455
|
return { ...reported, outcome: "planned", changes };
|
|
124747
125456
|
const applied = await applyCodexPluginReconcile(plan, { ...options.codexCommand === undefined ? {} : { codexCommand: options.codexCommand }, ...options.codexHome === undefined ? {} : { codexHome: options.codexHome }, ...options.connect ? { connect: options.connect } : {} });
|
|
124748
|
-
return { ...reported, outcome: "applied", changes: applied.changed, ...applied.receipt ? { receipt:
|
|
125457
|
+
return { ...reported, outcome: "applied", changes: applied.changed, ...applied.receipt ? { receipt: resolve47(applied.receipt) } : {}, backups: applied.backups };
|
|
124749
125458
|
} catch (error61) {
|
|
124750
125459
|
return { ...base3, ...classifyCodexReconcileError(error61) };
|
|
124751
125460
|
}
|
|
@@ -124781,23 +125490,23 @@ var init_codex_plugin_reconcile = __esm(() => {
|
|
|
124781
125490
|
});
|
|
124782
125491
|
|
|
124783
125492
|
// src/lib/claude-installer-binding.ts
|
|
124784
|
-
import { createHash as
|
|
124785
|
-
import { join as
|
|
125493
|
+
import { createHash as createHash53 } from "crypto";
|
|
125494
|
+
import { join as join83 } from "path";
|
|
124786
125495
|
function readPolicy(dataDir3) {
|
|
124787
|
-
let
|
|
125496
|
+
let snapshot3;
|
|
124788
125497
|
try {
|
|
124789
|
-
|
|
125498
|
+
snapshot3 = readManagedSkillPolicySnapshot(dataDir3);
|
|
124790
125499
|
} catch {
|
|
124791
125500
|
return refuse9("POLICY_UNREADABLE");
|
|
124792
125501
|
}
|
|
124793
|
-
return
|
|
125502
|
+
return snapshot3 ?? refuse9("POLICY_UNAVAILABLE");
|
|
124794
125503
|
}
|
|
124795
125504
|
function verifyClaudeInstallerDiscoveryBinding(policyPath) {
|
|
124796
125505
|
const dataDir3 = getDataDirReadOnly();
|
|
124797
|
-
if (policyPath !==
|
|
125506
|
+
if (policyPath !== join83(dataDir3, "agent-policy.json"))
|
|
124798
125507
|
refuse9("POLICY_PATH_MISMATCH");
|
|
124799
|
-
const
|
|
124800
|
-
const bridge =
|
|
125508
|
+
const snapshot3 = readPolicy(dataDir3);
|
|
125509
|
+
const bridge = snapshot3.value.bridge;
|
|
124801
125510
|
const binding = object14(bridge) && object14(bridge.discovery) ? bridge.discovery.claude : undefined;
|
|
124802
125511
|
if (!object14(binding))
|
|
124803
125512
|
return refuse9("BINDING_UNAVAILABLE");
|
|
@@ -124839,12 +125548,12 @@ function verifyClaudeInstallerDiscoveryBinding(policyPath) {
|
|
|
124839
125548
|
...directories.flatMap((row2) => row2.status === "drift" ? [{ kind: "directory", index: row2.index, path: row2.path, reason: row2.reason }] : []),
|
|
124840
125549
|
...accepted ? [] : [{ kind: "binding", reason: "DISCOVERY_REFUSED" }]
|
|
124841
125550
|
];
|
|
124842
|
-
if (readPolicy(dataDir3).text !==
|
|
125551
|
+
if (readPolicy(dataDir3).text !== snapshot3.text)
|
|
124843
125552
|
refuse9("POLICY_CHANGED");
|
|
124844
125553
|
return {
|
|
124845
125554
|
schema: CLAUDE_DISCOVERY_BINDING_VERIFICATION_SCHEMA,
|
|
124846
125555
|
ok: failures3.length === 0,
|
|
124847
|
-
policySha256:
|
|
125556
|
+
policySha256: createHash53("sha256").update(snapshot3.text).digest("hex"),
|
|
124848
125557
|
discoverySha256: hashClaudeDiscoveryBinding(binding),
|
|
124849
125558
|
agent: "claude",
|
|
124850
125559
|
method: binding.method,
|
|
@@ -124873,8 +125582,8 @@ var init_claude_installer_binding = __esm(() => {
|
|
|
124873
125582
|
});
|
|
124874
125583
|
|
|
124875
125584
|
// src/cli/commands/claude-installer-capture.ts
|
|
124876
|
-
import { closeSync as closeSync33, constants as constants33, fstatSync as fstatSync32, lstatSync as
|
|
124877
|
-
import { isAbsolute as
|
|
125585
|
+
import { closeSync as closeSync33, constants as constants33, fstatSync as fstatSync32, lstatSync as lstatSync53, openSync as openSync33, readSync as readSync25 } from "fs";
|
|
125586
|
+
import { isAbsolute as isAbsolute49, resolve as resolve48 } from "path";
|
|
124878
125587
|
function need11(condition, code2) {
|
|
124879
125588
|
if (!condition)
|
|
124880
125589
|
throw new CaptureRefusal(code2);
|
|
@@ -124888,11 +125597,11 @@ function exactKeys5(value, expected, operation) {
|
|
|
124888
125597
|
}
|
|
124889
125598
|
}
|
|
124890
125599
|
function canonicalPath2(value) {
|
|
124891
|
-
need11(typeof value === "string" &&
|
|
125600
|
+
need11(typeof value === "string" && isAbsolute49(value) && resolve48(value) === value && !/[\x00-\x1f\x7f]/.test(value), "INVALID_PATH: path fields must be normalized absolute paths without control characters");
|
|
124892
125601
|
}
|
|
124893
125602
|
function readRequest2(path2) {
|
|
124894
125603
|
canonicalPath2(path2);
|
|
124895
|
-
const before2 =
|
|
125604
|
+
const before2 = lstatSync53(path2, { throwIfNoEntry: false });
|
|
124896
125605
|
if (!before2?.isFile() || before2.isSymbolicLink() || before2.size > MAX_REQUEST_BYTES)
|
|
124897
125606
|
throw new CaptureRefusal("INVALID_REQUEST_FILE: --request must name a regular non-symlink JSON file no larger than 64 KiB");
|
|
124898
125607
|
const fd = openSync33(path2, constants33.O_RDONLY | (constants33.O_NOFOLLOW ?? 0) | constants33.O_NONBLOCK);
|
|
@@ -124907,7 +125616,7 @@ function readRequest2(path2) {
|
|
|
124907
125616
|
break;
|
|
124908
125617
|
length += count;
|
|
124909
125618
|
}
|
|
124910
|
-
const after2 = fstatSync32(fd), current =
|
|
125619
|
+
const after2 = fstatSync32(fd), current = lstatSync53(path2);
|
|
124911
125620
|
need11(length <= MAX_REQUEST_BYTES && length === opened.size && after2.size === opened.size && after2.mtimeMs === opened.mtimeMs && after2.ctimeMs === opened.ctimeMs && current.dev === opened.dev && current.ino === opened.ino && current.mtimeMs === opened.mtimeMs && current.ctimeMs === opened.ctimeMs, "INVALID_REQUEST_FILE: --request must remain unchanged while read");
|
|
124912
125621
|
try {
|
|
124913
125622
|
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes.subarray(0, length)));
|
|
@@ -125069,10 +125778,10 @@ __export(exports_agent_integration, {
|
|
|
125069
125778
|
MANAGED_HOOK_CONTEXT_MAX_SKILLS: () => MANAGED_HOOK_CONTEXT_MAX_SKILLS,
|
|
125070
125779
|
MANAGED_HOOK_CONTEXT_MAX_CHARS: () => MANAGED_HOOK_CONTEXT_MAX_CHARS
|
|
125071
125780
|
});
|
|
125072
|
-
import { existsSync as
|
|
125073
|
-
import { createHash as
|
|
125781
|
+
import { existsSync as existsSync37, lstatSync as lstatSync54, readFileSync as readFileSync50, readSync as readSync26, writeFileSync as writeFileSync30 } from "fs";
|
|
125782
|
+
import { createHash as createHash54 } from "crypto";
|
|
125074
125783
|
import { homedir as homedir15 } from "os";
|
|
125075
|
-
import { basename as basename27, isAbsolute as
|
|
125784
|
+
import { basename as basename27, isAbsolute as isAbsolute50, resolve as resolve49 } from "path";
|
|
125076
125785
|
function boundedEnvInteger(name, fallback, [min2, max2]) {
|
|
125077
125786
|
const raw = process.env[name];
|
|
125078
125787
|
if (raw === undefined || !/^[0-9]{1,6}$/.test(raw.trim()))
|
|
@@ -125256,9 +125965,9 @@ function registerAgentIntegration(parent) {
|
|
|
125256
125965
|
});
|
|
125257
125966
|
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) => {
|
|
125258
125967
|
try {
|
|
125259
|
-
if (!
|
|
125968
|
+
if (!isAbsolute50(options.cwd) || !isAbsolute50(options.output))
|
|
125260
125969
|
throw new Error("CODEX_NATIVE_SKILL_CATALOG_ABSOLUTE_PATH_REQUIRED");
|
|
125261
|
-
if (
|
|
125970
|
+
if (existsSync37(options.output))
|
|
125262
125971
|
throw new Error("CODEX_NATIVE_SKILL_CATALOG_OUTPUT_EXISTS");
|
|
125263
125972
|
let admission;
|
|
125264
125973
|
const catalog = await captureCodexNativeSkillCatalog({
|
|
@@ -125274,14 +125983,14 @@ function registerAgentIntegration(parent) {
|
|
|
125274
125983
|
const bytes = JSON.stringify(catalog, null, 2) + `
|
|
125275
125984
|
`;
|
|
125276
125985
|
try {
|
|
125277
|
-
|
|
125986
|
+
writeFileSync30(options.output, bytes, { flag: "wx", mode: 384 });
|
|
125278
125987
|
} catch (error61) {
|
|
125279
125988
|
if (error61 && typeof error61 === "object" && "code" in error61 && error61.code === "EEXIST")
|
|
125280
125989
|
throw new Error("CODEX_NATIVE_SKILL_CATALOG_OUTPUT_EXISTS");
|
|
125281
125990
|
throw error61;
|
|
125282
125991
|
}
|
|
125283
|
-
const written =
|
|
125284
|
-
if (!written.isFile() || written.isSymbolicLink() || (written.mode & 63) !== 0 ||
|
|
125992
|
+
const written = lstatSync54(options.output);
|
|
125993
|
+
if (!written.isFile() || written.isSymbolicLink() || (written.mode & 63) !== 0 || readFileSync50(options.output, "utf8") !== bytes)
|
|
125285
125994
|
throw new Error("CODEX_NATIVE_SKILL_CATALOG_READBACK_FAILED");
|
|
125286
125995
|
const receipt = {
|
|
125287
125996
|
version: catalog.version,
|
|
@@ -125289,7 +125998,7 @@ function registerAgentIntegration(parent) {
|
|
|
125289
125998
|
skillCount: catalog.skills.length,
|
|
125290
125999
|
output: options.output,
|
|
125291
126000
|
bytes: Buffer.byteLength(bytes),
|
|
125292
|
-
sha256:
|
|
126001
|
+
sha256: createHash54("sha256").update(bytes).digest("hex"),
|
|
125293
126002
|
...codexAdmissionReceipt(admission)
|
|
125294
126003
|
};
|
|
125295
126004
|
await writeCliOutput(options.json ? JSON.stringify(receipt) : `Captured ${receipt.skillCount} native Codex skill(s) in ${receipt.output}.`);
|
|
@@ -125323,7 +126032,7 @@ function registerAgentIntegration(parent) {
|
|
|
125323
126032
|
if (!options.reviewedHookSources)
|
|
125324
126033
|
throw new Error("Codex V6 requires a bounded --reviewed-hook-sources JSON array");
|
|
125325
126034
|
const { hashNativeJsonControls: hashNativeJsonControls2, readNativeSettingsWitnessFile: readNativeSettingsWitnessFile2 } = await Promise.resolve().then(() => (init_claude_settings_witness(), exports_claude_settings_witness));
|
|
125326
|
-
const metadata2 =
|
|
126035
|
+
const metadata2 = resolve49(options.reviewedHookSources);
|
|
125327
126036
|
const text5 = readNativeSettingsWitnessFile2(metadata2, { remaining: 1048576 }, basename27(metadata2));
|
|
125328
126037
|
hashNativeJsonControls2(`{"sources":${text5}}`);
|
|
125329
126038
|
await writeCliOutput(JSON.stringify(captureCodexSettingsV6(options.path, JSON.parse(text5)), null, 2));
|
|
@@ -125358,14 +126067,14 @@ function registerAgentIntegration(parent) {
|
|
|
125358
126067
|
});
|
|
125359
126068
|
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) => {
|
|
125360
126069
|
try {
|
|
125361
|
-
if (!
|
|
126070
|
+
if (!isAbsolute50(options.request))
|
|
125362
126071
|
throw new Error("CLAUDE_CANDIDATE_REVIEW_REFUSED: --request must be an absolute path");
|
|
125363
|
-
const stat3 =
|
|
126072
|
+
const stat3 = lstatSync54(options.request);
|
|
125364
126073
|
if (!stat3.isFile() || stat3.isSymbolicLink() || stat3.size > 1024 * 1024)
|
|
125365
126074
|
throw new Error("CLAUDE_CANDIDATE_REVIEW_REFUSED: request must be a regular file no larger than 1 MiB");
|
|
125366
|
-
const requestBytes =
|
|
126075
|
+
const requestBytes = readFileSync50(options.request);
|
|
125367
126076
|
const request2 = JSON.parse(requestBytes.toString("utf8"));
|
|
125368
|
-
const receipt = reviewClaudeProspectiveCandidate(request2,
|
|
126077
|
+
const receipt = reviewClaudeProspectiveCandidate(request2, createHash54("sha256").update(requestBytes).digest("hex"));
|
|
125369
126078
|
await writeCliOutput(options.json ? JSON.stringify(receipt) : `Claude plugin candidate review accepted for ${receipt.pluginIds.length} selected plugin(s); native state was not changed.`);
|
|
125370
126079
|
} catch (error61) {
|
|
125371
126080
|
console.error(error61 instanceof Error && error61.message.startsWith("CLAUDE_CANDIDATE_REVIEW_REFUSED:") ? error61.message : "CLAUDE_CANDIDATE_REVIEW_REFUSED: request or current preimage validation failed");
|
|
@@ -125437,8 +126146,8 @@ function registerAgentIntegration(parent) {
|
|
|
125437
126146
|
});
|
|
125438
126147
|
hook.command("install").option("--agent <agent>", `Agent to configure: ${INTEGRATION_AGENTS.join(", ")}, all (default: configured or detected agents; explicit all requires every adapter)`).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) => {
|
|
125439
126148
|
try {
|
|
125440
|
-
const discoveryInputs = options.discoveryInputs ? JSON.parse(
|
|
125441
|
-
const plan = planAgentIntegration({ projectDir: process.cwd(), agents: options.agent === undefined ? detectedIntegrationAgents() : agents(options.agent), command: options.command, profileId: options.selectionProfile, includeVendor: options.includeVendor, discoveryInputs, allowRootAliases: options.allowRootAliases, reviewedCacheAlias: options.reviewedCacheAlias, codexSkillDenials: options.codexSkillDenials ? JSON.parse(
|
|
126149
|
+
const discoveryInputs = options.discoveryInputs ? JSON.parse(readFileSync50(options.discoveryInputs, "utf8")) : undefined;
|
|
126150
|
+
const plan = planAgentIntegration({ projectDir: process.cwd(), agents: options.agent === undefined ? detectedIntegrationAgents() : agents(options.agent), command: options.command, profileId: options.selectionProfile, includeVendor: options.includeVendor, discoveryInputs, allowRootAliases: options.allowRootAliases, reviewedCacheAlias: options.reviewedCacheAlias, codexSkillDenials: options.codexSkillDenials ? JSON.parse(readFileSync50(options.codexSkillDenials, "utf8")) : undefined, codexNativeCatalog: options.codexNativeCatalog ? JSON.parse(readFileSync50(options.codexNativeCatalog, "utf8")) : undefined });
|
|
125442
126151
|
const result2 = options.apply ? applyAgentIntegration(plan) : { changed: [], backups: [] };
|
|
125443
126152
|
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) };
|
|
125444
126153
|
if (options.json)
|
|
@@ -125500,7 +126209,7 @@ function registerAgentIntegration(parent) {
|
|
|
125500
126209
|
try {
|
|
125501
126210
|
if (agents(options.agent).length !== 1)
|
|
125502
126211
|
throw new Error("A hook invocation requires one agent");
|
|
125503
|
-
const inputBytes =
|
|
126212
|
+
const inputBytes = readFileSync50(0), inputText = inputBytes.toString("utf8");
|
|
125504
126213
|
if (inputText.length > 1024 * 1024)
|
|
125505
126214
|
throw new Error("Hook input is too large");
|
|
125506
126215
|
let input3 = JSON.parse(inputText);
|
|
@@ -125513,12 +126222,12 @@ function registerAgentIntegration(parent) {
|
|
|
125513
126222
|
input3.hook_event_name = event;
|
|
125514
126223
|
const projects = [process.cwd()];
|
|
125515
126224
|
if (input3.cwd !== undefined) {
|
|
125516
|
-
if (typeof input3.cwd !== "string" || !
|
|
126225
|
+
if (typeof input3.cwd !== "string" || !isAbsolute50(input3.cwd) || input3.cwd.includes("\x00"))
|
|
125517
126226
|
throw new Error("Invalid native hook working directory");
|
|
125518
126227
|
projects.push(input3.cwd);
|
|
125519
126228
|
}
|
|
125520
126229
|
if (options.agent === "cursor") {
|
|
125521
|
-
if (!Array.isArray(input3.workspace_roots) || input3.workspace_roots.length > 32 || input3.workspace_roots.some((path2) => typeof path2 !== "string" || !
|
|
126230
|
+
if (!Array.isArray(input3.workspace_roots) || input3.workspace_roots.length > 32 || input3.workspace_roots.some((path2) => typeof path2 !== "string" || !isAbsolute50(path2) || path2.includes("\x00")))
|
|
125522
126231
|
throw new Error("Invalid Cursor workspace roots");
|
|
125523
126232
|
projects.push(...input3.workspace_roots);
|
|
125524
126233
|
input3.cwd ??= input3.workspace_roots[0] ?? process.cwd();
|
|
@@ -125666,7 +126375,7 @@ ${driftWarning}` : unavailable2, ...output2 }));
|
|
|
125666
126375
|
try {
|
|
125667
126376
|
if (options.agent && !INTEGRATION_AGENTS.includes(options.agent))
|
|
125668
126377
|
throw new Error(`Unsupported migration agent: ${options.agent}`);
|
|
125669
|
-
const discoveryInputs = options.discoveryInputs ? JSON.parse(
|
|
126378
|
+
const discoveryInputs = options.discoveryInputs ? JSON.parse(readFileSync50(options.discoveryInputs, "utf8")) : undefined;
|
|
125670
126379
|
if (options.targetManifest && (options.includeUnmanaged || options.includeVendor))
|
|
125671
126380
|
throw new Error("--target-manifest cannot be combined with broad native migration selectors");
|
|
125672
126381
|
const targetManifest = options.targetManifest ? readNativeMigrationTargetManifest(options.targetManifest) : undefined;
|
|
@@ -125861,7 +126570,7 @@ var exports_profiles = {};
|
|
|
125861
126570
|
__export(exports_profiles, {
|
|
125862
126571
|
registerProfiles: () => registerProfiles
|
|
125863
126572
|
});
|
|
125864
|
-
import { closeSync as closeSync34, constants as constants34, fstatSync as fstatSync33, openSync as openSync34, readSync as readSync27, writeFileSync as
|
|
126573
|
+
import { closeSync as closeSync34, constants as constants34, fstatSync as fstatSync33, openSync as openSync34, readSync as readSync27, writeFileSync as writeFileSync31 } from "fs";
|
|
125865
126574
|
function readProfileInput(path2) {
|
|
125866
126575
|
const fd = openSync34(path2, constants34.O_RDONLY | constants34.O_NONBLOCK);
|
|
125867
126576
|
try {
|
|
@@ -125889,7 +126598,7 @@ function registerProfiles(parent) {
|
|
|
125889
126598
|
try {
|
|
125890
126599
|
const profile = await readSkillProfile(id3), text5 = JSON.stringify(profile, null, 2);
|
|
125891
126600
|
if (options.save)
|
|
125892
|
-
|
|
126601
|
+
writeFileSync31(options.save, JSON.stringify(profile), { mode: 384, flag: "wx" });
|
|
125893
126602
|
await writeCliOutput(options.json ? text5 : `${profile.id} at ${profile.revision}: ${profile.selections.map((skill) => `${skill.slug}@${skill.version}`).join(", ")}`);
|
|
125894
126603
|
} catch (error61) {
|
|
125895
126604
|
console.error(error61.message);
|
|
@@ -126299,7 +127008,7 @@ import {
|
|
|
126299
127008
|
fstatSync as fstatSync34,
|
|
126300
127009
|
openSync as openSync35,
|
|
126301
127010
|
readSync as readSync28,
|
|
126302
|
-
writeFileSync as
|
|
127011
|
+
writeFileSync as writeFileSync32
|
|
126303
127012
|
} from "fs";
|
|
126304
127013
|
function readInput(path2) {
|
|
126305
127014
|
const fd = openSync35(path2, constants35.O_RDONLY | constants35.O_NONBLOCK);
|
|
@@ -126332,7 +127041,7 @@ function registerGrants(parent) {
|
|
|
126332
127041
|
try {
|
|
126333
127042
|
const policy = await readExecutionGrantPolicy(profile, options.revision);
|
|
126334
127043
|
if (options.save)
|
|
126335
|
-
|
|
127044
|
+
writeFileSync32(options.save, JSON.stringify(policy, null, 2), {
|
|
126336
127045
|
mode: 384,
|
|
126337
127046
|
flag: "wx"
|
|
126338
127047
|
});
|
|
@@ -126359,28 +127068,28 @@ var init_grants = __esm(() => {
|
|
|
126359
127068
|
});
|
|
126360
127069
|
|
|
126361
127070
|
// src/lib/station-hydrate.ts
|
|
126362
|
-
import { createHash as
|
|
127071
|
+
import { createHash as createHash55 } from "crypto";
|
|
126363
127072
|
import {
|
|
126364
127073
|
copyFileSync as copyFileSync3,
|
|
126365
|
-
mkdirSync as
|
|
126366
|
-
readdirSync as
|
|
126367
|
-
readFileSync as
|
|
127074
|
+
mkdirSync as mkdirSync34,
|
|
127075
|
+
readdirSync as readdirSync31,
|
|
127076
|
+
readFileSync as readFileSync51,
|
|
126368
127077
|
statSync as statSync26,
|
|
126369
|
-
writeFileSync as
|
|
127078
|
+
writeFileSync as writeFileSync33
|
|
126370
127079
|
} from "fs";
|
|
126371
|
-
import { dirname as
|
|
127080
|
+
import { dirname as dirname54, join as join84, resolve as resolve50, sep as sep25 } from "path";
|
|
126372
127081
|
function fail7(code2, message, detail = []) {
|
|
126373
127082
|
throw new StationSnapshotError(code2, message, detail);
|
|
126374
127083
|
}
|
|
126375
127084
|
function snapshotRootFor(repoRoot, stationId) {
|
|
126376
|
-
return
|
|
127085
|
+
return join84(repoRoot, "resources", stationId, "skills");
|
|
126377
127086
|
}
|
|
126378
127087
|
function readSnapshotManifest(repoRoot, stationId) {
|
|
126379
127088
|
const snapshotRoot = snapshotRootFor(repoRoot, stationId);
|
|
126380
|
-
const manifestPath =
|
|
127089
|
+
const manifestPath = join84(snapshotRoot, "sync-manifest.json");
|
|
126381
127090
|
let manifest;
|
|
126382
127091
|
try {
|
|
126383
|
-
manifest = JSON.parse(
|
|
127092
|
+
manifest = JSON.parse(readFileSync51(manifestPath, "utf8"));
|
|
126384
127093
|
} catch (error61) {
|
|
126385
127094
|
fail7("MANIFEST_UNREADABLE", `cannot read snapshot manifest: ${manifestPath}: ${error61.message}`);
|
|
126386
127095
|
}
|
|
@@ -126404,10 +127113,10 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
126404
127113
|
const hashMismatches = [];
|
|
126405
127114
|
const skippedByRule = [];
|
|
126406
127115
|
for (const agent of SYNC_AGENTS) {
|
|
126407
|
-
const agentRoot =
|
|
127116
|
+
const agentRoot = join84(snapshotRoot, "agent-homes", agent);
|
|
126408
127117
|
let identEntries;
|
|
126409
127118
|
try {
|
|
126410
|
-
identEntries =
|
|
127119
|
+
identEntries = readdirSync31(agentRoot, { withFileTypes: true });
|
|
126411
127120
|
} catch {
|
|
126412
127121
|
continue;
|
|
126413
127122
|
}
|
|
@@ -126415,10 +127124,10 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
126415
127124
|
if (!identEntry.isDirectory() || identEntry.name.startsWith(".")) {
|
|
126416
127125
|
continue;
|
|
126417
127126
|
}
|
|
126418
|
-
const identRoot =
|
|
127127
|
+
const identRoot = join84(agentRoot, identEntry.name);
|
|
126419
127128
|
const entries = walkEntries(identRoot);
|
|
126420
127129
|
for (const entry of entries) {
|
|
126421
|
-
const relativeParts = [identEntry.name, ...entry.relativePath.split(
|
|
127130
|
+
const relativeParts = [identEntry.name, ...entry.relativePath.split(sep25)];
|
|
126422
127131
|
if (entry.kind === "symlink") {
|
|
126423
127132
|
symlinks.push({ ident: identEntry.name, agent, relativePath: entry.relativePath });
|
|
126424
127133
|
continue;
|
|
@@ -126442,8 +127151,8 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
126442
127151
|
});
|
|
126443
127152
|
continue;
|
|
126444
127153
|
}
|
|
126445
|
-
const withinIdent = relativeParts.slice(1).join(
|
|
126446
|
-
const homeRelative = relativeParts.join(
|
|
127154
|
+
const withinIdent = relativeParts.slice(1).join(sep25);
|
|
127155
|
+
const homeRelative = relativeParts.join(sep25);
|
|
126447
127156
|
if (REFUSED_SCANNER_FLAGGED.has(homeRelative)) {
|
|
126448
127157
|
skippedByRule.push({
|
|
126449
127158
|
ident: identEntry.name,
|
|
@@ -126516,7 +127225,7 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
126516
127225
|
for (const copy of copies) {
|
|
126517
127226
|
let isStub = false;
|
|
126518
127227
|
try {
|
|
126519
|
-
isStub = isPointerSkillMd(
|
|
127228
|
+
isStub = isPointerSkillMd(readFileSync51(copy.fullPath, "utf8"));
|
|
126520
127229
|
} catch {
|
|
126521
127230
|
isStub = false;
|
|
126522
127231
|
}
|
|
@@ -126562,12 +127271,12 @@ function skillSha256(skill) {
|
|
|
126562
127271
|
return sha256File(skill.files[0].winner.fullPath);
|
|
126563
127272
|
}
|
|
126564
127273
|
const joined = skill.files.map((file2) => sha256File(file2.winner.fullPath));
|
|
126565
|
-
return
|
|
127274
|
+
return createHash55("sha256").update(joined.sort().join(`
|
|
126566
127275
|
`)).digest("hex");
|
|
126567
127276
|
}
|
|
126568
127277
|
function writeStationHydration(options) {
|
|
126569
|
-
const repoRoot =
|
|
126570
|
-
const cacheRoot =
|
|
127278
|
+
const repoRoot = resolve50(options.repoRoot ?? process.cwd());
|
|
127279
|
+
const cacheRoot = resolve50(options.cacheRoot ?? resolveCorpusRoot());
|
|
126571
127280
|
const plan = planStationHydration(options.stationId, repoRoot);
|
|
126572
127281
|
const resultSkills = plan.winners.map((skill) => ({
|
|
126573
127282
|
ident: skill.ident,
|
|
@@ -126600,7 +127309,7 @@ function writeStationHydration(options) {
|
|
|
126600
127309
|
const toWrite = [];
|
|
126601
127310
|
for (const skill of plan.winners) {
|
|
126602
127311
|
for (const file2 of skill.files) {
|
|
126603
|
-
const destination =
|
|
127312
|
+
const destination = join84(cacheRoot, skill.ident, file2.withinIdent);
|
|
126604
127313
|
const digest7 = sha256File(file2.winner.fullPath);
|
|
126605
127314
|
let existingDigest = null;
|
|
126606
127315
|
try {
|
|
@@ -126621,7 +127330,7 @@ function writeStationHydration(options) {
|
|
|
126621
127330
|
}
|
|
126622
127331
|
let written = 0;
|
|
126623
127332
|
for (const entry of toWrite) {
|
|
126624
|
-
|
|
127333
|
+
mkdirSync34(dirname54(entry.destination), { recursive: true });
|
|
126625
127334
|
copyFileSync3(entry.fullPath, entry.destination);
|
|
126626
127335
|
written += 1;
|
|
126627
127336
|
}
|
|
@@ -126642,9 +127351,9 @@ function writeStationHydration(options) {
|
|
|
126642
127351
|
},
|
|
126643
127352
|
skills: resultSkills
|
|
126644
127353
|
};
|
|
126645
|
-
const hydrationManifestPath =
|
|
126646
|
-
|
|
126647
|
-
|
|
127354
|
+
const hydrationManifestPath = join84(dirname54(cacheRoot), `hydration-${options.stationId}.json`);
|
|
127355
|
+
mkdirSync34(dirname54(hydrationManifestPath), { recursive: true });
|
|
127356
|
+
writeFileSync33(hydrationManifestPath, `${JSON.stringify(hydration, null, 2)}
|
|
126648
127357
|
`);
|
|
126649
127358
|
return {
|
|
126650
127359
|
...base3,
|
|
@@ -126718,389 +127427,6 @@ var init_hydrate = __esm(() => {
|
|
|
126718
127427
|
init_station_hydrate();
|
|
126719
127428
|
});
|
|
126720
127429
|
|
|
126721
|
-
// src/lib/prepare-skill.ts
|
|
126722
|
-
import { createHash as createHash55, randomUUID as randomUUID17 } from "crypto";
|
|
126723
|
-
import { chmodSync as chmodSync5, existsSync as existsSync37, lstatSync as lstatSync54, mkdirSync as mkdirSync34, mkdtempSync as mkdtempSync7, readFileSync as readFileSync51, readdirSync as readdirSync31, renameSync as renameSync18, rmSync as rmSync10, writeFileSync as writeFileSync33 } from "fs";
|
|
126724
|
-
import { tmpdir as tmpdir2 } from "os";
|
|
126725
|
-
import { dirname as dirname54, isAbsolute as isAbsolute50, join as join84, relative as relative17, resolve as resolve50, sep as sep25 } from "path";
|
|
126726
|
-
function prepareSkill(name, options) {
|
|
126727
|
-
const normalized2 = normalizePortableSkillName(name);
|
|
126728
|
-
if (!new RegExp(SEMVER_PATTERN).test(options.version))
|
|
126729
|
-
throw new Error("--version must be a semantic version, for example 0.2.0.");
|
|
126730
|
-
const path2 = join84(getPortableSkillsRootReadOnly(options), normalized2);
|
|
126731
|
-
assertPortableAuthoringPath(path2, options);
|
|
126732
|
-
const before2 = snapshot2(path2);
|
|
126733
|
-
const manifestFile = before2.files.find((file2) => file2.path === "skill.json");
|
|
126734
|
-
if (!manifestFile)
|
|
126735
|
-
throw new Error(`Skill '${normalized2}' needs skill.json. Import the source with skills port first.`);
|
|
126736
|
-
let manifest;
|
|
126737
|
-
try {
|
|
126738
|
-
const parsed = JSON.parse(manifestFile.bytes.toString("utf8"));
|
|
126739
|
-
if (!isRecord9(parsed))
|
|
126740
|
-
throw new Error("not an object");
|
|
126741
|
-
manifest = parsed;
|
|
126742
|
-
} catch {
|
|
126743
|
-
throw new Error("skill.json must contain a JSON object; no files were changed.");
|
|
126744
|
-
}
|
|
126745
|
-
const previousVersion = manifest.version;
|
|
126746
|
-
if (typeof previousVersion !== "string" || !new RegExp(SEMVER_PATTERN).test(previousVersion)) {
|
|
126747
|
-
throw new Error("skill.json must declare its current semantic version before preparation.");
|
|
126748
|
-
}
|
|
126749
|
-
if (manifest.kind !== undefined && manifest.kind !== "instruction" && manifest.kind !== "executable") {
|
|
126750
|
-
throw new Error("skill.json has an invalid kind; correct it before preparation.");
|
|
126751
|
-
}
|
|
126752
|
-
const kind = options.kind ?? manifest.kind;
|
|
126753
|
-
if (kind !== "instruction" && kind !== "executable") {
|
|
126754
|
-
throw new Error("This legacy manifest has no explicit kind. Pass --kind instruction or --kind executable; helper scripts alone do not determine a skill's kind.");
|
|
126755
|
-
}
|
|
126756
|
-
const document3 = before2.files.find((file2) => file2.path === "SKILL.md");
|
|
126757
|
-
const documentKind = document3 ? parseSkillFrontmatter(document3.bytes.toString("utf8"))?.kind : undefined;
|
|
126758
|
-
if (documentKind !== undefined && documentKind !== kind) {
|
|
126759
|
-
throw new Error("SKILL.md kind conflicts with the candidate skill.json kind. Review and align both declarations (or remove the optional frontmatter kind), then prepare again; no files were changed.");
|
|
126760
|
-
}
|
|
126761
|
-
if (manifest.provenance !== undefined && !isRecord9(manifest.provenance))
|
|
126762
|
-
throw new Error("skill.json provenance must be an object.");
|
|
126763
|
-
const provenance = manifest.provenance;
|
|
126764
|
-
const candidate2 = { ...manifest, version: options.version, kind, provenance: { ...provenance } };
|
|
126765
|
-
const staging = mkdtempSync7(join84(tmpdir2(), "skills-prepare-"));
|
|
126766
|
-
try {
|
|
126767
|
-
for (const directory3 of before2.directories)
|
|
126768
|
-
mkdirSync34(join84(staging, directory3), { recursive: true });
|
|
126769
|
-
for (const file2 of before2.files) {
|
|
126770
|
-
const destination = join84(staging, file2.path);
|
|
126771
|
-
mkdirSync34(dirname54(destination), { recursive: true });
|
|
126772
|
-
writeFileSync33(destination, file2.bytes, { mode: file2.mode });
|
|
126773
|
-
}
|
|
126774
|
-
const stagedManifest = join84(staging, "skill.json");
|
|
126775
|
-
writeFileSync33(stagedManifest, `${JSON.stringify(candidate2, null, 2)}
|
|
126776
|
-
`);
|
|
126777
|
-
const contentHash = computeContentHash(staging);
|
|
126778
|
-
const changed3 = options.version !== previousVersion || kind !== manifest.kind || contentHash !== provenance?.content_hash;
|
|
126779
|
-
if (changed3 && compareVersions(options.version, previousVersion) <= 0) {
|
|
126780
|
-
throw new Error(`Changed content requires a version greater than ${previousVersion}; pass --version <new-semver>.`);
|
|
126781
|
-
}
|
|
126782
|
-
candidate2.provenance.content_hash = contentHash;
|
|
126783
|
-
const candidateBytes = `${JSON.stringify(candidate2, null, 2)}
|
|
126784
|
-
`;
|
|
126785
|
-
writeFileSync33(stagedManifest, candidateBytes);
|
|
126786
|
-
validateRawCandidate(candidate2);
|
|
126787
|
-
validateRuntimeEntrypoint(candidate2, staging);
|
|
126788
|
-
const validation = validatePortableSkillDirectory(normalized2, staging);
|
|
126789
|
-
if (!validation.valid)
|
|
126790
|
-
throw new Error(`Skill '${normalized2}' cannot be prepared:
|
|
126791
|
-
${validation.issues.map((issue2) => `${issue2.code}: ${issue2.message}`).join(`
|
|
126792
|
-
`)}`);
|
|
126793
|
-
assertPackedCanonicalCoverage(staging);
|
|
126794
|
-
if (snapshot2(path2).identity !== before2.identity)
|
|
126795
|
-
throw new Error("Skill changed during preparation; review the draft and run prepare again.");
|
|
126796
|
-
if (changed3 && !options.dryRun) {
|
|
126797
|
-
const temporary = join84(path2, `.skill-prepare-${randomUUID17()}.tmp`);
|
|
126798
|
-
try {
|
|
126799
|
-
writeFileSync33(temporary, candidateBytes, { flag: "wx", mode: manifestFile.mode });
|
|
126800
|
-
chmodSync5(temporary, manifestFile.mode);
|
|
126801
|
-
renameSync18(temporary, join84(path2, "skill.json"));
|
|
126802
|
-
} finally {
|
|
126803
|
-
rmSync10(temporary, { force: true });
|
|
126804
|
-
}
|
|
126805
|
-
}
|
|
126806
|
-
return { name: normalized2, path: path2, previousVersion, version: options.version, kind, contentHash, changed: changed3, written: changed3 && !options.dryRun, warnings: validation.warnings };
|
|
126807
|
-
} finally {
|
|
126808
|
-
rmSync10(staging, { recursive: true, force: true });
|
|
126809
|
-
}
|
|
126810
|
-
}
|
|
126811
|
-
function assertPackedCanonicalCoverage(root2) {
|
|
126812
|
-
const canonicalPaths = new Set(collectBundleFiles(root2).map((file2) => file2.rel));
|
|
126813
|
-
const packedPaths = new Set(collectSkillBundleEntries(root2).map((entry) => entry.path));
|
|
126814
|
-
const omitted = [...canonicalPaths].filter((path2) => !packedPaths.has(path2)).sort();
|
|
126815
|
-
if (omitted.length === 0)
|
|
126816
|
-
return;
|
|
126817
|
-
const rendered = omitted.slice(0, MAX_PACK_PARITY_PATHS).map(escapedRelativePath).join(", ");
|
|
126818
|
-
const suffix = omitted.length > MAX_PACK_PARITY_PATHS ? ` (+${omitted.length - MAX_PACK_PARITY_PATHS} more)` : "";
|
|
126819
|
-
throw new Error(`Skill cannot be prepared because packSkillBundle excludes canonical source path(s): ${rendered}${suffix}. ` + "Rename or remove the excluded source before preparing; no files were changed.");
|
|
126820
|
-
}
|
|
126821
|
-
function escapedRelativePath(path2) {
|
|
126822
|
-
const bounded2 = path2.length > MAX_PACK_PARITY_PATH_CHARS - 8 ? `${path2.slice(0, MAX_PACK_PARITY_PATH_CHARS - 8)}...` : path2;
|
|
126823
|
-
return JSON.stringify(bounded2);
|
|
126824
|
-
}
|
|
126825
|
-
function validateRuntimeEntrypoint(candidate2, root2) {
|
|
126826
|
-
if (candidate2.kind !== "executable" || !isRecord9(candidate2.runtime))
|
|
126827
|
-
return;
|
|
126828
|
-
const entry = candidate2.runtime.entrypoint;
|
|
126829
|
-
if (typeof entry !== "string")
|
|
126830
|
-
return;
|
|
126831
|
-
const target = resolve50(root2, entry), rel = relative17(root2, target);
|
|
126832
|
-
if (isAbsolute50(entry) || /^[a-zA-Z]:/.test(entry) || entry.includes("\\") || !rel || rel === ".." || rel.startsWith(`..${sep25}`) || isAbsolute50(rel) || !existsSync37(target) || !lstatSync54(target).isFile()) {
|
|
126833
|
-
throw new Error("Executable runtime.entrypoint must name an existing regular file inside the skill directory.");
|
|
126834
|
-
}
|
|
126835
|
-
}
|
|
126836
|
-
function isRecord9(value) {
|
|
126837
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
126838
|
-
}
|
|
126839
|
-
function validateRawCandidate(candidate2) {
|
|
126840
|
-
for (const field4 of ["standard", "name", "description", "version"]) {
|
|
126841
|
-
if (typeof candidate2[field4] !== "string" || !candidate2[field4].trim())
|
|
126842
|
-
throw new Error(`skill.json ${field4} is required.`);
|
|
126843
|
-
}
|
|
126844
|
-
for (const field4 of ["displayName", "category"]) {
|
|
126845
|
-
if (candidate2[field4] !== undefined && (typeof candidate2[field4] !== "string" || !candidate2[field4].trim()))
|
|
126846
|
-
throw new Error(`Invalid skill.json ${field4}.`);
|
|
126847
|
-
}
|
|
126848
|
-
if (candidate2.tags !== undefined && (!Array.isArray(candidate2.tags) || candidate2.tags.some((tag) => typeof tag !== "string" || !tag.trim())))
|
|
126849
|
-
throw new Error("Invalid skill.json tags.");
|
|
126850
|
-
const issues = validatePortableManifestContract(candidate2, { strict: true });
|
|
126851
|
-
if (issues.length)
|
|
126852
|
-
throw new Error(`Invalid skill.json: ${issues.map((issue2) => issue2.code).join(", ")}`);
|
|
126853
|
-
validateRuntimeEntrypointCompatibility(candidate2);
|
|
126854
|
-
for (const field4 of ["inputs", "commands"]) {
|
|
126855
|
-
const items = candidate2[field4];
|
|
126856
|
-
if (items === undefined)
|
|
126857
|
-
continue;
|
|
126858
|
-
if (!Array.isArray(items))
|
|
126859
|
-
throw new Error(`skill.json ${field4} must be an array.`);
|
|
126860
|
-
for (const item of items) {
|
|
126861
|
-
if (!isRecord9(item) || typeof item.name !== "string" || !item.name.trim())
|
|
126862
|
-
throw new Error(`Invalid skill.json ${field4} name.`);
|
|
126863
|
-
if (field4 === "inputs" && (typeof item.type !== "string" || !item.type.trim() || item.required !== undefined && typeof item.required !== "boolean"))
|
|
126864
|
-
throw new Error("Invalid skill.json input type or required flag.");
|
|
126865
|
-
if (field4 === "commands") {
|
|
126866
|
-
if (!/^[a-z0-9][a-z0-9._-]*$/.test(item.name))
|
|
126867
|
-
throw new Error("Invalid skill.json command name.");
|
|
126868
|
-
for (const target of ["entry", "command"])
|
|
126869
|
-
if (item[target] !== undefined && (typeof item[target] !== "string" || !item[target].trim()))
|
|
126870
|
-
throw new Error(`Invalid skill.json command ${target}.`);
|
|
126871
|
-
if (!item.entry && !item.command)
|
|
126872
|
-
throw new Error("A skill.json command needs entry or command.");
|
|
126873
|
-
if (item.args !== undefined && (!Array.isArray(item.args) || item.args.some((arg) => typeof arg !== "string")))
|
|
126874
|
-
throw new Error("Invalid skill.json command args.");
|
|
126875
|
-
}
|
|
126876
|
-
}
|
|
126877
|
-
}
|
|
126878
|
-
}
|
|
126879
|
-
function validateRuntimeEntrypointCompatibility(candidate2) {
|
|
126880
|
-
if (candidate2.kind !== "executable" || !isRecord9(candidate2.runtime))
|
|
126881
|
-
return;
|
|
126882
|
-
const runtime = candidate2.runtime.runtime;
|
|
126883
|
-
const entrypoint = candidate2.runtime.entrypoint;
|
|
126884
|
-
if (typeof runtime !== "string" || typeof entrypoint !== "string")
|
|
126885
|
-
return;
|
|
126886
|
-
const extension = entrypoint.toLowerCase().split(".").pop();
|
|
126887
|
-
if (extension === "py" && runtime !== "python3") {
|
|
126888
|
-
throw new Error("Invalid skill.json: runtime.entrypoint with .py extension requires runtime python3.");
|
|
126889
|
-
}
|
|
126890
|
-
}
|
|
126891
|
-
function compareVersions(left, right) {
|
|
126892
|
-
const parts = (value) => value.split("+")[0].split(/-(.*)/s).slice(0, 2);
|
|
126893
|
-
const [a, ap] = parts(left), [b, bp] = parts(right);
|
|
126894
|
-
const ac = a.split(".").map(BigInt), bc = b.split(".").map(BigInt);
|
|
126895
|
-
for (let i = 0;i < 3; i++)
|
|
126896
|
-
if (ac[i] !== bc[i])
|
|
126897
|
-
return ac[i] > bc[i] ? 1 : -1;
|
|
126898
|
-
if (ap === undefined || bp === undefined)
|
|
126899
|
-
return ap === bp ? 0 : ap === undefined ? 1 : -1;
|
|
126900
|
-
const aa = ap.split("."), bb = bp.split(".");
|
|
126901
|
-
for (let i = 0;i < Math.max(aa.length, bb.length); i++) {
|
|
126902
|
-
const x = aa[i], y = bb[i];
|
|
126903
|
-
if (x === undefined || y === undefined)
|
|
126904
|
-
return x === y ? 0 : x === undefined ? -1 : 1;
|
|
126905
|
-
if (x === y)
|
|
126906
|
-
continue;
|
|
126907
|
-
const xn = /^\d+$/.test(x), yn = /^\d+$/.test(y);
|
|
126908
|
-
if (xn && yn)
|
|
126909
|
-
return BigInt(x) > BigInt(y) ? 1 : -1;
|
|
126910
|
-
if (xn !== yn)
|
|
126911
|
-
return xn ? -1 : 1;
|
|
126912
|
-
return x > y ? 1 : -1;
|
|
126913
|
-
}
|
|
126914
|
-
return 0;
|
|
126915
|
-
}
|
|
126916
|
-
function snapshot2(root2) {
|
|
126917
|
-
const rootStat = lstatSync54(root2);
|
|
126918
|
-
if (rootStat.isSymbolicLink() || !rootStat.isDirectory())
|
|
126919
|
-
throw new Error("Skill root must be a real directory, not a symlink.");
|
|
126920
|
-
const files = [], directories = [];
|
|
126921
|
-
const hash8 = createHash55("sha256").update(`${rootStat.dev}:${rootStat.ino}\x00`);
|
|
126922
|
-
let count = 0, bytes = 0;
|
|
126923
|
-
const walk2 = (relative18) => {
|
|
126924
|
-
for (const name of readdirSync31(join84(root2, relative18)).sort()) {
|
|
126925
|
-
if (IGNORED.has(name.toLowerCase()) || !relative18 && ROOT_IGNORED.has(name.toLowerCase()))
|
|
126926
|
-
continue;
|
|
126927
|
-
const path2 = relative18 ? `${relative18}/${name}` : name;
|
|
126928
|
-
if (++count > 20000)
|
|
126929
|
-
throw new Error("Skill exceeds the preparation entry limit (20000).");
|
|
126930
|
-
const absolute = join84(root2, path2), stat3 = lstatSync54(absolute);
|
|
126931
|
-
if (stat3.isSymbolicLink())
|
|
126932
|
-
throw new Error(`Skill contains a symlink: ${path2}. No files were changed.`);
|
|
126933
|
-
if (stat3.isDirectory() && name === "node_modules")
|
|
126934
|
-
continue;
|
|
126935
|
-
hash8.update(JSON.stringify([path2, stat3.dev, stat3.ino, stat3.mode]));
|
|
126936
|
-
if (stat3.isDirectory()) {
|
|
126937
|
-
directories.push(path2);
|
|
126938
|
-
walk2(path2);
|
|
126939
|
-
} else if (stat3.isFile()) {
|
|
126940
|
-
bytes += stat3.size;
|
|
126941
|
-
if (bytes > 50000000)
|
|
126942
|
-
throw new Error("Skill exceeds the preparation size limit (50000000 bytes).");
|
|
126943
|
-
const content = readFileSync51(absolute);
|
|
126944
|
-
if (content.length !== stat3.size)
|
|
126945
|
-
throw new Error("Skill changed during preparation.");
|
|
126946
|
-
files.push({ path: path2, bytes: content, mode: stat3.mode & 511 });
|
|
126947
|
-
hash8.update(`\x00${content.length}\x00`).update(content);
|
|
126948
|
-
} else
|
|
126949
|
-
throw new Error(`Unsupported skill entry: ${path2}.`);
|
|
126950
|
-
hash8.update("\x00");
|
|
126951
|
-
}
|
|
126952
|
-
};
|
|
126953
|
-
walk2("");
|
|
126954
|
-
return { files, directories, identity: hash8.digest("hex") };
|
|
126955
|
-
}
|
|
126956
|
-
var MAX_PACK_PARITY_PATHS = 8, MAX_PACK_PARITY_PATH_CHARS = 120, IGNORED, ROOT_IGNORED;
|
|
126957
|
-
var init_prepare_skill = __esm(() => {
|
|
126958
|
-
init_portable_skills();
|
|
126959
|
-
init_skill_contract();
|
|
126960
|
-
init_skill_hash();
|
|
126961
|
-
init_skill_bundle();
|
|
126962
|
-
init_skill_validation();
|
|
126963
|
-
init_authoring_path();
|
|
126964
|
-
IGNORED = new Set([".git", ".ds_store", ".system"]);
|
|
126965
|
-
ROOT_IGNORED = new Set(["dist", "build", ".turbo", ".skills-dependency-preparation"]);
|
|
126966
|
-
});
|
|
126967
|
-
|
|
126968
|
-
// src/cli/commands/portable-skills.ts
|
|
126969
|
-
var exports_portable_skills = {};
|
|
126970
|
-
__export(exports_portable_skills, {
|
|
126971
|
-
registerPortableSkillCommands: () => registerPortableSkillCommands
|
|
126972
|
-
});
|
|
126973
|
-
function registerPortableSkillCommands(parent) {
|
|
126974
|
-
parent.command("prepare").argument("<name>", "Name of a skill in the local corpus").requiredOption("--version <version>", "Reviewed semantic version for this draft").option("--kind <kind>", "Explicit instruction or executable kind; required for legacy kind-less manifests").option("--dry-run", "Validate the candidate and report its hash without changing the draft", false).option("--json", "Output result as JSON", false).description("Validate a reviewed draft and update its manifest version and content hash").action(async (name, options) => {
|
|
126975
|
-
try {
|
|
126976
|
-
const result2 = prepareSkill(name, { ...options, kind: options.kind === undefined ? undefined : parseSkillKind2(options.kind) });
|
|
126977
|
-
if (result2.written)
|
|
126978
|
-
clearRegistryCache();
|
|
126979
|
-
if (options.json)
|
|
126980
|
-
await writeCliOutput(JSON.stringify(result2, null, 2));
|
|
126981
|
-
else {
|
|
126982
|
-
console.log(source_default.green(`${result2.written ? "Prepared" : options.dryRun ? "Previewed" : "Already prepared"} ${result2.name}@${result2.version} (${result2.kind})`));
|
|
126983
|
-
console.log(source_default.dim(` Content hash: ${result2.contentHash}`));
|
|
126984
|
-
console.log(source_default.dim(` Manifest: ${result2.path}/skill.json`));
|
|
126985
|
-
if (result2.written)
|
|
126986
|
-
console.log(source_default.dim(` Publish: skills push ${result2.name}`));
|
|
126987
|
-
}
|
|
126988
|
-
} catch (error61) {
|
|
126989
|
-
if (options.json)
|
|
126990
|
-
await writeCliOutput(JSON.stringify({ error: error61.message }, null, 2));
|
|
126991
|
-
else
|
|
126992
|
-
console.error(source_default.red(error61.message));
|
|
126993
|
-
process.exitCode = 1;
|
|
126994
|
-
}
|
|
126995
|
-
});
|
|
126996
|
-
parent.command("new").alias("scaffold").argument("<name>", "Portable skill name").option("--description <description>", "Short description of what the skill does").option("--kind <kind>", "Skill class: executable (default) or instruction (prose only)", "executable").option("-o, --overwrite", "Replace an existing portable skill folder", false).option("--json", "Output result as JSON", false).description("Scaffold a portable skill under ~/.hasna/skills/installed/<name>").action((name, options) => {
|
|
126997
|
-
try {
|
|
126998
|
-
const kind = parseSkillKind2(options.kind);
|
|
126999
|
-
const result2 = scaffoldPortableSkill(name, {
|
|
127000
|
-
description: options.description,
|
|
127001
|
-
overwrite: options.overwrite,
|
|
127002
|
-
kind
|
|
127003
|
-
});
|
|
127004
|
-
clearRegistryCache();
|
|
127005
|
-
if (options.json) {
|
|
127006
|
-
console.log(JSON.stringify({ ...result2, manifest: result2.manifest }, null, 2));
|
|
127007
|
-
return;
|
|
127008
|
-
}
|
|
127009
|
-
console.log(source_default.green(`\u2713 Created ${kind} skill '${result2.name}'`));
|
|
127010
|
-
console.log(source_default.dim(` Path: ${result2.path}`));
|
|
127011
|
-
if (kind === "instruction") {
|
|
127012
|
-
console.log(source_default.dim(` Edit the prose in: ${result2.path}/SKILL.md`));
|
|
127013
|
-
console.log(source_default.dim(` Instruction skills are consumed by agents, not run with 'skills run'.`));
|
|
127014
|
-
} else {
|
|
127015
|
-
console.log(source_default.dim(` Agent instructions: ${result2.path}/AGENTS.md`));
|
|
127016
|
-
console.log(source_default.dim(` Validate: skills validate ${result2.name}`));
|
|
127017
|
-
console.log(source_default.dim(` Run: skills run ${result2.name} --help`));
|
|
127018
|
-
}
|
|
127019
|
-
} catch (error61) {
|
|
127020
|
-
writePortableError(error61, options.json);
|
|
127021
|
-
}
|
|
127022
|
-
});
|
|
127023
|
-
parent.command("port").alias("add").argument("<path>", "Existing skill folder (or parent directory with --all) to import").option("--name <name>", "Override the imported skill name").option("--all", "Import every skill subfolder of <path> (bulk import)", false).option("-o, --overwrite", "Replace an existing portable skill folder", false).option("--allow-shadow", "Allow an imported name that shadows a bundled official skill", false).option("--json", "Output result as JSON", false).description("Import an existing skill folder into ~/.hasna/skills/installed/<name>").action((path2, options) => {
|
|
127024
|
-
if (options.all) {
|
|
127025
|
-
handleBulkPort(path2, options);
|
|
127026
|
-
return;
|
|
127027
|
-
}
|
|
127028
|
-
try {
|
|
127029
|
-
const result2 = portPortableSkill(path2, {
|
|
127030
|
-
name: options.name,
|
|
127031
|
-
overwrite: options.overwrite,
|
|
127032
|
-
allowShadow: options.allowShadow
|
|
127033
|
-
});
|
|
127034
|
-
clearRegistryCache();
|
|
127035
|
-
const validation = validatePortableSkillDirectory(result2.name, result2.path);
|
|
127036
|
-
const payload = { ...result2, valid: validation.valid, issues: validation.issues, warnings: validation.warnings };
|
|
127037
|
-
if (!validation.valid)
|
|
127038
|
-
process.exitCode = 1;
|
|
127039
|
-
if (options.json) {
|
|
127040
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
127041
|
-
return;
|
|
127042
|
-
}
|
|
127043
|
-
console.log(validation.valid ? source_default.green(`\u2713 Ported portable skill '${result2.name}'`) : source_default.yellow(`Validation failed for imported files '${result2.name}'; correct the folder before using it.`));
|
|
127044
|
-
console.log(source_default.dim(` Path: ${result2.path}`));
|
|
127045
|
-
console.log(source_default.dim(` Valid: ${validation.valid ? "yes" : "no"}`));
|
|
127046
|
-
if (!validation.valid) {
|
|
127047
|
-
for (const issue2 of validation.issues)
|
|
127048
|
-
console.log(source_default.red(` \u2022 ${issue2.message}`));
|
|
127049
|
-
}
|
|
127050
|
-
} catch (error61) {
|
|
127051
|
-
writePortableError(error61, options.json);
|
|
127052
|
-
}
|
|
127053
|
-
});
|
|
127054
|
-
}
|
|
127055
|
-
function handleBulkPort(path2, options) {
|
|
127056
|
-
if (options.name) {
|
|
127057
|
-
writePortableError(new Error("--name cannot be used with --all"), options.json);
|
|
127058
|
-
return;
|
|
127059
|
-
}
|
|
127060
|
-
try {
|
|
127061
|
-
const summary = portPortableSkillDirectory(path2, { overwrite: options.overwrite, allowShadow: options.allowShadow });
|
|
127062
|
-
clearRegistryCache();
|
|
127063
|
-
if (options.json) {
|
|
127064
|
-
console.log(JSON.stringify(summary, null, 2));
|
|
127065
|
-
if (summary.failed > 0)
|
|
127066
|
-
process.exitCode = 1;
|
|
127067
|
-
return;
|
|
127068
|
-
}
|
|
127069
|
-
console.log(source_default.green(`\u2713 Imported ${summary.succeeded}/${summary.total} skill(s) from ${summary.root}`));
|
|
127070
|
-
for (const entry of summary.imported)
|
|
127071
|
-
console.log(source_default.dim(` \u2022 ${entry.name} \u2192 ${entry.path}`));
|
|
127072
|
-
if (summary.skipped.length > 0) {
|
|
127073
|
-
console.log(source_default.yellow(` Skipped ${summary.skipped.length}:`));
|
|
127074
|
-
for (const entry of summary.skipped)
|
|
127075
|
-
console.log(source_default.yellow(` \u2022 ${entry.sourcePath}: ${entry.reason}`));
|
|
127076
|
-
process.exitCode = 1;
|
|
127077
|
-
}
|
|
127078
|
-
} catch (error61) {
|
|
127079
|
-
writePortableError(error61, options.json);
|
|
127080
|
-
}
|
|
127081
|
-
}
|
|
127082
|
-
function parseSkillKind2(value) {
|
|
127083
|
-
if (value === undefined || value === "executable")
|
|
127084
|
-
return "executable";
|
|
127085
|
-
if (value === "instruction")
|
|
127086
|
-
return "instruction";
|
|
127087
|
-
throw new Error(`Invalid --kind '${value}'. Use 'executable' or 'instruction'.`);
|
|
127088
|
-
}
|
|
127089
|
-
function writePortableError(error61, json3) {
|
|
127090
|
-
const message = error61.message;
|
|
127091
|
-
if (json3)
|
|
127092
|
-
console.log(JSON.stringify({ error: message }, null, 2));
|
|
127093
|
-
else
|
|
127094
|
-
console.error(source_default.red(message));
|
|
127095
|
-
process.exitCode = 1;
|
|
127096
|
-
}
|
|
127097
|
-
var init_portable_skills2 = __esm(() => {
|
|
127098
|
-
init_source();
|
|
127099
|
-
init_registry();
|
|
127100
|
-
init_prepare_skill();
|
|
127101
|
-
init_portable_skills();
|
|
127102
|
-
});
|
|
127103
|
-
|
|
127104
127430
|
// src/cli/commands/metadata.ts
|
|
127105
127431
|
var exports_metadata = {};
|
|
127106
127432
|
__export(exports_metadata, {
|