@hasna/skills 0.10.92 → 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 +829 -726
- package/bin/mcp.js +95 -47
- 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 +161 -69
- package/dist/lib/agent-discovery.d.ts +13 -0
- 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 +161 -69
- 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;
|
|
@@ -65996,8 +66052,8 @@ function resolveDiscovery(options, exclusion) {
|
|
|
65996
66052
|
const configPath = exclusion?.nativeHome === undefined ? agentDiscoveryConfigPath(home, agent) : join46(nativeHome, agent === "codex" ? "config.toml" : "settings.json");
|
|
65997
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"]);
|
|
65998
66054
|
const config = configText === null ? {} : agent === "hermes" ? parseHermesConfig(configText) : parseConfig(configText, configPath, agent === "codex");
|
|
65999
|
-
const unresolved = (detail) => {
|
|
66000
|
-
throw new
|
|
66055
|
+
const unresolved = (detail, source) => {
|
|
66056
|
+
throw new NativeDiscoveryUnresolved(agent, detail, source);
|
|
66001
66057
|
};
|
|
66002
66058
|
let opaqueClosureVerified = false;
|
|
66003
66059
|
const verifyOpaqueClosure = (declarations) => {
|
|
@@ -66011,6 +66067,17 @@ function resolveDiscovery(options, exclusion) {
|
|
|
66011
66067
|
verifyDiscovery(retained, undefined, undefined, undefined, exclusion?.narrowed, canonical9(configPath));
|
|
66012
66068
|
opaqueClosureVerified = true;
|
|
66013
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
|
+
};
|
|
66014
66081
|
if (exclusion) {
|
|
66015
66082
|
if (configText !== null && agent !== "codex" && agent !== "hermes")
|
|
66016
66083
|
parseNativeJsonObject(configText);
|
|
@@ -66082,7 +66149,7 @@ function resolveDiscovery(options, exclusion) {
|
|
|
66082
66149
|
unresolved(`non-enumerable ${label} require a current settings review`);
|
|
66083
66150
|
};
|
|
66084
66151
|
if (hasHooks)
|
|
66085
|
-
verifyCurrentOpaqueSettings("hooks");
|
|
66152
|
+
reviewRemedy(canonical9(configPath), () => verifyCurrentOpaqueSettings("hooks"));
|
|
66086
66153
|
if (agent === "claude") {
|
|
66087
66154
|
if (config.env !== undefined) {
|
|
66088
66155
|
if (!object6(config.env) || Object.keys(config.env).length > 512)
|
|
@@ -66095,7 +66162,7 @@ function resolveDiscovery(options, exclusion) {
|
|
|
66095
66162
|
if (hasFunctionHookEnvironment) {
|
|
66096
66163
|
if (!["0", "1"].includes(config.env.CLAUDE_CODE_ENABLE_FUNCTION_HOOKS))
|
|
66097
66164
|
unresolved("malformed Claude env.CLAUDE_CODE_ENABLE_FUNCTION_HOOKS flag");
|
|
66098
|
-
verifyCurrentOpaqueSettings("function-hook environment controls");
|
|
66165
|
+
reviewRemedy(canonical9(configPath), () => verifyCurrentOpaqueSettings("function-hook environment controls"));
|
|
66099
66166
|
}
|
|
66100
66167
|
}
|
|
66101
66168
|
if (config.pluginConfigs !== undefined) {
|
|
@@ -66122,7 +66189,7 @@ function resolveDiscovery(options, exclusion) {
|
|
|
66122
66189
|
}
|
|
66123
66190
|
hasPluginConfigs = Object.keys(config.pluginConfigs).length > 0;
|
|
66124
66191
|
if (hasPluginConfigs)
|
|
66125
|
-
verifyCurrentOpaqueSettings("plugin configurations");
|
|
66192
|
+
reviewRemedy(canonical9(configPath), () => verifyCurrentOpaqueSettings("plugin configurations"));
|
|
66126
66193
|
}
|
|
66127
66194
|
if (config.enabledPlugins !== undefined && !object6(config.enabledPlugins))
|
|
66128
66195
|
unresolved("enabledPlugins must be an object");
|
|
@@ -66234,7 +66301,7 @@ function resolveDiscovery(options, exclusion) {
|
|
|
66234
66301
|
if (Object.keys(config.enabledPlugins ?? config.plugins ?? config.extensions ?? {}).length > 512)
|
|
66235
66302
|
unresolved("unbounded native registrations");
|
|
66236
66303
|
if (hasHooks || hasPluginConfigs || hasFunctionHookEnvironment)
|
|
66237
|
-
verifyOpaqueClosure([]);
|
|
66304
|
+
reviewRemedy(canonical9(configPath), () => verifyOpaqueClosure([]));
|
|
66238
66305
|
}
|
|
66239
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))
|
|
66240
66307
|
throw new Error("Invalid --discovery-inputs version or agents");
|
|
@@ -66487,40 +66554,41 @@ function resolveDiscovery(options, exclusion) {
|
|
|
66487
66554
|
if (exclusion && Object.keys(manifest).some((key2) => /skill|plugin|hook|marketplace|extension|instruction/i.test(key2) && !["skills", "hooks"].includes(key2)))
|
|
66488
66555
|
unresolved("unknown plugin discovery controls");
|
|
66489
66556
|
const hooks = witness(join46(root2, "hooks/hooks.json"));
|
|
66490
|
-
if (manifest.hooks !== undefined || hooks !== null)
|
|
66491
|
-
|
|
66492
|
-
|
|
66493
|
-
|
|
66494
|
-
|
|
66495
|
-
|
|
66496
|
-
if (
|
|
66497
|
-
|
|
66498
|
-
|
|
66499
|
-
|
|
66500
|
-
|
|
66501
|
-
|
|
66502
|
-
|
|
66503
|
-
|
|
66504
|
-
|
|
66505
|
-
|
|
66506
|
-
|
|
66507
|
-
|
|
66508
|
-
|
|
66509
|
-
scoped2
|
|
66510
|
-
|
|
66511
|
-
|
|
66512
|
-
|
|
66513
|
-
scoped2
|
|
66514
|
-
|
|
66515
|
-
|
|
66516
|
-
|
|
66517
|
-
|
|
66518
|
-
|
|
66519
|
-
|
|
66520
|
-
|
|
66521
|
-
|
|
66522
|
-
|
|
66523
|
-
|
|
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
|
+
});
|
|
66524
66592
|
const commands = join46(root2, "commands");
|
|
66525
66593
|
safe6(commands);
|
|
66526
66594
|
if (agent === "claude" && (manifest.commands !== undefined || lstatSync35(commands, { throwIfNoEntry: false })?.isDirectory() && entries(commands).length))
|
|
@@ -66630,7 +66698,7 @@ function resolveDiscovery(options, exclusion) {
|
|
|
66630
66698
|
unresolved("native discovery roots exceed their bound");
|
|
66631
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))) } : {} };
|
|
66632
66700
|
}
|
|
66633
|
-
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) => {
|
|
66634
66702
|
try {
|
|
66635
66703
|
return realpathSync12(path);
|
|
66636
66704
|
} catch {
|
|
@@ -66667,6 +66735,18 @@ var init_agent_discovery = __esm(() => {
|
|
|
66667
66735
|
init_codex_semantic_cache_witness();
|
|
66668
66736
|
init_agent_discovery_directories();
|
|
66669
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
|
+
};
|
|
66670
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"]);
|
|
66671
66751
|
CLAUDE_CONTROL_FILES = new Set(["settings.json", "settings.local.json", "installed_plugins.json", "known_marketplaces.json"]);
|
|
66672
66752
|
ADOPTABLE_SETTINGS_MATCHES = Object.freeze(["exact", "screen-reader-receipt"]);
|
|
@@ -66705,6 +66785,11 @@ function physicalPath(path) {
|
|
|
66705
66785
|
}
|
|
66706
66786
|
return resolve30(realpathSync13(cursor), ...suffix);
|
|
66707
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
|
+
}
|
|
66708
66793
|
function contains2(root2, path) {
|
|
66709
66794
|
const boundary = root2.endsWith(sep15) ? root2 : root2 + sep15;
|
|
66710
66795
|
return path === root2 || path.startsWith(boundary);
|
|
@@ -66805,11 +66890,18 @@ function assertPortableAuthoringPath(path, options = {}) {
|
|
|
66805
66890
|
if (native) {
|
|
66806
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.`);
|
|
66807
66892
|
}
|
|
66808
|
-
const roots =
|
|
66809
|
-
|
|
66810
|
-
|
|
66811
|
-
|
|
66812
|
-
|
|
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;
|
|
66813
66905
|
}
|
|
66814
66906
|
for (const root2 of roots) {
|
|
66815
66907
|
if (candidates.some((candidate2) => [resolve30(root2), physicalPath(root2)].some((nativeRoot) => contains2(nativeRoot, candidate2) || contains2(candidate2, nativeRoot)))) {
|
|
@@ -122461,10 +122553,403 @@ var init_completion = __esm(() => {
|
|
|
122461
122553
|
categoryNames = CATEGORIES.map((c) => c);
|
|
122462
122554
|
});
|
|
122463
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
|
+
|
|
122464
122949
|
// src/lib/portable-snapshot-filter.ts
|
|
122465
|
-
import { readdirSync as
|
|
122950
|
+
import { readdirSync as readdirSync28, statSync as statSync23 } from "fs";
|
|
122466
122951
|
import { homedir as homedir12 } from "os";
|
|
122467
|
-
import { join as
|
|
122952
|
+
import { join as join74, sep as sep22 } from "path";
|
|
122468
122953
|
function isExcludedSkillFileName(fileName) {
|
|
122469
122954
|
if (EXCLUDE_FILE_NAMES.has(fileName)) {
|
|
122470
122955
|
return true;
|
|
@@ -122487,27 +122972,27 @@ function isPortableWithinSkill(relativeParts) {
|
|
|
122487
122972
|
function homePathFor(definition, homesRoot) {
|
|
122488
122973
|
const home = homesRoot ?? homedir12();
|
|
122489
122974
|
if (definition.subClass === "skills" || definition.subClass === "custom") {
|
|
122490
|
-
return
|
|
122975
|
+
return join74(skillsDataRootForHome(home), definition.name);
|
|
122491
122976
|
}
|
|
122492
122977
|
if (definition.agent === "opencode") {
|
|
122493
|
-
return
|
|
122978
|
+
return join74(home, ".config", "opencode", "skills");
|
|
122494
122979
|
}
|
|
122495
|
-
return
|
|
122980
|
+
return join74(home, `.${definition.agent}`, "skills");
|
|
122496
122981
|
}
|
|
122497
122982
|
function destinationFor(definition, stationId, relativePath) {
|
|
122498
|
-
const category = definition.subClass === "agent-homes" ?
|
|
122499
|
-
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));
|
|
122500
122985
|
}
|
|
122501
122986
|
function walkEntries(absoluteRoot) {
|
|
122502
122987
|
let entries;
|
|
122503
122988
|
try {
|
|
122504
|
-
entries =
|
|
122989
|
+
entries = readdirSync28(absoluteRoot, { withFileTypes: true });
|
|
122505
122990
|
} catch {
|
|
122506
122991
|
return [];
|
|
122507
122992
|
}
|
|
122508
122993
|
const output2 = [];
|
|
122509
122994
|
for (const entry of entries) {
|
|
122510
|
-
const childFull =
|
|
122995
|
+
const childFull = join74(absoluteRoot, entry.name);
|
|
122511
122996
|
if (entry.isSymbolicLink()) {
|
|
122512
122997
|
output2.push({ kind: "symlink", relativePath: entry.name, fullPath: childFull });
|
|
122513
122998
|
continue;
|
|
@@ -122518,7 +123003,7 @@ function walkEntries(absoluteRoot) {
|
|
|
122518
123003
|
}
|
|
122519
123004
|
const nested = walkEntries(childFull);
|
|
122520
123005
|
for (const item of nested) {
|
|
122521
|
-
output2.push({ ...item, relativePath:
|
|
123006
|
+
output2.push({ ...item, relativePath: join74(entry.name, item.relativePath) });
|
|
122522
123007
|
}
|
|
122523
123008
|
continue;
|
|
122524
123009
|
}
|
|
@@ -122611,22 +123096,22 @@ var init_portable_snapshot_filter = __esm(() => {
|
|
|
122611
123096
|
});
|
|
122612
123097
|
|
|
122613
123098
|
// src/lib/station-snapshot.ts
|
|
122614
|
-
import { createHash as
|
|
123099
|
+
import { createHash as createHash48 } from "crypto";
|
|
122615
123100
|
import {
|
|
122616
123101
|
copyFileSync as copyFileSync2,
|
|
122617
|
-
mkdirSync as
|
|
122618
|
-
readFileSync as
|
|
123102
|
+
mkdirSync as mkdirSync31,
|
|
123103
|
+
readFileSync as readFileSync46,
|
|
122619
123104
|
statSync as statSync24,
|
|
122620
|
-
writeFileSync as
|
|
123105
|
+
writeFileSync as writeFileSync29
|
|
122621
123106
|
} from "fs";
|
|
122622
|
-
import { dirname as
|
|
123107
|
+
import { dirname as dirname48, isAbsolute as isAbsolute44, relative as relative16, resolve as resolve40, sep as sep23 } from "path";
|
|
122623
123108
|
function validateStationId(stationId) {
|
|
122624
123109
|
if (!/^[a-z0-9-]+$/.test(stationId)) {
|
|
122625
123110
|
throw new StationSnapshotError("INVALID_STATION", `station id must be a slug, got: ${stationId}`);
|
|
122626
123111
|
}
|
|
122627
123112
|
}
|
|
122628
123113
|
function sha256File(filePath) {
|
|
122629
|
-
return
|
|
123114
|
+
return createHash48("sha256").update(readFileSync46(filePath)).digest("hex");
|
|
122630
123115
|
}
|
|
122631
123116
|
function scanHome(definition, homesRoot) {
|
|
122632
123117
|
const homePath = homePathFor(definition, homesRoot);
|
|
@@ -122634,7 +123119,7 @@ function scanHome(definition, homesRoot) {
|
|
|
122634
123119
|
const portable = [];
|
|
122635
123120
|
const skipped = [];
|
|
122636
123121
|
for (const entry of entries) {
|
|
122637
|
-
const relativeParts = entry.relativePath.split(
|
|
123122
|
+
const relativeParts = entry.relativePath.split(sep23);
|
|
122638
123123
|
const fileName = relativeParts[relativeParts.length - 1];
|
|
122639
123124
|
if (entry.kind === "symlink") {
|
|
122640
123125
|
skipped.push({ relativePath: entry.relativePath, reason: "symlink" });
|
|
@@ -122697,7 +123182,7 @@ function humanHomes(scanned) {
|
|
|
122697
123182
|
}));
|
|
122698
123183
|
}
|
|
122699
123184
|
function writeStationSnapshot(options) {
|
|
122700
|
-
const repoRoot =
|
|
123185
|
+
const repoRoot = resolve40(options.repoRoot ?? process.cwd());
|
|
122701
123186
|
const { scanned, plans, totalBytes } = planStationSnapshot(options);
|
|
122702
123187
|
const manifestFiles = plans.map((plan) => ({
|
|
122703
123188
|
relativePath: plan.source.relativePath,
|
|
@@ -122723,9 +123208,9 @@ function writeStationSnapshot(options) {
|
|
|
122723
123208
|
const conflicts = [];
|
|
122724
123209
|
const untouched = [];
|
|
122725
123210
|
for (const plan of plans) {
|
|
122726
|
-
const destination =
|
|
122727
|
-
const destinationRelative =
|
|
122728
|
-
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)) {
|
|
122729
123214
|
throw new StationSnapshotError("DESTINATION_ESCAPE", `destination escapes repo root: ${plan.destination}`);
|
|
122730
123215
|
}
|
|
122731
123216
|
let existingDigest = null;
|
|
@@ -122746,8 +123231,8 @@ function writeStationSnapshot(options) {
|
|
|
122746
123231
|
}
|
|
122747
123232
|
let written = 0;
|
|
122748
123233
|
for (const plan of untouched) {
|
|
122749
|
-
const destination =
|
|
122750
|
-
|
|
123234
|
+
const destination = resolve40(repoRoot, plan.destination);
|
|
123235
|
+
mkdirSync31(dirname48(destination), { recursive: true });
|
|
122751
123236
|
copyFileSync2(plan.source.fullPath, destination);
|
|
122752
123237
|
written += 1;
|
|
122753
123238
|
}
|
|
@@ -122765,9 +123250,9 @@ function writeStationSnapshot(options) {
|
|
|
122765
123250
|
},
|
|
122766
123251
|
files: manifestFiles
|
|
122767
123252
|
};
|
|
122768
|
-
const manifestPath =
|
|
122769
|
-
|
|
122770
|
-
|
|
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)}
|
|
122771
123256
|
`);
|
|
122772
123257
|
return {
|
|
122773
123258
|
...base3,
|
|
@@ -122798,8 +123283,8 @@ var exports_create_sync_config = {};
|
|
|
122798
123283
|
__export(exports_create_sync_config, {
|
|
122799
123284
|
registerCreateSync: () => registerCreateSync
|
|
122800
123285
|
});
|
|
122801
|
-
import { existsSync as
|
|
122802
|
-
import { join as
|
|
123286
|
+
import { existsSync as existsSync36 } from "fs";
|
|
123287
|
+
import { join as join75 } from "path";
|
|
122803
123288
|
function registerCreateSync(parent) {
|
|
122804
123289
|
const configCmd = parent.command("config").description("Manage skills configuration");
|
|
122805
123290
|
configCmd.command("show", { isDefault: true }).option("--json", "Output as JSON", false).description("Show current merged configuration").action((options) => {
|
|
@@ -122879,15 +123364,15 @@ function registerCreateSync(parent) {
|
|
|
122879
123364
|
const pp = getConfigPath("project");
|
|
122880
123365
|
if (options.json) {
|
|
122881
123366
|
console.log(JSON.stringify({
|
|
122882
|
-
global: { path: gp, exists:
|
|
122883
|
-
project: { path: pp, exists:
|
|
123367
|
+
global: { path: gp, exists: existsSync36(gp) },
|
|
123368
|
+
project: { path: pp, exists: existsSync36(pp) }
|
|
122884
123369
|
}, null, 2));
|
|
122885
123370
|
return;
|
|
122886
123371
|
}
|
|
122887
|
-
console.log(`${source_default.cyan("global")}: ${gp}${
|
|
122888
|
-
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)")}`);
|
|
122889
123374
|
});
|
|
122890
|
-
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));
|
|
122891
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));
|
|
122892
123377
|
}
|
|
122893
123378
|
function handleCreate(name, options) {
|
|
@@ -122909,7 +123394,7 @@ function handleCreate(name, options) {
|
|
|
122909
123394
|
console.log(source_default.green(`\u2713 Created custom skill '${result2.name}' at ${result2.path}`));
|
|
122910
123395
|
console.log(source_default.dim(` Category: ${result2.manifest.category}`));
|
|
122911
123396
|
console.log(source_default.dim(` Tags: ${result2.manifest.tags?.join(", ")}`));
|
|
122912
|
-
console.log(` ${source_default.cyan("Edit:")} ${
|
|
123397
|
+
console.log(` ${source_default.cyan("Edit:")} ${join75(result2.path, options.kind === "instruction" ? "SKILL.md" : "src/index.ts")}`);
|
|
122913
123398
|
console.log(` ${source_default.cyan("Prepare:")} skills prepare ${result2.name} --version <new-semver>`);
|
|
122914
123399
|
}
|
|
122915
123400
|
} catch (error61) {
|
|
@@ -123188,6 +123673,7 @@ var init_create_sync_config = __esm(() => {
|
|
|
123188
123673
|
init_config();
|
|
123189
123674
|
init_auth_store();
|
|
123190
123675
|
init_portable_skills();
|
|
123676
|
+
init_portable_skills2();
|
|
123191
123677
|
init_registry();
|
|
123192
123678
|
init_profile_sync();
|
|
123193
123679
|
init_managed_policy();
|
|
@@ -123198,9 +123684,9 @@ var init_create_sync_config = __esm(() => {
|
|
|
123198
123684
|
});
|
|
123199
123685
|
|
|
123200
123686
|
// src/lib/claude-hook-disable.ts
|
|
123201
|
-
import { createHash as
|
|
123202
|
-
import { constants as constants32, closeSync as closeSync32, fchownSync as fchownSync2, fchmodSync as fchmodSync4, fsyncSync as fsyncSync11, lstatSync as
|
|
123203
|
-
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";
|
|
123204
123690
|
import { KernelLock as KernelLock2 } from "@hasna/contracts/kernel-lock";
|
|
123205
123691
|
function fail6(reason2) {
|
|
123206
123692
|
throw new Error(`CLAUDE_HOOK_DISABLE_${reason2}`);
|
|
@@ -123213,26 +123699,26 @@ function serialize(value) {
|
|
|
123213
123699
|
return value.kind === "string" ? JSON.stringify(value.value) : value.value;
|
|
123214
123700
|
}
|
|
123215
123701
|
function path(value) {
|
|
123216
|
-
if (!
|
|
123702
|
+
if (!isAbsolute45(value) || resolve41(value) !== value || /[\x00-\x1f\x7f]/.test(value))
|
|
123217
123703
|
fail6("PATH_INVALID");
|
|
123218
123704
|
}
|
|
123219
123705
|
function parents3(file2) {
|
|
123220
123706
|
safeParents(file2);
|
|
123221
123707
|
const observed = [];
|
|
123222
|
-
for (let directory3 =
|
|
123223
|
-
const stat3 =
|
|
123224
|
-
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) });
|
|
123225
123711
|
observed.push([directory3, identity6(stat3)]);
|
|
123226
|
-
if (directory3 ===
|
|
123712
|
+
if (directory3 === dirname49(directory3))
|
|
123227
123713
|
break;
|
|
123228
123714
|
}
|
|
123229
123715
|
return () => {
|
|
123230
123716
|
safeParents(file2);
|
|
123231
123717
|
for (const [directory3, expected] of observed) {
|
|
123232
|
-
const stat3 =
|
|
123718
|
+
const stat3 = lstatSync48(directory3, { bigint: true });
|
|
123233
123719
|
if (identity6(stat3) !== expected)
|
|
123234
123720
|
fail6("PARENT_CHANGED");
|
|
123235
|
-
assertNativePathAcl(directory3, stat3, { allowDenyOnly: true, creationParent: directory3 ===
|
|
123721
|
+
assertNativePathAcl(directory3, stat3, { allowDenyOnly: true, creationParent: directory3 === dirname49(file2) });
|
|
123236
123722
|
}
|
|
123237
123723
|
};
|
|
123238
123724
|
}
|
|
@@ -123240,24 +123726,24 @@ function executable2(command, expectedSha256) {
|
|
|
123240
123726
|
path(command);
|
|
123241
123727
|
if (!digest6(expectedSha256))
|
|
123242
123728
|
fail6("COMMAND_DIGEST_INVALID");
|
|
123243
|
-
const parentCheck = parents3(command), alias =
|
|
123729
|
+
const parentCheck = parents3(command), alias = lstatSync48(command, { bigint: true });
|
|
123244
123730
|
const resolved = resolveLauncherCommand(command);
|
|
123245
123731
|
if (alias.isSymbolicLink() && directAliasTarget(command) !== resolved.physical)
|
|
123246
123732
|
fail6("COMMAND_UNVERIFIED");
|
|
123247
123733
|
const commandFile = snapshot(resolved.physical, false, { readOnlyPackage: true });
|
|
123248
123734
|
if (commandFile.sha256 !== expectedSha256)
|
|
123249
123735
|
fail6("COMMAND_CHANGED");
|
|
123250
|
-
const entry = resolved.entry, packageRoot =
|
|
123251
|
-
if (entry !==
|
|
123736
|
+
const entry = resolved.entry, packageRoot = dirname49(dirname49(entry));
|
|
123737
|
+
if (entry !== join76(packageRoot, "bin", "index.js") || realpathSync21(entry) !== entry)
|
|
123252
123738
|
fail6("COMMAND_UNVERIFIED");
|
|
123253
123739
|
const entryFile = snapshot(entry, false, { readOnlyPackage: true });
|
|
123254
|
-
const manifest = snapshot(
|
|
123740
|
+
const manifest = snapshot(join76(packageRoot, "package.json"), false, { readOnlyPackage: true });
|
|
123255
123741
|
const parsed = parseNativeJsonObject(manifest.text);
|
|
123256
123742
|
if (string6(field3(parsed, "name")) !== "@hasna/skills" || string6(field3(field3(parsed, "bin"), "skills")) !== "bin/index.js")
|
|
123257
123743
|
fail6("COMMAND_UNVERIFIED");
|
|
123258
123744
|
return () => {
|
|
123259
123745
|
parentCheck();
|
|
123260
|
-
const current =
|
|
123746
|
+
const current = lstatSync48(command, { bigint: true });
|
|
123261
123747
|
if (movedIdentity(current) !== movedIdentity(alias) || current.ctimeNs !== alias.ctimeNs || alias.isSymbolicLink() && directAliasTarget(command) !== resolved.physical)
|
|
123262
123748
|
fail6("COMMAND_CHANGED");
|
|
123263
123749
|
unchanged2(commandFile);
|
|
@@ -123310,13 +123796,13 @@ function disableClaudeHooks(options) {
|
|
|
123310
123796
|
removed: [],
|
|
123311
123797
|
settingsWitness: "unchanged"
|
|
123312
123798
|
};
|
|
123313
|
-
const exists =
|
|
123799
|
+
const exists = lstatSync48(options.settings, { throwIfNoEntry: false });
|
|
123314
123800
|
if (!exists) {
|
|
123315
123801
|
if (options.expectedSettingsSha256 !== "absent" || options.expectedMode !== "absent")
|
|
123316
123802
|
fail6("TARGET_CHANGED");
|
|
123317
123803
|
checkCommand();
|
|
123318
123804
|
checkParents();
|
|
123319
|
-
if (
|
|
123805
|
+
if (lstatSync48(options.settings, { throwIfNoEntry: false }))
|
|
123320
123806
|
fail6("TARGET_CHANGED");
|
|
123321
123807
|
return base3;
|
|
123322
123808
|
}
|
|
@@ -123379,7 +123865,7 @@ function disableClaudeHooks(options) {
|
|
|
123379
123865
|
if (options.preimageDir === options.settings || options.preimageDir.startsWith(options.settings + "/"))
|
|
123380
123866
|
fail6("PREIMAGE_INVALID");
|
|
123381
123867
|
const checkPreimageParents = parents3(options.preimageDir);
|
|
123382
|
-
const lock = new KernelLock2(
|
|
123868
|
+
const lock = new KernelLock2(dirname49(options.settings), "skills-claude-hook-disable", { mode: "exclusive" });
|
|
123383
123869
|
let exchanged = false, preimage, displaced, receipt;
|
|
123384
123870
|
try {
|
|
123385
123871
|
if (!lock.trySync(1000))
|
|
@@ -123389,15 +123875,15 @@ function disableClaudeHooks(options) {
|
|
|
123389
123875
|
checkParents();
|
|
123390
123876
|
checkPreimageParents();
|
|
123391
123877
|
unchanged2(before2);
|
|
123392
|
-
|
|
123393
|
-
if ((
|
|
123878
|
+
mkdirSync32(options.preimageDir, { mode: 448 });
|
|
123879
|
+
if ((lstatSync48(options.preimageDir).mode & 511) !== 448)
|
|
123394
123880
|
fail6("PREIMAGE_INVALID");
|
|
123395
|
-
preimage =
|
|
123881
|
+
preimage = join76(options.preimageDir, "settings.preimage.json");
|
|
123396
123882
|
save(preimage, before2.bytes);
|
|
123397
123883
|
const preserved = snapshot(preimage, true), checkPreservationRoot = parents3(preimage);
|
|
123398
123884
|
if (preserved.sha256 !== before2.sha256 || !preserved.bytes.equals(before2.bytes))
|
|
123399
123885
|
fail6("PRESERVATION_FAILED");
|
|
123400
|
-
displaced =
|
|
123886
|
+
displaced = join76(dirname49(options.settings), `.settings.skills-disable-${randomUUID16()}.json`);
|
|
123401
123887
|
save(displaced, after2);
|
|
123402
123888
|
const fd = openSync32(displaced, constants32.O_RDONLY | constants32.O_NOFOLLOW);
|
|
123403
123889
|
try {
|
|
@@ -123411,7 +123897,7 @@ function disableClaudeHooks(options) {
|
|
|
123411
123897
|
assertNativePathAcl(displaced, staged.stat);
|
|
123412
123898
|
if (staged.sha256 !== result2.plannedAfterSha256)
|
|
123413
123899
|
fail6("STAGING_CHANGED");
|
|
123414
|
-
receipt =
|
|
123900
|
+
receipt = join76(options.preimageDir, "receipt.json");
|
|
123415
123901
|
const preparationBytes = `${JSON.stringify({
|
|
123416
123902
|
...result2,
|
|
123417
123903
|
state: "prepared",
|
|
@@ -123447,14 +123933,14 @@ function disableClaudeHooks(options) {
|
|
|
123447
123933
|
unchanged2(preserved);
|
|
123448
123934
|
if (movedIdentity(displacedOriginal.stat) !== movedIdentity(before2.stat) || displacedOriginal.sha256 !== before2.sha256 || movedIdentity(current.stat) !== movedIdentity(staged.stat) || current.sha256 !== staged.sha256)
|
|
123449
123935
|
fail6("POST_WRITE_CHANGED");
|
|
123450
|
-
const directory3 = openSync32(
|
|
123936
|
+
const directory3 = openSync32(dirname49(options.settings), constants32.O_RDONLY | constants32.O_DIRECTORY);
|
|
123451
123937
|
try {
|
|
123452
123938
|
fsyncSync11(directory3);
|
|
123453
123939
|
} finally {
|
|
123454
123940
|
closeSync32(directory3);
|
|
123455
123941
|
}
|
|
123456
123942
|
const applied = { ...result2, state: "applied", mutationOccurred: true, afterSha256: staged.sha256, preimage, displaced, receipt };
|
|
123457
|
-
const completed =
|
|
123943
|
+
const completed = join76(options.preimageDir, "completed.json");
|
|
123458
123944
|
const completionBytes = `${JSON.stringify({ ...applied, receipt: completed })}
|
|
123459
123945
|
`;
|
|
123460
123946
|
save(completed, completionBytes);
|
|
@@ -123486,7 +123972,7 @@ function disableClaudeHooks(options) {
|
|
|
123486
123972
|
}
|
|
123487
123973
|
}
|
|
123488
123974
|
}
|
|
123489
|
-
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}`;
|
|
123490
123976
|
var init_claude_hook_disable = __esm(() => {
|
|
123491
123977
|
init_agent_adapters();
|
|
123492
123978
|
init_claude_settings_witness();
|
|
@@ -123497,8 +123983,8 @@ var init_claude_hook_disable = __esm(() => {
|
|
|
123497
123983
|
});
|
|
123498
123984
|
|
|
123499
123985
|
// src/lib/agent-install-selection.ts
|
|
123500
|
-
import { lstatSync as
|
|
123501
|
-
import { dirname as
|
|
123986
|
+
import { lstatSync as lstatSync49 } from "fs";
|
|
123987
|
+
import { dirname as dirname50, join as join77, resolve as resolve42 } from "path";
|
|
123502
123988
|
import { homedir as homedir13 } from "os";
|
|
123503
123989
|
function detectedIntegrationAgents(options = {}) {
|
|
123504
123990
|
const home = options.home ?? homedir13(), env3 = options.env ?? process.env;
|
|
@@ -123511,7 +123997,7 @@ function detectedIntegrationAgents(options = {}) {
|
|
|
123511
123997
|
throw new Error("Invalid existing bridge agent inventory");
|
|
123512
123998
|
const present2 = (path2) => {
|
|
123513
123999
|
try {
|
|
123514
|
-
return
|
|
124000
|
+
return lstatSync49(path2, { throwIfNoEntry: false }) !== undefined;
|
|
123515
124001
|
} catch {
|
|
123516
124002
|
throw new Error("Agent presence cannot be inspected; review native configuration before hook installation");
|
|
123517
124003
|
}
|
|
@@ -123521,13 +124007,13 @@ function detectedIntegrationAgents(options = {}) {
|
|
|
123521
124007
|
return true;
|
|
123522
124008
|
if (agent === "sumi") {
|
|
123523
124009
|
const xdg = env3.XDG_CONFIG_HOME;
|
|
123524
|
-
const xdgSumi = xdg?.trim() ?
|
|
123525
|
-
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);
|
|
123526
124012
|
}
|
|
123527
124013
|
if (agent === "hermes" && env3.HERMES_HOME !== undefined)
|
|
123528
124014
|
return true;
|
|
123529
124015
|
const adapter = AGENT_ADAPTERS[agent];
|
|
123530
|
-
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))));
|
|
123531
124017
|
});
|
|
123532
124018
|
if (!selected.length && !options.allowEmpty)
|
|
123533
124019
|
throw new Error(`No configured or detected agents; select --agent <agent> explicitly (${INTEGRATION_AGENTS.join(", ")}).`);
|
|
@@ -123541,11 +124027,11 @@ var init_agent_install_selection = __esm(() => {
|
|
|
123541
124027
|
});
|
|
123542
124028
|
|
|
123543
124029
|
// src/lib/codex-hook-trust-identity.ts
|
|
123544
|
-
import { lstatSync as
|
|
123545
|
-
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";
|
|
123546
124032
|
import { isDeepStrictEqual as isDeepStrictEqual20 } from "util";
|
|
123547
124033
|
function commandPhysical(command) {
|
|
123548
|
-
need8(typeof command === "string" &&
|
|
124034
|
+
need8(typeof command === "string" && isAbsolute46(command) && !command.includes("\x00") && resolve43(command) === command, COMMAND_NOT_CANONICAL);
|
|
123549
124035
|
const physical = realpathSync22(command);
|
|
123550
124036
|
let spelled;
|
|
123551
124037
|
try {
|
|
@@ -123556,7 +124042,7 @@ function commandPhysical(command) {
|
|
|
123556
124042
|
safeParents(spelled);
|
|
123557
124043
|
let direct = false;
|
|
123558
124044
|
try {
|
|
123559
|
-
const leaf =
|
|
124045
|
+
const leaf = lstatSync50(spelled);
|
|
123560
124046
|
direct = leaf.isSymbolicLink() ? trustedAncestorOwner(leaf.uid) && directAliasTarget(spelled) === physical : spelled === physical;
|
|
123561
124047
|
} catch {}
|
|
123562
124048
|
need8(direct, COMMAND_ALIAS_UNSAFE);
|
|
@@ -123568,7 +124054,7 @@ function assertPinnedRuntime(runtime) {
|
|
|
123568
124054
|
} catch {
|
|
123569
124055
|
need8(false, "SKILLS_LAUNCHER_UNVERIFIED");
|
|
123570
124056
|
}
|
|
123571
|
-
need8(
|
|
124057
|
+
need8(resolve43(runtime) === runtime, "SKILLS_LAUNCHER_UNVERIFIED");
|
|
123572
124058
|
safeParents(runtime);
|
|
123573
124059
|
}
|
|
123574
124060
|
function skillsCommandTarget(command) {
|
|
@@ -123600,12 +124086,12 @@ function targetUnchanged(command, target) {
|
|
|
123600
124086
|
assertPinnedRuntime(target.runtime);
|
|
123601
124087
|
}
|
|
123602
124088
|
function inspectRecordedSkillsCli(command, expected) {
|
|
123603
|
-
need8(
|
|
124089
|
+
need8(isAbsolute46(command) && !command.includes("\x00"), "RECONCILE_RECORDED_COMMAND_REQUIRED");
|
|
123604
124090
|
const target = skillsCommandTarget(command), resolved = target.entry;
|
|
123605
124091
|
const cli = snapshot(resolved, false, { readOnlyPackage: true });
|
|
123606
|
-
const manifest = snapshot(
|
|
124092
|
+
const manifest = snapshot(join78(dirname51(dirname51(resolved)), "package.json"), false, { readOnlyPackage: true });
|
|
123607
124093
|
const pkg = JSON.parse(manifest.text);
|
|
123608
|
-
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");
|
|
123609
124095
|
need8((cli.stat.mode & 0o100n) !== 0n, "SKILLS_COMMAND_NOT_EXECUTABLE");
|
|
123610
124096
|
const receipt = { path: resolved, version: pkg.version, sha256: cli.sha256, manifestSha256: manifest.sha256 };
|
|
123611
124097
|
need8(isDeepStrictEqual20(receipt, expected), "RECONCILE_SKILLS_BINDING_CHANGED");
|
|
@@ -123619,16 +124105,16 @@ function bindSkillsCli(command, reviewed) {
|
|
|
123619
124105
|
const normal = Bun.which("skills", { PATH: process.env.PATH });
|
|
123620
124106
|
need8(normal, "SKILLS_COMMAND_UNAVAILABLE");
|
|
123621
124107
|
const normalTarget = skillsCommandTarget(normal), resolved = normalTarget.entry;
|
|
123622
|
-
const bound =
|
|
124108
|
+
const bound = isAbsolute46(command) ? command : command === "skills" ? normal : undefined;
|
|
123623
124109
|
need8(bound, "SKILLS_COMMAND_MISMATCH");
|
|
123624
124110
|
const boundTarget = skillsCommandTarget(bound);
|
|
123625
124111
|
need8(boundTarget.entry === resolved, "SKILLS_COMMAND_MISMATCH");
|
|
123626
124112
|
const entrypoint = reviewed?.path ?? process.argv[1];
|
|
123627
124113
|
need8(entrypoint && realpathSync22(entrypoint) === resolved, "SKILLS_ENTRYPOINT_MISMATCH");
|
|
123628
124114
|
const cli = snapshot(resolved, false, { readOnlyPackage: true });
|
|
123629
|
-
const manifest = snapshot(
|
|
124115
|
+
const manifest = snapshot(join78(dirname51(dirname51(resolved)), "package.json"), false, { readOnlyPackage: true });
|
|
123630
124116
|
const pkg = JSON.parse(manifest.text);
|
|
123631
|
-
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");
|
|
123632
124118
|
if (reviewed)
|
|
123633
124119
|
need8(/^[a-f0-9]{64}$/.test(reviewed.sha256) && reviewed.sha256 === cli.sha256 && reviewed.version === pkg.version, "SKILLS_RELEASE_MISMATCH");
|
|
123634
124120
|
need8((cli.stat.mode & 0o100n) !== 0n, "SKILLS_COMMAND_NOT_EXECUTABLE");
|
|
@@ -123647,7 +124133,7 @@ function captureSkillsCliProducerIdentity(command) {
|
|
|
123647
124133
|
const bound = bindSkillsCli(command);
|
|
123648
124134
|
const target = skillsCommandTarget(command);
|
|
123649
124135
|
const cli = snapshot(target.entry, false, { readOnlyPackage: true });
|
|
123650
|
-
const manifest = snapshot(
|
|
124136
|
+
const manifest = snapshot(join78(dirname51(dirname51(target.entry)), "package.json"), false, { readOnlyPackage: true });
|
|
123651
124137
|
const pkg = JSON.parse(manifest.text);
|
|
123652
124138
|
need8(pkg.name === "@hasna/skills" && pkg.version === bound.receipt.version && target.entry === bound.receipt.path, "SKILLS_PACKAGE_MISMATCH");
|
|
123653
124139
|
const launcher = target.launcher ?? cli;
|
|
@@ -123701,23 +124187,23 @@ var init_codex_hook_policy_recovery = __esm(() => {
|
|
|
123701
124187
|
});
|
|
123702
124188
|
|
|
123703
124189
|
// src/lib/agent-codex-trust.ts
|
|
123704
|
-
import { lstatSync as
|
|
123705
|
-
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";
|
|
123706
124192
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
123707
|
-
import { dirname as
|
|
124193
|
+
import { dirname as dirname52, isAbsolute as isAbsolute47, join as join79, resolve as resolve44 } from "path";
|
|
123708
124194
|
import { homedir as homedir14 } from "os";
|
|
123709
124195
|
import { isDeepStrictEqual as isDeepStrictEqual22 } from "util";
|
|
123710
124196
|
function verifyReleaseArtifact(proof, skillsCli) {
|
|
123711
|
-
need8(
|
|
124197
|
+
need8(isAbsolute47(proof.packageTarPath) && !proof.packageTarPath.includes("\x00"), "RECONCILE_RELEASE_PROVENANCE_PATH");
|
|
123712
124198
|
const tarPath = realpathSync23(proof.packageTarPath);
|
|
123713
124199
|
need8(statSync25(tarPath).isFile(), "RECONCILE_RELEASE_PROVENANCE_PATH");
|
|
123714
|
-
const tarBytes =
|
|
123715
|
-
need8(tarBytes.byteLength === proof.packageTarBytes &&
|
|
123716
|
-
const packageRoot =
|
|
123717
|
-
const manifest =
|
|
123718
|
-
need8(
|
|
123719
|
-
need8(
|
|
123720
|
-
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)]];
|
|
123721
124207
|
for (const [member, expected] of members2) {
|
|
123722
124208
|
let actual;
|
|
123723
124209
|
try {
|
|
@@ -123731,26 +124217,26 @@ function verifyReleaseArtifact(proof, skillsCli) {
|
|
|
123731
124217
|
async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc) {
|
|
123732
124218
|
let rpc;
|
|
123733
124219
|
try {
|
|
123734
|
-
const home =
|
|
123735
|
-
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);
|
|
123736
124222
|
need8(journal.startsWith(parent + "/") && /^[a-f0-9-]{36}$/.test(journal.slice(parent.length + 1)), "RECONCILE_JOURNAL_PATH");
|
|
123737
|
-
const dir =
|
|
124223
|
+
const dir = lstatSync51(journal, { throwIfNoEntry: false });
|
|
123738
124224
|
need8(!!dir && dir.isDirectory() && !dir.isSymbolicLink() && dir.uid === process.getuid() && (dir.mode & 511) === 448, "UNSAFE_JOURNAL");
|
|
123739
|
-
need8(!
|
|
123740
|
-
need8(
|
|
123741
|
-
const stoppedFile = snapshot(
|
|
123742
|
-
const intentFile = snapshot(
|
|
123743
|
-
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);
|
|
123744
124230
|
const legacyIntent = intent.home === undefined && intent.configSha256 === undefined && intent.configVersion === undefined && intent.declarations === undefined && intent.admitted === undefined && intent.nativeHooks === undefined;
|
|
123745
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");
|
|
123746
|
-
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 }));
|
|
123747
124233
|
const admitted = intent.admitted ?? intent.hooks;
|
|
123748
124234
|
const configSha256 = intent.configSha256 ?? configBefore.sha256, configVersion = intent.configVersion ?? intent.expectedVersion;
|
|
123749
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 };
|
|
123750
124236
|
need8(makePlanDigest(digestInput) === intent.planDigest, "RECONCILE_PLAN_CHANGED");
|
|
123751
124237
|
need8(isDeepStrictEqual22(intent.hooks, admitted.filter((hook) => !hook.enabled || hook.trustStatus !== "trusted")), "RECONCILE_INTENT_HOOKS_CHANGED");
|
|
123752
124238
|
need8(configBefore.sha256 === (intent.beforeSha256 ?? intent.configSha256) && hooksBefore.sha256 === intent.hooksSha256 && policyBefore.sha256 === intent.policySha256, "RECONCILE_JOURNAL_CHANGED");
|
|
123753
|
-
const policyCurrent = snapshot(
|
|
124239
|
+
const policyCurrent = snapshot(join79(dataDir3, "agent-policy.json"), true);
|
|
123754
124240
|
const { claudeDiscoveryRecovery: claudeRecovery, nativeHookAdditionsRecovery: additionsRecovery } = options;
|
|
123755
124241
|
const recovery = claudeRecovery ?? additionsRecovery;
|
|
123756
124242
|
need8(!recovery || !options.supersedeBinding, "RECONCILE_RECOVERY_MODE_CONFLICT");
|
|
@@ -123767,17 +124253,17 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
123767
124253
|
if (supersededBinding || recovery) {
|
|
123768
124254
|
need8(Array.isArray(intent.nativeHooks) && intent.nativeHooks.length > 0, "RECONCILE_NATIVE_INVENTORY_REQUIRED");
|
|
123769
124255
|
const proof = options.releaseProof;
|
|
123770
|
-
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");
|
|
123771
124257
|
}
|
|
123772
124258
|
const currentConfig = snapshot(intent.configPath, true);
|
|
123773
124259
|
const policy = parseManagedSkillPolicy(policyCurrent.text), binding = policy.bridge;
|
|
123774
|
-
const alias = binding.rootAliases?.find((item) => item.agent === "codex"), physicalRoot = alias?.target ??
|
|
123775
|
-
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");
|
|
123776
124262
|
const nativeConfigPath = intent.nativeConfigPath ?? intent.configPath;
|
|
123777
|
-
need8([
|
|
123778
|
-
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);
|
|
123779
124265
|
const hooksCurrent = snapshot(hooksPath);
|
|
123780
|
-
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;
|
|
123781
124267
|
const managedHooks = [...intent.hooks, ...additions?.hooks ?? []];
|
|
123782
124268
|
const managedKeys = managedHooks.map((h) => h.key);
|
|
123783
124269
|
const witnessSame = codexTrustReconcileWitness(configBefore.text, managedKeys) === codexTrustReconcileWitness(currentConfig.text, managedKeys);
|
|
@@ -123841,7 +124327,7 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
123841
124327
|
need8(isDeepStrictEqual22(actual, expected), "RECONCILE_UNRELATED_HOOK_CHANGED");
|
|
123842
124328
|
}
|
|
123843
124329
|
for (const wanted of intent.hooks) {
|
|
123844
|
-
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));
|
|
123845
124331
|
need8(matches2.length === 1, "RECONCILE_NATIVE_IDENTITY_CHANGED");
|
|
123846
124332
|
const h = matches2[0];
|
|
123847
124333
|
const trusted = h.trustStatus === "trusted";
|
|
@@ -123890,7 +124376,7 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
123890
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) };
|
|
123891
124377
|
if (preview2)
|
|
123892
124378
|
return receipt;
|
|
123893
|
-
save(
|
|
124379
|
+
save(join79(journal, "receipt.json"), json2(receipt));
|
|
123894
124380
|
return receipt;
|
|
123895
124381
|
} catch (error61) {
|
|
123896
124382
|
const message = error61 instanceof Error && error61.message.startsWith("CODEX_HOOK_TRUST_") ? error61.message : "CODEX_HOOK_TRUST_RECONCILE_REFUSED";
|
|
@@ -123900,21 +124386,21 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
123900
124386
|
}
|
|
123901
124387
|
}
|
|
123902
124388
|
async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRpc) {
|
|
123903
|
-
const home =
|
|
123904
|
-
return withCodexCorpusWriteAsync([
|
|
124389
|
+
const home = resolve44(options.home ?? homedir14());
|
|
124390
|
+
return withCodexCorpusWriteAsync([join79(home, ".codex")], (_assertCurrent, admission) => enrollCodexNativeHooksUnlocked(options, connect, admission), options);
|
|
123905
124391
|
}
|
|
123906
124392
|
async function enrollCodexNativeHooksUnlocked(options, connect, admission) {
|
|
123907
124393
|
let rpc, journal;
|
|
123908
124394
|
try {
|
|
123909
|
-
const home =
|
|
124395
|
+
const home = resolve44(options.home ?? homedir14()), dataDir3 = resolve44(options.dataDir ?? getDataDirReadOnly());
|
|
123910
124396
|
assertManagedAgentBridge("codex", { home, dataDir: dataDir3, projectDir: home });
|
|
123911
|
-
const policyFile = snapshot(
|
|
124397
|
+
const policyFile = snapshot(join79(dataDir3, "agent-policy.json"), true), policy = parseManagedSkillPolicy(policyFile.text);
|
|
123912
124398
|
const binding = policy.bridge, alias = binding.rootAliases?.find((item) => item.agent === "codex");
|
|
123913
124399
|
const skillsCli = bindSkillsCli(binding.commands.codex, options.reviewedSkillsCli);
|
|
123914
|
-
const physicalRoot = alias?.target ??
|
|
123915
|
-
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");
|
|
123916
124402
|
const codexHome = options.codexHome === undefined ? undefined : physicalRoot;
|
|
123917
|
-
const nativeConfigPath =
|
|
124403
|
+
const nativeConfigPath = join79(codexHome ?? join79(home, ".codex"), "config.toml");
|
|
123918
124404
|
const hooksFile = snapshot(hooksPath), configFile = snapshot(configPath, true), original = structure3(configFile.text);
|
|
123919
124405
|
const declarations = JSON.parse(hooksFile.text).hooks, expected = [];
|
|
123920
124406
|
for (const [event, [nativeEvent, label]] of Object.entries(eventNames)) {
|
|
@@ -123923,25 +124409,25 @@ async function enrollCodexNativeHooksUnlocked(options, connect, admission) {
|
|
|
123923
124409
|
need8(Array.isArray(groups), "DECLARATION_CHANGED");
|
|
123924
124410
|
const matches2 = groups.flatMap((group, gi) => (group.hooks ?? []).flatMap((entry, hi) => entry.command === command ? [{ group, entry, gi, hi }] : []));
|
|
123925
124411
|
need8(matches2.length === 1 && isDeepStrictEqual22(matches2[0].group, { hooks: [{ type: "command", command, timeout: 15 }] }), "DECLARATION_CHANGED");
|
|
123926
|
-
expected.push({ event: nativeEvent, key: `${
|
|
124412
|
+
expected.push({ event: nativeEvent, key: `${join79(home, ".codex/hooks.json")}:${label}:${matches2[0].gi}:${matches2[0].hi}`, command });
|
|
123927
124413
|
}
|
|
123928
124414
|
codexTrustTextWitness(configFile.text, expected.map((h) => h.key));
|
|
123929
124415
|
rpc = await connect({ command: options.codexCommand ?? "codex", home, codexHome });
|
|
123930
124416
|
need8(SUPPORTED_CODEX_HOOK_VERSIONS.includes(rpc.version), "NATIVE_UNSUPPORTED_VERSION");
|
|
123931
124417
|
const list2 = async () => {
|
|
123932
124418
|
const response = await rpc.request("hooks/list", { cwds: [home] });
|
|
123933
|
-
const nativeRoots = [...new Set([physicalRoot,
|
|
123934
|
-
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`));
|
|
123935
124421
|
const entry = response?.data?.[0];
|
|
123936
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");
|
|
123937
124423
|
return response.data[0].hooks;
|
|
123938
124424
|
};
|
|
123939
124425
|
const admit = (hooks) => expected.map((wanted) => {
|
|
123940
|
-
const canonicalKey = wanted.key.replace(
|
|
124426
|
+
const canonicalKey = wanted.key.replace(join79(home, ".codex/hooks.json"), hooksPath);
|
|
123941
124427
|
const matches2 = hooks.filter((h2) => h2.key === wanted.key || h2.key === canonicalKey);
|
|
123942
124428
|
need8(matches2.length === 1, "AMBIGUOUS_IDENTITY");
|
|
123943
124429
|
const h = matches2[0];
|
|
123944
|
-
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");
|
|
123945
124431
|
need8(hooks.filter((other) => other.command === wanted.command).length === 1, "AMBIGUOUS_IDENTITY");
|
|
123946
124432
|
need8(["trusted", "untrusted", "modified"].includes(h.trustStatus), "UNKNOWN_TRUST_STATUS");
|
|
123947
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 };
|
|
@@ -123967,27 +124453,27 @@ async function enrollCodexNativeHooksUnlocked(options, connect, admission) {
|
|
|
123967
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) };
|
|
123968
124454
|
if (options.apply)
|
|
123969
124455
|
need8(options.reviewedPlanDigest === planDigest, "PLAN_CHANGED: run the dry-run again and pass its reviewed --plan-digest with --apply");
|
|
123970
|
-
const parent =
|
|
123971
|
-
const st =
|
|
124456
|
+
const parent = join79(dataDir3, "native-hook-trust");
|
|
124457
|
+
const st = lstatSync51(parent, { throwIfNoEntry: false });
|
|
123972
124458
|
if (st) {
|
|
123973
124459
|
need8(st.isDirectory() && !st.isSymbolicLink() && st.uid === process.getuid() && (st.mode & 511) === 448, "UNSAFE_JOURNAL");
|
|
123974
|
-
const priorJournals =
|
|
124460
|
+
const priorJournals = readdirSync29(parent);
|
|
123975
124461
|
need8(priorJournals.length < 1000, "JOURNAL_BOUND");
|
|
123976
124462
|
for (const name of priorJournals) {
|
|
123977
124463
|
need8(/^[a-f0-9-]{36}$/.test(name), "UNSAFE_JOURNAL");
|
|
123978
|
-
need8(
|
|
124464
|
+
need8(lstatSync51(join79(parent, name, "receipt.json"), { throwIfNoEntry: false })?.isFile(), "RECONCILE_REQUIRED: inspect the incomplete private journal before applying again");
|
|
123979
124465
|
}
|
|
123980
124466
|
}
|
|
123981
124467
|
if (!options.apply || !planned.length)
|
|
123982
124468
|
return baseReceipt;
|
|
123983
124469
|
if (!st)
|
|
123984
|
-
|
|
123985
|
-
journal =
|
|
123986
|
-
|
|
123987
|
-
save(
|
|
123988
|
-
save(
|
|
123989
|
-
save(
|
|
123990
|
-
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) }));
|
|
123991
124477
|
need8(isDeepStrictEqual22(admit(await list2()), admitted), "NATIVE_DISCOVERY_CHANGED");
|
|
123992
124478
|
checkInputs();
|
|
123993
124479
|
let result2;
|
|
@@ -124003,7 +124489,7 @@ async function enrollCodexNativeHooksUnlocked(options, connect, admission) {
|
|
|
124003
124489
|
for (const h of planned)
|
|
124004
124490
|
expectedConfig.hooks.state[h.key] = { ...expectedConfig.hooks.state[h.key], enabled: true, trusted_hash: h.currentHash };
|
|
124005
124491
|
need8(isDeepStrictEqual22(structure3(after2.text), expectedConfig) && codexTrustTextWitness(after2.text, keys4) === preservationText, "PRESERVATION_FAILED");
|
|
124006
|
-
const savedConfig = snapshot(
|
|
124492
|
+
const savedConfig = snapshot(join79(journal, "config.before.toml"), true);
|
|
124007
124493
|
need8(savedConfig.sha256 === configFile.sha256, "JOURNAL_CHANGED");
|
|
124008
124494
|
const discoveryTransition = binding.discovery?.codex ? createCodexHookDiscoveryRecovery(binding.discovery.codex, savedConfig, after2, planned) : undefined;
|
|
124009
124495
|
unchanged2(policyFile);
|
|
@@ -124019,13 +124505,13 @@ async function enrollCodexNativeHooksUnlocked(options, connect, admission) {
|
|
|
124019
124505
|
skillsCli.recheck();
|
|
124020
124506
|
assertManagedAgentBridge("codex", { home, dataDir: dataDir3, projectDir: home, codexDiscoveryRecovery: discoveryTransition });
|
|
124021
124507
|
const receipt = { ...baseReceipt, applied: true, nativeEligible: true, bindingRefreshRequired: !!discoveryTransition, ...discoveryTransition ? { discoveryTransition } : {}, nativeConfigVersion: result2.version, journal, beforeSha256: configFile.sha256, afterSha256: after2.sha256, unrelatedSettingsAndCommentsPreserved: true };
|
|
124022
|
-
save(
|
|
124508
|
+
save(join79(journal, "receipt.json"), json2(receipt));
|
|
124023
124509
|
return receipt;
|
|
124024
124510
|
} catch (error61) {
|
|
124025
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";
|
|
124026
124512
|
if (journal) {
|
|
124027
124513
|
try {
|
|
124028
|
-
save(
|
|
124514
|
+
save(join79(journal, "stopped.json"), json2({ error: message.split(":")[0], automaticRollback: false, reconcileBeforeRetry: true, ...codexAdmissionReceipt(admission) }));
|
|
124029
124515
|
} catch {}
|
|
124030
124516
|
}
|
|
124031
124517
|
throw new Error(message);
|
|
@@ -124034,7 +124520,7 @@ async function enrollCodexNativeHooksUnlocked(options, connect, admission) {
|
|
|
124034
124520
|
}
|
|
124035
124521
|
}
|
|
124036
124522
|
var json2 = (value) => JSON.stringify(value, null, 2) + `
|
|
124037
|
-
`, 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) => {
|
|
124038
124524
|
const copy = structuredClone(value);
|
|
124039
124525
|
if (copy?.bridge?.commands && Object.hasOwn(copy.bridge.commands, "codex"))
|
|
124040
124526
|
copy.bridge.commands.codex = "<reviewed-codex-binding>";
|
|
@@ -124058,9 +124544,9 @@ var init_agent_codex_trust = __esm(() => {
|
|
|
124058
124544
|
});
|
|
124059
124545
|
|
|
124060
124546
|
// src/lib/codex-native-trust.ts
|
|
124061
|
-
import { createHash as
|
|
124062
|
-
import { readFileSync as
|
|
124063
|
-
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";
|
|
124064
124550
|
function refuse7(detail) {
|
|
124065
124551
|
throw new Error(`NATIVE_SKILL_POLICY_TRUST_REFUSED: ${detail}`);
|
|
124066
124552
|
}
|
|
@@ -124079,15 +124565,15 @@ function planCodexNativeTrust(options) {
|
|
|
124079
124565
|
refuse7("digests must be unique");
|
|
124080
124566
|
if (typeof expectedPolicySha256 !== "string" || !HEX_DIGEST2.test(expectedPolicySha256))
|
|
124081
124567
|
refuse7("--expected-policy-sha256 must be lowercase SHA-256 hex");
|
|
124082
|
-
const dataDir3 =
|
|
124568
|
+
const dataDir3 = resolve45(options.dataDir ?? getDataDirReadOnly()), policyPath = join80(dataDir3, "agent-policy.json");
|
|
124083
124569
|
assertOperatorTrustRoot(dataDir3);
|
|
124084
|
-
const
|
|
124085
|
-
if (!
|
|
124570
|
+
const snapshot3 = readManagedSkillPolicySnapshot(dataDir3);
|
|
124571
|
+
if (!snapshot3)
|
|
124086
124572
|
refuse7("no managed policy; run skills hook install first");
|
|
124087
|
-
const policySha256Before = sha7(
|
|
124573
|
+
const policySha256Before = sha7(snapshot3.text);
|
|
124088
124574
|
if (policySha256Before !== expectedPolicySha256)
|
|
124089
124575
|
refuse7("the managed policy bytes differ from --expected-policy-sha256; read the current file and pass its SHA-256");
|
|
124090
|
-
const policy =
|
|
124576
|
+
const policy = snapshot3.value;
|
|
124091
124577
|
if (!policy.bridge || typeof policy.bridge !== "object" || Array.isArray(policy.bridge))
|
|
124092
124578
|
refuse7("the managed policy has no bridge binding; run skills hook install first");
|
|
124093
124579
|
const current = parseCodexNativePolicyTrust(policy.bridge.codexNativePolicy);
|
|
@@ -124096,7 +124582,7 @@ function planCodexNativeTrust(options) {
|
|
|
124096
124582
|
const next = { ...policy, bridge: { ...policy.bridge, codexNativePolicy: { executableDigests } } };
|
|
124097
124583
|
assertAgentPolicyCollections(next);
|
|
124098
124584
|
const after2 = serializeManagedSkillPolicy(next);
|
|
124099
|
-
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 } };
|
|
124100
124586
|
return { schema: CODEX_NATIVE_TRUST_PLAN_SCHEMA, dataDir: dataDir3, policyPath, platform: platform2, digestsBefore: [...current.executableDigests[platform2] ?? []], digestsAfter: [...digests], policySha256Before, policySha256After: sha7(after2), plan };
|
|
124101
124587
|
}
|
|
124102
124588
|
function previewCodexNativeTrust(plan) {
|
|
@@ -124117,7 +124603,7 @@ function applyCodexNativeTrust(plan, expectedPolicySha256) {
|
|
|
124117
124603
|
const backupPath = result2.backups[0];
|
|
124118
124604
|
if (result2.backups.length !== 1 || typeof backupPath !== "string")
|
|
124119
124605
|
throw new Error("NATIVE_SKILL_POLICY_TRUST_WRITE_UNVERIFIED: the pre-change backup was not recorded");
|
|
124120
|
-
const backupSha256 = sha7(
|
|
124606
|
+
const backupSha256 = sha7(readFileSync48(backupPath));
|
|
124121
124607
|
if (backupSha256 !== plan.policySha256Before)
|
|
124122
124608
|
throw new Error(`NATIVE_SKILL_POLICY_TRUST_WRITE_UNVERIFIED: the backup at ${backupPath} does not read back as the pre-change policy`);
|
|
124123
124609
|
receipt.backup = { path: backupPath, sha256: backupSha256, verified: true };
|
|
@@ -124131,7 +124617,7 @@ function applyCodexNativeTrust(plan, expectedPolicySha256) {
|
|
|
124131
124617
|
receipt.readback = { policySha256: sha7(readback.text), digests, verified: true };
|
|
124132
124618
|
return receipt;
|
|
124133
124619
|
}
|
|
124134
|
-
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");
|
|
124135
124621
|
var init_codex_native_trust = __esm(() => {
|
|
124136
124622
|
init_config();
|
|
124137
124623
|
init_managed_policy();
|
|
@@ -124143,9 +124629,9 @@ var init_codex_native_trust = __esm(() => {
|
|
|
124143
124629
|
});
|
|
124144
124630
|
|
|
124145
124631
|
// src/lib/claude-prospective-review.ts
|
|
124146
|
-
import { createHash as
|
|
124147
|
-
import { lstatSync as
|
|
124148
|
-
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";
|
|
124149
124635
|
import { isDeepStrictEqual as isDeepStrictEqual23 } from "util";
|
|
124150
124636
|
function hashClaudeDiscoveryBinding(binding) {
|
|
124151
124637
|
return sha8(JSON.stringify(binding));
|
|
@@ -124196,7 +124682,7 @@ function keys4(value, expected, name) {
|
|
|
124196
124682
|
refuse8(`${name} has an unsupported shape`);
|
|
124197
124683
|
}
|
|
124198
124684
|
function abs(path2, name) {
|
|
124199
|
-
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)
|
|
124200
124686
|
refuse8(`${name} must be a normalized absolute path`);
|
|
124201
124687
|
}
|
|
124202
124688
|
function validUtcTimestamp(value) {
|
|
@@ -124204,27 +124690,27 @@ function validUtcTimestamp(value) {
|
|
|
124204
124690
|
}
|
|
124205
124691
|
function safeFile(path2, name) {
|
|
124206
124692
|
abs(path2, name);
|
|
124207
|
-
for (let at2 =
|
|
124208
|
-
const stat4 =
|
|
124693
|
+
for (let at2 = dirname53(path2);; at2 = dirname53(at2)) {
|
|
124694
|
+
const stat4 = lstatSync52(at2, { throwIfNoEntry: false });
|
|
124209
124695
|
if (!stat4?.isDirectory() || stat4.isSymbolicLink())
|
|
124210
124696
|
refuse8(`${name} traverses a symlink or non-directory`);
|
|
124211
|
-
if (at2 ===
|
|
124697
|
+
if (at2 === dirname53(at2))
|
|
124212
124698
|
break;
|
|
124213
124699
|
}
|
|
124214
|
-
const stat3 =
|
|
124700
|
+
const stat3 = lstatSync52(path2, { throwIfNoEntry: false });
|
|
124215
124701
|
if (!stat3?.isFile() || stat3.isSymbolicLink() || stat3.size > MAX_CANDIDATE_BYTES)
|
|
124216
124702
|
refuse8(`${name} is not a bounded regular file`);
|
|
124217
|
-
return
|
|
124703
|
+
return readFileSync49(path2);
|
|
124218
124704
|
}
|
|
124219
124705
|
function canonicalDir(path2, name) {
|
|
124220
124706
|
abs(path2, name);
|
|
124221
|
-
if (realpathSync24(path2) !== path2 || !
|
|
124707
|
+
if (realpathSync24(path2) !== path2 || !lstatSync52(path2).isDirectory())
|
|
124222
124708
|
refuse8(`${name} is not a canonical directory`);
|
|
124223
|
-
for (let at2 = path2;; at2 =
|
|
124224
|
-
const stat3 =
|
|
124709
|
+
for (let at2 = path2;; at2 = dirname53(at2)) {
|
|
124710
|
+
const stat3 = lstatSync52(at2);
|
|
124225
124711
|
if (!stat3.isDirectory() || stat3.isSymbolicLink())
|
|
124226
124712
|
refuse8(`${name} traverses a symlink`);
|
|
124227
|
-
if (at2 ===
|
|
124713
|
+
if (at2 === dirname53(at2))
|
|
124228
124714
|
break;
|
|
124229
124715
|
}
|
|
124230
124716
|
}
|
|
@@ -124233,17 +124719,17 @@ function captureClaudeProspectiveCandidateClosure(root2) {
|
|
|
124233
124719
|
const entries = [];
|
|
124234
124720
|
let bytes = 0;
|
|
124235
124721
|
const walk2 = (directory3) => {
|
|
124236
|
-
const beforeDirectory =
|
|
124237
|
-
const names =
|
|
124722
|
+
const beforeDirectory = lstatSync52(directory3);
|
|
124723
|
+
const names = readdirSync30(directory3).sort();
|
|
124238
124724
|
if (new Set(names).size !== names.length)
|
|
124239
124725
|
refuse8("candidate contains ambiguous directory entries");
|
|
124240
124726
|
for (const name of names) {
|
|
124241
124727
|
if (!name || name === "." || name === ".." || name.includes("/") || name.includes("\\") || /[\x00-\x1f\x7f]/.test(name))
|
|
124242
124728
|
refuse8("candidate contains an unsupported path member");
|
|
124243
|
-
const path2 =
|
|
124729
|
+
const path2 = join81(directory3, name), rel = relative17(root2, path2).split(sep24).join("/");
|
|
124244
124730
|
if (entries.length >= MAX_CANDIDATE_FILES)
|
|
124245
124731
|
refuse8("candidate closure exceeds its member limit");
|
|
124246
|
-
const stat3 =
|
|
124732
|
+
const stat3 = lstatSync52(path2);
|
|
124247
124733
|
if (stat3.isSymbolicLink())
|
|
124248
124734
|
refuse8("candidate closure contains a symlink");
|
|
124249
124735
|
if (stat3.isDirectory()) {
|
|
@@ -124253,14 +124739,14 @@ function captureClaudeProspectiveCandidateClosure(root2) {
|
|
|
124253
124739
|
bytes += stat3.size;
|
|
124254
124740
|
if (bytes > MAX_CANDIDATE_BYTES)
|
|
124255
124741
|
refuse8("candidate closure exceeds its byte limit");
|
|
124256
|
-
const content = safeFile(path2, "candidate member"), after2 =
|
|
124742
|
+
const content = safeFile(path2, "candidate member"), after2 = lstatSync52(path2);
|
|
124257
124743
|
if (!after2.isFile() || after2.dev !== stat3.dev || after2.ino !== stat3.ino || after2.size !== stat3.size || after2.mtimeMs !== stat3.mtimeMs || after2.ctimeMs !== stat3.ctimeMs)
|
|
124258
124744
|
refuse8("candidate member changed while being read");
|
|
124259
124745
|
entries.push([rel, "file", sha8(content)]);
|
|
124260
124746
|
} else
|
|
124261
124747
|
refuse8("candidate closure contains a special file");
|
|
124262
124748
|
}
|
|
124263
|
-
const afterDirectory =
|
|
124749
|
+
const afterDirectory = lstatSync52(directory3);
|
|
124264
124750
|
if (!afterDirectory.isDirectory() || afterDirectory.dev !== beforeDirectory.dev || afterDirectory.ino !== beforeDirectory.ino || afterDirectory.mtimeMs !== beforeDirectory.mtimeMs || afterDirectory.ctimeMs !== beforeDirectory.ctimeMs)
|
|
124265
124751
|
refuse8("candidate directory changed while being read");
|
|
124266
124752
|
};
|
|
@@ -124271,9 +124757,9 @@ function captureClaudeProspectiveCandidateClosure(root2) {
|
|
|
124271
124757
|
function captureClaudeInstallerCandidate(root2, policyPath) {
|
|
124272
124758
|
const closureSha256 = captureClaudeProspectiveCandidateClosure(root2);
|
|
124273
124759
|
const dataDir3 = getDataDirReadOnly();
|
|
124274
|
-
if (policyPath !==
|
|
124760
|
+
if (policyPath !== join81(dataDir3, "agent-policy.json"))
|
|
124275
124761
|
refuse8("Skills policy path differs from the package-owned resolver");
|
|
124276
|
-
const catalogPath =
|
|
124762
|
+
const catalogPath = join81(root2, ".claude-plugin/marketplace.json");
|
|
124277
124763
|
const catalog = jsonFile(catalogPath, "candidate catalog");
|
|
124278
124764
|
if (!catalog || !Array.isArray(catalog.plugins))
|
|
124279
124765
|
refuse8("candidate catalog is invalid");
|
|
@@ -124284,13 +124770,13 @@ function captureClaudeInstallerCandidate(root2, policyPath) {
|
|
|
124284
124770
|
if (names.has(row2.name))
|
|
124285
124771
|
refuse8("candidate catalog repeats a plugin name");
|
|
124286
124772
|
names.add(row2.name);
|
|
124287
|
-
if (typeof row2.source !== "string" ||
|
|
124773
|
+
if (typeof row2.source !== "string" || isAbsolute48(row2.source) || /[\\\x00-\x1f\x7f]/.test(row2.source))
|
|
124288
124774
|
refuse8("candidate catalog plugin source is invalid");
|
|
124289
|
-
const pluginRoot =
|
|
124290
|
-
const relativeRoot =
|
|
124291
|
-
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))
|
|
124292
124778
|
refuse8("candidate plugin source escapes the candidate root");
|
|
124293
|
-
const path2 =
|
|
124779
|
+
const path2 = join81(pluginRoot, ".claude-plugin/plugin.json");
|
|
124294
124780
|
return { plugin: row2.name, ...captureClaudePluginManifestFile(path2) };
|
|
124295
124781
|
});
|
|
124296
124782
|
const policy = readManagedSkillPolicySnapshot(dataDir3);
|
|
@@ -124307,7 +124793,7 @@ function captureClaudeInstallerCandidate(root2, policyPath) {
|
|
|
124307
124793
|
}
|
|
124308
124794
|
function readWitness(path2, expected, name) {
|
|
124309
124795
|
abs(path2, `${name} path`);
|
|
124310
|
-
const stat3 =
|
|
124796
|
+
const stat3 = lstatSync52(path2, { throwIfNoEntry: false });
|
|
124311
124797
|
if (expected === null) {
|
|
124312
124798
|
if (stat3)
|
|
124313
124799
|
refuse8(`${name} was absent at review but is now present`);
|
|
@@ -124359,7 +124845,7 @@ function unreviewedProjectSettingsRefusal(path2, expected, name) {
|
|
|
124359
124845
|
return null;
|
|
124360
124846
|
}
|
|
124361
124847
|
function jsonFile(path2, name) {
|
|
124362
|
-
const stat3 =
|
|
124848
|
+
const stat3 = lstatSync52(path2, { throwIfNoEntry: false });
|
|
124363
124849
|
if (!stat3)
|
|
124364
124850
|
return null;
|
|
124365
124851
|
let value;
|
|
@@ -124389,9 +124875,9 @@ function assertNoUnprojectedSkillPayload(root2, catalogPath, marketplace, plugin
|
|
|
124389
124875
|
if (entries[0].skills !== undefined || entries[0].commands !== undefined)
|
|
124390
124876
|
refuse8("candidate marketplace entry declares native skill or command payloads");
|
|
124391
124877
|
const walk2 = (directory3) => {
|
|
124392
|
-
for (const name of
|
|
124393
|
-
const path2 =
|
|
124394
|
-
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);
|
|
124395
124881
|
if (stat3.isDirectory())
|
|
124396
124882
|
walk2(path2);
|
|
124397
124883
|
else if (stat3.isFile()) {
|
|
@@ -124436,10 +124922,10 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
|
|
|
124436
124922
|
abs(request2.skills.dataDir, "Skills data directory");
|
|
124437
124923
|
if (!SHA2562.test(request2.skills.policySha256) || !SHA2562.test(request2.skills.discoverySha256))
|
|
124438
124924
|
refuse8("invalid Skills policy or discovery SHA-256");
|
|
124439
|
-
const
|
|
124440
|
-
if (!
|
|
124925
|
+
const snapshot3 = readManagedSkillPolicySnapshot(request2.skills.dataDir);
|
|
124926
|
+
if (!snapshot3 || sha8(snapshot3.text) !== request2.skills.policySha256)
|
|
124441
124927
|
refuse8("Skills policy preimage changed or is absent");
|
|
124442
|
-
const aliases =
|
|
124928
|
+
const aliases = snapshot3.value.bridge?.rootAliases ?? [];
|
|
124443
124929
|
if (!Array.isArray(aliases))
|
|
124444
124930
|
refuse8("Skills policy root aliases are invalid");
|
|
124445
124931
|
try {
|
|
@@ -124448,16 +124934,16 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
|
|
|
124448
124934
|
refuse8("Skills policy root alias changed or is invalid");
|
|
124449
124935
|
}
|
|
124450
124936
|
const canonical9 = (path2) => canonicalAgentPath(path2, aliases);
|
|
124451
|
-
const configRoot = canonical9(
|
|
124937
|
+
const configRoot = canonical9(join81(request2.native.home, ".claude"));
|
|
124452
124938
|
canonicalDir(configRoot, "Claude config root");
|
|
124453
124939
|
if (request2.native.configRoot !== configRoot)
|
|
124454
124940
|
refuse8("Claude config root differs from the current resolver and reviewed root alias");
|
|
124455
|
-
const pluginRoot =
|
|
124941
|
+
const pluginRoot = join81(configRoot, "plugins");
|
|
124456
124942
|
if (process.env.CLAUDE_CODE_PLUGIN_CACHE_DIR)
|
|
124457
124943
|
refuse8("CLAUDE_CODE_PLUGIN_CACHE_DIR overrides are unsupported by this prospective schema; bind and review the actual cache root first");
|
|
124458
124944
|
if (request2.native.pluginRoot !== pluginRoot)
|
|
124459
124945
|
refuse8("Claude plugin root differs from the supported standard plugins directory; Cowork and custom cache roots require a separately bound schema");
|
|
124460
|
-
const pluginRootStat =
|
|
124946
|
+
const pluginRootStat = lstatSync52(pluginRoot, { throwIfNoEntry: false });
|
|
124461
124947
|
if (pluginRootStat && (!pluginRootStat.isDirectory() || pluginRootStat.isSymbolicLink()))
|
|
124462
124948
|
refuse8("Claude plugin root is not a regular directory");
|
|
124463
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");
|
|
@@ -124475,9 +124961,9 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
|
|
|
124475
124961
|
if (hasOwn2(candidate2, "settingsMarketplaceSource") && (candidate2.settingsMarketplaceSource !== "directory" || request2.scope.kind !== "user" || !measuredSettingsWriter))
|
|
124476
124962
|
refuse8("settings marketplace source operation requires directory source and a measured Claude user-scope writer");
|
|
124477
124963
|
abs(candidate2.catalogPath, "catalog path");
|
|
124478
|
-
if (!candidate2.catalogPath.startsWith(candidate2.root +
|
|
124964
|
+
if (!candidate2.catalogPath.startsWith(candidate2.root + sep24))
|
|
124479
124965
|
refuse8("catalog must be inside the frozen candidate root");
|
|
124480
|
-
if (candidate2.catalogPath !==
|
|
124966
|
+
if (candidate2.catalogPath !== join81(candidate2.root, ".claude-plugin", "marketplace.json"))
|
|
124481
124967
|
refuse8("marketplace catalog must be at the frozen root's .claude-plugin/marketplace.json path");
|
|
124482
124968
|
if (!SHA2562.test(candidate2.catalogSha256) || !SHA2562.test(candidate2.closureSha256))
|
|
124483
124969
|
refuse8("candidate digests must be lowercase SHA-256");
|
|
@@ -124519,11 +125005,11 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
|
|
|
124519
125005
|
if (Buffer.byteLength(canonicalJson3(selected.optionsPatch), "utf8") > 16 * 1024)
|
|
124520
125006
|
refuse8("selected plugin optionsPatch exceeds its byte bound");
|
|
124521
125007
|
abs(selected.manifestPath, "manifest path");
|
|
124522
|
-
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"))
|
|
124523
125009
|
refuse8("plugin manifest must be under a plugin root inside the frozen marketplace closure");
|
|
124524
|
-
const pluginRoot2 =
|
|
124525
|
-
const relativePluginRoot =
|
|
124526
|
-
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))
|
|
124527
125013
|
refuse8("plugin root must be a child of the frozen marketplace root");
|
|
124528
125014
|
canonicalDir(pluginRoot2, "candidate plugin root");
|
|
124529
125015
|
if (!SHA2562.test(selected.manifestSha256))
|
|
@@ -124555,9 +125041,9 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
|
|
|
124555
125041
|
refuse8("selected plugins collide after native cache path sanitization");
|
|
124556
125042
|
sanitizedCachePaths.add(sanitizedCachePath);
|
|
124557
125043
|
const sourcePath = selectedEntry.source;
|
|
124558
|
-
const sourceRelativePath =
|
|
125044
|
+
const sourceRelativePath = relative17(candidate2.root, pluginRoot2).split(sep24).join("/");
|
|
124559
125045
|
const expectedSourcePath = sourceRelativePath === "." ? "./" : `./${sourceRelativePath}`;
|
|
124560
|
-
if (typeof sourcePath !== "string" ||
|
|
125046
|
+
if (typeof sourcePath !== "string" || isAbsolute48(sourcePath) || sourcePath !== expectedSourcePath || resolve46(dirname53(dirname53(candidate2.catalogPath)), sourcePath) !== pluginRoot2)
|
|
124561
125047
|
refuse8("marketplace source does not resolve to the frozen plugin closure; external source mapping is not supported by this schema");
|
|
124562
125048
|
const entry = captureClaudeMarketplaceEntry(candidate2.catalogPath, candidate2.marketplace, selected.plugin);
|
|
124563
125049
|
if (entry.sha256.length !== 64)
|
|
@@ -124577,12 +125063,12 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
|
|
|
124577
125063
|
for (const name of ["settings", "projectSettings", "projectSettingsLocal", "marketplaces", "installedPlugins"])
|
|
124578
125064
|
keys4(request2.preimages[name], ["path", "sha256"], `${name} preimage`);
|
|
124579
125065
|
const expectedPaths = {
|
|
124580
|
-
settings: request2.scope.kind === "project" ? canonical9(
|
|
124581
|
-
userSettings: canonical9(
|
|
124582
|
-
projectSettings: canonical9(
|
|
124583
|
-
projectSettingsLocal: canonical9(
|
|
124584
|
-
marketplaces: canonical9(
|
|
124585
|
-
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"))
|
|
124586
125072
|
};
|
|
124587
125073
|
for (const name of Object.keys(expectedPaths)) {
|
|
124588
125074
|
const witness = request2.preimages[name];
|
|
@@ -124617,8 +125103,8 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
|
|
|
124617
125103
|
if (hashClaudeDiscoveryBinding(binding) !== request2.skills.discoverySha256)
|
|
124618
125104
|
refuse8("Skills discovery preimage differs from its reviewed binding");
|
|
124619
125105
|
assertProjectDiscovery("claude", [request2.scope.projectPath], request2.native.home, canonical9, binding);
|
|
124620
|
-
const stored =
|
|
124621
|
-
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)
|
|
124622
125108
|
refuse8("Skills policy does not bind the reviewed current Claude discovery");
|
|
124623
125109
|
assertExemptUnchanged();
|
|
124624
125110
|
const delta = request2.delta;
|
|
@@ -124715,7 +125201,7 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
|
|
|
124715
125201
|
const manifestVersion = manifest.version;
|
|
124716
125202
|
const sanitizeComponent = (value) => value.replace(/[^A-Za-z0-9_-]/g, "-");
|
|
124717
125203
|
const sanitizedVersion = manifestVersion === "." || manifestVersion === ".." ? "-" : manifestVersion.replace(/[^A-Za-z0-9._-]/g, "-");
|
|
124718
|
-
const expectedInstallPath =
|
|
125204
|
+
const expectedInstallPath = join81(pluginRoot, "cache", sanitizeComponent(candidate2.marketplace), sanitizeComponent(plugin), sanitizedVersion);
|
|
124719
125205
|
if (expectedInstallPath.length > 4096)
|
|
124720
125206
|
refuse8("candidate native cache path exceeds its bound");
|
|
124721
125207
|
const targetRegistration = {
|
|
@@ -124854,7 +125340,7 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
|
|
|
124854
125340
|
{ name: "project-settings-local", path: expectedPaths.projectSettingsLocal, sha256: request2.preimages.projectSettingsLocal.sha256 },
|
|
124855
125341
|
{ name: "known-marketplaces", path: expectedPaths.marketplaces, sha256: request2.preimages.marketplaces.sha256 },
|
|
124856
125342
|
{ name: "installed-plugins", path: expectedPaths.installedPlugins, sha256: request2.preimages.installedPlugins.sha256 },
|
|
124857
|
-
{ name: "skills-policy", path:
|
|
125343
|
+
{ name: "skills-policy", path: join81(request2.skills.dataDir, "agent-policy.json"), sha256: request2.skills.policySha256 }
|
|
124858
125344
|
],
|
|
124859
125345
|
ownedDelta: { settingsEnabledPlugins: selectedPluginIds, selectedPluginOptions: selectedOptionPaths, functionHooksEnv: candidate2.functionHooksEnv ?? null, knownMarketplace: candidate2.marketplace, installedPlugins: selectedPluginIds, unrelatedGraph: "preserved-by-complete-semantic-documents" },
|
|
124860
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",
|
|
@@ -124862,7 +125348,7 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
|
|
|
124862
125348
|
limitation: "validation-only; native installer atomicity and crash recovery are not established"
|
|
124863
125349
|
};
|
|
124864
125350
|
}
|
|
124865
|
-
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;
|
|
124866
125352
|
var init_claude_prospective_review = __esm(() => {
|
|
124867
125353
|
init_agent_discovery();
|
|
124868
125354
|
init_agent_integration();
|
|
@@ -124896,7 +125382,7 @@ var init_claude_prospective_review = __esm(() => {
|
|
|
124896
125382
|
|
|
124897
125383
|
// src/lib/codex-plugin-reconcile.ts
|
|
124898
125384
|
import { realpathSync as realpathSync25 } from "fs";
|
|
124899
|
-
import { join as
|
|
125385
|
+
import { join as join82, resolve as resolve47 } from "path";
|
|
124900
125386
|
function boundedMessage(text5) {
|
|
124901
125387
|
const line = text5.replace(/[\u0000-\u001f\u007f-\u009f\u061c\u200e\u200f\u2028-\u202e\u2066-\u2069]/g, " ").trim();
|
|
124902
125388
|
return line.length > 800 ? `${line.slice(0, 797)}...` : line;
|
|
@@ -124930,20 +125416,20 @@ async function runCodexPluginReconcile(options) {
|
|
|
124930
125416
|
const mode = options.apply ? "apply" : "preview";
|
|
124931
125417
|
const base3 = emptyResult(mode);
|
|
124932
125418
|
try {
|
|
124933
|
-
const home =
|
|
125419
|
+
const home = resolve47(options.home), projectDir = resolve47(options.projectDir), dataDir3 = options.dataDir ?? getDataDirReadOnly();
|
|
124934
125420
|
if (options.codexHome !== undefined) {
|
|
124935
125421
|
const canonical9 = (path2) => {
|
|
124936
125422
|
try {
|
|
124937
125423
|
return realpathSync25(path2);
|
|
124938
125424
|
} catch {
|
|
124939
|
-
return
|
|
125425
|
+
return resolve47(path2);
|
|
124940
125426
|
}
|
|
124941
125427
|
};
|
|
124942
|
-
if (canonical9(options.codexHome) !== canonical9(
|
|
125428
|
+
if (canonical9(options.codexHome) !== canonical9(join82(home, ".codex")))
|
|
124943
125429
|
throw new CodexPluginReconcileRefusal("CODEX_RECONCILE_CODEX_HOME_UNSUPPORTED", "CODEX_HOME names a Codex home other than the one the Skills binding reviews");
|
|
124944
125430
|
}
|
|
124945
125431
|
const capture2 = options.captureCatalog ?? captureCodexNativeSkillCatalog;
|
|
124946
|
-
const remoteReceipts = captureCodexRemoteReceiptSnapshot(
|
|
125432
|
+
const remoteReceipts = captureCodexRemoteReceiptSnapshot(join82(home, ".codex/plugins/cache"));
|
|
124947
125433
|
const captured = await capture2({ command: options.codexCommand ?? "codex", home, ...options.codexHome === undefined ? {} : { codexHome: options.codexHome }, cwd: projectDir });
|
|
124948
125434
|
const catalog = captured && typeof captured === "object" ? { ...captured, remoteReceipts } : captured;
|
|
124949
125435
|
base3.codexVersion = typeof catalog?.version === "string" ? catalog.version : null;
|
|
@@ -124968,7 +125454,7 @@ async function runCodexPluginReconcile(options) {
|
|
|
124968
125454
|
if (!options.apply)
|
|
124969
125455
|
return { ...reported, outcome: "planned", changes };
|
|
124970
125456
|
const applied = await applyCodexPluginReconcile(plan, { ...options.codexCommand === undefined ? {} : { codexCommand: options.codexCommand }, ...options.codexHome === undefined ? {} : { codexHome: options.codexHome }, ...options.connect ? { connect: options.connect } : {} });
|
|
124971
|
-
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 };
|
|
124972
125458
|
} catch (error61) {
|
|
124973
125459
|
return { ...base3, ...classifyCodexReconcileError(error61) };
|
|
124974
125460
|
}
|
|
@@ -125004,23 +125490,23 @@ var init_codex_plugin_reconcile = __esm(() => {
|
|
|
125004
125490
|
});
|
|
125005
125491
|
|
|
125006
125492
|
// src/lib/claude-installer-binding.ts
|
|
125007
|
-
import { createHash as
|
|
125008
|
-
import { join as
|
|
125493
|
+
import { createHash as createHash53 } from "crypto";
|
|
125494
|
+
import { join as join83 } from "path";
|
|
125009
125495
|
function readPolicy(dataDir3) {
|
|
125010
|
-
let
|
|
125496
|
+
let snapshot3;
|
|
125011
125497
|
try {
|
|
125012
|
-
|
|
125498
|
+
snapshot3 = readManagedSkillPolicySnapshot(dataDir3);
|
|
125013
125499
|
} catch {
|
|
125014
125500
|
return refuse9("POLICY_UNREADABLE");
|
|
125015
125501
|
}
|
|
125016
|
-
return
|
|
125502
|
+
return snapshot3 ?? refuse9("POLICY_UNAVAILABLE");
|
|
125017
125503
|
}
|
|
125018
125504
|
function verifyClaudeInstallerDiscoveryBinding(policyPath) {
|
|
125019
125505
|
const dataDir3 = getDataDirReadOnly();
|
|
125020
|
-
if (policyPath !==
|
|
125506
|
+
if (policyPath !== join83(dataDir3, "agent-policy.json"))
|
|
125021
125507
|
refuse9("POLICY_PATH_MISMATCH");
|
|
125022
|
-
const
|
|
125023
|
-
const bridge =
|
|
125508
|
+
const snapshot3 = readPolicy(dataDir3);
|
|
125509
|
+
const bridge = snapshot3.value.bridge;
|
|
125024
125510
|
const binding = object14(bridge) && object14(bridge.discovery) ? bridge.discovery.claude : undefined;
|
|
125025
125511
|
if (!object14(binding))
|
|
125026
125512
|
return refuse9("BINDING_UNAVAILABLE");
|
|
@@ -125062,12 +125548,12 @@ function verifyClaudeInstallerDiscoveryBinding(policyPath) {
|
|
|
125062
125548
|
...directories.flatMap((row2) => row2.status === "drift" ? [{ kind: "directory", index: row2.index, path: row2.path, reason: row2.reason }] : []),
|
|
125063
125549
|
...accepted ? [] : [{ kind: "binding", reason: "DISCOVERY_REFUSED" }]
|
|
125064
125550
|
];
|
|
125065
|
-
if (readPolicy(dataDir3).text !==
|
|
125551
|
+
if (readPolicy(dataDir3).text !== snapshot3.text)
|
|
125066
125552
|
refuse9("POLICY_CHANGED");
|
|
125067
125553
|
return {
|
|
125068
125554
|
schema: CLAUDE_DISCOVERY_BINDING_VERIFICATION_SCHEMA,
|
|
125069
125555
|
ok: failures3.length === 0,
|
|
125070
|
-
policySha256:
|
|
125556
|
+
policySha256: createHash53("sha256").update(snapshot3.text).digest("hex"),
|
|
125071
125557
|
discoverySha256: hashClaudeDiscoveryBinding(binding),
|
|
125072
125558
|
agent: "claude",
|
|
125073
125559
|
method: binding.method,
|
|
@@ -125096,8 +125582,8 @@ var init_claude_installer_binding = __esm(() => {
|
|
|
125096
125582
|
});
|
|
125097
125583
|
|
|
125098
125584
|
// src/cli/commands/claude-installer-capture.ts
|
|
125099
|
-
import { closeSync as closeSync33, constants as constants33, fstatSync as fstatSync32, lstatSync as
|
|
125100
|
-
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";
|
|
125101
125587
|
function need11(condition, code2) {
|
|
125102
125588
|
if (!condition)
|
|
125103
125589
|
throw new CaptureRefusal(code2);
|
|
@@ -125111,11 +125597,11 @@ function exactKeys5(value, expected, operation) {
|
|
|
125111
125597
|
}
|
|
125112
125598
|
}
|
|
125113
125599
|
function canonicalPath2(value) {
|
|
125114
|
-
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");
|
|
125115
125601
|
}
|
|
125116
125602
|
function readRequest2(path2) {
|
|
125117
125603
|
canonicalPath2(path2);
|
|
125118
|
-
const before2 =
|
|
125604
|
+
const before2 = lstatSync53(path2, { throwIfNoEntry: false });
|
|
125119
125605
|
if (!before2?.isFile() || before2.isSymbolicLink() || before2.size > MAX_REQUEST_BYTES)
|
|
125120
125606
|
throw new CaptureRefusal("INVALID_REQUEST_FILE: --request must name a regular non-symlink JSON file no larger than 64 KiB");
|
|
125121
125607
|
const fd = openSync33(path2, constants33.O_RDONLY | (constants33.O_NOFOLLOW ?? 0) | constants33.O_NONBLOCK);
|
|
@@ -125130,7 +125616,7 @@ function readRequest2(path2) {
|
|
|
125130
125616
|
break;
|
|
125131
125617
|
length += count;
|
|
125132
125618
|
}
|
|
125133
|
-
const after2 = fstatSync32(fd), current =
|
|
125619
|
+
const after2 = fstatSync32(fd), current = lstatSync53(path2);
|
|
125134
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");
|
|
125135
125621
|
try {
|
|
125136
125622
|
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes.subarray(0, length)));
|
|
@@ -125292,10 +125778,10 @@ __export(exports_agent_integration, {
|
|
|
125292
125778
|
MANAGED_HOOK_CONTEXT_MAX_SKILLS: () => MANAGED_HOOK_CONTEXT_MAX_SKILLS,
|
|
125293
125779
|
MANAGED_HOOK_CONTEXT_MAX_CHARS: () => MANAGED_HOOK_CONTEXT_MAX_CHARS
|
|
125294
125780
|
});
|
|
125295
|
-
import { existsSync as
|
|
125296
|
-
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";
|
|
125297
125783
|
import { homedir as homedir15 } from "os";
|
|
125298
|
-
import { basename as basename27, isAbsolute as
|
|
125784
|
+
import { basename as basename27, isAbsolute as isAbsolute50, resolve as resolve49 } from "path";
|
|
125299
125785
|
function boundedEnvInteger(name, fallback, [min2, max2]) {
|
|
125300
125786
|
const raw = process.env[name];
|
|
125301
125787
|
if (raw === undefined || !/^[0-9]{1,6}$/.test(raw.trim()))
|
|
@@ -125479,9 +125965,9 @@ function registerAgentIntegration(parent) {
|
|
|
125479
125965
|
});
|
|
125480
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) => {
|
|
125481
125967
|
try {
|
|
125482
|
-
if (!
|
|
125968
|
+
if (!isAbsolute50(options.cwd) || !isAbsolute50(options.output))
|
|
125483
125969
|
throw new Error("CODEX_NATIVE_SKILL_CATALOG_ABSOLUTE_PATH_REQUIRED");
|
|
125484
|
-
if (
|
|
125970
|
+
if (existsSync37(options.output))
|
|
125485
125971
|
throw new Error("CODEX_NATIVE_SKILL_CATALOG_OUTPUT_EXISTS");
|
|
125486
125972
|
let admission;
|
|
125487
125973
|
const catalog = await captureCodexNativeSkillCatalog({
|
|
@@ -125497,14 +125983,14 @@ function registerAgentIntegration(parent) {
|
|
|
125497
125983
|
const bytes = JSON.stringify(catalog, null, 2) + `
|
|
125498
125984
|
`;
|
|
125499
125985
|
try {
|
|
125500
|
-
|
|
125986
|
+
writeFileSync30(options.output, bytes, { flag: "wx", mode: 384 });
|
|
125501
125987
|
} catch (error61) {
|
|
125502
125988
|
if (error61 && typeof error61 === "object" && "code" in error61 && error61.code === "EEXIST")
|
|
125503
125989
|
throw new Error("CODEX_NATIVE_SKILL_CATALOG_OUTPUT_EXISTS");
|
|
125504
125990
|
throw error61;
|
|
125505
125991
|
}
|
|
125506
|
-
const written =
|
|
125507
|
-
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)
|
|
125508
125994
|
throw new Error("CODEX_NATIVE_SKILL_CATALOG_READBACK_FAILED");
|
|
125509
125995
|
const receipt = {
|
|
125510
125996
|
version: catalog.version,
|
|
@@ -125512,7 +125998,7 @@ function registerAgentIntegration(parent) {
|
|
|
125512
125998
|
skillCount: catalog.skills.length,
|
|
125513
125999
|
output: options.output,
|
|
125514
126000
|
bytes: Buffer.byteLength(bytes),
|
|
125515
|
-
sha256:
|
|
126001
|
+
sha256: createHash54("sha256").update(bytes).digest("hex"),
|
|
125516
126002
|
...codexAdmissionReceipt(admission)
|
|
125517
126003
|
};
|
|
125518
126004
|
await writeCliOutput(options.json ? JSON.stringify(receipt) : `Captured ${receipt.skillCount} native Codex skill(s) in ${receipt.output}.`);
|
|
@@ -125546,7 +126032,7 @@ function registerAgentIntegration(parent) {
|
|
|
125546
126032
|
if (!options.reviewedHookSources)
|
|
125547
126033
|
throw new Error("Codex V6 requires a bounded --reviewed-hook-sources JSON array");
|
|
125548
126034
|
const { hashNativeJsonControls: hashNativeJsonControls2, readNativeSettingsWitnessFile: readNativeSettingsWitnessFile2 } = await Promise.resolve().then(() => (init_claude_settings_witness(), exports_claude_settings_witness));
|
|
125549
|
-
const metadata2 =
|
|
126035
|
+
const metadata2 = resolve49(options.reviewedHookSources);
|
|
125550
126036
|
const text5 = readNativeSettingsWitnessFile2(metadata2, { remaining: 1048576 }, basename27(metadata2));
|
|
125551
126037
|
hashNativeJsonControls2(`{"sources":${text5}}`);
|
|
125552
126038
|
await writeCliOutput(JSON.stringify(captureCodexSettingsV6(options.path, JSON.parse(text5)), null, 2));
|
|
@@ -125581,14 +126067,14 @@ function registerAgentIntegration(parent) {
|
|
|
125581
126067
|
});
|
|
125582
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) => {
|
|
125583
126069
|
try {
|
|
125584
|
-
if (!
|
|
126070
|
+
if (!isAbsolute50(options.request))
|
|
125585
126071
|
throw new Error("CLAUDE_CANDIDATE_REVIEW_REFUSED: --request must be an absolute path");
|
|
125586
|
-
const stat3 =
|
|
126072
|
+
const stat3 = lstatSync54(options.request);
|
|
125587
126073
|
if (!stat3.isFile() || stat3.isSymbolicLink() || stat3.size > 1024 * 1024)
|
|
125588
126074
|
throw new Error("CLAUDE_CANDIDATE_REVIEW_REFUSED: request must be a regular file no larger than 1 MiB");
|
|
125589
|
-
const requestBytes =
|
|
126075
|
+
const requestBytes = readFileSync50(options.request);
|
|
125590
126076
|
const request2 = JSON.parse(requestBytes.toString("utf8"));
|
|
125591
|
-
const receipt = reviewClaudeProspectiveCandidate(request2,
|
|
126077
|
+
const receipt = reviewClaudeProspectiveCandidate(request2, createHash54("sha256").update(requestBytes).digest("hex"));
|
|
125592
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.`);
|
|
125593
126079
|
} catch (error61) {
|
|
125594
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");
|
|
@@ -125660,8 +126146,8 @@ function registerAgentIntegration(parent) {
|
|
|
125660
126146
|
});
|
|
125661
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) => {
|
|
125662
126148
|
try {
|
|
125663
|
-
const discoveryInputs = options.discoveryInputs ? JSON.parse(
|
|
125664
|
-
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 });
|
|
125665
126151
|
const result2 = options.apply ? applyAgentIntegration(plan) : { changed: [], backups: [] };
|
|
125666
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) };
|
|
125667
126153
|
if (options.json)
|
|
@@ -125723,7 +126209,7 @@ function registerAgentIntegration(parent) {
|
|
|
125723
126209
|
try {
|
|
125724
126210
|
if (agents(options.agent).length !== 1)
|
|
125725
126211
|
throw new Error("A hook invocation requires one agent");
|
|
125726
|
-
const inputBytes =
|
|
126212
|
+
const inputBytes = readFileSync50(0), inputText = inputBytes.toString("utf8");
|
|
125727
126213
|
if (inputText.length > 1024 * 1024)
|
|
125728
126214
|
throw new Error("Hook input is too large");
|
|
125729
126215
|
let input3 = JSON.parse(inputText);
|
|
@@ -125736,12 +126222,12 @@ function registerAgentIntegration(parent) {
|
|
|
125736
126222
|
input3.hook_event_name = event;
|
|
125737
126223
|
const projects = [process.cwd()];
|
|
125738
126224
|
if (input3.cwd !== undefined) {
|
|
125739
|
-
if (typeof input3.cwd !== "string" || !
|
|
126225
|
+
if (typeof input3.cwd !== "string" || !isAbsolute50(input3.cwd) || input3.cwd.includes("\x00"))
|
|
125740
126226
|
throw new Error("Invalid native hook working directory");
|
|
125741
126227
|
projects.push(input3.cwd);
|
|
125742
126228
|
}
|
|
125743
126229
|
if (options.agent === "cursor") {
|
|
125744
|
-
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")))
|
|
125745
126231
|
throw new Error("Invalid Cursor workspace roots");
|
|
125746
126232
|
projects.push(...input3.workspace_roots);
|
|
125747
126233
|
input3.cwd ??= input3.workspace_roots[0] ?? process.cwd();
|
|
@@ -125889,7 +126375,7 @@ ${driftWarning}` : unavailable2, ...output2 }));
|
|
|
125889
126375
|
try {
|
|
125890
126376
|
if (options.agent && !INTEGRATION_AGENTS.includes(options.agent))
|
|
125891
126377
|
throw new Error(`Unsupported migration agent: ${options.agent}`);
|
|
125892
|
-
const discoveryInputs = options.discoveryInputs ? JSON.parse(
|
|
126378
|
+
const discoveryInputs = options.discoveryInputs ? JSON.parse(readFileSync50(options.discoveryInputs, "utf8")) : undefined;
|
|
125893
126379
|
if (options.targetManifest && (options.includeUnmanaged || options.includeVendor))
|
|
125894
126380
|
throw new Error("--target-manifest cannot be combined with broad native migration selectors");
|
|
125895
126381
|
const targetManifest = options.targetManifest ? readNativeMigrationTargetManifest(options.targetManifest) : undefined;
|
|
@@ -126084,7 +126570,7 @@ var exports_profiles = {};
|
|
|
126084
126570
|
__export(exports_profiles, {
|
|
126085
126571
|
registerProfiles: () => registerProfiles
|
|
126086
126572
|
});
|
|
126087
|
-
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";
|
|
126088
126574
|
function readProfileInput(path2) {
|
|
126089
126575
|
const fd = openSync34(path2, constants34.O_RDONLY | constants34.O_NONBLOCK);
|
|
126090
126576
|
try {
|
|
@@ -126112,7 +126598,7 @@ function registerProfiles(parent) {
|
|
|
126112
126598
|
try {
|
|
126113
126599
|
const profile = await readSkillProfile(id3), text5 = JSON.stringify(profile, null, 2);
|
|
126114
126600
|
if (options.save)
|
|
126115
|
-
|
|
126601
|
+
writeFileSync31(options.save, JSON.stringify(profile), { mode: 384, flag: "wx" });
|
|
126116
126602
|
await writeCliOutput(options.json ? text5 : `${profile.id} at ${profile.revision}: ${profile.selections.map((skill) => `${skill.slug}@${skill.version}`).join(", ")}`);
|
|
126117
126603
|
} catch (error61) {
|
|
126118
126604
|
console.error(error61.message);
|
|
@@ -126522,7 +127008,7 @@ import {
|
|
|
126522
127008
|
fstatSync as fstatSync34,
|
|
126523
127009
|
openSync as openSync35,
|
|
126524
127010
|
readSync as readSync28,
|
|
126525
|
-
writeFileSync as
|
|
127011
|
+
writeFileSync as writeFileSync32
|
|
126526
127012
|
} from "fs";
|
|
126527
127013
|
function readInput(path2) {
|
|
126528
127014
|
const fd = openSync35(path2, constants35.O_RDONLY | constants35.O_NONBLOCK);
|
|
@@ -126555,7 +127041,7 @@ function registerGrants(parent) {
|
|
|
126555
127041
|
try {
|
|
126556
127042
|
const policy = await readExecutionGrantPolicy(profile, options.revision);
|
|
126557
127043
|
if (options.save)
|
|
126558
|
-
|
|
127044
|
+
writeFileSync32(options.save, JSON.stringify(policy, null, 2), {
|
|
126559
127045
|
mode: 384,
|
|
126560
127046
|
flag: "wx"
|
|
126561
127047
|
});
|
|
@@ -126582,28 +127068,28 @@ var init_grants = __esm(() => {
|
|
|
126582
127068
|
});
|
|
126583
127069
|
|
|
126584
127070
|
// src/lib/station-hydrate.ts
|
|
126585
|
-
import { createHash as
|
|
127071
|
+
import { createHash as createHash55 } from "crypto";
|
|
126586
127072
|
import {
|
|
126587
127073
|
copyFileSync as copyFileSync3,
|
|
126588
|
-
mkdirSync as
|
|
126589
|
-
readdirSync as
|
|
126590
|
-
readFileSync as
|
|
127074
|
+
mkdirSync as mkdirSync34,
|
|
127075
|
+
readdirSync as readdirSync31,
|
|
127076
|
+
readFileSync as readFileSync51,
|
|
126591
127077
|
statSync as statSync26,
|
|
126592
|
-
writeFileSync as
|
|
127078
|
+
writeFileSync as writeFileSync33
|
|
126593
127079
|
} from "fs";
|
|
126594
|
-
import { dirname as
|
|
127080
|
+
import { dirname as dirname54, join as join84, resolve as resolve50, sep as sep25 } from "path";
|
|
126595
127081
|
function fail7(code2, message, detail = []) {
|
|
126596
127082
|
throw new StationSnapshotError(code2, message, detail);
|
|
126597
127083
|
}
|
|
126598
127084
|
function snapshotRootFor(repoRoot, stationId) {
|
|
126599
|
-
return
|
|
127085
|
+
return join84(repoRoot, "resources", stationId, "skills");
|
|
126600
127086
|
}
|
|
126601
127087
|
function readSnapshotManifest(repoRoot, stationId) {
|
|
126602
127088
|
const snapshotRoot = snapshotRootFor(repoRoot, stationId);
|
|
126603
|
-
const manifestPath =
|
|
127089
|
+
const manifestPath = join84(snapshotRoot, "sync-manifest.json");
|
|
126604
127090
|
let manifest;
|
|
126605
127091
|
try {
|
|
126606
|
-
manifest = JSON.parse(
|
|
127092
|
+
manifest = JSON.parse(readFileSync51(manifestPath, "utf8"));
|
|
126607
127093
|
} catch (error61) {
|
|
126608
127094
|
fail7("MANIFEST_UNREADABLE", `cannot read snapshot manifest: ${manifestPath}: ${error61.message}`);
|
|
126609
127095
|
}
|
|
@@ -126627,10 +127113,10 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
126627
127113
|
const hashMismatches = [];
|
|
126628
127114
|
const skippedByRule = [];
|
|
126629
127115
|
for (const agent of SYNC_AGENTS) {
|
|
126630
|
-
const agentRoot =
|
|
127116
|
+
const agentRoot = join84(snapshotRoot, "agent-homes", agent);
|
|
126631
127117
|
let identEntries;
|
|
126632
127118
|
try {
|
|
126633
|
-
identEntries =
|
|
127119
|
+
identEntries = readdirSync31(agentRoot, { withFileTypes: true });
|
|
126634
127120
|
} catch {
|
|
126635
127121
|
continue;
|
|
126636
127122
|
}
|
|
@@ -126638,10 +127124,10 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
126638
127124
|
if (!identEntry.isDirectory() || identEntry.name.startsWith(".")) {
|
|
126639
127125
|
continue;
|
|
126640
127126
|
}
|
|
126641
|
-
const identRoot =
|
|
127127
|
+
const identRoot = join84(agentRoot, identEntry.name);
|
|
126642
127128
|
const entries = walkEntries(identRoot);
|
|
126643
127129
|
for (const entry of entries) {
|
|
126644
|
-
const relativeParts = [identEntry.name, ...entry.relativePath.split(
|
|
127130
|
+
const relativeParts = [identEntry.name, ...entry.relativePath.split(sep25)];
|
|
126645
127131
|
if (entry.kind === "symlink") {
|
|
126646
127132
|
symlinks.push({ ident: identEntry.name, agent, relativePath: entry.relativePath });
|
|
126647
127133
|
continue;
|
|
@@ -126665,8 +127151,8 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
126665
127151
|
});
|
|
126666
127152
|
continue;
|
|
126667
127153
|
}
|
|
126668
|
-
const withinIdent = relativeParts.slice(1).join(
|
|
126669
|
-
const homeRelative = relativeParts.join(
|
|
127154
|
+
const withinIdent = relativeParts.slice(1).join(sep25);
|
|
127155
|
+
const homeRelative = relativeParts.join(sep25);
|
|
126670
127156
|
if (REFUSED_SCANNER_FLAGGED.has(homeRelative)) {
|
|
126671
127157
|
skippedByRule.push({
|
|
126672
127158
|
ident: identEntry.name,
|
|
@@ -126739,7 +127225,7 @@ function planStationHydration(stationId, repoRoot) {
|
|
|
126739
127225
|
for (const copy of copies) {
|
|
126740
127226
|
let isStub = false;
|
|
126741
127227
|
try {
|
|
126742
|
-
isStub = isPointerSkillMd(
|
|
127228
|
+
isStub = isPointerSkillMd(readFileSync51(copy.fullPath, "utf8"));
|
|
126743
127229
|
} catch {
|
|
126744
127230
|
isStub = false;
|
|
126745
127231
|
}
|
|
@@ -126785,12 +127271,12 @@ function skillSha256(skill) {
|
|
|
126785
127271
|
return sha256File(skill.files[0].winner.fullPath);
|
|
126786
127272
|
}
|
|
126787
127273
|
const joined = skill.files.map((file2) => sha256File(file2.winner.fullPath));
|
|
126788
|
-
return
|
|
127274
|
+
return createHash55("sha256").update(joined.sort().join(`
|
|
126789
127275
|
`)).digest("hex");
|
|
126790
127276
|
}
|
|
126791
127277
|
function writeStationHydration(options) {
|
|
126792
|
-
const repoRoot =
|
|
126793
|
-
const cacheRoot =
|
|
127278
|
+
const repoRoot = resolve50(options.repoRoot ?? process.cwd());
|
|
127279
|
+
const cacheRoot = resolve50(options.cacheRoot ?? resolveCorpusRoot());
|
|
126794
127280
|
const plan = planStationHydration(options.stationId, repoRoot);
|
|
126795
127281
|
const resultSkills = plan.winners.map((skill) => ({
|
|
126796
127282
|
ident: skill.ident,
|
|
@@ -126823,7 +127309,7 @@ function writeStationHydration(options) {
|
|
|
126823
127309
|
const toWrite = [];
|
|
126824
127310
|
for (const skill of plan.winners) {
|
|
126825
127311
|
for (const file2 of skill.files) {
|
|
126826
|
-
const destination =
|
|
127312
|
+
const destination = join84(cacheRoot, skill.ident, file2.withinIdent);
|
|
126827
127313
|
const digest7 = sha256File(file2.winner.fullPath);
|
|
126828
127314
|
let existingDigest = null;
|
|
126829
127315
|
try {
|
|
@@ -126844,7 +127330,7 @@ function writeStationHydration(options) {
|
|
|
126844
127330
|
}
|
|
126845
127331
|
let written = 0;
|
|
126846
127332
|
for (const entry of toWrite) {
|
|
126847
|
-
|
|
127333
|
+
mkdirSync34(dirname54(entry.destination), { recursive: true });
|
|
126848
127334
|
copyFileSync3(entry.fullPath, entry.destination);
|
|
126849
127335
|
written += 1;
|
|
126850
127336
|
}
|
|
@@ -126865,9 +127351,9 @@ function writeStationHydration(options) {
|
|
|
126865
127351
|
},
|
|
126866
127352
|
skills: resultSkills
|
|
126867
127353
|
};
|
|
126868
|
-
const hydrationManifestPath =
|
|
126869
|
-
|
|
126870
|
-
|
|
127354
|
+
const hydrationManifestPath = join84(dirname54(cacheRoot), `hydration-${options.stationId}.json`);
|
|
127355
|
+
mkdirSync34(dirname54(hydrationManifestPath), { recursive: true });
|
|
127356
|
+
writeFileSync33(hydrationManifestPath, `${JSON.stringify(hydration, null, 2)}
|
|
126871
127357
|
`);
|
|
126872
127358
|
return {
|
|
126873
127359
|
...base3,
|
|
@@ -126941,389 +127427,6 @@ var init_hydrate = __esm(() => {
|
|
|
126941
127427
|
init_station_hydrate();
|
|
126942
127428
|
});
|
|
126943
127429
|
|
|
126944
|
-
// src/lib/prepare-skill.ts
|
|
126945
|
-
import { createHash as createHash55, randomUUID as randomUUID17 } from "crypto";
|
|
126946
|
-
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";
|
|
126947
|
-
import { tmpdir as tmpdir2 } from "os";
|
|
126948
|
-
import { dirname as dirname54, isAbsolute as isAbsolute50, join as join84, relative as relative17, resolve as resolve50, sep as sep25 } from "path";
|
|
126949
|
-
function prepareSkill(name, options) {
|
|
126950
|
-
const normalized2 = normalizePortableSkillName(name);
|
|
126951
|
-
if (!new RegExp(SEMVER_PATTERN).test(options.version))
|
|
126952
|
-
throw new Error("--version must be a semantic version, for example 0.2.0.");
|
|
126953
|
-
const path2 = join84(getPortableSkillsRootReadOnly(options), normalized2);
|
|
126954
|
-
assertPortableAuthoringPath(path2, options);
|
|
126955
|
-
const before2 = snapshot2(path2);
|
|
126956
|
-
const manifestFile = before2.files.find((file2) => file2.path === "skill.json");
|
|
126957
|
-
if (!manifestFile)
|
|
126958
|
-
throw new Error(`Skill '${normalized2}' needs skill.json. Import the source with skills port first.`);
|
|
126959
|
-
let manifest;
|
|
126960
|
-
try {
|
|
126961
|
-
const parsed = JSON.parse(manifestFile.bytes.toString("utf8"));
|
|
126962
|
-
if (!isRecord9(parsed))
|
|
126963
|
-
throw new Error("not an object");
|
|
126964
|
-
manifest = parsed;
|
|
126965
|
-
} catch {
|
|
126966
|
-
throw new Error("skill.json must contain a JSON object; no files were changed.");
|
|
126967
|
-
}
|
|
126968
|
-
const previousVersion = manifest.version;
|
|
126969
|
-
if (typeof previousVersion !== "string" || !new RegExp(SEMVER_PATTERN).test(previousVersion)) {
|
|
126970
|
-
throw new Error("skill.json must declare its current semantic version before preparation.");
|
|
126971
|
-
}
|
|
126972
|
-
if (manifest.kind !== undefined && manifest.kind !== "instruction" && manifest.kind !== "executable") {
|
|
126973
|
-
throw new Error("skill.json has an invalid kind; correct it before preparation.");
|
|
126974
|
-
}
|
|
126975
|
-
const kind = options.kind ?? manifest.kind;
|
|
126976
|
-
if (kind !== "instruction" && kind !== "executable") {
|
|
126977
|
-
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.");
|
|
126978
|
-
}
|
|
126979
|
-
const document3 = before2.files.find((file2) => file2.path === "SKILL.md");
|
|
126980
|
-
const documentKind = document3 ? parseSkillFrontmatter(document3.bytes.toString("utf8"))?.kind : undefined;
|
|
126981
|
-
if (documentKind !== undefined && documentKind !== kind) {
|
|
126982
|
-
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.");
|
|
126983
|
-
}
|
|
126984
|
-
if (manifest.provenance !== undefined && !isRecord9(manifest.provenance))
|
|
126985
|
-
throw new Error("skill.json provenance must be an object.");
|
|
126986
|
-
const provenance = manifest.provenance;
|
|
126987
|
-
const candidate2 = { ...manifest, version: options.version, kind, provenance: { ...provenance } };
|
|
126988
|
-
const staging = mkdtempSync7(join84(tmpdir2(), "skills-prepare-"));
|
|
126989
|
-
try {
|
|
126990
|
-
for (const directory3 of before2.directories)
|
|
126991
|
-
mkdirSync34(join84(staging, directory3), { recursive: true });
|
|
126992
|
-
for (const file2 of before2.files) {
|
|
126993
|
-
const destination = join84(staging, file2.path);
|
|
126994
|
-
mkdirSync34(dirname54(destination), { recursive: true });
|
|
126995
|
-
writeFileSync33(destination, file2.bytes, { mode: file2.mode });
|
|
126996
|
-
}
|
|
126997
|
-
const stagedManifest = join84(staging, "skill.json");
|
|
126998
|
-
writeFileSync33(stagedManifest, `${JSON.stringify(candidate2, null, 2)}
|
|
126999
|
-
`);
|
|
127000
|
-
const contentHash = computeContentHash(staging);
|
|
127001
|
-
const changed3 = options.version !== previousVersion || kind !== manifest.kind || contentHash !== provenance?.content_hash;
|
|
127002
|
-
if (changed3 && compareVersions(options.version, previousVersion) <= 0) {
|
|
127003
|
-
throw new Error(`Changed content requires a version greater than ${previousVersion}; pass --version <new-semver>.`);
|
|
127004
|
-
}
|
|
127005
|
-
candidate2.provenance.content_hash = contentHash;
|
|
127006
|
-
const candidateBytes = `${JSON.stringify(candidate2, null, 2)}
|
|
127007
|
-
`;
|
|
127008
|
-
writeFileSync33(stagedManifest, candidateBytes);
|
|
127009
|
-
validateRawCandidate(candidate2);
|
|
127010
|
-
validateRuntimeEntrypoint(candidate2, staging);
|
|
127011
|
-
const validation = validatePortableSkillDirectory(normalized2, staging);
|
|
127012
|
-
if (!validation.valid)
|
|
127013
|
-
throw new Error(`Skill '${normalized2}' cannot be prepared:
|
|
127014
|
-
${validation.issues.map((issue2) => `${issue2.code}: ${issue2.message}`).join(`
|
|
127015
|
-
`)}`);
|
|
127016
|
-
assertPackedCanonicalCoverage(staging);
|
|
127017
|
-
if (snapshot2(path2).identity !== before2.identity)
|
|
127018
|
-
throw new Error("Skill changed during preparation; review the draft and run prepare again.");
|
|
127019
|
-
if (changed3 && !options.dryRun) {
|
|
127020
|
-
const temporary = join84(path2, `.skill-prepare-${randomUUID17()}.tmp`);
|
|
127021
|
-
try {
|
|
127022
|
-
writeFileSync33(temporary, candidateBytes, { flag: "wx", mode: manifestFile.mode });
|
|
127023
|
-
chmodSync5(temporary, manifestFile.mode);
|
|
127024
|
-
renameSync18(temporary, join84(path2, "skill.json"));
|
|
127025
|
-
} finally {
|
|
127026
|
-
rmSync10(temporary, { force: true });
|
|
127027
|
-
}
|
|
127028
|
-
}
|
|
127029
|
-
return { name: normalized2, path: path2, previousVersion, version: options.version, kind, contentHash, changed: changed3, written: changed3 && !options.dryRun, warnings: validation.warnings };
|
|
127030
|
-
} finally {
|
|
127031
|
-
rmSync10(staging, { recursive: true, force: true });
|
|
127032
|
-
}
|
|
127033
|
-
}
|
|
127034
|
-
function assertPackedCanonicalCoverage(root2) {
|
|
127035
|
-
const canonicalPaths = new Set(collectBundleFiles(root2).map((file2) => file2.rel));
|
|
127036
|
-
const packedPaths = new Set(collectSkillBundleEntries(root2).map((entry) => entry.path));
|
|
127037
|
-
const omitted = [...canonicalPaths].filter((path2) => !packedPaths.has(path2)).sort();
|
|
127038
|
-
if (omitted.length === 0)
|
|
127039
|
-
return;
|
|
127040
|
-
const rendered = omitted.slice(0, MAX_PACK_PARITY_PATHS).map(escapedRelativePath).join(", ");
|
|
127041
|
-
const suffix = omitted.length > MAX_PACK_PARITY_PATHS ? ` (+${omitted.length - MAX_PACK_PARITY_PATHS} more)` : "";
|
|
127042
|
-
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.");
|
|
127043
|
-
}
|
|
127044
|
-
function escapedRelativePath(path2) {
|
|
127045
|
-
const bounded2 = path2.length > MAX_PACK_PARITY_PATH_CHARS - 8 ? `${path2.slice(0, MAX_PACK_PARITY_PATH_CHARS - 8)}...` : path2;
|
|
127046
|
-
return JSON.stringify(bounded2);
|
|
127047
|
-
}
|
|
127048
|
-
function validateRuntimeEntrypoint(candidate2, root2) {
|
|
127049
|
-
if (candidate2.kind !== "executable" || !isRecord9(candidate2.runtime))
|
|
127050
|
-
return;
|
|
127051
|
-
const entry = candidate2.runtime.entrypoint;
|
|
127052
|
-
if (typeof entry !== "string")
|
|
127053
|
-
return;
|
|
127054
|
-
const target = resolve50(root2, entry), rel = relative17(root2, target);
|
|
127055
|
-
if (isAbsolute50(entry) || /^[a-zA-Z]:/.test(entry) || entry.includes("\\") || !rel || rel === ".." || rel.startsWith(`..${sep25}`) || isAbsolute50(rel) || !existsSync37(target) || !lstatSync54(target).isFile()) {
|
|
127056
|
-
throw new Error("Executable runtime.entrypoint must name an existing regular file inside the skill directory.");
|
|
127057
|
-
}
|
|
127058
|
-
}
|
|
127059
|
-
function isRecord9(value) {
|
|
127060
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
127061
|
-
}
|
|
127062
|
-
function validateRawCandidate(candidate2) {
|
|
127063
|
-
for (const field4 of ["standard", "name", "description", "version"]) {
|
|
127064
|
-
if (typeof candidate2[field4] !== "string" || !candidate2[field4].trim())
|
|
127065
|
-
throw new Error(`skill.json ${field4} is required.`);
|
|
127066
|
-
}
|
|
127067
|
-
for (const field4 of ["displayName", "category"]) {
|
|
127068
|
-
if (candidate2[field4] !== undefined && (typeof candidate2[field4] !== "string" || !candidate2[field4].trim()))
|
|
127069
|
-
throw new Error(`Invalid skill.json ${field4}.`);
|
|
127070
|
-
}
|
|
127071
|
-
if (candidate2.tags !== undefined && (!Array.isArray(candidate2.tags) || candidate2.tags.some((tag) => typeof tag !== "string" || !tag.trim())))
|
|
127072
|
-
throw new Error("Invalid skill.json tags.");
|
|
127073
|
-
const issues = validatePortableManifestContract(candidate2, { strict: true });
|
|
127074
|
-
if (issues.length)
|
|
127075
|
-
throw new Error(`Invalid skill.json: ${issues.map((issue2) => issue2.code).join(", ")}`);
|
|
127076
|
-
validateRuntimeEntrypointCompatibility(candidate2);
|
|
127077
|
-
for (const field4 of ["inputs", "commands"]) {
|
|
127078
|
-
const items = candidate2[field4];
|
|
127079
|
-
if (items === undefined)
|
|
127080
|
-
continue;
|
|
127081
|
-
if (!Array.isArray(items))
|
|
127082
|
-
throw new Error(`skill.json ${field4} must be an array.`);
|
|
127083
|
-
for (const item of items) {
|
|
127084
|
-
if (!isRecord9(item) || typeof item.name !== "string" || !item.name.trim())
|
|
127085
|
-
throw new Error(`Invalid skill.json ${field4} name.`);
|
|
127086
|
-
if (field4 === "inputs" && (typeof item.type !== "string" || !item.type.trim() || item.required !== undefined && typeof item.required !== "boolean"))
|
|
127087
|
-
throw new Error("Invalid skill.json input type or required flag.");
|
|
127088
|
-
if (field4 === "commands") {
|
|
127089
|
-
if (!/^[a-z0-9][a-z0-9._-]*$/.test(item.name))
|
|
127090
|
-
throw new Error("Invalid skill.json command name.");
|
|
127091
|
-
for (const target of ["entry", "command"])
|
|
127092
|
-
if (item[target] !== undefined && (typeof item[target] !== "string" || !item[target].trim()))
|
|
127093
|
-
throw new Error(`Invalid skill.json command ${target}.`);
|
|
127094
|
-
if (!item.entry && !item.command)
|
|
127095
|
-
throw new Error("A skill.json command needs entry or command.");
|
|
127096
|
-
if (item.args !== undefined && (!Array.isArray(item.args) || item.args.some((arg) => typeof arg !== "string")))
|
|
127097
|
-
throw new Error("Invalid skill.json command args.");
|
|
127098
|
-
}
|
|
127099
|
-
}
|
|
127100
|
-
}
|
|
127101
|
-
}
|
|
127102
|
-
function validateRuntimeEntrypointCompatibility(candidate2) {
|
|
127103
|
-
if (candidate2.kind !== "executable" || !isRecord9(candidate2.runtime))
|
|
127104
|
-
return;
|
|
127105
|
-
const runtime = candidate2.runtime.runtime;
|
|
127106
|
-
const entrypoint = candidate2.runtime.entrypoint;
|
|
127107
|
-
if (typeof runtime !== "string" || typeof entrypoint !== "string")
|
|
127108
|
-
return;
|
|
127109
|
-
const extension = entrypoint.toLowerCase().split(".").pop();
|
|
127110
|
-
if (extension === "py" && runtime !== "python3") {
|
|
127111
|
-
throw new Error("Invalid skill.json: runtime.entrypoint with .py extension requires runtime python3.");
|
|
127112
|
-
}
|
|
127113
|
-
}
|
|
127114
|
-
function compareVersions(left, right) {
|
|
127115
|
-
const parts = (value) => value.split("+")[0].split(/-(.*)/s).slice(0, 2);
|
|
127116
|
-
const [a, ap] = parts(left), [b, bp] = parts(right);
|
|
127117
|
-
const ac = a.split(".").map(BigInt), bc = b.split(".").map(BigInt);
|
|
127118
|
-
for (let i = 0;i < 3; i++)
|
|
127119
|
-
if (ac[i] !== bc[i])
|
|
127120
|
-
return ac[i] > bc[i] ? 1 : -1;
|
|
127121
|
-
if (ap === undefined || bp === undefined)
|
|
127122
|
-
return ap === bp ? 0 : ap === undefined ? 1 : -1;
|
|
127123
|
-
const aa = ap.split("."), bb = bp.split(".");
|
|
127124
|
-
for (let i = 0;i < Math.max(aa.length, bb.length); i++) {
|
|
127125
|
-
const x = aa[i], y = bb[i];
|
|
127126
|
-
if (x === undefined || y === undefined)
|
|
127127
|
-
return x === y ? 0 : x === undefined ? -1 : 1;
|
|
127128
|
-
if (x === y)
|
|
127129
|
-
continue;
|
|
127130
|
-
const xn = /^\d+$/.test(x), yn = /^\d+$/.test(y);
|
|
127131
|
-
if (xn && yn)
|
|
127132
|
-
return BigInt(x) > BigInt(y) ? 1 : -1;
|
|
127133
|
-
if (xn !== yn)
|
|
127134
|
-
return xn ? -1 : 1;
|
|
127135
|
-
return x > y ? 1 : -1;
|
|
127136
|
-
}
|
|
127137
|
-
return 0;
|
|
127138
|
-
}
|
|
127139
|
-
function snapshot2(root2) {
|
|
127140
|
-
const rootStat = lstatSync54(root2);
|
|
127141
|
-
if (rootStat.isSymbolicLink() || !rootStat.isDirectory())
|
|
127142
|
-
throw new Error("Skill root must be a real directory, not a symlink.");
|
|
127143
|
-
const files = [], directories = [];
|
|
127144
|
-
const hash8 = createHash55("sha256").update(`${rootStat.dev}:${rootStat.ino}\x00`);
|
|
127145
|
-
let count = 0, bytes = 0;
|
|
127146
|
-
const walk2 = (relative18) => {
|
|
127147
|
-
for (const name of readdirSync31(join84(root2, relative18)).sort()) {
|
|
127148
|
-
if (IGNORED.has(name.toLowerCase()) || !relative18 && ROOT_IGNORED.has(name.toLowerCase()))
|
|
127149
|
-
continue;
|
|
127150
|
-
const path2 = relative18 ? `${relative18}/${name}` : name;
|
|
127151
|
-
if (++count > 20000)
|
|
127152
|
-
throw new Error("Skill exceeds the preparation entry limit (20000).");
|
|
127153
|
-
const absolute = join84(root2, path2), stat3 = lstatSync54(absolute);
|
|
127154
|
-
if (stat3.isSymbolicLink())
|
|
127155
|
-
throw new Error(`Skill contains a symlink: ${path2}. No files were changed.`);
|
|
127156
|
-
if (stat3.isDirectory() && name === "node_modules")
|
|
127157
|
-
continue;
|
|
127158
|
-
hash8.update(JSON.stringify([path2, stat3.dev, stat3.ino, stat3.mode]));
|
|
127159
|
-
if (stat3.isDirectory()) {
|
|
127160
|
-
directories.push(path2);
|
|
127161
|
-
walk2(path2);
|
|
127162
|
-
} else if (stat3.isFile()) {
|
|
127163
|
-
bytes += stat3.size;
|
|
127164
|
-
if (bytes > 50000000)
|
|
127165
|
-
throw new Error("Skill exceeds the preparation size limit (50000000 bytes).");
|
|
127166
|
-
const content = readFileSync51(absolute);
|
|
127167
|
-
if (content.length !== stat3.size)
|
|
127168
|
-
throw new Error("Skill changed during preparation.");
|
|
127169
|
-
files.push({ path: path2, bytes: content, mode: stat3.mode & 511 });
|
|
127170
|
-
hash8.update(`\x00${content.length}\x00`).update(content);
|
|
127171
|
-
} else
|
|
127172
|
-
throw new Error(`Unsupported skill entry: ${path2}.`);
|
|
127173
|
-
hash8.update("\x00");
|
|
127174
|
-
}
|
|
127175
|
-
};
|
|
127176
|
-
walk2("");
|
|
127177
|
-
return { files, directories, identity: hash8.digest("hex") };
|
|
127178
|
-
}
|
|
127179
|
-
var MAX_PACK_PARITY_PATHS = 8, MAX_PACK_PARITY_PATH_CHARS = 120, IGNORED, ROOT_IGNORED;
|
|
127180
|
-
var init_prepare_skill = __esm(() => {
|
|
127181
|
-
init_portable_skills();
|
|
127182
|
-
init_skill_contract();
|
|
127183
|
-
init_skill_hash();
|
|
127184
|
-
init_skill_bundle();
|
|
127185
|
-
init_skill_validation();
|
|
127186
|
-
init_authoring_path();
|
|
127187
|
-
IGNORED = new Set([".git", ".ds_store", ".system"]);
|
|
127188
|
-
ROOT_IGNORED = new Set(["dist", "build", ".turbo", ".skills-dependency-preparation"]);
|
|
127189
|
-
});
|
|
127190
|
-
|
|
127191
|
-
// src/cli/commands/portable-skills.ts
|
|
127192
|
-
var exports_portable_skills = {};
|
|
127193
|
-
__export(exports_portable_skills, {
|
|
127194
|
-
registerPortableSkillCommands: () => registerPortableSkillCommands
|
|
127195
|
-
});
|
|
127196
|
-
function registerPortableSkillCommands(parent) {
|
|
127197
|
-
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) => {
|
|
127198
|
-
try {
|
|
127199
|
-
const result2 = prepareSkill(name, { ...options, kind: options.kind === undefined ? undefined : parseSkillKind2(options.kind) });
|
|
127200
|
-
if (result2.written)
|
|
127201
|
-
clearRegistryCache();
|
|
127202
|
-
if (options.json)
|
|
127203
|
-
await writeCliOutput(JSON.stringify(result2, null, 2));
|
|
127204
|
-
else {
|
|
127205
|
-
console.log(source_default.green(`${result2.written ? "Prepared" : options.dryRun ? "Previewed" : "Already prepared"} ${result2.name}@${result2.version} (${result2.kind})`));
|
|
127206
|
-
console.log(source_default.dim(` Content hash: ${result2.contentHash}`));
|
|
127207
|
-
console.log(source_default.dim(` Manifest: ${result2.path}/skill.json`));
|
|
127208
|
-
if (result2.written)
|
|
127209
|
-
console.log(source_default.dim(` Publish: skills push ${result2.name}`));
|
|
127210
|
-
}
|
|
127211
|
-
} catch (error61) {
|
|
127212
|
-
if (options.json)
|
|
127213
|
-
await writeCliOutput(JSON.stringify({ error: error61.message }, null, 2));
|
|
127214
|
-
else
|
|
127215
|
-
console.error(source_default.red(error61.message));
|
|
127216
|
-
process.exitCode = 1;
|
|
127217
|
-
}
|
|
127218
|
-
});
|
|
127219
|
-
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) => {
|
|
127220
|
-
try {
|
|
127221
|
-
const kind = parseSkillKind2(options.kind);
|
|
127222
|
-
const result2 = scaffoldPortableSkill(name, {
|
|
127223
|
-
description: options.description,
|
|
127224
|
-
overwrite: options.overwrite,
|
|
127225
|
-
kind
|
|
127226
|
-
});
|
|
127227
|
-
clearRegistryCache();
|
|
127228
|
-
if (options.json) {
|
|
127229
|
-
console.log(JSON.stringify({ ...result2, manifest: result2.manifest }, null, 2));
|
|
127230
|
-
return;
|
|
127231
|
-
}
|
|
127232
|
-
console.log(source_default.green(`\u2713 Created ${kind} skill '${result2.name}'`));
|
|
127233
|
-
console.log(source_default.dim(` Path: ${result2.path}`));
|
|
127234
|
-
if (kind === "instruction") {
|
|
127235
|
-
console.log(source_default.dim(` Edit the prose in: ${result2.path}/SKILL.md`));
|
|
127236
|
-
console.log(source_default.dim(` Instruction skills are consumed by agents, not run with 'skills run'.`));
|
|
127237
|
-
} else {
|
|
127238
|
-
console.log(source_default.dim(` Agent instructions: ${result2.path}/AGENTS.md`));
|
|
127239
|
-
console.log(source_default.dim(` Validate: skills validate ${result2.name}`));
|
|
127240
|
-
console.log(source_default.dim(` Run: skills run ${result2.name} --help`));
|
|
127241
|
-
}
|
|
127242
|
-
} catch (error61) {
|
|
127243
|
-
writePortableError(error61, options.json);
|
|
127244
|
-
}
|
|
127245
|
-
});
|
|
127246
|
-
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) => {
|
|
127247
|
-
if (options.all) {
|
|
127248
|
-
handleBulkPort(path2, options);
|
|
127249
|
-
return;
|
|
127250
|
-
}
|
|
127251
|
-
try {
|
|
127252
|
-
const result2 = portPortableSkill(path2, {
|
|
127253
|
-
name: options.name,
|
|
127254
|
-
overwrite: options.overwrite,
|
|
127255
|
-
allowShadow: options.allowShadow
|
|
127256
|
-
});
|
|
127257
|
-
clearRegistryCache();
|
|
127258
|
-
const validation = validatePortableSkillDirectory(result2.name, result2.path);
|
|
127259
|
-
const payload = { ...result2, valid: validation.valid, issues: validation.issues, warnings: validation.warnings };
|
|
127260
|
-
if (!validation.valid)
|
|
127261
|
-
process.exitCode = 1;
|
|
127262
|
-
if (options.json) {
|
|
127263
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
127264
|
-
return;
|
|
127265
|
-
}
|
|
127266
|
-
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.`));
|
|
127267
|
-
console.log(source_default.dim(` Path: ${result2.path}`));
|
|
127268
|
-
console.log(source_default.dim(` Valid: ${validation.valid ? "yes" : "no"}`));
|
|
127269
|
-
if (!validation.valid) {
|
|
127270
|
-
for (const issue2 of validation.issues)
|
|
127271
|
-
console.log(source_default.red(` \u2022 ${issue2.message}`));
|
|
127272
|
-
}
|
|
127273
|
-
} catch (error61) {
|
|
127274
|
-
writePortableError(error61, options.json);
|
|
127275
|
-
}
|
|
127276
|
-
});
|
|
127277
|
-
}
|
|
127278
|
-
function handleBulkPort(path2, options) {
|
|
127279
|
-
if (options.name) {
|
|
127280
|
-
writePortableError(new Error("--name cannot be used with --all"), options.json);
|
|
127281
|
-
return;
|
|
127282
|
-
}
|
|
127283
|
-
try {
|
|
127284
|
-
const summary = portPortableSkillDirectory(path2, { overwrite: options.overwrite, allowShadow: options.allowShadow });
|
|
127285
|
-
clearRegistryCache();
|
|
127286
|
-
if (options.json) {
|
|
127287
|
-
console.log(JSON.stringify(summary, null, 2));
|
|
127288
|
-
if (summary.failed > 0)
|
|
127289
|
-
process.exitCode = 1;
|
|
127290
|
-
return;
|
|
127291
|
-
}
|
|
127292
|
-
console.log(source_default.green(`\u2713 Imported ${summary.succeeded}/${summary.total} skill(s) from ${summary.root}`));
|
|
127293
|
-
for (const entry of summary.imported)
|
|
127294
|
-
console.log(source_default.dim(` \u2022 ${entry.name} \u2192 ${entry.path}`));
|
|
127295
|
-
if (summary.skipped.length > 0) {
|
|
127296
|
-
console.log(source_default.yellow(` Skipped ${summary.skipped.length}:`));
|
|
127297
|
-
for (const entry of summary.skipped)
|
|
127298
|
-
console.log(source_default.yellow(` \u2022 ${entry.sourcePath}: ${entry.reason}`));
|
|
127299
|
-
process.exitCode = 1;
|
|
127300
|
-
}
|
|
127301
|
-
} catch (error61) {
|
|
127302
|
-
writePortableError(error61, options.json);
|
|
127303
|
-
}
|
|
127304
|
-
}
|
|
127305
|
-
function parseSkillKind2(value) {
|
|
127306
|
-
if (value === undefined || value === "executable")
|
|
127307
|
-
return "executable";
|
|
127308
|
-
if (value === "instruction")
|
|
127309
|
-
return "instruction";
|
|
127310
|
-
throw new Error(`Invalid --kind '${value}'. Use 'executable' or 'instruction'.`);
|
|
127311
|
-
}
|
|
127312
|
-
function writePortableError(error61, json3) {
|
|
127313
|
-
const message = error61.message;
|
|
127314
|
-
if (json3)
|
|
127315
|
-
console.log(JSON.stringify({ error: message }, null, 2));
|
|
127316
|
-
else
|
|
127317
|
-
console.error(source_default.red(message));
|
|
127318
|
-
process.exitCode = 1;
|
|
127319
|
-
}
|
|
127320
|
-
var init_portable_skills2 = __esm(() => {
|
|
127321
|
-
init_source();
|
|
127322
|
-
init_registry();
|
|
127323
|
-
init_prepare_skill();
|
|
127324
|
-
init_portable_skills();
|
|
127325
|
-
});
|
|
127326
|
-
|
|
127327
127430
|
// src/cli/commands/metadata.ts
|
|
127328
127431
|
var exports_metadata = {};
|
|
127329
127432
|
__export(exports_metadata, {
|