@hasna/skills 0.9.17 → 0.9.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/index.js +96 -35
- package/bin/mcp.js +17 -3
- package/bin/migrate.js +1 -1
- package/bin/server.js +1 -1
- package/bin/worker.js +1 -1
- package/dist/index.js +17 -3
- package/dist/lib/codex-native-config.d.ts +5 -0
- package/dist/sdk/index.js +17 -3
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -36868,7 +36868,7 @@ var package_default;
|
|
|
36868
36868
|
var init_package = __esm(() => {
|
|
36869
36869
|
package_default = {
|
|
36870
36870
|
name: "@hasna/skills",
|
|
36871
|
-
version: "0.9.
|
|
36871
|
+
version: "0.9.19",
|
|
36872
36872
|
description: "Skills library for AI coding agents",
|
|
36873
36873
|
type: "module",
|
|
36874
36874
|
bin: {
|
|
@@ -48622,7 +48622,8 @@ async function resolveSkillsConnection(env3 = process.env, options = {}) {
|
|
|
48622
48622
|
}
|
|
48623
48623
|
let completed;
|
|
48624
48624
|
try {
|
|
48625
|
-
|
|
48625
|
+
const explicitVault = Boolean(snapshotEnv.HASNA_SECRETS_API_URL?.trim() && snapshotEnv.HASNA_SECRETS_API_KEY_OVERRIDE?.trim());
|
|
48626
|
+
completed = await completePointerCredential(SKILLS_APP, pointer, explicitVault ? snapshotEnv : env3);
|
|
48626
48627
|
} catch (error) {
|
|
48627
48628
|
const translated = asSkillsFleetCredentialError(error, pointer.pointerVaultKey);
|
|
48628
48629
|
if (translated)
|
|
@@ -79049,7 +79050,20 @@ class RemoteSkillsClient {
|
|
|
79049
79050
|
const url2 = new URL(upload.uploadUrl);
|
|
79050
79051
|
if (url2.username || url2.password || url2.hash || url2.protocol !== "https:" && !(url2.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url2.hostname)))
|
|
79051
79052
|
throw new Error("Unsafe input upload URL");
|
|
79052
|
-
const
|
|
79053
|
+
const headers = new Headers({ "Content-Type": file2.contentType ?? "application/octet-stream" });
|
|
79054
|
+
if (upload.uploadHeaders !== undefined) {
|
|
79055
|
+
if (upload.uploadHeaders === null || typeof upload.uploadHeaders !== "object" || Array.isArray(upload.uploadHeaders))
|
|
79056
|
+
throw new Error("Invalid input upload headers");
|
|
79057
|
+
const seen = new Set;
|
|
79058
|
+
for (const [name, value] of Object.entries(upload.uploadHeaders)) {
|
|
79059
|
+
const lowerName = name.toLowerCase();
|
|
79060
|
+
if (seen.has(lowerName) || !["if-none-match", "x-amz-tagging"].includes(lowerName) || typeof value !== "string" || value.length === 0 || value.length > 2048 || !/^[\x20-\x7e]+$/.test(value) || value.trim() !== value || lowerName === "if-none-match" && value !== "*")
|
|
79061
|
+
throw new Error("Invalid input upload headers");
|
|
79062
|
+
seen.add(lowerName);
|
|
79063
|
+
headers.set(name, value);
|
|
79064
|
+
}
|
|
79065
|
+
}
|
|
79066
|
+
const uploaded = await fetch(url2, { method: "PUT", body: file2.bytes, headers, redirect: "error", signal: AbortSignal.timeout(60000) });
|
|
79053
79067
|
if (!uploaded.ok)
|
|
79054
79068
|
throw new Error("Input upload failed");
|
|
79055
79069
|
await uploaded.body?.cancel();
|
|
@@ -108490,6 +108504,52 @@ var init_create_sync_config = __esm(() => {
|
|
|
108490
108504
|
init_station_snapshot();
|
|
108491
108505
|
});
|
|
108492
108506
|
|
|
108507
|
+
// src/lib/codex-native-config.ts
|
|
108508
|
+
import { isDeepStrictEqual as isDeepStrictEqual9 } from "util";
|
|
108509
|
+
function isRecord6(value) {
|
|
108510
|
+
if (!value || typeof value !== "object")
|
|
108511
|
+
return false;
|
|
108512
|
+
const prototype = Object.getPrototypeOf(value);
|
|
108513
|
+
return prototype === Object.prototype || prototype === null;
|
|
108514
|
+
}
|
|
108515
|
+
function decimalIdentity(text4) {
|
|
108516
|
+
if (text4.length > 4096)
|
|
108517
|
+
return;
|
|
108518
|
+
const match = /^(-?)(0|[1-9][0-9]*)(?:\.([0-9]+))?(?:[eE]([+-]?[0-9]+))?$/.exec(text4);
|
|
108519
|
+
if (!match || !Number.isFinite(Number(text4)))
|
|
108520
|
+
return;
|
|
108521
|
+
const fraction = match[3] ?? "";
|
|
108522
|
+
const digits = (match[2] + fraction).replace(/^0+/, "");
|
|
108523
|
+
if (!digits)
|
|
108524
|
+
return match[1] + "0";
|
|
108525
|
+
const coefficient = digits.replace(/0+$/, "");
|
|
108526
|
+
const exponent = BigInt(match[4] ?? "0") - BigInt(fraction.length) + BigInt(digits.length - coefficient.length);
|
|
108527
|
+
return `${match[1]}${coefficient}e${exponent}`;
|
|
108528
|
+
}
|
|
108529
|
+
function codexNativeConfigEqual(native, expected) {
|
|
108530
|
+
if (isDeepStrictEqual9(native, expected))
|
|
108531
|
+
return true;
|
|
108532
|
+
if (typeof expected === "number") {
|
|
108533
|
+
if (!Number.isFinite(expected) || !isRecord6(native))
|
|
108534
|
+
return false;
|
|
108535
|
+
const keys3 = Object.keys(native);
|
|
108536
|
+
const value = native[NUMBER_TAG];
|
|
108537
|
+
if (keys3.length !== 1 || keys3[0] !== NUMBER_TAG || typeof value !== "string")
|
|
108538
|
+
return false;
|
|
108539
|
+
const actual2 = decimalIdentity(value);
|
|
108540
|
+
return actual2 !== undefined && actual2 === decimalIdentity(Object.is(expected, -0) ? "-0" : String(expected));
|
|
108541
|
+
}
|
|
108542
|
+
if (Array.isArray(native) || Array.isArray(expected)) {
|
|
108543
|
+
return Array.isArray(native) && Array.isArray(expected) && native.length === expected.length && native.every((value, index) => codexNativeConfigEqual(value, expected[index]));
|
|
108544
|
+
}
|
|
108545
|
+
if (!isRecord6(native) || !isRecord6(expected))
|
|
108546
|
+
return false;
|
|
108547
|
+
const actual = native, wanted = expected;
|
|
108548
|
+
return Object.keys(actual).length === Object.keys(wanted).length && Object.keys(wanted).every((key) => Object.hasOwn(actual, key) && codexNativeConfigEqual(actual[key], wanted[key]));
|
|
108549
|
+
}
|
|
108550
|
+
var NUMBER_TAG = "$serde_json::private::Number";
|
|
108551
|
+
var init_codex_native_config = () => {};
|
|
108552
|
+
|
|
108493
108553
|
// src/lib/codex-hook-rpc.ts
|
|
108494
108554
|
import { execFile, spawn as spawn3 } from "child_process";
|
|
108495
108555
|
import { realpathSync as realpathSync12, statSync as statSync20 } from "fs";
|
|
@@ -108633,7 +108693,7 @@ var init_codex_hook_rpc = __esm(() => {
|
|
|
108633
108693
|
// src/lib/codex-hook-trust-identity.ts
|
|
108634
108694
|
import { realpathSync as realpathSync13 } from "fs";
|
|
108635
108695
|
import { dirname as dirname30, isAbsolute as isAbsolute25, join as join56 } from "path";
|
|
108636
|
-
import { isDeepStrictEqual as
|
|
108696
|
+
import { isDeepStrictEqual as isDeepStrictEqual10 } from "util";
|
|
108637
108697
|
function inspectRecordedSkillsCli(command, expected) {
|
|
108638
108698
|
need4(isAbsolute25(command) && !command.includes("\x00"), "RECONCILE_RECORDED_COMMAND_REQUIRED");
|
|
108639
108699
|
const resolved = realpathSync13(command);
|
|
@@ -108643,7 +108703,7 @@ function inspectRecordedSkillsCli(command, expected) {
|
|
|
108643
108703
|
need4(pkg.name === "@hasna/skills" && /^\d+\.\d+\.\d+$/.test(pkg.version) && pkg.bin?.skills === "bin/index.js" && resolved === join56(dirname30(manifest.file), "bin/index.js"), "SKILLS_PACKAGE_MISMATCH");
|
|
108644
108704
|
need4((cli.stat.mode & 0o100n) !== 0n, "SKILLS_COMMAND_NOT_EXECUTABLE");
|
|
108645
108705
|
const receipt = { path: resolved, version: pkg.version, sha256: cli.sha256, manifestSha256: manifest.sha256 };
|
|
108646
|
-
need4(
|
|
108706
|
+
need4(isDeepStrictEqual10(receipt, expected), "RECONCILE_SKILLS_BINDING_CHANGED");
|
|
108647
108707
|
return { receipt, recheck() {
|
|
108648
108708
|
need4(realpathSync13(command) === resolved, "SKILLS_COMMAND_CHANGED");
|
|
108649
108709
|
unchanged2(cli);
|
|
@@ -108679,19 +108739,19 @@ var init_codex_hook_trust_identity = __esm(() => {
|
|
|
108679
108739
|
});
|
|
108680
108740
|
|
|
108681
108741
|
// src/lib/codex-hook-policy-recovery.ts
|
|
108682
|
-
import { isDeepStrictEqual as
|
|
108742
|
+
import { isDeepStrictEqual as isDeepStrictEqual11 } from "util";
|
|
108683
108743
|
function reviewedClaudeDiscoveryChange(beforeText, currentText, options) {
|
|
108684
108744
|
need4(typeof options.reason === "string" && options.reason.trim().length > 0 && options.reason.length <= 512 && !/[\x00-\x1f\x7f]/.test(options.reason), "RECONCILE_RECOVERY_REASON_REQUIRED");
|
|
108685
108745
|
need4(options.reviewedPlanDigest === undefined || /^[a-f0-9]{64}$/.test(options.reviewedPlanDigest), "RECONCILE_RECOVERY_PLAN_CHANGED");
|
|
108686
108746
|
const before2 = parseManagedSkillPolicy(beforeText), current = parseManagedSkillPolicy(currentText);
|
|
108687
108747
|
const oldDiscovery = before2.bridge?.discovery?.claude, newDiscovery = current.bridge?.discovery?.claude;
|
|
108688
108748
|
need4(oldDiscovery?.agent === "claude" && newDiscovery?.agent === "claude" && oldDiscovery.method === "reviewed" && newDiscovery.method === "reviewed", "RECONCILE_POLICY_CHANGED");
|
|
108689
|
-
const changes = ["roots", "sources"].filter((key) => !
|
|
108749
|
+
const changes = ["roots", "sources"].filter((key) => !isDeepStrictEqual11(oldDiscovery[key], newDiscovery[key])).map((key) => ({ path: `bridge.discovery.claude.${key}`, before: oldDiscovery[key], after: newDiscovery[key] }));
|
|
108690
108750
|
need4(changes.length > 0, "RECONCILE_POLICY_CHANGED");
|
|
108691
108751
|
const compared = structuredClone(current);
|
|
108692
108752
|
compared.bridge.discovery.claude.roots = oldDiscovery.roots;
|
|
108693
108753
|
compared.bridge.discovery.claude.sources = oldDiscovery.sources;
|
|
108694
|
-
need4(
|
|
108754
|
+
need4(isDeepStrictEqual11(before2, compared), "RECONCILE_POLICY_CHANGED");
|
|
108695
108755
|
verifyAgentDiscovery(newDiscovery);
|
|
108696
108756
|
return { reason: options.reason, changes };
|
|
108697
108757
|
}
|
|
@@ -108707,7 +108767,7 @@ import { createHash as createHash27, randomUUID as randomUUID10 } from "crypto";
|
|
|
108707
108767
|
import { execFileSync } from "child_process";
|
|
108708
108768
|
import { dirname as dirname31, isAbsolute as isAbsolute26, join as join57, resolve as resolve23 } from "path";
|
|
108709
108769
|
import { homedir as homedir12 } from "os";
|
|
108710
|
-
import { isDeepStrictEqual as
|
|
108770
|
+
import { isDeepStrictEqual as isDeepStrictEqual12 } from "util";
|
|
108711
108771
|
function verifyReleaseArtifact(proof, skillsCli) {
|
|
108712
108772
|
need4(isAbsolute26(proof.packageTarPath) && !proof.packageTarPath.includes("\x00"), "RECONCILE_RELEASE_PROVENANCE_PATH");
|
|
108713
108773
|
const tarPath = realpathSync14(proof.packageTarPath);
|
|
@@ -108749,7 +108809,7 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
108749
108809
|
const configSha256 = intent.configSha256 ?? configBefore.sha256, configVersion = intent.configVersion ?? intent.expectedVersion;
|
|
108750
108810
|
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 };
|
|
108751
108811
|
need4(makePlanDigest(digestInput) === intent.planDigest, "RECONCILE_PLAN_CHANGED");
|
|
108752
|
-
need4(
|
|
108812
|
+
need4(isDeepStrictEqual12(intent.hooks, admitted.filter((hook) => !hook.enabled || hook.trustStatus !== "trusted")), "RECONCILE_INTENT_HOOKS_CHANGED");
|
|
108753
108813
|
need4(configBefore.sha256 === (intent.beforeSha256 ?? intent.configSha256) && hooksBefore.sha256 === intent.hooksSha256 && policyBefore.sha256 === intent.policySha256, "RECONCILE_JOURNAL_CHANGED");
|
|
108754
108814
|
const policyCurrent = snapshot(join57(dataDir3, "agent-policy.json"), true);
|
|
108755
108815
|
const { claudeDiscoveryRecovery: claudeRecovery, nativeHookAdditionsRecovery: additionsRecovery } = options;
|
|
@@ -108762,7 +108822,7 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
108762
108822
|
if (policyCurrent.sha256 !== policyBefore.sha256 && !claudeRecovery) {
|
|
108763
108823
|
need4(options.supersedeBinding === true, "RECONCILE_POLICY_CHANGED");
|
|
108764
108824
|
const oldPolicy = JSON.parse(policyBefore.text), currentPolicy = JSON.parse(policyCurrent.text);
|
|
108765
|
-
need4(
|
|
108825
|
+
need4(isDeepStrictEqual12(withoutCodexCommand(oldPolicy), withoutCodexCommand(currentPolicy)), "RECONCILE_POLICY_CHANGED");
|
|
108766
108826
|
supersededBinding = true;
|
|
108767
108827
|
}
|
|
108768
108828
|
if (supersededBinding || recovery) {
|
|
@@ -108798,7 +108858,7 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
108798
108858
|
need4(state?.enabled === true && typeof state.trusted_hash === "string" && /^sha256:[a-f0-9]{64}$/.test(state.trusted_hash), "RECONCILE_CONFIG_DRIFT");
|
|
108799
108859
|
}
|
|
108800
108860
|
} else
|
|
108801
|
-
need4(
|
|
108861
|
+
need4(isDeepStrictEqual12(expectedConfig, current), "RECONCILE_CONFIG_DRIFT");
|
|
108802
108862
|
const configDiscoveryRecovery = recovery && binding.discovery?.codex ? createCodexHookDiscoveryRecovery(binding.discovery.codex, configBefore, currentConfig, intent.hooks, additions?.trustProof) : undefined;
|
|
108803
108863
|
assertManagedAgentBridge("codex", { home, dataDir: dataDir3, projectDir: home, codexDiscoveryRecovery: configDiscoveryRecovery });
|
|
108804
108864
|
const recoveryCli = recovery ? bindSkillsCli("skills", options.reviewedSkillsCli) : undefined;
|
|
@@ -108807,14 +108867,14 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
108807
108867
|
const skillsCli = recovery ? inspectRecordedSkillsCli(binding.commands.codex, intent.skillsCli) : bindSkillsCli(binding.commands.codex, options.reviewedSkillsCli);
|
|
108808
108868
|
if (supersededBinding)
|
|
108809
108869
|
verifyReleaseArtifact(options.releaseProof, skillsCli);
|
|
108810
|
-
const skillsBindingChanged = !
|
|
108870
|
+
const skillsBindingChanged = !isDeepStrictEqual12(skillsCli.receipt, intent.skillsCli);
|
|
108811
108871
|
need4(!skillsBindingChanged || supersededBinding, "RECONCILE_SKILLS_BINDING_CHANGED");
|
|
108812
108872
|
if (!additions && hooksCurrent.sha256 !== intent.hooksSha256 && hooksCurrent.sha256 !== hooksBefore.sha256) {
|
|
108813
108873
|
need4(supersededBinding, "RECONCILE_HOOKS_CHANGED");
|
|
108814
108874
|
const currentDeclarations = JSON.parse(hooksCurrent.text).hooks;
|
|
108815
108875
|
for (const [event, [nativeEvent]] of Object.entries(eventNames)) {
|
|
108816
108876
|
const command = renderAgentHookCommand(binding.commands.codex, "codex", binding.profiles.codex, event);
|
|
108817
|
-
need4(
|
|
108877
|
+
need4(isDeepStrictEqual12(currentDeclarations?.[event], [{ hooks: [{ type: "command", command, timeout: 15 }] }]), "RECONCILE_BINDING_HOOK_CHANGED");
|
|
108818
108878
|
}
|
|
108819
108879
|
}
|
|
108820
108880
|
rpc = await connect({ command: options.codexCommand ?? "codex", home, codexHome });
|
|
@@ -108837,9 +108897,9 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
108837
108897
|
delete identity4.command;
|
|
108838
108898
|
return identity4;
|
|
108839
108899
|
};
|
|
108840
|
-
need4(
|
|
108900
|
+
need4(isDeepStrictEqual12(stable(actual), stable(expected)), "RECONCILE_NATIVE_IDENTITY_CHANGED");
|
|
108841
108901
|
if (!plannedKeys.has(expected.key))
|
|
108842
|
-
need4(
|
|
108902
|
+
need4(isDeepStrictEqual12(actual, expected), "RECONCILE_UNRELATED_HOOK_CHANGED");
|
|
108843
108903
|
}
|
|
108844
108904
|
for (const wanted of intent.hooks) {
|
|
108845
108905
|
const matches2 = nativeHooks.filter((h2) => h2.key === wanted.key || h2.key === wanted.key.replace(join57(home, ".codex/hooks.json"), hooksPath));
|
|
@@ -108856,12 +108916,12 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
108856
108916
|
}
|
|
108857
108917
|
const config2 = await rpc.request("config/read", { includeLayers: true, cwd: home });
|
|
108858
108918
|
const layers = config2?.layers?.filter((layer) => layer.name?.type === "user" && layer.name.file === nativeConfigPath && !layer.name.profile);
|
|
108859
|
-
need4(layers?.length === 1 && !layers[0].disabledReason &&
|
|
108919
|
+
need4(layers?.length === 1 && !layers[0].disabledReason && codexNativeConfigEqual(layers[0].config, current), "RECONCILE_NATIVE_CONFIG_MISMATCH");
|
|
108860
108920
|
const finalDiscovery = await rpc.request("hooks/list", { cwds: [home] });
|
|
108861
|
-
need4(Array.isArray(finalDiscovery?.data) && finalDiscovery.data.length === 1 && finalDiscovery.data[0].cwd === home && !finalDiscovery.data[0].errors?.length && !finalDiscovery.data[0].warnings?.length && (!recordedNativeHooks ||
|
|
108921
|
+
need4(Array.isArray(finalDiscovery?.data) && finalDiscovery.data.length === 1 && finalDiscovery.data[0].cwd === home && !finalDiscovery.data[0].errors?.length && !finalDiscovery.data[0].warnings?.length && (!recordedNativeHooks || isDeepStrictEqual12(finalDiscovery.data[0].hooks.map(safeNativeHook), nativeHooks.map(safeNativeHook))), "RECONCILE_NATIVE_STATE_CHANGED");
|
|
108862
108922
|
const finalConfig = await rpc.request("config/read", { includeLayers: true, cwd: home });
|
|
108863
108923
|
const finalLayers = finalConfig?.layers?.filter((layer) => layer.name?.type === "user" && layer.name.file === nativeConfigPath && !layer.name.profile);
|
|
108864
|
-
need4(finalLayers?.length === 1 && !finalLayers[0].disabledReason &&
|
|
108924
|
+
need4(finalLayers?.length === 1 && !finalLayers[0].disabledReason && codexNativeConfigEqual(finalLayers[0].config, current), "RECONCILE_NATIVE_CONFIG_CHANGED");
|
|
108865
108925
|
if (recovery) {
|
|
108866
108926
|
for (const response of [config2, finalConfig]) {
|
|
108867
108927
|
const layerIndex = response.layers.findIndex((layer) => layer.name?.type === "user" && layer.name.file === nativeConfigPath && !layer.name.profile);
|
|
@@ -108919,7 +108979,7 @@ async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRp
|
|
|
108919
108979
|
const groups = declarations[event];
|
|
108920
108980
|
need4(Array.isArray(groups), "DECLARATION_CHANGED");
|
|
108921
108981
|
const matches2 = groups.flatMap((group, gi) => (group.hooks ?? []).flatMap((entry, hi) => entry.command === command ? [{ group, entry, gi, hi }] : []));
|
|
108922
|
-
need4(matches2.length === 1 &&
|
|
108982
|
+
need4(matches2.length === 1 && isDeepStrictEqual12(matches2[0].group, { hooks: [{ type: "command", command, timeout: 15 }] }), "DECLARATION_CHANGED");
|
|
108923
108983
|
expected.push({ event: nativeEvent, key: `${join57(home, ".codex/hooks.json")}:${label}:${matches2[0].gi}:${matches2[0].hi}`, command });
|
|
108924
108984
|
}
|
|
108925
108985
|
codexTrustTextWitness(configFile.text, expected.map((h) => h.key));
|
|
@@ -108943,7 +109003,7 @@ async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRp
|
|
|
108943
109003
|
const discovered = await list2(), admitted = admit(discovered);
|
|
108944
109004
|
const config2 = await rpc.request("config/read", { includeLayers: true, cwd: home });
|
|
108945
109005
|
const layers = config2?.layers?.filter((layer) => layer.name?.type === "user" && layer.name.file === nativeConfigPath && !layer.name.profile);
|
|
108946
|
-
need4(layers?.length === 1 && /^sha256:[a-f0-9]{64}$/.test(layers[0].version) && !layers[0].disabledReason &&
|
|
109006
|
+
need4(layers?.length === 1 && /^sha256:[a-f0-9]{64}$/.test(layers[0].version) && !layers[0].disabledReason && codexNativeConfigEqual(layers[0].config, original), "NATIVE_CONFIG_MISMATCH");
|
|
108947
109007
|
need4(config2.config && config2.config.features?.hooks !== false, "NATIVE_HOOKS_DISABLED");
|
|
108948
109008
|
need4(config2.layers.slice(0, config2.layers.indexOf(layers[0])).every((layer) => layer.config?.hooks === undefined && layer.config?.features?.hooks === undefined), "NATIVE_CONFIG_OVERRIDDEN");
|
|
108949
109009
|
const keys3 = admitted.map((h) => h.key), planned = admitted.filter((h) => !h.enabled || h.trustStatus !== "trusted");
|
|
@@ -108982,7 +109042,7 @@ async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRp
|
|
|
108982
109042
|
save(join57(journal, "hooks.before.json"), hooksFile.bytes);
|
|
108983
109043
|
save(join57(journal, "policy.before.json"), policyFile.bytes);
|
|
108984
109044
|
save(join57(journal, "intent.json"), json2({ ...digestInput, planDigest, hooks: planned, nativeHooks: discovered.map(safeNativeHook) }));
|
|
108985
|
-
need4(
|
|
109045
|
+
need4(isDeepStrictEqual12(admit(await list2()), admitted), "NATIVE_DISCOVERY_CHANGED");
|
|
108986
109046
|
checkInputs();
|
|
108987
109047
|
let result2;
|
|
108988
109048
|
try {
|
|
@@ -108996,7 +109056,7 @@ async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRp
|
|
|
108996
109056
|
expectedConfig.hooks.state ??= {};
|
|
108997
109057
|
for (const h of planned)
|
|
108998
109058
|
expectedConfig.hooks.state[h.key] = { ...expectedConfig.hooks.state[h.key], enabled: true, trusted_hash: h.currentHash };
|
|
108999
|
-
need4(
|
|
109059
|
+
need4(isDeepStrictEqual12(structure3(after2.text), expectedConfig) && codexTrustTextWitness(after2.text, keys3) === preservationText, "PRESERVATION_FAILED");
|
|
109000
109060
|
const savedConfig = snapshot(join57(journal, "config.before.toml"), true);
|
|
109001
109061
|
need4(savedConfig.sha256 === configFile.sha256, "JOURNAL_CHANGED");
|
|
109002
109062
|
const discoveryTransition = binding.discovery?.codex ? createCodexHookDiscoveryRecovery(binding.discovery.codex, savedConfig, after2, planned) : undefined;
|
|
@@ -109004,8 +109064,8 @@ async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRp
|
|
|
109004
109064
|
unchanged2(hooksFile);
|
|
109005
109065
|
assertManagedAgentBridge("codex", { home, dataDir: dataDir3, projectDir: home, codexDiscoveryRecovery: discoveryTransition });
|
|
109006
109066
|
const current = await list2(), confirmed = admit(current);
|
|
109007
|
-
need4(confirmed.every((h) => h.enabled && h.trustStatus === "trusted") &&
|
|
109008
|
-
need4(
|
|
109067
|
+
need4(confirmed.every((h) => h.enabled && h.trustStatus === "trusted") && isDeepStrictEqual12(confirmed.map((h) => [h.key, h.currentHash]), admitted.map((h) => [h.key, h.currentHash])), "NATIVE_NOT_ELIGIBLE");
|
|
109068
|
+
need4(isDeepStrictEqual12(current.filter((h) => !keys3.includes(h.key)), discovered.filter((h) => !keys3.includes(h.key))), "UNRELATED_HOOK_CHANGED");
|
|
109009
109069
|
unchanged2(after2);
|
|
109010
109070
|
unchanged2(savedConfig);
|
|
109011
109071
|
unchanged2(hooksFile);
|
|
@@ -109035,6 +109095,7 @@ var json2 = (value) => JSON.stringify(value, null, 2) + `
|
|
|
109035
109095
|
return copy;
|
|
109036
109096
|
};
|
|
109037
109097
|
var init_agent_codex_trust = __esm(() => {
|
|
109098
|
+
init_codex_native_config();
|
|
109038
109099
|
init_config();
|
|
109039
109100
|
init_managed_policy();
|
|
109040
109101
|
init_agent_integration();
|
|
@@ -110044,7 +110105,7 @@ function prepareSkill(name, options) {
|
|
|
110044
110105
|
let manifest;
|
|
110045
110106
|
try {
|
|
110046
110107
|
const parsed = JSON.parse(manifestFile.bytes.toString("utf8"));
|
|
110047
|
-
if (!
|
|
110108
|
+
if (!isRecord7(parsed))
|
|
110048
110109
|
throw new Error("not an object");
|
|
110049
110110
|
manifest = parsed;
|
|
110050
110111
|
} catch {
|
|
@@ -110066,7 +110127,7 @@ function prepareSkill(name, options) {
|
|
|
110066
110127
|
if (documentKind !== undefined && documentKind !== kind) {
|
|
110067
110128
|
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.");
|
|
110068
110129
|
}
|
|
110069
|
-
if (manifest.provenance !== undefined && !
|
|
110130
|
+
if (manifest.provenance !== undefined && !isRecord7(manifest.provenance))
|
|
110070
110131
|
throw new Error("skill.json provenance must be an object.");
|
|
110071
110132
|
const provenance = manifest.provenance;
|
|
110072
110133
|
const candidate2 = { ...manifest, version: options.version, kind, provenance: { ...provenance } };
|
|
@@ -110131,7 +110192,7 @@ function escapedRelativePath(path) {
|
|
|
110131
110192
|
return JSON.stringify(bounded);
|
|
110132
110193
|
}
|
|
110133
110194
|
function validateRuntimeEntrypoint(candidate2, root2) {
|
|
110134
|
-
if (candidate2.kind !== "executable" || !
|
|
110195
|
+
if (candidate2.kind !== "executable" || !isRecord7(candidate2.runtime))
|
|
110135
110196
|
return;
|
|
110136
110197
|
const entry = candidate2.runtime.entrypoint;
|
|
110137
110198
|
if (typeof entry !== "string")
|
|
@@ -110141,7 +110202,7 @@ function validateRuntimeEntrypoint(candidate2, root2) {
|
|
|
110141
110202
|
throw new Error("Executable runtime.entrypoint must name an existing regular file inside the skill directory.");
|
|
110142
110203
|
}
|
|
110143
110204
|
}
|
|
110144
|
-
function
|
|
110205
|
+
function isRecord7(value) {
|
|
110145
110206
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
110146
110207
|
}
|
|
110147
110208
|
function validateRawCandidate(candidate2) {
|
|
@@ -110166,7 +110227,7 @@ function validateRawCandidate(candidate2) {
|
|
|
110166
110227
|
if (!Array.isArray(items))
|
|
110167
110228
|
throw new Error(`skill.json ${field2} must be an array.`);
|
|
110168
110229
|
for (const item of items) {
|
|
110169
|
-
if (!
|
|
110230
|
+
if (!isRecord7(item) || typeof item.name !== "string" || !item.name.trim())
|
|
110170
110231
|
throw new Error(`Invalid skill.json ${field2} name.`);
|
|
110171
110232
|
if (field2 === "inputs" && (typeof item.type !== "string" || !item.type.trim() || item.required !== undefined && typeof item.required !== "boolean"))
|
|
110172
110233
|
throw new Error("Invalid skill.json input type or required flag.");
|
|
@@ -110185,7 +110246,7 @@ function validateRawCandidate(candidate2) {
|
|
|
110185
110246
|
}
|
|
110186
110247
|
}
|
|
110187
110248
|
function validateRuntimeEntrypointCompatibility(candidate2) {
|
|
110188
|
-
if (candidate2.kind !== "executable" || !
|
|
110249
|
+
if (candidate2.kind !== "executable" || !isRecord7(candidate2.runtime))
|
|
110189
110250
|
return;
|
|
110190
110251
|
const runtime = candidate2.runtime.runtime;
|
|
110191
110252
|
const entrypoint = candidate2.runtime.entrypoint;
|
|
@@ -111400,7 +111461,7 @@ async function apiRequest(path, options, instance) {
|
|
|
111400
111461
|
const origin = instance ?? getApiUrl(`${(options?.method || "GET").toUpperCase()} ${path}`);
|
|
111401
111462
|
return new RemoteSkillsAuthClient(origin).request(path, options);
|
|
111402
111463
|
}
|
|
111403
|
-
function
|
|
111464
|
+
function isRecord8(value) {
|
|
111404
111465
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
111405
111466
|
}
|
|
111406
111467
|
function commandErrorPayload(err, fallback) {
|
|
@@ -111414,7 +111475,7 @@ function commandErrorPayload(err, fallback) {
|
|
|
111414
111475
|
...err.apiUrl ? { apiUrl: err.apiUrl } : {}
|
|
111415
111476
|
};
|
|
111416
111477
|
}
|
|
111417
|
-
const code2 =
|
|
111478
|
+
const code2 = isRecord8(err) && typeof err.code === "string" ? err.code : undefined;
|
|
111418
111479
|
return {
|
|
111419
111480
|
error: err?.message || fallback,
|
|
111420
111481
|
...code2 ? { code: code2 } : {}
|
|
@@ -111442,7 +111503,7 @@ function stringField2(value) {
|
|
|
111442
111503
|
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
111443
111504
|
}
|
|
111444
111505
|
function recordField(value) {
|
|
111445
|
-
return
|
|
111506
|
+
return isRecord8(value) ? value : undefined;
|
|
111446
111507
|
}
|
|
111447
111508
|
function identityApiKey(live) {
|
|
111448
111509
|
const root2 = recordField(live);
|
|
@@ -111932,7 +111993,7 @@ function registerAuth(parent) {
|
|
|
111932
111993
|
} catch {}
|
|
111933
111994
|
payload.permissions = { publish: access?.permissions?.publish ?? null, profilesWrite: access?.permissions?.profilesWrite ?? null };
|
|
111934
111995
|
const apiKey = identityApiKey(res);
|
|
111935
|
-
if (access?.scopes && (apiKey === undefined ||
|
|
111996
|
+
if (access?.scopes && (apiKey === undefined || isRecord8(apiKey) && !Object.hasOwn(apiKey, "scopes"))) {
|
|
111936
111997
|
payload.scopes = access.scopes;
|
|
111937
111998
|
}
|
|
111938
111999
|
if (options.json) {
|
package/bin/mcp.js
CHANGED
|
@@ -20019,7 +20019,7 @@ var package_default;
|
|
|
20019
20019
|
var init_package = __esm(() => {
|
|
20020
20020
|
package_default = {
|
|
20021
20021
|
name: "@hasna/skills",
|
|
20022
|
-
version: "0.9.
|
|
20022
|
+
version: "0.9.19",
|
|
20023
20023
|
description: "Skills library for AI coding agents",
|
|
20024
20024
|
type: "module",
|
|
20025
20025
|
bin: {
|
|
@@ -21311,7 +21311,8 @@ async function resolveSkillsConnection(env = process.env, options = {}) {
|
|
|
21311
21311
|
}
|
|
21312
21312
|
let completed;
|
|
21313
21313
|
try {
|
|
21314
|
-
|
|
21314
|
+
const explicitVault = Boolean(snapshotEnv.HASNA_SECRETS_API_URL?.trim() && snapshotEnv.HASNA_SECRETS_API_KEY_OVERRIDE?.trim());
|
|
21315
|
+
completed = await completePointerCredential(SKILLS_APP, pointer, explicitVault ? snapshotEnv : env);
|
|
21315
21316
|
} catch (error61) {
|
|
21316
21317
|
const translated = asSkillsFleetCredentialError(error61, pointer.pointerVaultKey);
|
|
21317
21318
|
if (translated)
|
|
@@ -52027,7 +52028,20 @@ class RemoteSkillsClient {
|
|
|
52027
52028
|
const url2 = new URL(upload.uploadUrl);
|
|
52028
52029
|
if (url2.username || url2.password || url2.hash || url2.protocol !== "https:" && !(url2.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url2.hostname)))
|
|
52029
52030
|
throw new Error("Unsafe input upload URL");
|
|
52030
|
-
const
|
|
52031
|
+
const headers = new Headers({ "Content-Type": file2.contentType ?? "application/octet-stream" });
|
|
52032
|
+
if (upload.uploadHeaders !== undefined) {
|
|
52033
|
+
if (upload.uploadHeaders === null || typeof upload.uploadHeaders !== "object" || Array.isArray(upload.uploadHeaders))
|
|
52034
|
+
throw new Error("Invalid input upload headers");
|
|
52035
|
+
const seen = new Set;
|
|
52036
|
+
for (const [name, value] of Object.entries(upload.uploadHeaders)) {
|
|
52037
|
+
const lowerName = name.toLowerCase();
|
|
52038
|
+
if (seen.has(lowerName) || !["if-none-match", "x-amz-tagging"].includes(lowerName) || typeof value !== "string" || value.length === 0 || value.length > 2048 || !/^[\x20-\x7e]+$/.test(value) || value.trim() !== value || lowerName === "if-none-match" && value !== "*")
|
|
52039
|
+
throw new Error("Invalid input upload headers");
|
|
52040
|
+
seen.add(lowerName);
|
|
52041
|
+
headers.set(name, value);
|
|
52042
|
+
}
|
|
52043
|
+
}
|
|
52044
|
+
const uploaded = await fetch(url2, { method: "PUT", body: file2.bytes, headers, redirect: "error", signal: AbortSignal.timeout(60000) });
|
|
52031
52045
|
if (!uploaded.ok)
|
|
52032
52046
|
throw new Error("Input upload failed");
|
|
52033
52047
|
await uploaded.body?.cancel();
|
package/bin/migrate.js
CHANGED
package/bin/server.js
CHANGED
|
@@ -24122,7 +24122,7 @@ var init_dist_es9 = __esm(() => {
|
|
|
24122
24122
|
// package.json
|
|
24123
24123
|
var package_default = {
|
|
24124
24124
|
name: "@hasna/skills",
|
|
24125
|
-
version: "0.9.
|
|
24125
|
+
version: "0.9.19",
|
|
24126
24126
|
description: "Skills library for AI coding agents",
|
|
24127
24127
|
type: "module",
|
|
24128
24128
|
bin: {
|
package/bin/worker.js
CHANGED
|
@@ -24125,7 +24125,7 @@ import { randomUUID as randomUUID6 } from "crypto";
|
|
|
24125
24125
|
// package.json
|
|
24126
24126
|
var package_default = {
|
|
24127
24127
|
name: "@hasna/skills",
|
|
24128
|
-
version: "0.9.
|
|
24128
|
+
version: "0.9.19",
|
|
24129
24129
|
description: "Skills library for AI coding agents",
|
|
24130
24130
|
type: "module",
|
|
24131
24131
|
bin: {
|
package/dist/index.js
CHANGED
|
@@ -9611,7 +9611,7 @@ import { isAbsolute as isAbsolute11, join as join23, resolve as resolve11 } from
|
|
|
9611
9611
|
// package.json
|
|
9612
9612
|
var package_default = {
|
|
9613
9613
|
name: "@hasna/skills",
|
|
9614
|
-
version: "0.9.
|
|
9614
|
+
version: "0.9.19",
|
|
9615
9615
|
description: "Skills library for AI coding agents",
|
|
9616
9616
|
type: "module",
|
|
9617
9617
|
bin: {
|
|
@@ -10891,7 +10891,8 @@ async function resolveSkillsConnection(env = process.env, options = {}) {
|
|
|
10891
10891
|
}
|
|
10892
10892
|
let completed;
|
|
10893
10893
|
try {
|
|
10894
|
-
|
|
10894
|
+
const explicitVault = Boolean(snapshotEnv.HASNA_SECRETS_API_URL?.trim() && snapshotEnv.HASNA_SECRETS_API_KEY_OVERRIDE?.trim());
|
|
10895
|
+
completed = await completePointerCredential(SKILLS_APP, pointer, explicitVault ? snapshotEnv : env);
|
|
10895
10896
|
} catch (error) {
|
|
10896
10897
|
const translated = asSkillsFleetCredentialError(error, pointer.pointerVaultKey);
|
|
10897
10898
|
if (translated)
|
|
@@ -39095,7 +39096,20 @@ class RemoteSkillsClient {
|
|
|
39095
39096
|
const url2 = new URL(upload.uploadUrl);
|
|
39096
39097
|
if (url2.username || url2.password || url2.hash || url2.protocol !== "https:" && !(url2.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url2.hostname)))
|
|
39097
39098
|
throw new Error("Unsafe input upload URL");
|
|
39098
|
-
const
|
|
39099
|
+
const headers = new Headers({ "Content-Type": file2.contentType ?? "application/octet-stream" });
|
|
39100
|
+
if (upload.uploadHeaders !== undefined) {
|
|
39101
|
+
if (upload.uploadHeaders === null || typeof upload.uploadHeaders !== "object" || Array.isArray(upload.uploadHeaders))
|
|
39102
|
+
throw new Error("Invalid input upload headers");
|
|
39103
|
+
const seen = new Set;
|
|
39104
|
+
for (const [name, value] of Object.entries(upload.uploadHeaders)) {
|
|
39105
|
+
const lowerName = name.toLowerCase();
|
|
39106
|
+
if (seen.has(lowerName) || !["if-none-match", "x-amz-tagging"].includes(lowerName) || typeof value !== "string" || value.length === 0 || value.length > 2048 || !/^[\x20-\x7e]+$/.test(value) || value.trim() !== value || lowerName === "if-none-match" && value !== "*")
|
|
39107
|
+
throw new Error("Invalid input upload headers");
|
|
39108
|
+
seen.add(lowerName);
|
|
39109
|
+
headers.set(name, value);
|
|
39110
|
+
}
|
|
39111
|
+
}
|
|
39112
|
+
const uploaded = await fetch(url2, { method: "PUT", body: file2.bytes, headers, redirect: "error", signal: AbortSignal.timeout(60000) });
|
|
39099
39113
|
if (!uploaded.ok)
|
|
39100
39114
|
throw new Error("Input upload failed");
|
|
39101
39115
|
await uploaded.body?.cancel();
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Codex config/read can expose serde_json's numeric wrapper for TOML floats.
|
|
2
|
+
* Admit it only against an equal numeric value from the complete disk parse.
|
|
3
|
+
* Ordinary objects, strings, native trust state and unknown fields stay exact.
|
|
4
|
+
*/
|
|
5
|
+
export declare function codexNativeConfigEqual(native: unknown, expected: unknown): boolean;
|
package/dist/sdk/index.js
CHANGED
|
@@ -32373,7 +32373,7 @@ var init_event_sink = __esm(() => {
|
|
|
32373
32373
|
// package.json
|
|
32374
32374
|
var package_default = {
|
|
32375
32375
|
name: "@hasna/skills",
|
|
32376
|
-
version: "0.9.
|
|
32376
|
+
version: "0.9.19",
|
|
32377
32377
|
description: "Skills library for AI coding agents",
|
|
32378
32378
|
type: "module",
|
|
32379
32379
|
bin: {
|
|
@@ -33712,7 +33712,8 @@ async function resolveSkillsConnection(env = process.env, options = {}) {
|
|
|
33712
33712
|
}
|
|
33713
33713
|
let completed;
|
|
33714
33714
|
try {
|
|
33715
|
-
|
|
33715
|
+
const explicitVault = Boolean(snapshotEnv.HASNA_SECRETS_API_URL?.trim() && snapshotEnv.HASNA_SECRETS_API_KEY_OVERRIDE?.trim());
|
|
33716
|
+
completed = await completePointerCredential(SKILLS_APP, pointer, explicitVault ? snapshotEnv : env);
|
|
33716
33717
|
} catch (error) {
|
|
33717
33718
|
const translated = asSkillsFleetCredentialError(error, pointer.pointerVaultKey);
|
|
33718
33719
|
if (translated)
|
|
@@ -89665,7 +89666,20 @@ class RemoteSkillsClient {
|
|
|
89665
89666
|
const url2 = new URL(upload.uploadUrl);
|
|
89666
89667
|
if (url2.username || url2.password || url2.hash || url2.protocol !== "https:" && !(url2.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url2.hostname)))
|
|
89667
89668
|
throw new Error("Unsafe input upload URL");
|
|
89668
|
-
const
|
|
89669
|
+
const headers = new Headers({ "Content-Type": file2.contentType ?? "application/octet-stream" });
|
|
89670
|
+
if (upload.uploadHeaders !== undefined) {
|
|
89671
|
+
if (upload.uploadHeaders === null || typeof upload.uploadHeaders !== "object" || Array.isArray(upload.uploadHeaders))
|
|
89672
|
+
throw new Error("Invalid input upload headers");
|
|
89673
|
+
const seen = new Set;
|
|
89674
|
+
for (const [name, value] of Object.entries(upload.uploadHeaders)) {
|
|
89675
|
+
const lowerName = name.toLowerCase();
|
|
89676
|
+
if (seen.has(lowerName) || !["if-none-match", "x-amz-tagging"].includes(lowerName) || typeof value !== "string" || value.length === 0 || value.length > 2048 || !/^[\x20-\x7e]+$/.test(value) || value.trim() !== value || lowerName === "if-none-match" && value !== "*")
|
|
89677
|
+
throw new Error("Invalid input upload headers");
|
|
89678
|
+
seen.add(lowerName);
|
|
89679
|
+
headers.set(name, value);
|
|
89680
|
+
}
|
|
89681
|
+
}
|
|
89682
|
+
const uploaded = await fetch(url2, { method: "PUT", body: file2.bytes, headers, redirect: "error", signal: AbortSignal.timeout(60000) });
|
|
89669
89683
|
if (!uploaded.ok)
|
|
89670
89684
|
throw new Error("Input upload failed");
|
|
89671
89685
|
await uploaded.body?.cancel();
|