@hasna/hooks 0.10.9 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -0
- package/bin/hooks-mcp.js +352 -182
- package/bin/index.js +2314 -1607
- package/bin/native-readiness-entry.js +355 -0
- package/dist/index.js +502 -284
- package/dist/lib/codex-safety-check.d.ts +2 -1
- package/dist/lib/codex-safety-trust.d.ts +40 -8
- package/dist/lib/installer.d.ts +11 -0
- package/dist/lib/native-readiness-command.d.ts +14 -0
- package/dist/lib/native-safety-registration.d.ts +38 -7
- package/dist/lib/readiness-process.d.ts +20 -0
- package/dist/native-readiness.d.ts +68 -0
- package/dist/native-readiness.js +328 -0
- package/hooks/native-readiness-entry.ts +31 -0
- package/package.json +7 -2
package/dist/index.js
CHANGED
|
@@ -4762,12 +4762,12 @@ __export(exports_store, {
|
|
|
4762
4762
|
checkScriptHash: () => checkScriptHash,
|
|
4763
4763
|
LockFileError: () => LockFileError
|
|
4764
4764
|
});
|
|
4765
|
-
import { createHash as
|
|
4766
|
-
import { existsSync as
|
|
4765
|
+
import { createHash as createHash3 } from "crypto";
|
|
4766
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync2, renameSync } from "fs";
|
|
4767
4767
|
import { readFile } from "fs/promises";
|
|
4768
4768
|
import { randomBytes } from "crypto";
|
|
4769
4769
|
function sha256Of(input) {
|
|
4770
|
-
return
|
|
4770
|
+
return createHash3("sha256").update(input).digest("hex");
|
|
4771
4771
|
}
|
|
4772
4772
|
async function sha256File(path) {
|
|
4773
4773
|
const buf = await readFile(path);
|
|
@@ -4828,11 +4828,11 @@ function assertLockShape(parsed, path) {
|
|
|
4828
4828
|
}
|
|
4829
4829
|
function readLock() {
|
|
4830
4830
|
const path = getLockPath();
|
|
4831
|
-
if (!
|
|
4831
|
+
if (!existsSync8(path))
|
|
4832
4832
|
return { hooks: {} };
|
|
4833
4833
|
let raw;
|
|
4834
4834
|
try {
|
|
4835
|
-
raw =
|
|
4835
|
+
raw = readFileSync5(path, "utf-8");
|
|
4836
4836
|
} catch {
|
|
4837
4837
|
throw new LockFileError(`hooks.lock (${path}) could not be read. Repair: fix permissions or move the file aside and run 'hooks sync'.`);
|
|
4838
4838
|
}
|
|
@@ -4898,15 +4898,15 @@ function removeHookFromStore(name) {
|
|
|
4898
4898
|
function checkScriptHash(name, actual) {
|
|
4899
4899
|
const db = storeDb();
|
|
4900
4900
|
const record = db ? getHookRecord(db, name) : null;
|
|
4901
|
-
const
|
|
4902
|
-
const expected = record?.sha256 ??
|
|
4901
|
+
const pin2 = getPinnedHook(name);
|
|
4902
|
+
const expected = record?.sha256 ?? pin2?.sha256 ?? null;
|
|
4903
4903
|
if (expected !== null && expected !== actual) {
|
|
4904
4904
|
return { ok: false, pinned: true, expected, actual, name };
|
|
4905
4905
|
}
|
|
4906
4906
|
if (expected === null) {
|
|
4907
4907
|
const now2 = new Date().toISOString();
|
|
4908
|
-
const version =
|
|
4909
|
-
const source =
|
|
4908
|
+
const version = pin2?.version ?? record?.version ?? "0.0.0";
|
|
4909
|
+
const source = pin2?.source ?? record?.source_type ?? "local";
|
|
4910
4910
|
if (db) {
|
|
4911
4911
|
upsertHookRecord(db, {
|
|
4912
4912
|
name,
|
|
@@ -4923,9 +4923,9 @@ function checkScriptHash(name, actual) {
|
|
|
4923
4923
|
if (db) {
|
|
4924
4924
|
upsertHookRecord(db, {
|
|
4925
4925
|
name,
|
|
4926
|
-
version: record?.version ??
|
|
4926
|
+
version: record?.version ?? pin2?.version ?? "0.0.0",
|
|
4927
4927
|
sha256: expected,
|
|
4928
|
-
source_type: record?.source_type ??
|
|
4928
|
+
source_type: record?.source_type ?? pin2?.source ?? "local",
|
|
4929
4929
|
last_verified_at: now
|
|
4930
4930
|
});
|
|
4931
4931
|
}
|
|
@@ -4935,7 +4935,7 @@ async function verifyScriptHash(name, scriptPath) {
|
|
|
4935
4935
|
return checkScriptHash(name, await sha256File(scriptPath));
|
|
4936
4936
|
}
|
|
4937
4937
|
function retrustHook(name, scriptPath, version, source) {
|
|
4938
|
-
const actual = sha256Of(
|
|
4938
|
+
const actual = sha256Of(readFileSync5(scriptPath));
|
|
4939
4939
|
const db = storeDb();
|
|
4940
4940
|
if (db) {
|
|
4941
4941
|
upsertHookRecord(db, {
|
|
@@ -4974,12 +4974,12 @@ __export(exports_profiles, {
|
|
|
4974
4974
|
deleteProfile: () => deleteProfile,
|
|
4975
4975
|
createProfile: () => createProfile
|
|
4976
4976
|
});
|
|
4977
|
-
import { existsSync as
|
|
4978
|
-
import { join as
|
|
4977
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync8, readdirSync as readdirSync8, rmSync, cpSync as cpSync2 } from "fs";
|
|
4978
|
+
import { join as join11 } from "path";
|
|
4979
4979
|
function getProfilesDir() {
|
|
4980
|
-
const newDir =
|
|
4981
|
-
const oldDir =
|
|
4982
|
-
if (!
|
|
4980
|
+
const newDir = join11(getEffectiveDataRoot(), "profiles");
|
|
4981
|
+
const oldDir = join11(getHomeDir(), ".hooks", "profiles");
|
|
4982
|
+
if (!existsSync12(newDir) && existsSync12(oldDir)) {
|
|
4983
4983
|
mkdirSync9(newDir, { recursive: true });
|
|
4984
4984
|
cpSync2(oldDir, newDir, { recursive: true });
|
|
4985
4985
|
}
|
|
@@ -4987,12 +4987,12 @@ function getProfilesDir() {
|
|
|
4987
4987
|
}
|
|
4988
4988
|
function ensureProfilesDir() {
|
|
4989
4989
|
const dir = getProfilesDir();
|
|
4990
|
-
if (!
|
|
4990
|
+
if (!existsSync12(dir)) {
|
|
4991
4991
|
mkdirSync9(dir, { recursive: true });
|
|
4992
4992
|
}
|
|
4993
4993
|
}
|
|
4994
4994
|
function profilePath(id) {
|
|
4995
|
-
return
|
|
4995
|
+
return join11(getProfilesDir(), `${id}.json`);
|
|
4996
4996
|
}
|
|
4997
4997
|
function shortUuid() {
|
|
4998
4998
|
return crypto.randomUUID().slice(0, 8);
|
|
@@ -5018,23 +5018,23 @@ function createProfile(input2) {
|
|
|
5018
5018
|
function getProfile(id) {
|
|
5019
5019
|
const path = profilePath(id);
|
|
5020
5020
|
try {
|
|
5021
|
-
if (!
|
|
5021
|
+
if (!existsSync12(path))
|
|
5022
5022
|
return null;
|
|
5023
|
-
return JSON.parse(
|
|
5023
|
+
return JSON.parse(readFileSync8(path, "utf-8"));
|
|
5024
5024
|
} catch {
|
|
5025
5025
|
return null;
|
|
5026
5026
|
}
|
|
5027
5027
|
}
|
|
5028
5028
|
function listProfiles() {
|
|
5029
5029
|
const dir = getProfilesDir();
|
|
5030
|
-
if (!
|
|
5030
|
+
if (!existsSync12(dir))
|
|
5031
5031
|
return [];
|
|
5032
5032
|
try {
|
|
5033
5033
|
const files = readdirSync8(dir).filter((f) => f.endsWith(".json"));
|
|
5034
5034
|
const profiles = [];
|
|
5035
5035
|
for (const file2 of files) {
|
|
5036
5036
|
try {
|
|
5037
|
-
const content =
|
|
5037
|
+
const content = readFileSync8(join11(dir, file2), "utf-8");
|
|
5038
5038
|
profiles.push(JSON.parse(content));
|
|
5039
5039
|
} catch {}
|
|
5040
5040
|
}
|
|
@@ -5057,7 +5057,7 @@ function updateProfile(id, data) {
|
|
|
5057
5057
|
}
|
|
5058
5058
|
function deleteProfile(id) {
|
|
5059
5059
|
const path = profilePath(id);
|
|
5060
|
-
if (!
|
|
5060
|
+
if (!existsSync12(path))
|
|
5061
5061
|
return false;
|
|
5062
5062
|
try {
|
|
5063
5063
|
rmSync(path);
|
|
@@ -5087,7 +5087,7 @@ function importProfiles(profiles) {
|
|
|
5087
5087
|
continue;
|
|
5088
5088
|
}
|
|
5089
5089
|
const path = profilePath(profile.agent_id);
|
|
5090
|
-
if (
|
|
5090
|
+
if (existsSync12(path)) {
|
|
5091
5091
|
skipped++;
|
|
5092
5092
|
continue;
|
|
5093
5093
|
}
|
|
@@ -5110,9 +5110,9 @@ import { hostname as osHostname } from "os";
|
|
|
5110
5110
|
import { isAbsolute as isAbsolute7, join as join22 } from "path";
|
|
5111
5111
|
import { createRequire as createRequire2 } from "module";
|
|
5112
5112
|
import { fileURLToPath as fileURLToPath22 } from "url";
|
|
5113
|
-
import { closeSync as
|
|
5113
|
+
import { closeSync as closeSync5, fstatSync as fstatSync4, lstatSync as lstatSync7, openSync as openSync5, readFileSync as readFileSync9, realpathSync as realpathSync7, statSync } from "fs";
|
|
5114
5114
|
import { O_NOFOLLOW, O_NONBLOCK, O_RDONLY } from "constants";
|
|
5115
|
-
import { basename as basename5, dirname as dirname11, join as
|
|
5115
|
+
import { basename as basename5, dirname as dirname11, join as join12, relative, sep as sep3 } from "path";
|
|
5116
5116
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "url";
|
|
5117
5117
|
function envToken2(name) {
|
|
5118
5118
|
return name.toUpperCase().replace(/-/g, "_");
|
|
@@ -5187,15 +5187,15 @@ function o(e2, r2, t2) {
|
|
|
5187
5187
|
}
|
|
5188
5188
|
function entryPoint(directory2) {
|
|
5189
5189
|
const root = realpathSync7(directory2);
|
|
5190
|
-
const fd =
|
|
5190
|
+
const fd = openSync5(join12(root, "package.json"), O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
|
|
5191
5191
|
let pkg;
|
|
5192
5192
|
try {
|
|
5193
|
-
const stat2 =
|
|
5193
|
+
const stat2 = fstatSync4(fd);
|
|
5194
5194
|
if (!stat2.isFile() || stat2.size > MAX_PACKAGE_BYTES2)
|
|
5195
5195
|
throw new Error("Invalid Secrets package metadata");
|
|
5196
|
-
pkg = JSON.parse(
|
|
5196
|
+
pkg = JSON.parse(readFileSync9(fd, "utf8"));
|
|
5197
5197
|
} finally {
|
|
5198
|
-
|
|
5198
|
+
closeSync5(fd);
|
|
5199
5199
|
}
|
|
5200
5200
|
if (!pkg || typeof pkg !== "object" || !("name" in pkg) || pkg.name !== PACKAGE) {
|
|
5201
5201
|
throw new Error("Invalid Secrets package identity");
|
|
@@ -5207,7 +5207,7 @@ function entryPoint(directory2) {
|
|
|
5207
5207
|
if (/[\\%?#\0]/.test(target) || parts.some((part) => !part || part === "." || part === ".." || part === "node_modules")) {
|
|
5208
5208
|
throw new Error("Invalid Secrets import export");
|
|
5209
5209
|
}
|
|
5210
|
-
const file2 = realpathSync7(
|
|
5210
|
+
const file2 = realpathSync7(join12(root, ...parts));
|
|
5211
5211
|
const within = relative(root, file2);
|
|
5212
5212
|
if (!within || within === ".." || within.startsWith(`..${sep3}`) || !statSync(file2).isFile()) {
|
|
5213
5213
|
throw new Error("Secrets import export escapes its package or is not a file");
|
|
@@ -5218,7 +5218,7 @@ function resolveInstalledSecrets(parentUrl) {
|
|
|
5218
5218
|
let directory2 = dirname11(fileURLToPath3(parentUrl));
|
|
5219
5219
|
for (;; ) {
|
|
5220
5220
|
if (basename5(directory2) !== "node_modules") {
|
|
5221
|
-
const candidate =
|
|
5221
|
+
const candidate = join12(directory2, "node_modules", PACKAGE);
|
|
5222
5222
|
let absent = false;
|
|
5223
5223
|
try {
|
|
5224
5224
|
lstatSync7(candidate);
|
|
@@ -5288,9 +5288,9 @@ function parseEnvFile(text3) {
|
|
|
5288
5288
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
|
|
5289
5289
|
continue;
|
|
5290
5290
|
let value = withoutExport.slice(equals + 1).trim();
|
|
5291
|
-
const
|
|
5292
|
-
if (
|
|
5293
|
-
if (value.length < 2 || !value.endsWith(
|
|
5291
|
+
const quote4 = value[0];
|
|
5292
|
+
if (quote4 === '"' || quote4 === "'") {
|
|
5293
|
+
if (value.length < 2 || !value.endsWith(quote4)) {
|
|
5294
5294
|
unusable.add(key);
|
|
5295
5295
|
continue;
|
|
5296
5296
|
}
|
|
@@ -5740,9 +5740,9 @@ async function completePointerCredential(name, pointerResolution, env = process.
|
|
|
5740
5740
|
}
|
|
5741
5741
|
let client;
|
|
5742
5742
|
try {
|
|
5743
|
-
const
|
|
5744
|
-
if (!
|
|
5745
|
-
throw new CredentialResolutionError("secrets", "The Secrets vault requires an independent, non-reference bootstrap credential.",
|
|
5743
|
+
const bootstrap2 = resolveCredential("secrets", secretsEnv);
|
|
5744
|
+
if (!bootstrap2 || bootstrap2.tier === "pointer") {
|
|
5745
|
+
throw new CredentialResolutionError("secrets", "The Secrets vault requires an independent, non-reference bootstrap credential.", bootstrap2 ? [bootstrap2.source] : []);
|
|
5746
5746
|
}
|
|
5747
5747
|
client = secretsSdk.createSecretsClientFromEnv(secretsEnv);
|
|
5748
5748
|
} catch {
|
|
@@ -6203,11 +6203,11 @@ var init_db_writer = __esm(() => {
|
|
|
6203
6203
|
|
|
6204
6204
|
// src/lib/hook-env.ts
|
|
6205
6205
|
import { dirname as dirname24, isAbsolute as isAbsolute21 } from "path";
|
|
6206
|
-
import { existsSync as
|
|
6206
|
+
import { existsSync as existsSync15, realpathSync as realpathSync13, statSync as statSync2 } from "fs";
|
|
6207
6207
|
import { tmpdir as tmpdir2 } from "os";
|
|
6208
6208
|
function isDeniedEnvName(name) {
|
|
6209
6209
|
const upper = name.toUpperCase();
|
|
6210
|
-
if (DENY_PREFIXES.some((
|
|
6210
|
+
if (DENY_PREFIXES.some((prefix3) => upper.startsWith(prefix3)))
|
|
6211
6211
|
return true;
|
|
6212
6212
|
if (DENY_SUFFIXES.some((suffix) => upper.endsWith(suffix)))
|
|
6213
6213
|
return true;
|
|
@@ -6216,7 +6216,7 @@ function isDeniedEnvName(name) {
|
|
|
6216
6216
|
function isInterpreterInjectionEnvName(name) {
|
|
6217
6217
|
if (INTERPRETER_INJECTION.has(name))
|
|
6218
6218
|
return true;
|
|
6219
|
-
return INTERPRETER_INJECTION_PREFIXES.some((
|
|
6219
|
+
return INTERPRETER_INJECTION_PREFIXES.some((prefix3) => name.startsWith(prefix3));
|
|
6220
6220
|
}
|
|
6221
6221
|
function isBashFunctionEnvName(name) {
|
|
6222
6222
|
return name.startsWith("BASH_FUNC_");
|
|
@@ -6251,7 +6251,7 @@ function isUnsafePathEntry(entry, home) {
|
|
|
6251
6251
|
}
|
|
6252
6252
|
function sanitizeHookPath(pathValue, home) {
|
|
6253
6253
|
const baseline = PATH_BASELINE.slice();
|
|
6254
|
-
if (process.platform === "darwin" &&
|
|
6254
|
+
if (process.platform === "darwin" && existsSync15("/opt/homebrew/bin"))
|
|
6255
6255
|
baseline.push("/opt/homebrew/bin");
|
|
6256
6256
|
const bunDir = dirname24(process.execPath);
|
|
6257
6257
|
const seen = new Set;
|
|
@@ -6378,7 +6378,7 @@ __export(exports_run, {
|
|
|
6378
6378
|
});
|
|
6379
6379
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
6380
6380
|
import { rmSync as rmSync4, writeFileSync as writeFileSync13 } from "fs";
|
|
6381
|
-
import { basename as basename11, dirname as dirname25, isAbsolute as isAbsolute23, join as
|
|
6381
|
+
import { basename as basename11, dirname as dirname25, isAbsolute as isAbsolute23, join as join29 } from "path";
|
|
6382
6382
|
function extensionOf(scriptPath) {
|
|
6383
6383
|
const base = basename11(scriptPath);
|
|
6384
6384
|
const dot = base.lastIndexOf(".");
|
|
@@ -6449,7 +6449,7 @@ async function readPipeWithDeadline(stream, deadlineMs) {
|
|
|
6449
6449
|
async function executeVerifiedScript(options) {
|
|
6450
6450
|
const scriptDir = dirname25(options.scriptPath);
|
|
6451
6451
|
const interpreter = interpreterFor(options.name, options.scriptPath, options.content);
|
|
6452
|
-
const tempPath =
|
|
6452
|
+
const tempPath = join29(scriptDir, `.hooks-verified-${randomBytes2(12).toString("hex")}.${interpreter.tempExt}`);
|
|
6453
6453
|
let proc = null;
|
|
6454
6454
|
let timedOut = false;
|
|
6455
6455
|
const timeoutTimer = options.timeout ? setTimeout(() => {
|
|
@@ -7542,9 +7542,9 @@ function getHook(name) {
|
|
|
7542
7542
|
return HOOKS.find((h) => h.name === name);
|
|
7543
7543
|
}
|
|
7544
7544
|
// src/lib/installer.ts
|
|
7545
|
-
import { existsSync as
|
|
7545
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync7, readdirSync as readdirSync4, realpathSync as realpathSync5, rmdirSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
7546
7546
|
import { join as join10, dirname as dirname7, basename as basename3, resolve as resolve7 } from "path";
|
|
7547
|
-
import { homedir as
|
|
7547
|
+
import { homedir as homedir6 } from "os";
|
|
7548
7548
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7549
7549
|
|
|
7550
7550
|
// src/lib/resolve.ts
|
|
@@ -7769,6 +7769,169 @@ function installedNativeSafetyCommand(name) {
|
|
|
7769
7769
|
return buildNativeSafetyCommand({ name, runtime, runtimeSHA256: pinEligibleFile(runtime, 200000000, true), worker: canonicalWorker, workerSHA256: pinEligibleFile(canonicalWorker, 256000), home, path: `${dirname3(runtime)}:${join7(home, ".bun/bin")}:/usr/local/bin:/usr/bin:/bin` });
|
|
7770
7770
|
}
|
|
7771
7771
|
|
|
7772
|
+
// src/lib/native-readiness-command.ts
|
|
7773
|
+
import { createHash as createHash2 } from "crypto";
|
|
7774
|
+
import { constants as constants2, closeSync as closeSync2, existsSync as existsSync7, fstatSync as fstatSync2, lstatSync as lstatSync2, openSync as openSync2, readFileSync as readFileSync4, realpathSync as realpathSync3 } from "fs";
|
|
7775
|
+
import { dirname as dirname4, isAbsolute as isAbsolute3, join as join8 } from "path";
|
|
7776
|
+
import { homedir as homedir4 } from "os";
|
|
7777
|
+
|
|
7778
|
+
// src/lib/readiness-process.ts
|
|
7779
|
+
var supervisor = String.raw`
|
|
7780
|
+
const {spawn}=require("node:child_process");
|
|
7781
|
+
const spec=JSON.parse(process.argv.at(-1));
|
|
7782
|
+
// This runtime was created detached and is its group's live leader. Its
|
|
7783
|
+
// independent timer and parent pipe remain effective if the observer exits
|
|
7784
|
+
// or its event loop stops responding. Only this freshly created tree is hit.
|
|
7785
|
+
const cleanup=()=>{try{process.kill(-process.pid,"SIGKILL")}catch{process.exit(1)}};
|
|
7786
|
+
setTimeout(cleanup,spec.timeout);
|
|
7787
|
+
process.stdin.once("end",cleanup);
|
|
7788
|
+
process.stdin.once("error",cleanup);
|
|
7789
|
+
process.stdin.on("data",cleanup);
|
|
7790
|
+
process.stdin.resume();
|
|
7791
|
+
for(const signal of ["SIGTERM","SIGINT","SIGHUP"])process.once(signal,cleanup);
|
|
7792
|
+
let done=false, out=Buffer.alloc(0), err=Buffer.alloc(0);
|
|
7793
|
+
function report(status,error){
|
|
7794
|
+
if(done)return; done=true;
|
|
7795
|
+
process.stdout.write(JSON.stringify({status,error,stdout:out.toString("utf8"),stderr:err.toString("utf8")})+"\n");
|
|
7796
|
+
}
|
|
7797
|
+
const child=spawn(spec.command,spec.args,{stdio:["ignore","pipe","pipe"],env:process.env});
|
|
7798
|
+
for(const [stream,name] of [[child.stdout,"stdout"],[child.stderr,"stderr"]]){
|
|
7799
|
+
stream.on("data",bytes=>{
|
|
7800
|
+
if(done)return;
|
|
7801
|
+
const before=name==="stdout"?out:err;
|
|
7802
|
+
if(before.length+bytes.length>spec.maxBuffer){report(null,{code:"ENOBUFS"});return}
|
|
7803
|
+
if(name==="stdout")out=Buffer.concat([out,bytes]);else err=Buffer.concat([err,bytes]);
|
|
7804
|
+
});
|
|
7805
|
+
stream.on("error",()=>report(null,{code:"ECHILD"}));
|
|
7806
|
+
}
|
|
7807
|
+
child.on("error",()=>report(null,{code:"ECHILD"}));
|
|
7808
|
+
// A successful observation includes complete streams. A nested client that
|
|
7809
|
+
// keeps them open after its parent exits is a failure requiring group cleanup.
|
|
7810
|
+
child.on("exit",()=>setTimeout(()=>report(null,{code:"ECHILD"}),100));
|
|
7811
|
+
child.on("close",(status,signal)=>report(status,signal?{code:"ECHILD"}:undefined));
|
|
7812
|
+
`;
|
|
7813
|
+
|
|
7814
|
+
// src/native-readiness.ts
|
|
7815
|
+
function isReadinessProvider(value) {
|
|
7816
|
+
return value === "claude" || value === "codex" || value === "sumi";
|
|
7817
|
+
}
|
|
7818
|
+
var safeFailureCodes = new Set([
|
|
7819
|
+
"unsupported_platform",
|
|
7820
|
+
"station_detection_failed",
|
|
7821
|
+
"station_mismatch",
|
|
7822
|
+
"hosted_unavailable",
|
|
7823
|
+
"authentication_refused",
|
|
7824
|
+
"station_setup_required",
|
|
7825
|
+
"invalid_status",
|
|
7826
|
+
"deadline_exceeded",
|
|
7827
|
+
"retention_mismatch",
|
|
7828
|
+
"managed_instructions_refused",
|
|
7829
|
+
"removal_instruction_missing",
|
|
7830
|
+
"instructions_changed",
|
|
7831
|
+
"native_config_changed",
|
|
7832
|
+
"guard_refused",
|
|
7833
|
+
"owner_refused"
|
|
7834
|
+
]);
|
|
7835
|
+
|
|
7836
|
+
// src/lib/native-readiness-command.ts
|
|
7837
|
+
var quote2 = (value) => "'" + value.replaceAll("'", "'\\''") + "'";
|
|
7838
|
+
var bootstrap = String.raw`/* hasna-native-readiness-v1 */ (() => {
|
|
7839
|
+
const fs = require("node:fs"), path = require("node:path"), crypto = require("node:crypto");
|
|
7840
|
+
const failed = () => process.stdout.write(JSON.stringify({hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:"Trash readiness needs attention (runner integrity). Preserve files; do not fall back to direct deletion."}})+"\n");
|
|
7841
|
+
try {
|
|
7842
|
+
const b = JSON.parse(Buffer.from(process.argv[1],"base64url").toString("utf8"));
|
|
7843
|
+
if (!["claude","codex","sumi"].includes(b.provider) || process.argv[2] !== b.provider || process.env.HOME !== b.home) throw Error();
|
|
7844
|
+
const check = (file, expected, maximum, executable) => {
|
|
7845
|
+
if (!path.isAbsolute(file) || fs.realpathSync(file) !== file) throw Error();
|
|
7846
|
+
for (let p = path.dirname(file);; p = path.dirname(p)) {
|
|
7847
|
+
const s = fs.lstatSync(p);
|
|
7848
|
+
if (!s.isDirectory() || ![0,process.getuid()].includes(s.uid) || s.mode & 0o022) throw Error();
|
|
7849
|
+
if (p === path.dirname(p)) break;
|
|
7850
|
+
}
|
|
7851
|
+
const fd = fs.openSync(file,fs.constants.O_RDONLY|fs.constants.O_NOFOLLOW|fs.constants.O_NONBLOCK);
|
|
7852
|
+
try {
|
|
7853
|
+
const s = fs.fstatSync(fd);
|
|
7854
|
+
if (!s.isFile() || ![0,process.getuid()].includes(s.uid) || s.mode & 0o022 || s.size > maximum || executable && !(s.mode & 0o100)) throw Error();
|
|
7855
|
+
const bytes = Buffer.alloc(s.size+1); let length=0;
|
|
7856
|
+
while (length<bytes.length) { const n=fs.readSync(fd,bytes,length,bytes.length-length,null); if(!n)break; length+=n; }
|
|
7857
|
+
const after=fs.fstatSync(fd);
|
|
7858
|
+
if(length!==s.size || after.size!==s.size || after.mtimeMs!==s.mtimeMs || after.ctimeMs!==s.ctimeMs)throw Error();
|
|
7859
|
+
const content=bytes.subarray(0,length);
|
|
7860
|
+
if(crypto.createHash("sha256").update(content).digest("hex")!==expected)throw Error();
|
|
7861
|
+
return content;
|
|
7862
|
+
} finally { fs.closeSync(fd); }
|
|
7863
|
+
};
|
|
7864
|
+
check(b.runtime,b.runtimeSHA256,200000000,true);
|
|
7865
|
+
const bytes=check(b.worker,b.workerSHA256,256000,false);
|
|
7866
|
+
new Function("require","process",bytes.toString("utf8"))(require,process);
|
|
7867
|
+
} catch { failed(); }
|
|
7868
|
+
})();`;
|
|
7869
|
+
var prefix2 = "/usr/bin/env -i ";
|
|
7870
|
+
function valid(b) {
|
|
7871
|
+
if (!b || Object.keys(b).sort().join() !== "home,provider,runtime,runtimeSHA256,worker,workerSHA256" || !isReadinessProvider(b.provider) || ![b.home, b.runtime, b.worker].every((p) => typeof p === "string" && isAbsolute3(p) && !/[\0\r\n]/.test(p)) || ![b.runtimeSHA256, b.workerSHA256].every((h) => typeof h === "string" && /^[a-f0-9]{64}$/.test(h)))
|
|
7872
|
+
throw new Error("Invalid readiness command binding");
|
|
7873
|
+
}
|
|
7874
|
+
function buildNativeReadinessCommand(binding) {
|
|
7875
|
+
valid(binding);
|
|
7876
|
+
const encoded = Buffer.from(JSON.stringify(binding)).toString("base64url");
|
|
7877
|
+
return prefix2 + ["HOME=" + binding.home, "PATH=/usr/bin:/bin", binding.runtime, "--config=/dev/null", "--no-env-file", "--no-macros", "--no-install", "--eval", bootstrap, encoded, binding.provider].map(quote2).join(" ");
|
|
7878
|
+
}
|
|
7879
|
+
function nativeReadinessCommandBinding(command) {
|
|
7880
|
+
if (!command.startsWith(prefix2) || !command.includes(quote2(bootstrap)))
|
|
7881
|
+
return;
|
|
7882
|
+
const tail = command.slice(command.indexOf(quote2(bootstrap)) + quote2(bootstrap).length);
|
|
7883
|
+
const match = /^ '([A-Za-z0-9_-]{1,16384})' '(claude|codex|sumi)'$/.exec(tail);
|
|
7884
|
+
if (!match)
|
|
7885
|
+
return;
|
|
7886
|
+
try {
|
|
7887
|
+
const b = JSON.parse(Buffer.from(match[1], "base64url").toString("utf8"));
|
|
7888
|
+
return buildNativeReadinessCommand(b) === command ? b : undefined;
|
|
7889
|
+
} catch {
|
|
7890
|
+
return;
|
|
7891
|
+
}
|
|
7892
|
+
}
|
|
7893
|
+
function pin(path, maximum, executable) {
|
|
7894
|
+
if (realpathSync3(path) !== path)
|
|
7895
|
+
throw new Error("Readiness runner requires canonical files");
|
|
7896
|
+
for (let p = dirname4(path);; p = dirname4(p)) {
|
|
7897
|
+
const s = lstatSync2(p);
|
|
7898
|
+
if (!s.isDirectory() || ![0, process.getuid?.()].includes(s.uid) || s.mode & 18)
|
|
7899
|
+
throw new Error("Readiness runner requires safe ancestry");
|
|
7900
|
+
if (p === dirname4(p))
|
|
7901
|
+
break;
|
|
7902
|
+
}
|
|
7903
|
+
const fd = openSync2(path, constants2.O_RDONLY | constants2.O_NOFOLLOW | constants2.O_NONBLOCK);
|
|
7904
|
+
try {
|
|
7905
|
+
const s = fstatSync2(fd);
|
|
7906
|
+
if (!s.isFile() || ![0, process.getuid?.()].includes(s.uid) || s.mode & 18 || s.size > maximum || executable && !(s.mode & 64))
|
|
7907
|
+
throw new Error("Readiness runner requires safe regular files");
|
|
7908
|
+
const bytes = readFileSync4(fd);
|
|
7909
|
+
if (bytes.length > maximum)
|
|
7910
|
+
throw new Error("Readiness runner is too large");
|
|
7911
|
+
return createHash2("sha256").update(bytes).digest("hex");
|
|
7912
|
+
} finally {
|
|
7913
|
+
closeSync2(fd);
|
|
7914
|
+
}
|
|
7915
|
+
}
|
|
7916
|
+
function nativeReadinessCommandVerified(command) {
|
|
7917
|
+
const b = nativeReadinessCommandBinding(command);
|
|
7918
|
+
if (!b)
|
|
7919
|
+
return false;
|
|
7920
|
+
try {
|
|
7921
|
+
return pin(b.runtime, 200000000, true) === b.runtimeSHA256 && pin(b.worker, 256000, false) === b.workerSHA256;
|
|
7922
|
+
} catch {
|
|
7923
|
+
return false;
|
|
7924
|
+
}
|
|
7925
|
+
}
|
|
7926
|
+
function installedNativeReadinessCommand(provider) {
|
|
7927
|
+
const candidates = [join8(import.meta.dir, "../../bin/native-readiness-entry.js"), join8(import.meta.dir, "../bin/native-readiness-entry.js"), join8(import.meta.dir, "native-readiness-entry.js")];
|
|
7928
|
+
const selected = candidates.find(existsSync7);
|
|
7929
|
+
if (!selected)
|
|
7930
|
+
throw new Error("Build or install the native readiness runner first");
|
|
7931
|
+
const runtime = realpathSync3(process.execPath), worker = realpathSync3(selected);
|
|
7932
|
+
return buildNativeReadinessCommand({ provider, home: realpathSync3(homedir4()), runtime, runtimeSHA256: pin(runtime, 200000000, true), worker, workerSHA256: pin(worker, 256000, false) });
|
|
7933
|
+
}
|
|
7934
|
+
|
|
7772
7935
|
// src/lib/mementos-options.ts
|
|
7773
7936
|
function validateMementosRegistration(name, value) {
|
|
7774
7937
|
if (value === undefined)
|
|
@@ -7833,6 +7996,8 @@ function registrationName(command) {
|
|
|
7833
7996
|
const native = nativeSafetyRegistration(command);
|
|
7834
7997
|
if (native)
|
|
7835
7998
|
return native;
|
|
7999
|
+
if (nativeReadinessCommandBinding(command))
|
|
8000
|
+
return "trash-readiness";
|
|
7836
8001
|
if (/^hooks run mementos-context(?:\s|$)/.test(command.trim())) {
|
|
7837
8002
|
try {
|
|
7838
8003
|
retainedMementosRegistration([command.trim()]);
|
|
@@ -7852,16 +8017,16 @@ function matchersOverlap(a, b) {
|
|
|
7852
8017
|
init_store();
|
|
7853
8018
|
|
|
7854
8019
|
// src/lib/codex-settings.ts
|
|
7855
|
-
import { closeSync as
|
|
7856
|
-
import { dirname as
|
|
7857
|
-
import { createHash as
|
|
8020
|
+
import { closeSync as closeSync3, existsSync as existsSync9, lstatSync as lstatSync3, mkdirSync as mkdirSync4, openSync as openSync3, readFileSync as readFileSync6, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
|
|
8021
|
+
import { dirname as dirname5 } from "path";
|
|
8022
|
+
import { createHash as createHash4, randomUUID } from "crypto";
|
|
7858
8023
|
var snapshots = new WeakMap;
|
|
7859
8024
|
function current(path) {
|
|
7860
8025
|
try {
|
|
7861
|
-
const stat =
|
|
8026
|
+
const stat = lstatSync3(path);
|
|
7862
8027
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 1048576)
|
|
7863
8028
|
throw new Error("invalid_file");
|
|
7864
|
-
return
|
|
8029
|
+
return readFileSync6(path, "utf8");
|
|
7865
8030
|
} catch (error) {
|
|
7866
8031
|
if (error.code === "ENOENT")
|
|
7867
8032
|
return null;
|
|
@@ -7898,7 +8063,7 @@ function capturedCodexSettings(settings) {
|
|
|
7898
8063
|
function assertSettingsPreimage(raw, expected) {
|
|
7899
8064
|
if (expected === undefined)
|
|
7900
8065
|
return;
|
|
7901
|
-
if (!/^(?:absent|[a-f0-9]{64})$/.test(expected) || (raw === null ? "absent" :
|
|
8066
|
+
if (!/^(?:absent|[a-f0-9]{64})$/.test(expected) || (raw === null ? "absent" : createHash4("sha256").update(raw).digest("hex")) !== expected) {
|
|
7902
8067
|
throw new Error("Hook settings changed since planning; no settings were written.");
|
|
7903
8068
|
}
|
|
7904
8069
|
}
|
|
@@ -7912,11 +8077,11 @@ function writeCodexSettings(path, settings, expectedSha256) {
|
|
|
7912
8077
|
` });
|
|
7913
8078
|
}
|
|
7914
8079
|
function writeHookSettingsPreimage(path, settings, before) {
|
|
7915
|
-
mkdirSync4(
|
|
8080
|
+
mkdirSync4(dirname5(path), { recursive: true, mode: 448 });
|
|
7916
8081
|
const lock = `${path}.hooks-write.lock`;
|
|
7917
8082
|
let fd;
|
|
7918
8083
|
try {
|
|
7919
|
-
fd =
|
|
8084
|
+
fd = openSync3(lock, "wx", 384);
|
|
7920
8085
|
} catch {
|
|
7921
8086
|
throw new Error("Codex settings are being edited; retry after the active writer finishes.");
|
|
7922
8087
|
}
|
|
@@ -7931,23 +8096,23 @@ function writeHookSettingsPreimage(path, settings, before) {
|
|
|
7931
8096
|
throw new Error("Hook settings changed since reading; re-read before retrying.");
|
|
7932
8097
|
renameSync2(temp, path);
|
|
7933
8098
|
} finally {
|
|
7934
|
-
if (
|
|
8099
|
+
if (existsSync9(temp))
|
|
7935
8100
|
unlinkSync(temp);
|
|
7936
|
-
|
|
8101
|
+
closeSync3(fd);
|
|
7937
8102
|
unlinkSync(lock);
|
|
7938
8103
|
}
|
|
7939
8104
|
}
|
|
7940
8105
|
|
|
7941
8106
|
// src/lib/claude-skills-coordination.ts
|
|
7942
|
-
import { createHash as
|
|
8107
|
+
import { createHash as createHash6 } from "crypto";
|
|
7943
8108
|
|
|
7944
8109
|
// ../../node_modules/.bun/@hasna+skills@0.9.17+a00d3064559565bb/node_modules/@hasna/skills/dist/index.js
|
|
7945
|
-
import { existsSync as
|
|
7946
|
-
import { homedir as
|
|
7947
|
-
import { join as
|
|
8110
|
+
import { existsSync as existsSync10 } from "fs";
|
|
8111
|
+
import { homedir as homedir5 } from "os";
|
|
8112
|
+
import { join as join9, resolve as resolve3 } from "path";
|
|
7948
8113
|
import { homedir as pathsResolverHomedir2 } from "os";
|
|
7949
8114
|
import { join as pathsResolverJoin2 } from "path";
|
|
7950
|
-
import { createHash as
|
|
8115
|
+
import { createHash as createHash5 } from "crypto";
|
|
7951
8116
|
import { existsSync as existsSync52, readdirSync as readdirSync3, readFileSync as readFileSync42, statSync as statSync3 } from "fs";
|
|
7952
8117
|
import { join as join52, sep as sep2 } from "path";
|
|
7953
8118
|
import { basename as basename2, isAbsolute as isAbsolute22, resolve as resolve22 } from "path";
|
|
@@ -7955,23 +8120,23 @@ import { createHash as createHash16 } from "crypto";
|
|
|
7955
8120
|
import { lstatSync as lstatSync20, readFileSync as readFileSync16, readdirSync as readdirSync9, realpathSync as realpathSync4 } from "fs";
|
|
7956
8121
|
import { basename as basename8, dirname as dirname16, isAbsolute as isAbsolute14, join as join26, resolve as resolve14, sep as sep5 } from "path";
|
|
7957
8122
|
import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
|
|
7958
|
-
import { dirname as dirname32, isAbsolute as
|
|
7959
|
-
import { lstatSync as
|
|
8123
|
+
import { dirname as dirname32, isAbsolute as isAbsolute32, join as join72, resolve as resolve32 } from "path";
|
|
8124
|
+
import { lstatSync as lstatSync32, readFileSync as readFileSync62, opendirSync } from "fs";
|
|
7960
8125
|
import { createHash as createHash22 } from "crypto";
|
|
7961
8126
|
import { lstatSync as lstatSync4, opendirSync as opendirSync2 } from "fs";
|
|
7962
8127
|
import { dirname as dirname42, isAbsolute as isAbsolute4, join as join82, resolve as resolve4 } from "path";
|
|
7963
8128
|
import { createHash as createHash32 } from "crypto";
|
|
7964
|
-
import { closeSync as
|
|
7965
|
-
import { dirname as
|
|
8129
|
+
import { closeSync as closeSync4, constants as constants3, fstatSync as fstatSync3, lstatSync as lstatSync5, openSync as openSync4, readSync } from "fs";
|
|
8130
|
+
import { dirname as dirname52, isAbsolute as isAbsolute5, resolve as resolve5 } from "path";
|
|
7966
8131
|
import { createHash as createHash42 } from "crypto";
|
|
7967
8132
|
import { lstatSync as lstatSync6, readlinkSync } from "fs";
|
|
7968
|
-
import { dirname as dirname6, isAbsolute as isAbsolute6, join as
|
|
8133
|
+
import { dirname as dirname6, isAbsolute as isAbsolute6, join as join92, parse, resolve as resolve6, sep as sep22 } from "path";
|
|
7969
8134
|
import { closeSync as closeSync12, constants as constants9, fstatSync as fstatSync12, lstatSync as lstatSync17, openSync as openSync12, opendirSync as opendirSync5, readSync as readSync8 } from "fs";
|
|
7970
8135
|
import { basename as basename6, dirname as dirname13, isAbsolute as isAbsolute12, join as join25, resolve as resolve12 } from "path";
|
|
7971
8136
|
import { isAbsolute as isAbsolute11, join as join23, resolve as resolve11 } from "path";
|
|
7972
8137
|
import { createRequire } from "module";
|
|
7973
8138
|
import { createHash as createHash10, randomUUID as randomUUID2 } from "crypto";
|
|
7974
|
-
import { existsSync as existsSync82, lstatSync as lstatSync14, statSync as statSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync11, readdirSync as readdirSync7, opendirSync as opendirSync3, readlinkSync as readlinkSync2, realpathSync as
|
|
8139
|
+
import { existsSync as existsSync82, lstatSync as lstatSync14, statSync as statSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync11, readdirSync as readdirSync7, opendirSync as opendirSync3, readlinkSync as readlinkSync2, realpathSync as realpathSync32, renameSync as renameSync22, rmdirSync as rmdirSync2, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3, chmodSync, openSync as openSync10, closeSync as closeSync10, fsyncSync as fsyncSync2, fstatSync as fstatSync10, readSync as readSync6, constants as constants7, linkSync } from "fs";
|
|
7975
8140
|
import { dirname as dirname10, join as join16, resolve as resolve9, relative as relative4, isAbsolute as isAbsolute9, sep as sep4 } from "path";
|
|
7976
8141
|
import { homedir as homedir22 } from "os";
|
|
7977
8142
|
import { closeSync as closeSync8, constants as constants5, fstatSync as fstatSync8, lstatSync as lstatSync11, openSync as openSync8, readSync as readSync4 } from "fs";
|
|
@@ -8272,8 +8437,8 @@ var require_directives = __commonJS((exports) => {
|
|
|
8272
8437
|
if (parts.length < 2)
|
|
8273
8438
|
return false;
|
|
8274
8439
|
}
|
|
8275
|
-
const [handle,
|
|
8276
|
-
this.tags[handle] =
|
|
8440
|
+
const [handle, prefix3] = parts;
|
|
8441
|
+
this.tags[handle] = prefix3;
|
|
8277
8442
|
return true;
|
|
8278
8443
|
}
|
|
8279
8444
|
case "%YAML": {
|
|
@@ -8317,10 +8482,10 @@ var require_directives = __commonJS((exports) => {
|
|
|
8317
8482
|
const [, handle, suffix] = source.match(/^(.*!)([^!]*)$/s);
|
|
8318
8483
|
if (!suffix)
|
|
8319
8484
|
onError(`The ${source} tag has no suffix`);
|
|
8320
|
-
const
|
|
8321
|
-
if (
|
|
8485
|
+
const prefix3 = this.tags[handle];
|
|
8486
|
+
if (prefix3) {
|
|
8322
8487
|
try {
|
|
8323
|
-
return
|
|
8488
|
+
return prefix3 + decodeURIComponent(suffix);
|
|
8324
8489
|
} catch (error) {
|
|
8325
8490
|
onError(String(error));
|
|
8326
8491
|
return null;
|
|
@@ -8332,9 +8497,9 @@ var require_directives = __commonJS((exports) => {
|
|
|
8332
8497
|
return null;
|
|
8333
8498
|
}
|
|
8334
8499
|
tagString(tag) {
|
|
8335
|
-
for (const [handle,
|
|
8336
|
-
if (tag.startsWith(
|
|
8337
|
-
return handle + escapeTagName(tag.substring(
|
|
8500
|
+
for (const [handle, prefix3] of Object.entries(this.tags)) {
|
|
8501
|
+
if (tag.startsWith(prefix3))
|
|
8502
|
+
return handle + escapeTagName(tag.substring(prefix3.length));
|
|
8338
8503
|
}
|
|
8339
8504
|
return tag[0] === "!" ? tag : `!<${tag}>`;
|
|
8340
8505
|
}
|
|
@@ -8351,11 +8516,11 @@ var require_directives = __commonJS((exports) => {
|
|
|
8351
8516
|
tagNames = Object.keys(tags);
|
|
8352
8517
|
} else
|
|
8353
8518
|
tagNames = [];
|
|
8354
|
-
for (const [handle,
|
|
8355
|
-
if (handle === "!!" &&
|
|
8519
|
+
for (const [handle, prefix3] of tagEntries) {
|
|
8520
|
+
if (handle === "!!" && prefix3 === "tag:yaml.org,2002:")
|
|
8356
8521
|
continue;
|
|
8357
|
-
if (!doc || tagNames.some((tn) => tn.startsWith(
|
|
8358
|
-
lines.push(`%TAG ${handle} ${
|
|
8522
|
+
if (!doc || tagNames.some((tn) => tn.startsWith(prefix3)))
|
|
8523
|
+
lines.push(`%TAG ${handle} ${prefix3}`);
|
|
8359
8524
|
}
|
|
8360
8525
|
return lines.join(`
|
|
8361
8526
|
`);
|
|
@@ -8386,14 +8551,14 @@ var require_anchors = __commonJS((exports) => {
|
|
|
8386
8551
|
});
|
|
8387
8552
|
return anchors;
|
|
8388
8553
|
}
|
|
8389
|
-
function findNewAnchor(
|
|
8554
|
+
function findNewAnchor(prefix3, exclude) {
|
|
8390
8555
|
for (let i = 1;; ++i) {
|
|
8391
|
-
const name = `${
|
|
8556
|
+
const name = `${prefix3}${i}`;
|
|
8392
8557
|
if (!exclude.has(name))
|
|
8393
8558
|
return name;
|
|
8394
8559
|
}
|
|
8395
8560
|
}
|
|
8396
|
-
function createNodeAnchors(doc,
|
|
8561
|
+
function createNodeAnchors(doc, prefix3) {
|
|
8397
8562
|
const aliasObjects = [];
|
|
8398
8563
|
const sourceObjects = new Map;
|
|
8399
8564
|
let prevAnchors = null;
|
|
@@ -8401,7 +8566,7 @@ var require_anchors = __commonJS((exports) => {
|
|
|
8401
8566
|
onAnchor: (source) => {
|
|
8402
8567
|
aliasObjects.push(source);
|
|
8403
8568
|
prevAnchors ?? (prevAnchors = anchorNames(doc));
|
|
8404
|
-
const anchor = findNewAnchor(
|
|
8569
|
+
const anchor = findNewAnchor(prefix3, prevAnchors);
|
|
8405
8570
|
prevAnchors.add(anchor);
|
|
8406
8571
|
return anchor;
|
|
8407
8572
|
},
|
|
@@ -10226,10 +10391,10 @@ var require_int = __commonJS((exports) => {
|
|
|
10226
10391
|
var stringifyNumber = require_stringifyNumber();
|
|
10227
10392
|
var intIdentify = (value) => typeof value === "bigint" || Number.isInteger(value);
|
|
10228
10393
|
var intResolve = (str, offset, radix, { intAsBigInt }) => intAsBigInt ? BigInt(str) : parseInt(str.substring(offset), radix);
|
|
10229
|
-
function intStringify(node, radix,
|
|
10394
|
+
function intStringify(node, radix, prefix3) {
|
|
10230
10395
|
const { value } = node;
|
|
10231
10396
|
if (intIdentify(value) && value >= 0)
|
|
10232
|
-
return
|
|
10397
|
+
return prefix3 + value.toString(radix);
|
|
10233
10398
|
return stringifyNumber.stringifyNumber(node);
|
|
10234
10399
|
}
|
|
10235
10400
|
var intOct = {
|
|
@@ -10640,11 +10805,11 @@ var require_int2 = __commonJS((exports) => {
|
|
|
10640
10805
|
const n = parseInt(str, radix);
|
|
10641
10806
|
return sign === "-" ? -1 * n : n;
|
|
10642
10807
|
}
|
|
10643
|
-
function intStringify(node, radix,
|
|
10808
|
+
function intStringify(node, radix, prefix3) {
|
|
10644
10809
|
const { value } = node;
|
|
10645
10810
|
if (intIdentify(value)) {
|
|
10646
10811
|
const str = value.toString(radix);
|
|
10647
|
-
return value < 0 ? "-" +
|
|
10812
|
+
return value < 0 ? "-" + prefix3 + str.substr(1) : prefix3 + str;
|
|
10648
10813
|
}
|
|
10649
10814
|
return stringifyNumber.stringifyNumber(node);
|
|
10650
10815
|
}
|
|
@@ -13558,9 +13723,9 @@ var require_lexer = __commonJS((exports) => {
|
|
|
13558
13723
|
}
|
|
13559
13724
|
}
|
|
13560
13725
|
*parseQuotedScalar() {
|
|
13561
|
-
const
|
|
13562
|
-
let end = this.buffer.indexOf(
|
|
13563
|
-
if (
|
|
13726
|
+
const quote3 = this.charAt(0);
|
|
13727
|
+
let end = this.buffer.indexOf(quote3, this.pos + 1);
|
|
13728
|
+
if (quote3 === "'") {
|
|
13564
13729
|
while (end !== -1 && this.buffer[end + 1] === "'")
|
|
13565
13730
|
end = this.buffer.indexOf("'", end + 2);
|
|
13566
13731
|
} else {
|
|
@@ -14845,10 +15010,10 @@ var SKILLS_HOME_ENV = "SKILLS_HOME";
|
|
|
14845
15010
|
var DEFAULT_SQLITE_FILENAME = "server.db";
|
|
14846
15011
|
var GLOBAL_CONFIG_FILENAME = "config.json";
|
|
14847
15012
|
function effectiveHome() {
|
|
14848
|
-
return process.env["HOME"] || process.env["USERPROFILE"] ||
|
|
15013
|
+
return process.env["HOME"] || process.env["USERPROFILE"] || homedir5() || "/tmp";
|
|
14849
15014
|
}
|
|
14850
15015
|
function legacyDataRoot() {
|
|
14851
|
-
return
|
|
15016
|
+
return join9(effectiveHome(), ".hasna", "skills");
|
|
14852
15017
|
}
|
|
14853
15018
|
function resolverDataRoot(home = effectiveHome(), env) {
|
|
14854
15019
|
return dataDir2({ app: "skills", home, env });
|
|
@@ -14857,7 +15022,7 @@ function adoptResolverDataRoot2(resolved, env = process.env) {
|
|
|
14857
15022
|
const dataOverride = env.HASNA_DATA_HOME;
|
|
14858
15023
|
if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
|
|
14859
15024
|
return true;
|
|
14860
|
-
return
|
|
15025
|
+
return existsSync10(join9(resolved, DEFAULT_SQLITE_FILENAME)) || existsSync10(join9(resolved, GLOBAL_CONFIG_FILENAME));
|
|
14861
15026
|
}
|
|
14862
15027
|
function exactDataRoot() {
|
|
14863
15028
|
for (const key of [DATA_DIR_ENV, HASNA_SKILLS_HOME_ENV, SKILLS_HOME_ENV]) {
|
|
@@ -15091,7 +15256,7 @@ function* bundleHashParts(files) {
|
|
|
15091
15256
|
yield new TextEncoder().encode("\x00");
|
|
15092
15257
|
}
|
|
15093
15258
|
function hashBundleFiles(files) {
|
|
15094
|
-
const hash =
|
|
15259
|
+
const hash = createHash5(CONTENT_HASH_ALGORITHM);
|
|
15095
15260
|
for (const part of bundleHashParts(files))
|
|
15096
15261
|
hash.update(part);
|
|
15097
15262
|
return hash.digest("hex");
|
|
@@ -15546,10 +15711,10 @@ function quoted(value) {
|
|
|
15546
15711
|
throw new Error("Invalid Hermes Skills command");
|
|
15547
15712
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
15548
15713
|
}
|
|
15549
|
-
function hermesHookDefinitions(
|
|
15550
|
-
if (!
|
|
15714
|
+
function hermesHookDefinitions(supervisor2) {
|
|
15715
|
+
if (!isAbsolute32(supervisor2.runtime) || !isAbsolute32(supervisor2.path))
|
|
15551
15716
|
throw new Error("Expected absolute Hermes supervisor paths");
|
|
15552
|
-
const entry = (event) => ({ command: `${quoted(
|
|
15717
|
+
const entry = (event) => ({ command: `${quoted(supervisor2.runtime)} ${quoted(supervisor2.path)} --event ${event}`, timeout: 15, ...event === "pre_tool_call" ? { fail_closed: true } : {} });
|
|
15553
15718
|
return { pre_llm_call: entry("pre_llm_call"), pre_tool_call: entry("pre_tool_call") };
|
|
15554
15719
|
}
|
|
15555
15720
|
function assertHermesEnvironment(home) {
|
|
@@ -15560,30 +15725,30 @@ function assertHermesEnvironment(home) {
|
|
|
15560
15725
|
throw new Error(`NATIVE_SKILL_DRIFT: Hermes ${variable} requires a dedicated discovery adapter; unset it before using this adapter`);
|
|
15561
15726
|
}
|
|
15562
15727
|
const selected = process.env.HERMES_HOME?.trim();
|
|
15563
|
-
if (selected && (!
|
|
15728
|
+
if (selected && (!isAbsolute32(selected) || resolve32(selected) !== join72(resolve32(home), ".hermes")))
|
|
15564
15729
|
throw new Error("NATIVE_SKILL_DRIFT: custom Hermes homes/profiles require their own reviewed bridge");
|
|
15565
15730
|
if (process.env.HERMES_ENABLE_PROJECT_PLUGINS?.trim() && !["0", "false", "no", "off"].includes(process.env.HERMES_ENABLE_PROJECT_PLUGINS.trim().toLowerCase()))
|
|
15566
15731
|
throw new Error("NATIVE_SKILL_DRIFT: Hermes project plugins require a dedicated discovery review");
|
|
15567
15732
|
}
|
|
15568
15733
|
function read(path) {
|
|
15569
15734
|
for (let current2 = path;; current2 = dirname32(current2)) {
|
|
15570
|
-
if (
|
|
15735
|
+
if (lstatSync32(current2, { throwIfNoEntry: false })?.isSymbolicLink())
|
|
15571
15736
|
throw new Error("NATIVE_SKILL_DRIFT: symlink Hermes protection input");
|
|
15572
15737
|
if (dirname32(current2) === current2)
|
|
15573
15738
|
break;
|
|
15574
15739
|
}
|
|
15575
|
-
const stat =
|
|
15740
|
+
const stat = lstatSync32(path, { throwIfNoEntry: false });
|
|
15576
15741
|
if (!stat)
|
|
15577
15742
|
return null;
|
|
15578
15743
|
if (!stat.isFile() || stat.size > 1024 * 1024)
|
|
15579
15744
|
throw new Error("NATIVE_SKILL_DRIFT: invalid Hermes protection input");
|
|
15580
|
-
return
|
|
15745
|
+
return readFileSync62(path, "utf8");
|
|
15581
15746
|
}
|
|
15582
|
-
function assertHermesProtection(home, config, command, profile,
|
|
15747
|
+
function assertHermesProtection(home, config, command, profile, supervisor2) {
|
|
15583
15748
|
assertHermesEnvironment(home);
|
|
15584
15749
|
if (read(join72(home, ".hermes", HERMES_OPT_OUT)) === null)
|
|
15585
15750
|
throw new Error("NATIVE_SKILL_DRIFT: Hermes bundled skill reseeding is not disabled; run skills hook install");
|
|
15586
|
-
if (!
|
|
15751
|
+
if (!supervisor2 || !isAbsolute32(supervisor2.path) || supervisor2.runtime !== process.execPath || read(supervisor2.path) !== renderHermesSupervisor(command, profile))
|
|
15587
15752
|
throw new Error("NATIVE_SKILL_DRIFT: Hermes owned supervisor changed; run skills hook install");
|
|
15588
15753
|
const raw = read(join72(home, ".hermes/shell-hooks-allowlist.json"));
|
|
15589
15754
|
let trust;
|
|
@@ -15592,7 +15757,7 @@ function assertHermesProtection(home, config, command, profile, supervisor) {
|
|
|
15592
15757
|
} catch {
|
|
15593
15758
|
throw new Error("NATIVE_SKILL_DRIFT: invalid Hermes native hook trust");
|
|
15594
15759
|
}
|
|
15595
|
-
for (const [event, entry] of Object.entries(hermesHookDefinitions(
|
|
15760
|
+
for (const [event, entry] of Object.entries(hermesHookDefinitions(supervisor2))) {
|
|
15596
15761
|
if (!Array.isArray(config.hooks?.[event]) || config.hooks[event].filter((actual) => sameEntry(actual, entry)).length !== 1)
|
|
15597
15762
|
throw new Error("NATIVE_SKILL_DRIFT: Hermes required native hooks changed; run skills hook install");
|
|
15598
15763
|
if (!Array.isArray(trust?.approvals) || !trust.approvals.some((approval) => object2(approval) && approval.event === event && approval.command === entry.command))
|
|
@@ -15610,7 +15775,7 @@ function assertHermesProtection(home, config, command, profile, supervisor) {
|
|
|
15610
15775
|
function assertNoHermesLegacyShadow(root) {
|
|
15611
15776
|
let entries = 0;
|
|
15612
15777
|
for (let cursor = resolve32(root);; cursor = dirname32(cursor)) {
|
|
15613
|
-
if (
|
|
15778
|
+
if (lstatSync32(cursor, { throwIfNoEntry: false })?.isSymbolicLink())
|
|
15614
15779
|
throw new Error("NATIVE_SKILL_DRIFT: symlink Hermes legacy discovery root");
|
|
15615
15780
|
if (dirname32(cursor) === cursor)
|
|
15616
15781
|
break;
|
|
@@ -15618,7 +15783,7 @@ function assertNoHermesLegacyShadow(root) {
|
|
|
15618
15783
|
function scan(path, depth) {
|
|
15619
15784
|
if (++entries > 1e4 || depth > 64)
|
|
15620
15785
|
throw new Error("NATIVE_SKILL_DRIFT: Hermes legacy discovery limit exceeded; review the root before continuing");
|
|
15621
|
-
const stat =
|
|
15786
|
+
const stat = lstatSync32(path, { throwIfNoEntry: false });
|
|
15622
15787
|
if (!stat)
|
|
15623
15788
|
return;
|
|
15624
15789
|
if (stat.isSymbolicLink())
|
|
@@ -15628,9 +15793,9 @@ function assertNoHermesLegacyShadow(root) {
|
|
|
15628
15793
|
throw new Error("NATIVE_SKILL_DRIFT: unsupported Hermes legacy discovery input");
|
|
15629
15794
|
return;
|
|
15630
15795
|
}
|
|
15631
|
-
if (
|
|
15796
|
+
if (lstatSync32(join72(path, "skills-cli.md"), { throwIfNoEntry: false }))
|
|
15632
15797
|
throw new Error("NATIVE_SKILL_DRIFT: legacy Hermes skills-cli.md can shadow the owned bridge; preserve and retire it before continuing");
|
|
15633
|
-
const hasDescriptor = Boolean(
|
|
15798
|
+
const hasDescriptor = Boolean(lstatSync32(join72(path, "SKILL.md"), { throwIfNoEntry: false }));
|
|
15634
15799
|
const directory = opendirSync(path);
|
|
15635
15800
|
try {
|
|
15636
15801
|
for (let child = directory.readSync();child; child = directory.readSync()) {
|
|
@@ -15763,10 +15928,10 @@ function discoveryByteBudget() {
|
|
|
15763
15928
|
function safe2(path) {
|
|
15764
15929
|
if (typeof path !== "string" || !isAbsolute5(path) || path !== resolve5(path) || path.length > AGENT_POLICY_LIMITS.pathCharacters || path.includes("\x00"))
|
|
15765
15930
|
throw new Error("Expected a canonical absolute raw discovery path");
|
|
15766
|
-
for (let cursor = path;; cursor =
|
|
15931
|
+
for (let cursor = path;; cursor = dirname52(cursor)) {
|
|
15767
15932
|
if (lstatSync5(cursor, { throwIfNoEntry: false })?.isSymbolicLink())
|
|
15768
15933
|
throw new Error("Refusing symlink raw discovery input");
|
|
15769
|
-
if (
|
|
15934
|
+
if (dirname52(cursor) === cursor)
|
|
15770
15935
|
break;
|
|
15771
15936
|
}
|
|
15772
15937
|
}
|
|
@@ -15794,9 +15959,9 @@ function hashRawDiscoveryFile(path, budget, changes) {
|
|
|
15794
15959
|
throw new Error("Unsupported or oversized raw discovery input");
|
|
15795
15960
|
if (initial.size > budget.remaining)
|
|
15796
15961
|
throw new Error("Raw discovery aggregate byte limit exceeded");
|
|
15797
|
-
const fd =
|
|
15962
|
+
const fd = openSync4(path, constants3.O_RDONLY | constants3.O_NOFOLLOW | constants3.O_NONBLOCK);
|
|
15798
15963
|
try {
|
|
15799
|
-
const opened =
|
|
15964
|
+
const opened = fstatSync3(fd);
|
|
15800
15965
|
if (!opened.isFile() || !same2(initial, opened))
|
|
15801
15966
|
throw new Error("Raw discovery input changed during open");
|
|
15802
15967
|
const hash = createHash32("sha256"), buffer = Buffer.alloc(64 * 1024);
|
|
@@ -15811,13 +15976,13 @@ function hashRawDiscoveryFile(path, budget, changes) {
|
|
|
15811
15976
|
consume(budget, count);
|
|
15812
15977
|
hash.update(buffer.subarray(0, count));
|
|
15813
15978
|
}
|
|
15814
|
-
const final =
|
|
15979
|
+
const final = fstatSync3(fd), current2 = lstatSync5(path, { throwIfNoEntry: false });
|
|
15815
15980
|
safe2(path);
|
|
15816
15981
|
if (!current2?.isFile() || !same2(opened, final) || !same2(final, current2) || bytes !== final.size)
|
|
15817
15982
|
throw new Error("Raw discovery input changed during read");
|
|
15818
15983
|
return hash.digest("hex");
|
|
15819
15984
|
} finally {
|
|
15820
|
-
|
|
15985
|
+
closeSync4(fd);
|
|
15821
15986
|
}
|
|
15822
15987
|
}
|
|
15823
15988
|
function identity2(stat, mutable = false) {
|
|
@@ -15863,7 +16028,7 @@ function trace(path) {
|
|
|
15863
16028
|
cursor = dirname6(cursor);
|
|
15864
16029
|
continue;
|
|
15865
16030
|
}
|
|
15866
|
-
const candidate =
|
|
16031
|
+
const candidate = join92(cursor, component);
|
|
15867
16032
|
if (candidate.length > AGENT_POLICY_LIMITS.pathCharacters)
|
|
15868
16033
|
throw new Error("Discovery resolved path length limit exceeded");
|
|
15869
16034
|
const stat = lstatSync6(candidate, { bigint: true, throwIfNoEntry: false });
|
|
@@ -16873,7 +17038,7 @@ function assertSafePath(path) {
|
|
|
16873
17038
|
}
|
|
16874
17039
|
function readRootAlias(home, agent) {
|
|
16875
17040
|
assertSafePath(home);
|
|
16876
|
-
const realHome =
|
|
17041
|
+
const realHome = realpathSync32(home), alias = join16(realHome, `.${agent}`);
|
|
16877
17042
|
const stat = lstatSync14(alias, { throwIfNoEntry: false, bigint: true });
|
|
16878
17043
|
if (!stat?.isSymbolicLink())
|
|
16879
17044
|
throw new Error(`Agent root alias changed after planning: ${alias}`);
|
|
@@ -16882,7 +17047,7 @@ function readRootAlias(home, agent) {
|
|
|
16882
17047
|
const targetStat = lstatSync14(targetPath, { throwIfNoEntry: false });
|
|
16883
17048
|
if (!targetStat?.isDirectory())
|
|
16884
17049
|
throw new Error(`Agent root alias must target an existing directory: ${alias}`);
|
|
16885
|
-
const target =
|
|
17050
|
+
const target = realpathSync32(targetPath), inside = relative4(realHome, target);
|
|
16886
17051
|
if (!inside || inside === ".." || inside.startsWith(`..${sep4}`) || isAbsolute9(inside))
|
|
16887
17052
|
throw new Error(`Agent root alias target must be inside the real home: ${alias}`);
|
|
16888
17053
|
return { agent, home: realHome, alias, target, link, aliasIdentity: `${stat.dev}:${stat.ino}:${stat.ctimeNs}`, targetIdentity: `${targetStat.dev}:${targetStat.ino}` };
|
|
@@ -17416,10 +17581,10 @@ function assertManagedAgentBridge(agent, options = {}) {
|
|
|
17416
17581
|
if (options.profileId !== undefined && options.profileId !== profile)
|
|
17417
17582
|
throw new Error("NATIVE_SKILL_DRIFT: the hook selection profile differs from its managed binding; restart the native client after reviewing skills hook install");
|
|
17418
17583
|
if (agent === "hermes") {
|
|
17419
|
-
const
|
|
17420
|
-
if (
|
|
17584
|
+
const supervisor2 = binding.supervisors?.hermes;
|
|
17585
|
+
if (supervisor2?.path !== join16(dataDir22, "agent-hooks", "hermes.js") || supervisor2?.sha256 !== sha(renderHermesSupervisor(command, profile)))
|
|
17421
17586
|
throw new Error("NATIVE_SKILL_DRIFT: Hermes supervisor binding changed; run skills hook install");
|
|
17422
|
-
assertHermesProtection(home, config, command, profile,
|
|
17587
|
+
assertHermesProtection(home, config, command, profile, supervisor2);
|
|
17423
17588
|
} else if (agent === "opencode") {
|
|
17424
17589
|
if (JSON.stringify(config.permission?.skill) !== JSON.stringify({ "*": "deny", [CLI_BRIDGE_NAME]: "allow" }) || readOptional(join16(home, ".config", "opencode", "plugins", "skills-cli.js")) !== renderOpenCodePlugin(command, profile))
|
|
17425
17590
|
throw new Error("NATIVE_SKILL_DRIFT: OpenCode bridge protection changed; run skills hook install");
|
|
@@ -17711,9 +17876,9 @@ function snapshotPluginTree(root, options = {}) {
|
|
|
17711
17876
|
const pins = directoryNames(absolute);
|
|
17712
17877
|
pluginNeed(pins.length <= 1024, "Native plugin pin count exceeds its limit");
|
|
17713
17878
|
directories.push({ path: absolute, stat: entryStat, names: pins });
|
|
17714
|
-
for (const
|
|
17715
|
-
pluginNeed(/^[1-9][0-9]{0,9}$/.test(
|
|
17716
|
-
const pinPath = join21(absolute,
|
|
17879
|
+
for (const pin2 of pins) {
|
|
17880
|
+
pluginNeed(/^[1-9][0-9]{0,9}$/.test(pin2) && Number(pin2) <= 2147483647, "Unrecognized native plugin pin name");
|
|
17881
|
+
const pinPath = join21(absolute, pin2), pinStat = stat(pinPath);
|
|
17717
17882
|
pluginNeed(pinStat, "Native plugin pin disappeared");
|
|
17718
17883
|
const bytes2 = readBytes(pinPath, 4096, "native-pin");
|
|
17719
17884
|
let value;
|
|
@@ -17723,7 +17888,7 @@ function snapshotPluginTree(root, options = {}) {
|
|
|
17723
17888
|
pluginRefusal("Malformed native plugin process pin");
|
|
17724
17889
|
}
|
|
17725
17890
|
pluginKeys(value, ["pid", "procStart", "procStartFt"]);
|
|
17726
|
-
pluginNeed(value.pid === Number(
|
|
17891
|
+
pluginNeed(value.pid === Number(pin2), "Native plugin pin identity differs from its name");
|
|
17727
17892
|
for (const key of ["procStart", "procStartFt"])
|
|
17728
17893
|
pluginNeed(value[key] === undefined || typeof value[key] === "string" && value[key].length <= 128 && !/[\x00-\x1f\x7f]/.test(value[key]), "Invalid native plugin process-start witness");
|
|
17729
17894
|
same3(pinStat, stat(pinPath));
|
|
@@ -17931,10 +18096,10 @@ function validatePlan(value) {
|
|
|
17931
18096
|
pluginNeed(observedEvidenceDigest(plan.observation) === plan.evidenceDigest, "Plugin receipt observed evidence changed");
|
|
17932
18097
|
pluginNeed(identityDigest(plan) === plan.planDigest, "Plugin receipt immutable identity changed");
|
|
17933
18098
|
}
|
|
17934
|
-
var
|
|
18099
|
+
var quote3 = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
17935
18100
|
var resolverGuard = 'exec 9<"$1"; actual=$(/usr/bin/sha256sum /proc/self/fd/9); actual=${actual%% *}; [ "sha256:$actual" = "$2" ]; exec /proc/self/fd/9 integration plugin resolve --binding "$3"';
|
|
17936
18101
|
function pluginResolverCommand(binding) {
|
|
17937
|
-
return `${
|
|
18102
|
+
return `${quote3("/bin/sh")} -eu -c ${quote3(resolverGuard)} plugin-resolver ${quote3(binding.target.resolver.executable)} ${quote3(binding.target.resolver.digest)} ${quote3(bindingId(binding))}`;
|
|
17938
18103
|
}
|
|
17939
18104
|
function pluginBindingPath(storeRoot, id) {
|
|
17940
18105
|
pluginNeed(/^[a-f0-9]{64}$/.test(id), "Invalid plugin binding identifier");
|
|
@@ -21661,11 +21826,11 @@ function isValidCIDRv6(value) {
|
|
|
21661
21826
|
const parts = value.split("/");
|
|
21662
21827
|
if (parts.length !== 2)
|
|
21663
21828
|
return false;
|
|
21664
|
-
const [address,
|
|
21665
|
-
if (!
|
|
21829
|
+
const [address, prefix3] = parts;
|
|
21830
|
+
if (!prefix3)
|
|
21666
21831
|
return false;
|
|
21667
|
-
const prefixNum = Number(
|
|
21668
|
-
if (`${prefixNum}` !==
|
|
21832
|
+
const prefixNum = Number(prefix3);
|
|
21833
|
+
if (`${prefixNum}` !== prefix3)
|
|
21669
21834
|
return false;
|
|
21670
21835
|
if (prefixNum < 0 || prefixNum > 128)
|
|
21671
21836
|
return false;
|
|
@@ -31547,8 +31712,8 @@ function generateStringFormatCheck(doc, ctx, def, accessor) {
|
|
|
31547
31712
|
doc.write(`if (!${accessor}.includes(${esc(def.includes)})) return INVALID;`);
|
|
31548
31713
|
break;
|
|
31549
31714
|
case "starts_with": {
|
|
31550
|
-
const
|
|
31551
|
-
doc.write(`if (${accessor}.slice(0, ${
|
|
31715
|
+
const prefix3 = def.prefix;
|
|
31716
|
+
doc.write(`if (${accessor}.slice(0, ${prefix3.length}) !== ${esc(prefix3)}) return INVALID;`);
|
|
31552
31717
|
break;
|
|
31553
31718
|
}
|
|
31554
31719
|
case "ends_with": {
|
|
@@ -32281,8 +32446,8 @@ function generateDiscriminatedUnionCheck(doc, ctx, def, accessor) {
|
|
|
32281
32446
|
claimed.add(value);
|
|
32282
32447
|
}
|
|
32283
32448
|
const conditions = Array.from(values, (value) => literalEquality(ctx, discVar, value));
|
|
32284
|
-
const
|
|
32285
|
-
doc.write(`${
|
|
32449
|
+
const prefix3 = firstBranch ? "if" : "else if";
|
|
32450
|
+
doc.write(`${prefix3} (${conditions.join(" || ")}) {`);
|
|
32286
32451
|
doc.indented((d) => {
|
|
32287
32452
|
const branchOutput = generateCheck(d, ctx, option, accessor);
|
|
32288
32453
|
d.write(`${outputVar} = ${branchOutput};`);
|
|
@@ -33136,12 +33301,12 @@ function _includes(includes, params) {
|
|
|
33136
33301
|
includes
|
|
33137
33302
|
});
|
|
33138
33303
|
}
|
|
33139
|
-
function _startsWith(
|
|
33304
|
+
function _startsWith(prefix3, params) {
|
|
33140
33305
|
return new $ZodCheckStartsWith({
|
|
33141
33306
|
check: "string_format",
|
|
33142
33307
|
format: "starts_with",
|
|
33143
33308
|
...normalizeParams2(params),
|
|
33144
|
-
prefix:
|
|
33309
|
+
prefix: prefix3
|
|
33145
33310
|
});
|
|
33146
33311
|
}
|
|
33147
33312
|
function _endsWith(suffix, params) {
|
|
@@ -39109,7 +39274,7 @@ function coordinateClaudeSettings(before, replacement, options) {
|
|
|
39109
39274
|
const plan = planClaudeHookEventsUpdate({
|
|
39110
39275
|
...options,
|
|
39111
39276
|
events: selected,
|
|
39112
|
-
expectedSettingsSha256: before === null ? "absent" :
|
|
39277
|
+
expectedSettingsSha256: before === null ? "absent" : createHash6("sha256").update(before).digest("hex"),
|
|
39113
39278
|
replacement
|
|
39114
39279
|
});
|
|
39115
39280
|
if (!plan)
|
|
@@ -39120,7 +39285,7 @@ function coordinateClaudeSettings(before, replacement, options) {
|
|
|
39120
39285
|
|
|
39121
39286
|
// src/lib/installer.ts
|
|
39122
39287
|
var __dirname2 = dirname7(fileURLToPath2(import.meta.url));
|
|
39123
|
-
var HOOKS_DIR =
|
|
39288
|
+
var HOOKS_DIR = existsSync11(join10(__dirname2, "..", "..", "hooks", "hook-gitguard")) ? join10(__dirname2, "..", "..", "hooks") : join10(__dirname2, "..", "hooks");
|
|
39124
39289
|
function normalizeHookName(name) {
|
|
39125
39290
|
return name.startsWith("hook-") ? name : `hook-${name}`;
|
|
39126
39291
|
}
|
|
@@ -39208,11 +39373,11 @@ function getSettingsPath(scope = "global", target = "claude", codewithConfigPath
|
|
|
39208
39373
|
return globalOverride;
|
|
39209
39374
|
const dir = getTargetSettingsDir(target);
|
|
39210
39375
|
if (target === "codex")
|
|
39211
|
-
return join10(scope === "project" ? process.cwd() :
|
|
39376
|
+
return join10(scope === "project" ? process.cwd() : homedir6(), dir, "hooks.json");
|
|
39212
39377
|
if (scope === "project") {
|
|
39213
39378
|
return target === "codewith" ? join10(process.cwd(), dir, "config.toml") : join10(process.cwd(), dir, "settings.json");
|
|
39214
39379
|
}
|
|
39215
|
-
return target === "codewith" ? join10(
|
|
39380
|
+
return target === "codewith" ? join10(homedir6(), dir, "config.toml") : join10(homedir6(), dir, "settings.json");
|
|
39216
39381
|
}
|
|
39217
39382
|
function getHookPath(name) {
|
|
39218
39383
|
const shortName = shortHookName(name);
|
|
@@ -39222,7 +39387,7 @@ function getHookPath(name) {
|
|
|
39222
39387
|
return join10(HOOKS_DIR, normalizeHookName(shortName));
|
|
39223
39388
|
}
|
|
39224
39389
|
function hookExists(name) {
|
|
39225
|
-
return
|
|
39390
|
+
return existsSync11(getHookPath(name));
|
|
39226
39391
|
}
|
|
39227
39392
|
var settingsPreimages = new WeakMap;
|
|
39228
39393
|
function readSettings2(scope = "global", target = "claude") {
|
|
@@ -39230,7 +39395,7 @@ function readSettings2(scope = "global", target = "claude") {
|
|
|
39230
39395
|
if (target === "codex")
|
|
39231
39396
|
return readCodexSettings(path);
|
|
39232
39397
|
try {
|
|
39233
|
-
if (
|
|
39398
|
+
if (existsSync11(path)) {
|
|
39234
39399
|
const text3 = readFileSync7(path, "utf-8"), value = JSON.parse(text3);
|
|
39235
39400
|
settingsPreimages.set(value, { path, text: text3 });
|
|
39236
39401
|
return value;
|
|
@@ -39239,14 +39404,14 @@ function readSettings2(scope = "global", target = "claude") {
|
|
|
39239
39404
|
console.warn("[hooks] Cannot read hook settings; repair the selected file before installation.");
|
|
39240
39405
|
}
|
|
39241
39406
|
const empty = {};
|
|
39242
|
-
if (!
|
|
39407
|
+
if (!existsSync11(path))
|
|
39243
39408
|
settingsPreimages.set(empty, { path, text: null });
|
|
39244
39409
|
return empty;
|
|
39245
39410
|
}
|
|
39246
39411
|
function settingsIdentity(path) {
|
|
39247
|
-
if (
|
|
39412
|
+
if (existsSync11(path))
|
|
39248
39413
|
return realpathSync5(path);
|
|
39249
|
-
return
|
|
39414
|
+
return existsSync11(dirname7(path)) ? join10(realpathSync5(dirname7(path)), basename3(path)) : resolve7(path);
|
|
39250
39415
|
}
|
|
39251
39416
|
function writeSettings(settings, scope = "global", target = "claude", expectedSettingsSha256) {
|
|
39252
39417
|
const path = getSettingsPath(scope, target);
|
|
@@ -39258,21 +39423,21 @@ function writeSettings(settings, scope = "global", target = "claude", expectedSe
|
|
|
39258
39423
|
throw new Error("Hook settings require a valid captured preimage.");
|
|
39259
39424
|
assertSettingsPreimage(captured.text, expectedSettingsSha256);
|
|
39260
39425
|
}
|
|
39261
|
-
if (target === "claude" && settingsIdentity(path) === settingsIdentity(join10(
|
|
39426
|
+
if (target === "claude" && settingsIdentity(path) === settingsIdentity(join10(homedir6(), ".claude/settings.json"))) {
|
|
39262
39427
|
const before = settingsPreimages.get(settings);
|
|
39263
39428
|
if (!before || before.path !== path)
|
|
39264
39429
|
throw new Error("Claude settings require a valid captured preimage before installation.");
|
|
39265
|
-
const current2 =
|
|
39430
|
+
const current2 = existsSync11(path) ? readFileSync7(path, "utf8") : null;
|
|
39266
39431
|
if (current2 !== before.text)
|
|
39267
39432
|
throw new Error("Claude settings changed during installation; no settings were written.");
|
|
39268
39433
|
if (coordinateClaudeSettings(before.text, JSON.stringify(settings, null, 2) + `
|
|
39269
|
-
`, { home:
|
|
39434
|
+
`, { home: homedir6() }))
|
|
39270
39435
|
return;
|
|
39271
39436
|
}
|
|
39272
39437
|
if (expectedSettingsSha256 !== undefined)
|
|
39273
39438
|
return writeHookSettingsPreimage(path, settings, captured.text);
|
|
39274
39439
|
const dir = dirname7(path);
|
|
39275
|
-
if (!
|
|
39440
|
+
if (!existsSync11(dir)) {
|
|
39276
39441
|
mkdirSync6(dir, { recursive: true });
|
|
39277
39442
|
}
|
|
39278
39443
|
writeFileSync5(path, JSON.stringify(settings, null, 2) + `
|
|
@@ -39371,7 +39536,7 @@ function buildCodewithTomlFragment(name, profile, mementos) {
|
|
|
39371
39536
|
function readCodewithConfig(scope, configPath) {
|
|
39372
39537
|
const path = getSettingsPath(scope, "codewith", configPath);
|
|
39373
39538
|
try {
|
|
39374
|
-
return
|
|
39539
|
+
return existsSync11(path) ? readFileSync7(path, "utf-8") : "";
|
|
39375
39540
|
} catch {
|
|
39376
39541
|
return "";
|
|
39377
39542
|
}
|
|
@@ -39379,9 +39544,9 @@ function readCodewithConfig(scope, configPath) {
|
|
|
39379
39544
|
function appendCodewithFragment(fragment, scope, configPath) {
|
|
39380
39545
|
const path = getSettingsPath(scope, "codewith", configPath);
|
|
39381
39546
|
const dir = dirname7(path);
|
|
39382
|
-
if (!
|
|
39547
|
+
if (!existsSync11(dir))
|
|
39383
39548
|
mkdirSync6(dir, { recursive: true });
|
|
39384
|
-
const existing =
|
|
39549
|
+
const existing = existsSync11(path) ? readFileSync7(path, "utf-8") : "";
|
|
39385
39550
|
const sep3 = existing.trim() ? `
|
|
39386
39551
|
|
|
39387
39552
|
` : "";
|
|
@@ -39577,8 +39742,40 @@ function previewNativeSafetyRegistration(target, scope = "global") {
|
|
|
39577
39742
|
throw new Error("Native safety registration could not be rendered.");
|
|
39578
39743
|
return result;
|
|
39579
39744
|
}
|
|
39745
|
+
function previewNativeReadinessRegistration(target) {
|
|
39746
|
+
let result;
|
|
39747
|
+
registerHook("trash-readiness", "global", target, undefined, undefined, (settings) => {
|
|
39748
|
+
const before = target === "codex" ? capturedCodexSettings(settings) : settingsPreimages.get(settings)?.text;
|
|
39749
|
+
if (before === undefined)
|
|
39750
|
+
throw new Error("Hook settings require a valid captured preimage.");
|
|
39751
|
+
result = { before, after: JSON.stringify(settings, null, 2) + `
|
|
39752
|
+
`, command: installedNativeReadinessCommand(target) };
|
|
39753
|
+
});
|
|
39754
|
+
if (!result)
|
|
39755
|
+
throw new Error("Native readiness registration could not be rendered.");
|
|
39756
|
+
return result;
|
|
39757
|
+
}
|
|
39758
|
+
function installNativeReadinessRegistration(target, expectedSettingsSha256) {
|
|
39759
|
+
if (!/^(?:absent|[a-f0-9]{64})$/.test(expectedSettingsSha256))
|
|
39760
|
+
throw new Error("Exact predecessor required");
|
|
39761
|
+
registerHook("trash-readiness", "global", target, undefined, undefined, (settings, scope, selected) => writeSettings(settings, scope, selected, expectedSettingsSha256));
|
|
39762
|
+
return { success: true };
|
|
39763
|
+
}
|
|
39580
39764
|
function registerHook(name, scope = "global", target = "claude", profile, mementos, write = writeSettings) {
|
|
39581
|
-
const
|
|
39765
|
+
const readiness = name === "trash-readiness";
|
|
39766
|
+
if (readiness && (target === "gemini" || profile || scope !== "global"))
|
|
39767
|
+
throw new Error("Unsupported readiness target");
|
|
39768
|
+
const meta3 = readiness ? {
|
|
39769
|
+
name,
|
|
39770
|
+
event: "SessionStart",
|
|
39771
|
+
matcher: "^(startup|resume|clear|fork)$",
|
|
39772
|
+
timeoutSeconds: 20,
|
|
39773
|
+
displayName: "Trash Readiness",
|
|
39774
|
+
description: "Read-only native startup readiness",
|
|
39775
|
+
version: "0.1.0",
|
|
39776
|
+
category: "Environment",
|
|
39777
|
+
tags: ["trash", "startup"]
|
|
39778
|
+
} : isNativeSafetyName(name) ? getHook(name) : resolveHookMeta(name);
|
|
39582
39779
|
if (!meta3)
|
|
39583
39780
|
return;
|
|
39584
39781
|
const eventKeys = getHookEvents(meta3).map((event) => {
|
|
@@ -39595,18 +39792,20 @@ function registerHook(name, scope = "global", target = "claude", profile, mement
|
|
|
39595
39792
|
const settings = readSettings2(scope, target);
|
|
39596
39793
|
if (!settings.hooks)
|
|
39597
39794
|
settings.hooks = {};
|
|
39598
|
-
if (isNativeSafetyName(name)) {
|
|
39795
|
+
if (isNativeSafetyName(name) || readiness) {
|
|
39599
39796
|
for (const groups of Object.values(settings.hooks))
|
|
39600
39797
|
for (const group of groups)
|
|
39601
39798
|
for (const hook of group.hooks ?? []) {
|
|
39602
39799
|
if (typeof hook.command === "string" && /(?:^|\s)hasna-native-safety-v\d+(?:\s|$)/.test(hook.command) && !registrationName(hook.command)) {
|
|
39603
39800
|
throw new Error("Unknown managed safety definition; reconcile its exact version before replacing it.");
|
|
39604
39801
|
}
|
|
39802
|
+
if (readiness && typeof hook.command === "string" && hook.command.includes("hasna-native-readiness-v") && registrationName(hook.command) !== "trash-readiness")
|
|
39803
|
+
throw new Error("Unknown managed readiness definition");
|
|
39605
39804
|
}
|
|
39606
39805
|
}
|
|
39607
39806
|
const retained = name === "mementos-context" && mementos === undefined ? retainedMementosRegistration(Object.values(settings.hooks).flatMap((groups) => Array.isArray(groups) ? groups.flatMap((group) => Array.isArray(group.hooks) ? group.hooks.map((hook) => hook.command).filter((command) => typeof command === "string") : []) : [])) : undefined;
|
|
39608
|
-
const hookCommand = isNativeSafetyName(name) ? installedNativeSafetyCommand(name) : (profile ? `hooks run ${name} --profile ${profile}` : `hooks run ${name}`) + mementosCommandSuffix(mementos ?? retained);
|
|
39609
|
-
if (target === "codex" || target === "claude" && isNativeSafetyName(name)) {
|
|
39807
|
+
const hookCommand = readiness ? installedNativeReadinessCommand(target) : isNativeSafetyName(name) ? installedNativeSafetyCommand(name) : (profile ? `hooks run ${name} --profile ${profile}` : `hooks run ${name}`) + mementosCommandSuffix(mementos ?? retained);
|
|
39808
|
+
if (target === "codex" || target === "claude" && (isNativeSafetyName(name) || readiness)) {
|
|
39610
39809
|
const positions = [];
|
|
39611
39810
|
for (const [event, groups] of Object.entries(settings.hooks)) {
|
|
39612
39811
|
for (const entry of groups) {
|
|
@@ -39616,8 +39815,8 @@ function registerHook(name, scope = "global", target = "claude", profile, mement
|
|
|
39616
39815
|
if (match && (match[1] ?? match[2]) === name || registrationName(command) === name) {
|
|
39617
39816
|
if (registrationName(command) !== name)
|
|
39618
39817
|
throw new Error("Unrecognized Codex registration; reconcile it before updating this hook.");
|
|
39619
|
-
if (isNativeSafetyName(name) && entry.hooks[index].async !== undefined && entry.hooks[index].async !== false)
|
|
39620
|
-
throw new Error("Asynchronous
|
|
39818
|
+
if ((isNativeSafetyName(name) || readiness) && (entry.hooks[index].async !== undefined && entry.hooks[index].async !== false || entry.hooks[index].asyncRewake === true))
|
|
39819
|
+
throw new Error("Asynchronous managed native hooks are refused; synchronous registration is required");
|
|
39621
39820
|
positions.push({ event, entry, index });
|
|
39622
39821
|
}
|
|
39623
39822
|
}
|
|
@@ -39795,7 +39994,7 @@ function codewithHasHookEntryInText(configText, name) {
|
|
|
39795
39994
|
}
|
|
39796
39995
|
function codewithHasHookEntry(name, scope = "global") {
|
|
39797
39996
|
const path = getSettingsPath(scope, "codewith");
|
|
39798
|
-
if (!
|
|
39997
|
+
if (!existsSync11(path))
|
|
39799
39998
|
return false;
|
|
39800
39999
|
try {
|
|
39801
40000
|
return codewithHasHookEntryInText(readFileSync7(path, "utf-8"), name);
|
|
@@ -39843,7 +40042,7 @@ function uninstallHook(name, scope = "global", target = "claude") {
|
|
|
39843
40042
|
const registrationsRemaining = [];
|
|
39844
40043
|
if (target === "codewith" || target === "all") {
|
|
39845
40044
|
const codewithPath = getSettingsPath(scope, "codewith");
|
|
39846
|
-
if (
|
|
40045
|
+
if (existsSync11(codewithPath)) {
|
|
39847
40046
|
const before = readFileSync7(codewithPath, "utf-8");
|
|
39848
40047
|
const after = removeCodewithHookEntry(before, shortName);
|
|
39849
40048
|
if (after.removed) {
|
|
@@ -39886,7 +40085,7 @@ function uninstallHook(name, scope = "global", target = "claude") {
|
|
|
39886
40085
|
// src/index.ts
|
|
39887
40086
|
init_profiles();
|
|
39888
40087
|
init_config();
|
|
39889
|
-
import { existsSync as
|
|
40088
|
+
import { existsSync as existsSync16, readFileSync as readFileSync25 } from "fs";
|
|
39890
40089
|
|
|
39891
40090
|
// src/lib/transport.ts
|
|
39892
40091
|
init_transport();
|
|
@@ -40026,15 +40225,15 @@ init_manifest();
|
|
|
40026
40225
|
// src/lib/custom-install.ts
|
|
40027
40226
|
init_config();
|
|
40028
40227
|
init_manifest();
|
|
40029
|
-
import { existsSync as
|
|
40030
|
-
import { join as
|
|
40228
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync11, mkdtempSync, readFileSync as readFileSync12, rmSync as rmSync2 } from "fs";
|
|
40229
|
+
import { join as join13 } from "path";
|
|
40031
40230
|
import { tmpdir } from "os";
|
|
40032
40231
|
function manifestAt(dir) {
|
|
40033
|
-
const path =
|
|
40034
|
-
if (!
|
|
40232
|
+
const path = join13(dir, "manifest.json");
|
|
40233
|
+
if (!existsSync13(path))
|
|
40035
40234
|
return;
|
|
40036
40235
|
try {
|
|
40037
|
-
return parseManifest(
|
|
40236
|
+
return parseManifest(readFileSync12(path, "utf-8"));
|
|
40038
40237
|
} catch {
|
|
40039
40238
|
return;
|
|
40040
40239
|
}
|
|
@@ -40053,8 +40252,8 @@ function writeFromManifest(manifest, srcDir, source, kind) {
|
|
|
40053
40252
|
return { name, version: manifest.version, kind, source, dir, scriptPath };
|
|
40054
40253
|
}
|
|
40055
40254
|
async function cloneToTemp(gitUrl) {
|
|
40056
|
-
const tmp = mkdtempSync(
|
|
40057
|
-
const proc = Bun.spawn(["git", "clone", "--depth", "1", "--quiet", gitUrl,
|
|
40255
|
+
const tmp = mkdtempSync(join13(tmpdir(), "hooks-install-"));
|
|
40256
|
+
const proc = Bun.spawn(["git", "clone", "--depth", "1", "--quiet", gitUrl, join13(tmp, "repo")], {
|
|
40058
40257
|
stdout: "pipe",
|
|
40059
40258
|
stderr: "pipe"
|
|
40060
40259
|
});
|
|
@@ -40067,7 +40266,7 @@ async function cloneToTemp(gitUrl) {
|
|
|
40067
40266
|
rmSync2(tmp, { recursive: true, force: true });
|
|
40068
40267
|
throw new Error(`git clone failed (${exitCode}): ${stderr.trim()}`);
|
|
40069
40268
|
}
|
|
40070
|
-
return
|
|
40269
|
+
return join13(tmp, "repo");
|
|
40071
40270
|
}
|
|
40072
40271
|
function isGitUrl(value) {
|
|
40073
40272
|
return value.startsWith("git@") || value.startsWith("ssh://") || value.startsWith("file://") || value.endsWith(".git");
|
|
@@ -40077,7 +40276,7 @@ function isCustomSource(value) {
|
|
|
40077
40276
|
return true;
|
|
40078
40277
|
if (value.startsWith("http://") || value.startsWith("https://"))
|
|
40079
40278
|
return true;
|
|
40080
|
-
return
|
|
40279
|
+
return existsSync13(value);
|
|
40081
40280
|
}
|
|
40082
40281
|
async function installCustomSource(source) {
|
|
40083
40282
|
if (isGitUrl(source)) {
|
|
@@ -40091,11 +40290,11 @@ async function installCustomSource(source) {
|
|
|
40091
40290
|
if (source.startsWith("http://") || source.startsWith("https://")) {
|
|
40092
40291
|
return installManifestUrl(source);
|
|
40093
40292
|
}
|
|
40094
|
-
if (!
|
|
40293
|
+
if (!existsSync13(source)) {
|
|
40095
40294
|
throw new Error(`No such local path: ${source}`);
|
|
40096
40295
|
}
|
|
40097
40296
|
if (source.endsWith("manifest.json")) {
|
|
40098
|
-
const manifest = parseManifest(
|
|
40297
|
+
const manifest = parseManifest(readFileSync12(source, "utf-8"));
|
|
40099
40298
|
return writeFromManifest(manifest, source.substring(0, source.lastIndexOf("/")), source, "local");
|
|
40100
40299
|
}
|
|
40101
40300
|
return copyManifestDir(source, source, "local");
|
|
@@ -40125,16 +40324,16 @@ async function installManifestUrl(url2) {
|
|
|
40125
40324
|
return { name, version: manifest.version, kind: "url", source: url2, dir, scriptPath };
|
|
40126
40325
|
}
|
|
40127
40326
|
// src/lib/sync.ts
|
|
40128
|
-
import { createHash as
|
|
40129
|
-
import { readFileSync as
|
|
40327
|
+
import { createHash as createHash7 } from "crypto";
|
|
40328
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
40130
40329
|
init_db();
|
|
40131
40330
|
init_store();
|
|
40132
40331
|
init_manifest();
|
|
40133
40332
|
function sha256OfText(text3) {
|
|
40134
|
-
return
|
|
40333
|
+
return createHash7("sha256").update(text3).digest("hex");
|
|
40135
40334
|
}
|
|
40136
40335
|
function sha256OfPath(path) {
|
|
40137
|
-
return
|
|
40336
|
+
return createHash7("sha256").update(readFileSync14(path)).digest("hex");
|
|
40138
40337
|
}
|
|
40139
40338
|
function collectBundledCatalog() {
|
|
40140
40339
|
const out = [];
|
|
@@ -40189,13 +40388,13 @@ async function fetchRemoteState(apiUrl, apiKey) {
|
|
|
40189
40388
|
}
|
|
40190
40389
|
return { catalog: catalog.hooks, remoteLock };
|
|
40191
40390
|
}
|
|
40192
|
-
function classifyRemotePin(
|
|
40193
|
-
const pinnedLocally =
|
|
40391
|
+
function classifyRemotePin(pin2, remoteVersion, remoteSha) {
|
|
40392
|
+
const pinnedLocally = pin2 !== undefined && pin2.version === remoteVersion && pin2.sha256 === remoteSha;
|
|
40194
40393
|
if (pinnedLocally)
|
|
40195
40394
|
return "unchanged";
|
|
40196
|
-
if (
|
|
40395
|
+
if (pin2 !== undefined && pin2.pinned === true && pin2.version !== remoteVersion)
|
|
40197
40396
|
return "unchanged";
|
|
40198
|
-
if (
|
|
40397
|
+
if (pin2 !== undefined)
|
|
40199
40398
|
return "updated";
|
|
40200
40399
|
return "added";
|
|
40201
40400
|
}
|
|
@@ -40205,7 +40404,7 @@ function computeDiff(catalog, remoteLock) {
|
|
|
40205
40404
|
for (const entry of catalog) {
|
|
40206
40405
|
if (!entry.name || !entry.version || !entry.sha256)
|
|
40207
40406
|
continue;
|
|
40208
|
-
const
|
|
40407
|
+
const pin2 = local.hooks[entry.name];
|
|
40209
40408
|
if (remoteLock) {
|
|
40210
40409
|
const remotePin = remoteLock.hooks[entry.name];
|
|
40211
40410
|
if (!remotePin) {
|
|
@@ -40215,9 +40414,9 @@ function computeDiff(catalog, remoteLock) {
|
|
|
40215
40414
|
if (remotePin.version !== entry.version) {
|
|
40216
40415
|
throw new Error(`remote registry is ambiguous for '${entry.name}': lock pins ${remotePin.version} but catalog latest is ${entry.version}. ` + `Fix the registry (or its lock) before syncing \u2014 nothing was changed.`);
|
|
40217
40416
|
}
|
|
40218
|
-
diff[classifyRemotePin(
|
|
40417
|
+
diff[classifyRemotePin(pin2, remotePin.version, remotePin.sha256)].push(entry.name);
|
|
40219
40418
|
} else {
|
|
40220
|
-
diff[classifyRemotePin(
|
|
40419
|
+
diff[classifyRemotePin(pin2, entry.version, entry.sha256)].push(entry.name);
|
|
40221
40420
|
}
|
|
40222
40421
|
}
|
|
40223
40422
|
return diff;
|
|
@@ -40435,8 +40634,8 @@ async function installPinnedFromBundled(name, version2) {
|
|
|
40435
40634
|
// src/serve.ts
|
|
40436
40635
|
init_manifest();
|
|
40437
40636
|
init_store();
|
|
40438
|
-
import { readFileSync as
|
|
40439
|
-
import { dirname as dirname17, join as
|
|
40637
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
40638
|
+
import { dirname as dirname17, join as join17 } from "path";
|
|
40440
40639
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
40441
40640
|
|
|
40442
40641
|
// src/lib/secure-compare.ts
|
|
@@ -40811,8 +41010,8 @@ var SERVE_HOST = "127.0.0.1";
|
|
|
40811
41010
|
var SERVE_SERVICE_NAME = "hooks-registry";
|
|
40812
41011
|
function packageVersion() {
|
|
40813
41012
|
try {
|
|
40814
|
-
const pkgPath =
|
|
40815
|
-
return JSON.parse(
|
|
41013
|
+
const pkgPath = join17(dirname17(fileURLToPath4(import.meta.url)), "..", "package.json");
|
|
41014
|
+
return JSON.parse(readFileSync15(pkgPath, "utf-8")).version || "0.0.0";
|
|
40816
41015
|
} catch {
|
|
40817
41016
|
return "0.0.0";
|
|
40818
41017
|
}
|
|
@@ -40913,8 +41112,8 @@ function handleServeRequest(req, apiKey) {
|
|
|
40913
41112
|
for (const entry of catalog)
|
|
40914
41113
|
byVersion[entry.name] = entry.versions;
|
|
40915
41114
|
const hooks = {};
|
|
40916
|
-
for (const [name,
|
|
40917
|
-
hooks[name] = { ...
|
|
41115
|
+
for (const [name, pin2] of Object.entries(lock.hooks)) {
|
|
41116
|
+
hooks[name] = { ...pin2, versions: byVersion[name] ?? [pin2.version] };
|
|
40918
41117
|
}
|
|
40919
41118
|
return json2({ hooks });
|
|
40920
41119
|
});
|
|
@@ -41057,23 +41256,23 @@ async function provisionCloudflareResources(options) {
|
|
|
41057
41256
|
return result;
|
|
41058
41257
|
}
|
|
41059
41258
|
// src/lib/codex-safety-trust.ts
|
|
41060
|
-
import { createHash as
|
|
41061
|
-
import { constants as
|
|
41062
|
-
import { dirname as dirname21, isAbsolute as isAbsolute18, join as
|
|
41063
|
-
import { homedir as
|
|
41259
|
+
import { createHash as createHash17, randomUUID as randomUUID3 } from "crypto";
|
|
41260
|
+
import { constants as constants14, closeSync as closeSync15, fsyncSync as fsyncSync5, fstatSync as fstatSync7, lstatSync as lstatSync15, mkdirSync as mkdirSync13, openSync as openSync15, readFileSync as readFileSync20, realpathSync as realpathSync10, writeFileSync as writeFileSync11 } from "fs";
|
|
41261
|
+
import { dirname as dirname21, isAbsolute as isAbsolute18, join as join20, relative as relative2 } from "path";
|
|
41262
|
+
import { homedir as homedir8 } from "os";
|
|
41064
41263
|
|
|
41065
41264
|
// src/lib/codex-safety-check.ts
|
|
41066
41265
|
import { spawn, execFile as execFile2 } from "child_process";
|
|
41067
|
-
import { createHash as
|
|
41068
|
-
import { constants as
|
|
41266
|
+
import { createHash as createHash12 } from "crypto";
|
|
41267
|
+
import { constants as constants12, openSync as openSync7, closeSync as closeSync7, fstatSync as fstatSync6, lstatSync as lstatSync10, readFileSync as readFileSync19, realpathSync as realpathSync9 } from "fs";
|
|
41069
41268
|
import { dirname as dirname20, isAbsolute as isAbsolute17, resolve as resolve17 } from "path";
|
|
41070
41269
|
import { promisify } from "util";
|
|
41071
41270
|
|
|
41072
41271
|
// src/native-safety.ts
|
|
41073
41272
|
import { execFile } from "child_process";
|
|
41074
|
-
import { closeSync as
|
|
41075
|
-
import { homedir as
|
|
41076
|
-
import { dirname as dirname19, isAbsolute as isAbsolute15, join as
|
|
41273
|
+
import { closeSync as closeSync6, constants as constants4, existsSync as existsSync14, fstatSync as fstatSync5, fsyncSync, linkSync as linkSync3, lstatSync as lstatSync9, mkdirSync as mkdirSync12, openSync as openSync6, readFileSync as readFileSync17, realpathSync as realpathSync8, renameSync as renameSync3, unlinkSync as unlinkSync5, writeFileSync as writeFileSync10 } from "fs";
|
|
41274
|
+
import { homedir as homedir7 } from "os";
|
|
41275
|
+
import { dirname as dirname19, isAbsolute as isAbsolute15, join as join19, resolve as resolve15 } from "path";
|
|
41077
41276
|
class NativeSafetyError extends Error {
|
|
41078
41277
|
code;
|
|
41079
41278
|
reason;
|
|
@@ -41143,7 +41342,7 @@ async function evaluateRecord(event, record3, signal, executionAgent) {
|
|
|
41143
41342
|
} catch {}
|
|
41144
41343
|
throw new NativeSafetyError("guard_failed");
|
|
41145
41344
|
}
|
|
41146
|
-
async function verifyNativeSafetyCommand(command, home =
|
|
41345
|
+
async function verifyNativeSafetyCommand(command, home = homedir7()) {
|
|
41147
41346
|
home = canonicalHome(home);
|
|
41148
41347
|
const record3 = parseRecord(JSON.stringify({ schema: "hasna.hooks.native-safety.v1", harness: "sumi", command }), home);
|
|
41149
41348
|
const verdict = await evaluateRecord({ hook_event_name: "PreToolUse", tool_name: "Bash", cwd: home, tool_input: { command: "rm -- .hasna-trash-verification-only" } }, record3);
|
|
@@ -41167,7 +41366,7 @@ function need5(condition, code2) {
|
|
|
41167
41366
|
if (!condition)
|
|
41168
41367
|
throw new CodexSafetyError(code2);
|
|
41169
41368
|
}
|
|
41170
|
-
var digest3 = (bytes) =>
|
|
41369
|
+
var digest3 = (bytes) => createHash12("sha256").update(bytes).digest("hex");
|
|
41171
41370
|
function nativeHeader(bytes) {
|
|
41172
41371
|
if (bytes.length < 64 || !["arm64", "x64"].includes(process.arch))
|
|
41173
41372
|
return false;
|
|
@@ -41192,16 +41391,16 @@ function executable(command) {
|
|
|
41192
41391
|
if (parent === dirname20(parent))
|
|
41193
41392
|
break;
|
|
41194
41393
|
}
|
|
41195
|
-
const fd =
|
|
41394
|
+
const fd = openSync7(path, constants12.O_RDONLY | constants12.O_NOFOLLOW | constants12.O_NONBLOCK);
|
|
41196
41395
|
try {
|
|
41197
|
-
const stat2 =
|
|
41396
|
+
const stat2 = fstatSync6(fd);
|
|
41198
41397
|
need5(stat2.isFile() && [0, process.getuid?.()].includes(stat2.uid) && !(stat2.mode & 18) && !!(stat2.mode & 64) && stat2.size <= 256000000, "native_unsafe_executable");
|
|
41199
|
-
const bytes =
|
|
41398
|
+
const bytes = readFileSync19(fd);
|
|
41200
41399
|
need5(bytes.length <= 256000000, "native_unavailable");
|
|
41201
41400
|
need5(nativeHeader(bytes), "native_executable_required");
|
|
41202
41401
|
return { path, sha256: digest3(bytes) };
|
|
41203
41402
|
} finally {
|
|
41204
|
-
|
|
41403
|
+
closeSync7(fd);
|
|
41205
41404
|
}
|
|
41206
41405
|
} catch (error61) {
|
|
41207
41406
|
if (error61 instanceof CodexSafetyError)
|
|
@@ -41346,7 +41545,7 @@ function supportedDiscoveryWarnings(hooks, warnings) {
|
|
|
41346
41545
|
}
|
|
41347
41546
|
return warnings.every((warning) => typeof warning === "string" && allowed.delete(warning));
|
|
41348
41547
|
}
|
|
41349
|
-
function selectCodexSafety(discovery, configuration, home, cwd, featureEnabled, requireReady = true) {
|
|
41548
|
+
function selectCodexSafety(discovery, configuration, home, cwd, featureEnabled, requireReady = true, readiness = false) {
|
|
41350
41549
|
const data = discovery?.data;
|
|
41351
41550
|
need5(Array.isArray(data) && data.length === 1 && data[0]?.cwd === cwd && Array.isArray(data[0]?.hooks) && data[0].hooks.length <= 1e4 && Array.isArray(data[0].errors) && data[0].errors.length === 0 && Array.isArray(data[0].warnings) && supportedDiscoveryWarnings(data[0].hooks, data[0].warnings), "native_discovery_failed");
|
|
41352
41551
|
const config2 = configuration?.config;
|
|
@@ -41357,12 +41556,14 @@ function selectCodexSafety(discovery, configuration, home, cwd, featureEnabled,
|
|
|
41357
41556
|
if (/\bhasna-native-safety-v\d+\b/.test(hook2.command))
|
|
41358
41557
|
need5(nativeSafetyCommandBinding(hook2.command), "guard_definition_invalid");
|
|
41359
41558
|
need5(!/^(?:hooks run trash-guard|hook-trash-guard)(?:\s|$)/.test(hook2.command.trim()), "guard_definition_invalid");
|
|
41559
|
+
if (readiness && hook2.command.includes("hasna-native-readiness-v"))
|
|
41560
|
+
need5(nativeReadinessCommandBinding(hook2.command), "guard_definition_invalid");
|
|
41360
41561
|
}
|
|
41361
|
-
const selected = data[0].hooks.filter((hook2) => typeof hook2?.command === "string" && nativeSafetyCommandBinding(hook2.command)?.name === "trash-guard");
|
|
41562
|
+
const selected = data[0].hooks.filter((hook2) => typeof hook2?.command === "string" && (readiness ? !!nativeReadinessCommandBinding(hook2.command) : nativeSafetyCommandBinding(hook2.command)?.name === "trash-guard"));
|
|
41362
41563
|
need5(selected.length !== 0, "guard_missing");
|
|
41363
41564
|
need5(selected.length === 1, "guard_ambiguous");
|
|
41364
|
-
const hook = selected[0], binding = nativeSafetyCommandBinding(hook.command);
|
|
41365
|
-
need5(binding.home === home && hook.eventName === "preToolUse" && hook.handlerType === "command" && hook.matcher === matcher && hook.timeoutSec === 10 && hook.async === false && hook.statusMessage === null && hook.additionalContextLimit === null && typeof hook.key === "string" && hook.key.length <= 4096 && typeof hook.sourcePath === "string" && isAbsolute17(hook.sourcePath) && hook.pluginId === null && hook.isManaged === false && ["user", "project"].includes(hook.source) && typeof hook.currentHash === "string" && /^sha256:[a-f0-9]{64}$/.test(hook.currentHash), "guard_definition_invalid");
|
|
41565
|
+
const hook = selected[0], binding = readiness ? nativeReadinessCommandBinding(hook.command) : nativeSafetyCommandBinding(hook.command);
|
|
41566
|
+
need5(binding.home === home && (!readiness || binding?.provider === "codex") && hook.eventName === (readiness ? "sessionStart" : "preToolUse") && hook.handlerType === "command" && hook.matcher === (readiness ? "^(startup|resume|clear|fork)$" : matcher) && hook.timeoutSec === (readiness ? 20 : 10) && hook.async === false && hook.statusMessage === null && hook.additionalContextLimit === null && typeof hook.key === "string" && hook.key.length <= 4096 && typeof hook.sourcePath === "string" && isAbsolute17(hook.sourcePath) && hook.pluginId === null && hook.isManaged === false && ["user", "project"].includes(hook.source) && typeof hook.currentHash === "string" && /^sha256:[a-f0-9]{64}$/.test(hook.currentHash), "guard_definition_invalid");
|
|
41366
41567
|
need5(typeof hook.enabled === "boolean" && ["trusted", "untrusted", "modified"].includes(hook.trustStatus), "guard_definition_invalid");
|
|
41367
41568
|
if (requireReady) {
|
|
41368
41569
|
need5(hook.enabled === true, "guard_disabled");
|
|
@@ -41395,7 +41596,7 @@ async function readCodexSafety(rpc, home, cwd, options = {}) {
|
|
|
41395
41596
|
}
|
|
41396
41597
|
} while (cursor !== null);
|
|
41397
41598
|
}
|
|
41398
|
-
return { guard: selectCodexSafety(hooks, configuration, home, cwd, enabled, options.requireReady ?? true), discovery: hooks, configuration };
|
|
41599
|
+
return { guard: selectCodexSafety(hooks, configuration, home, cwd, enabled, options.requireReady ?? true, options.readiness ?? false), discovery: hooks, configuration };
|
|
41399
41600
|
}
|
|
41400
41601
|
|
|
41401
41602
|
// src/lib/codex-safety-trust.ts
|
|
@@ -41413,7 +41614,7 @@ function need7(value, code2 = "native_changed") {
|
|
|
41413
41614
|
if (!value)
|
|
41414
41615
|
throw new CodexTrustError(code2);
|
|
41415
41616
|
}
|
|
41416
|
-
var sha4 = (value) =>
|
|
41617
|
+
var sha4 = (value) => createHash17("sha256").update(value).digest("hex");
|
|
41417
41618
|
function canonical4(value) {
|
|
41418
41619
|
if (Array.isArray(value))
|
|
41419
41620
|
return "[" + value.map(canonical4).join(",") + "]";
|
|
@@ -41454,37 +41655,37 @@ function safeDirectory2(path) {
|
|
|
41454
41655
|
}
|
|
41455
41656
|
function readPrivate(path) {
|
|
41456
41657
|
safeDirectory2(dirname21(path));
|
|
41457
|
-
const fd =
|
|
41658
|
+
const fd = openSync15(path, constants14.O_RDONLY | constants14.O_NOFOLLOW | constants14.O_NONBLOCK);
|
|
41458
41659
|
try {
|
|
41459
|
-
const s =
|
|
41660
|
+
const s = fstatSync7(fd);
|
|
41460
41661
|
need7(s.isFile() && s.uid === process.getuid?.() && !(s.mode & 63) && s.size <= 4194304, "preimage_unsafe");
|
|
41461
|
-
const bytes =
|
|
41662
|
+
const bytes = readFileSync20(fd);
|
|
41462
41663
|
need7(bytes.length <= 4194304, "preimage_unsafe");
|
|
41463
41664
|
return bytes;
|
|
41464
41665
|
} finally {
|
|
41465
|
-
|
|
41666
|
+
closeSync15(fd);
|
|
41466
41667
|
}
|
|
41467
41668
|
}
|
|
41468
41669
|
function syncDirectory(path) {
|
|
41469
|
-
const fd =
|
|
41670
|
+
const fd = openSync15(path, constants14.O_RDONLY | constants14.O_DIRECTORY | constants14.O_NOFOLLOW);
|
|
41470
41671
|
try {
|
|
41471
41672
|
fsyncSync5(fd);
|
|
41472
41673
|
} finally {
|
|
41473
|
-
|
|
41674
|
+
closeSync15(fd);
|
|
41474
41675
|
}
|
|
41475
41676
|
}
|
|
41476
41677
|
function exclusive(path, bytes) {
|
|
41477
|
-
const fd =
|
|
41678
|
+
const fd = openSync15(path, constants14.O_CREAT | constants14.O_EXCL | constants14.O_WRONLY | constants14.O_NOFOLLOW, 384);
|
|
41478
41679
|
try {
|
|
41479
41680
|
writeFileSync11(fd, bytes);
|
|
41480
41681
|
fsyncSync5(fd);
|
|
41481
41682
|
} finally {
|
|
41482
|
-
|
|
41683
|
+
closeSync15(fd);
|
|
41483
41684
|
}
|
|
41484
41685
|
syncDirectory(dirname21(path));
|
|
41485
41686
|
}
|
|
41486
|
-
async function inspect(rpc, home, cwd) {
|
|
41487
|
-
const observed = await readCodexSafety(rpc, home, cwd, { includeLayers: true, requireReady: false });
|
|
41687
|
+
async function inspect(rpc, home, cwd, readiness = false) {
|
|
41688
|
+
const observed = await readCodexSafety(rpc, home, cwd, { includeLayers: true, requireReady: false, readiness });
|
|
41488
41689
|
const { guard, configuration, discovery } = observed;
|
|
41489
41690
|
const layers = configuration?.layers;
|
|
41490
41691
|
need7(Array.isArray(layers) && layers.length <= 1000, "native_config_invalid");
|
|
@@ -41501,7 +41702,7 @@ async function inspect(rpc, home, cwd) {
|
|
|
41501
41702
|
need7(rawState === undefined || rawState && typeof rawState === "object" && !Array.isArray(rawState), "native_config_invalid");
|
|
41502
41703
|
const desired = { ...rawState, enabled: true, trusted_hash: guard.currentHash };
|
|
41503
41704
|
const planBase = {
|
|
41504
|
-
schema: "hasna.hooks.codex-safety-trust/v1",
|
|
41705
|
+
schema: readiness ? "hasna.hooks.codex-readiness-trust/v1" : "hasna.hooks.codex-safety-trust/v1",
|
|
41505
41706
|
nativeVersion: rpc.version,
|
|
41506
41707
|
cwd,
|
|
41507
41708
|
key: guard.key,
|
|
@@ -41509,37 +41710,44 @@ async function inspect(rpc, home, cwd) {
|
|
|
41509
41710
|
configPath: path,
|
|
41510
41711
|
configVersion: layer.version,
|
|
41511
41712
|
action: guard.enabled && guard.trustStatus === "trusted" ? "unchanged" : "trust_and_enable",
|
|
41512
|
-
guardVerified:
|
|
41713
|
+
guardVerified: !readiness,
|
|
41714
|
+
...readiness ? { readinessRunnerVerified: true } : {},
|
|
41513
41715
|
nativeAdoptionVerified: false
|
|
41514
41716
|
};
|
|
41515
41717
|
const plan = { ...planBase, planDigest: sha4(canonical4({ planBase, configuration, discovery, raw: sha4(before) })) };
|
|
41516
41718
|
return { ...observed, layer, layers, canonicalPath, before, desired, plan };
|
|
41517
41719
|
}
|
|
41518
41720
|
async function context2(options, dependencies) {
|
|
41519
|
-
const home = realpathSync10(options.home ??
|
|
41721
|
+
const home = realpathSync10(options.home ?? homedir8()), cwd = realpathSync10(options.cwd ?? process.cwd());
|
|
41520
41722
|
need7(isAbsolute18(home) && isAbsolute18(cwd), "input_invalid");
|
|
41521
41723
|
return { home, cwd, rpc: await (dependencies.connect ?? connectCodexSafety)({ ...options, home }) };
|
|
41522
41724
|
}
|
|
41523
|
-
async function
|
|
41725
|
+
async function verify(command, home, readiness, dependencies) {
|
|
41726
|
+
if (readiness)
|
|
41727
|
+
need7(nativeReadinessCommandVerified(command));
|
|
41728
|
+
else
|
|
41729
|
+
await (dependencies.verify ?? verifyNativeSafetyCommand)(command, home);
|
|
41730
|
+
}
|
|
41731
|
+
async function planTrust(options = {}, dependencies = {}) {
|
|
41524
41732
|
const { home, cwd, rpc } = await context2(options, dependencies);
|
|
41525
41733
|
try {
|
|
41526
|
-
const before = await inspect(rpc, home, cwd);
|
|
41527
|
-
await
|
|
41528
|
-
need7((await inspect(rpc, home, cwd)).plan.planDigest === before.plan.planDigest);
|
|
41734
|
+
const before = await inspect(rpc, home, cwd, options.readiness);
|
|
41735
|
+
await verify(before.guard.command, home, !!options.readiness, dependencies);
|
|
41736
|
+
need7((await inspect(rpc, home, cwd, options.readiness)).plan.planDigest === before.plan.planDigest);
|
|
41529
41737
|
return before.plan;
|
|
41530
41738
|
} finally {
|
|
41531
41739
|
await rpc.close();
|
|
41532
41740
|
}
|
|
41533
41741
|
}
|
|
41534
|
-
async function
|
|
41742
|
+
async function applyTrust(options, dependencies = {}) {
|
|
41535
41743
|
need7(/^[a-f0-9]{64}$/.test(options.expectedPlanDigest), "input_invalid");
|
|
41536
41744
|
const selected = { ...options }, { home, cwd, rpc } = await context2(selected, dependencies);
|
|
41537
41745
|
let operationId;
|
|
41538
41746
|
try {
|
|
41539
|
-
const before = await inspect(rpc, home, cwd);
|
|
41747
|
+
const before = await inspect(rpc, home, cwd, selected.readiness);
|
|
41540
41748
|
need7(before.plan.planDigest === selected.expectedPlanDigest);
|
|
41541
|
-
await
|
|
41542
|
-
need7((await inspect(rpc, home, cwd)).plan.planDigest === before.plan.planDigest);
|
|
41749
|
+
await verify(before.guard.command, home, !!selected.readiness, dependencies);
|
|
41750
|
+
need7((await inspect(rpc, home, cwd, selected.readiness)).plan.planDigest === before.plan.planDigest);
|
|
41543
41751
|
if (before.plan.action === "unchanged")
|
|
41544
41752
|
return { ok: true, changed: false, ...before.plan };
|
|
41545
41753
|
const expectedConfiguration = structuredClone(before.configuration);
|
|
@@ -41555,8 +41763,8 @@ async function applyCodexSafetyTrust(options, dependencies = {}) {
|
|
|
41555
41763
|
expectedGuard.trustStatus = "trusted";
|
|
41556
41764
|
operationId = randomUUID3();
|
|
41557
41765
|
let directory2 = home;
|
|
41558
|
-
for (const part of [".hasna", "hooks", "native", "codex-trash-trust", operationId]) {
|
|
41559
|
-
directory2 =
|
|
41766
|
+
for (const part of [".hasna", "hooks", "native", selected.readiness ? "codex-readiness-trust" : "codex-trash-trust", operationId]) {
|
|
41767
|
+
directory2 = join20(directory2, part);
|
|
41560
41768
|
try {
|
|
41561
41769
|
mkdirSync13(directory2, { mode: 448 });
|
|
41562
41770
|
syncDirectory(dirname21(directory2));
|
|
@@ -41566,8 +41774,8 @@ async function applyCodexSafetyTrust(options, dependencies = {}) {
|
|
|
41566
41774
|
}
|
|
41567
41775
|
safeDirectory2(directory2);
|
|
41568
41776
|
}
|
|
41569
|
-
exclusive(
|
|
41570
|
-
exclusive(
|
|
41777
|
+
exclusive(join20(directory2, "config.before"), before.before);
|
|
41778
|
+
exclusive(join20(directory2, "intent.json"), JSON.stringify({ operationId, plan: before.plan, configSHA256: sha4(before.before) }) + `
|
|
41571
41779
|
`);
|
|
41572
41780
|
need7(sha4(readPrivate(before.canonicalPath)) === sha4(before.before));
|
|
41573
41781
|
const result = await rpc.request("config/batchWrite", {
|
|
@@ -41577,7 +41785,7 @@ async function applyCodexSafetyTrust(options, dependencies = {}) {
|
|
|
41577
41785
|
edits: [{ keyPath: "hooks.state." + JSON.stringify(before.guard.key), value: before.desired, mergeStrategy: "replace" }]
|
|
41578
41786
|
});
|
|
41579
41787
|
need7(result?.status === "ok" && result.filePath === before.plan.configPath && result.overriddenMetadata === null && /^sha256:[a-f0-9]{64}$/.test(result.version));
|
|
41580
|
-
const after = await inspect(rpc, home, cwd);
|
|
41788
|
+
const after = await inspect(rpc, home, cwd, selected.readiness);
|
|
41581
41789
|
expectedLayer.version = result.version;
|
|
41582
41790
|
need7(after.layer.version === result.version && after.plan.action === "unchanged" && mergedConfiguration(after.configuration.config) === mergedConfiguration(expectedConfiguration.config) && canonical4(after.configuration.layers) === canonical4(expectedConfiguration.layers) && canonical4(after.discovery) === canonical4(expectedDiscovery));
|
|
41583
41791
|
const receipt = {
|
|
@@ -41586,12 +41794,13 @@ async function applyCodexSafetyTrust(options, dependencies = {}) {
|
|
|
41586
41794
|
operationId,
|
|
41587
41795
|
planDigest: before.plan.planDigest,
|
|
41588
41796
|
configVersion: result.version,
|
|
41589
|
-
guardVerified:
|
|
41797
|
+
guardVerified: !selected.readiness,
|
|
41798
|
+
...selected.readiness ? { readinessRunnerVerified: true } : {},
|
|
41590
41799
|
nativeDiscoveryVerified: true,
|
|
41591
41800
|
nativeAdoptionVerified: false,
|
|
41592
41801
|
unrelatedConfigurationPreserved: true
|
|
41593
41802
|
};
|
|
41594
|
-
exclusive(
|
|
41803
|
+
exclusive(join20(directory2, "receipt.json"), JSON.stringify(receipt) + `
|
|
41595
41804
|
`);
|
|
41596
41805
|
return receipt;
|
|
41597
41806
|
} catch (error61) {
|
|
@@ -41602,11 +41811,13 @@ async function applyCodexSafetyTrust(options, dependencies = {}) {
|
|
|
41602
41811
|
await rpc.close();
|
|
41603
41812
|
}
|
|
41604
41813
|
}
|
|
41814
|
+
var planCodexSafetyTrust = (options = {}, dependencies = {}) => planTrust({ ...options, readiness: false }, dependencies);
|
|
41815
|
+
var applyCodexSafetyTrust = (options, dependencies = {}) => applyTrust({ ...options, readiness: false }, dependencies);
|
|
41605
41816
|
// src/lib/claude-safety-check.ts
|
|
41606
|
-
import { constants as
|
|
41607
|
-
import { createHash as
|
|
41608
|
-
import { dirname as dirname22, isAbsolute as isAbsolute19, join as
|
|
41609
|
-
import { homedir as
|
|
41817
|
+
import { constants as constants15, closeSync as closeSync17, fstatSync as fstatSync15, lstatSync as lstatSync21, openSync as openSync17, readFileSync as readFileSync21, realpathSync as realpathSync11 } from "fs";
|
|
41818
|
+
import { createHash as createHash19 } from "crypto";
|
|
41819
|
+
import { dirname as dirname22, isAbsolute as isAbsolute19, join as join24, resolve as resolve18 } from "path";
|
|
41820
|
+
import { homedir as homedir9 } from "os";
|
|
41610
41821
|
class ClaudeSafetyError extends Error {
|
|
41611
41822
|
code;
|
|
41612
41823
|
constructor(code2) {
|
|
@@ -41619,7 +41830,7 @@ function need8(value, code2) {
|
|
|
41619
41830
|
if (!value)
|
|
41620
41831
|
throw new ClaudeSafetyError(code2);
|
|
41621
41832
|
}
|
|
41622
|
-
var sha6 = (value) =>
|
|
41833
|
+
var sha6 = (value) => createHash19("sha256").update(value).digest("hex");
|
|
41623
41834
|
function read3(path) {
|
|
41624
41835
|
try {
|
|
41625
41836
|
const resolved = realpathSync11(path);
|
|
@@ -41629,15 +41840,15 @@ function read3(path) {
|
|
|
41629
41840
|
if (dirname22(parent) === parent)
|
|
41630
41841
|
break;
|
|
41631
41842
|
}
|
|
41632
|
-
const fd =
|
|
41843
|
+
const fd = openSync17(resolved, constants15.O_RDONLY | constants15.O_NOFOLLOW | constants15.O_NONBLOCK);
|
|
41633
41844
|
try {
|
|
41634
|
-
const s =
|
|
41845
|
+
const s = fstatSync15(fd);
|
|
41635
41846
|
need8(s.isFile() && [0, process.getuid?.()].includes(s.uid) && !(s.mode & 18) && s.size <= 1048576, "settings_unsafe");
|
|
41636
|
-
const raw =
|
|
41847
|
+
const raw = readFileSync21(fd, "utf8");
|
|
41637
41848
|
need8(Buffer.byteLength(raw) <= 1048576, "settings_unsafe");
|
|
41638
41849
|
return { path, resolved, sha256: sha6(raw), raw };
|
|
41639
41850
|
} finally {
|
|
41640
|
-
|
|
41851
|
+
closeSync17(fd);
|
|
41641
41852
|
}
|
|
41642
41853
|
} catch (error61) {
|
|
41643
41854
|
if (error61.code === "ENOENT")
|
|
@@ -41648,11 +41859,11 @@ function read3(path) {
|
|
|
41648
41859
|
}
|
|
41649
41860
|
}
|
|
41650
41861
|
async function verifyClaudeSafetyConfiguration(options = {}, dependencies = {}) {
|
|
41651
|
-
const home = realpathSync11(options.home ??
|
|
41862
|
+
const home = realpathSync11(options.home ?? homedir9()), cwd = realpathSync11(options.cwd ?? process.cwd());
|
|
41652
41863
|
const paths = [
|
|
41653
|
-
options.userSettingsPath ??
|
|
41654
|
-
|
|
41655
|
-
options.localSettingsPath ??
|
|
41864
|
+
options.userSettingsPath ?? join24(home, ".claude/settings.json"),
|
|
41865
|
+
join24(cwd, ".claude/settings.json"),
|
|
41866
|
+
options.localSettingsPath ?? join24(cwd, ".claude/settings.local.json")
|
|
41656
41867
|
];
|
|
41657
41868
|
need8(paths.every((path) => isAbsolute19(path) && resolve18(path) === path), "settings_unsafe");
|
|
41658
41869
|
const inspect2 = () => [...new Set(paths)].map(read3);
|
|
@@ -41713,10 +41924,10 @@ async function verifyClaudeSafetyConfiguration(options = {}, dependencies = {})
|
|
|
41713
41924
|
};
|
|
41714
41925
|
}
|
|
41715
41926
|
// src/lib/native-safety-registration.ts
|
|
41716
|
-
import { createHash as
|
|
41717
|
-
import { constants as
|
|
41718
|
-
import { basename as basename9, dirname as dirname23, isAbsolute as isAbsolute20, join as
|
|
41719
|
-
import { homedir as
|
|
41927
|
+
import { createHash as createHash21, randomUUID as randomUUID5 } from "crypto";
|
|
41928
|
+
import { constants as constants16, closeSync as closeSync18, fstatSync as fstatSync17, fsyncSync as fsyncSync7, lstatSync as lstatSync23, mkdirSync as mkdirSync14, openSync as openSync18, readFileSync as readFileSync23, realpathSync as realpathSync12, writeFileSync as writeFileSync12 } from "fs";
|
|
41929
|
+
import { basename as basename9, dirname as dirname23, isAbsolute as isAbsolute20, join as join27, relative as relative3, resolve as resolve19 } from "path";
|
|
41930
|
+
import { homedir as homedir10 } from "os";
|
|
41720
41931
|
class NativeRegistrationError extends Error {
|
|
41721
41932
|
code;
|
|
41722
41933
|
operationId;
|
|
@@ -41731,7 +41942,7 @@ var need9 = (value, code2) => {
|
|
|
41731
41942
|
if (!value)
|
|
41732
41943
|
throw new NativeRegistrationError(code2);
|
|
41733
41944
|
};
|
|
41734
|
-
var sha7 = (value) =>
|
|
41945
|
+
var sha7 = (value) => createHash21("sha256").update(value).digest("hex");
|
|
41735
41946
|
function canonical6(value) {
|
|
41736
41947
|
if (Array.isArray(value))
|
|
41737
41948
|
return "[" + value.map(canonical6).join(",") + "]";
|
|
@@ -41765,7 +41976,7 @@ function read5(path) {
|
|
|
41765
41976
|
const suffix = [basename9(path)];
|
|
41766
41977
|
for (;; ) {
|
|
41767
41978
|
try {
|
|
41768
|
-
resolved =
|
|
41979
|
+
resolved = join27(realpathSync12(ancestor), ...suffix);
|
|
41769
41980
|
break;
|
|
41770
41981
|
} catch (missing2) {
|
|
41771
41982
|
if (missing2.code !== "ENOENT" || dirname23(ancestor) === ancestor || suffix.length >= 64)
|
|
@@ -41787,22 +41998,22 @@ function read5(path) {
|
|
|
41787
41998
|
safeDirectory3(dirname23(resolved));
|
|
41788
41999
|
let fd;
|
|
41789
42000
|
try {
|
|
41790
|
-
fd =
|
|
42001
|
+
fd = openSync18(resolved, constants16.O_RDONLY | constants16.O_NOFOLLOW | constants16.O_NONBLOCK);
|
|
41791
42002
|
} catch (error61) {
|
|
41792
42003
|
if (error61.code === "ENOENT")
|
|
41793
42004
|
return { resolved, raw: null, sha256: "absent" };
|
|
41794
42005
|
throw new NativeRegistrationError("settings_unsafe");
|
|
41795
42006
|
}
|
|
41796
42007
|
try {
|
|
41797
|
-
const s =
|
|
42008
|
+
const s = fstatSync17(fd);
|
|
41798
42009
|
need9(s.isFile() && s.uid === process.getuid?.() && !(s.mode & 18) && s.nlink === 1 && s.size <= 1048576, "settings_unsafe");
|
|
41799
|
-
const bytes =
|
|
42010
|
+
const bytes = readFileSync23(fd);
|
|
41800
42011
|
need9(bytes.length <= 1048576, "settings_unsafe");
|
|
41801
42012
|
const raw = bytes.toString("utf8");
|
|
41802
42013
|
need9(Buffer.from(raw).equals(bytes), "settings_invalid");
|
|
41803
42014
|
return { resolved, raw, sha256: sha7(bytes) };
|
|
41804
42015
|
} finally {
|
|
41805
|
-
|
|
42016
|
+
closeSync18(fd);
|
|
41806
42017
|
}
|
|
41807
42018
|
}
|
|
41808
42019
|
function assertLosslessJson(raw) {
|
|
@@ -41860,7 +42071,7 @@ function validateSettings(raw) {
|
|
|
41860
42071
|
need9(group && Array.isArray(group.hooks), "settings_invalid");
|
|
41861
42072
|
for (const hook of group.hooks) {
|
|
41862
42073
|
need9(hook && typeof hook === "object", "settings_invalid");
|
|
41863
|
-
if (registrationName(hook.command)
|
|
42074
|
+
if (["trash-guard", "trash-readiness"].includes(registrationName(hook.command) ?? "")) {
|
|
41864
42075
|
need9((hook.async === undefined || hook.async === false) && hook.asyncRewake !== true, "registration_conflict");
|
|
41865
42076
|
}
|
|
41866
42077
|
}
|
|
@@ -41871,7 +42082,7 @@ function validateSettings(raw) {
|
|
|
41871
42082
|
}
|
|
41872
42083
|
function inspect2(options) {
|
|
41873
42084
|
need9(options?.target === "codex" || options?.target === "claude", "input_invalid");
|
|
41874
|
-
const home = realpathSync12(
|
|
42085
|
+
const home = realpathSync12(homedir10()), path = getSettingsPath("global", options.target);
|
|
41875
42086
|
need9(isAbsolute20(path) && resolve19(path) === path, "input_invalid");
|
|
41876
42087
|
const nativeHome = options.target === "codex" ? process.env.CODEX_HOME : process.env.CLAUDE_CONFIG_DIR;
|
|
41877
42088
|
if (nativeHome !== undefined) {
|
|
@@ -41884,7 +42095,7 @@ function inspect2(options) {
|
|
|
41884
42095
|
const parsed = validateSettings(before.raw);
|
|
41885
42096
|
let preview2;
|
|
41886
42097
|
try {
|
|
41887
|
-
preview2 = previewNativeSafetyRegistration(options.target);
|
|
42098
|
+
preview2 = (options.readiness ? previewNativeReadinessRegistration : previewNativeSafetyRegistration)(options.target);
|
|
41888
42099
|
} catch {
|
|
41889
42100
|
throw new NativeRegistrationError("registration_conflict");
|
|
41890
42101
|
}
|
|
@@ -41892,7 +42103,7 @@ function inspect2(options) {
|
|
|
41892
42103
|
validateSettings(preview2.after);
|
|
41893
42104
|
const changed = canonical6(parsed) !== canonical6(JSON.parse(preview2.after));
|
|
41894
42105
|
const base = {
|
|
41895
|
-
schema: "hasna.hooks.native-safety-registration/v1",
|
|
42106
|
+
schema: options.readiness ? "hasna.hooks.native-readiness-registration/v1" : "hasna.hooks.native-safety-registration/v1",
|
|
41896
42107
|
target: options.target,
|
|
41897
42108
|
settingsPath: path,
|
|
41898
42109
|
resolvedPath: before.resolved,
|
|
@@ -41900,7 +42111,8 @@ function inspect2(options) {
|
|
|
41900
42111
|
desiredSHA256: changed ? sha7(preview2.after) : before.sha256,
|
|
41901
42112
|
commandSHA256: sha7(preview2.command),
|
|
41902
42113
|
action: changed ? "register" : "unchanged",
|
|
41903
|
-
guardVerified:
|
|
42114
|
+
guardVerified: !options.readiness,
|
|
42115
|
+
...options.readiness ? { readinessRunnerVerified: true } : {},
|
|
41904
42116
|
nativeAdoptionVerified: false
|
|
41905
42117
|
};
|
|
41906
42118
|
const plan = { ...base, planDigest: sha7(canonical6(base)) };
|
|
@@ -41908,37 +42120,40 @@ function inspect2(options) {
|
|
|
41908
42120
|
return { home, before, preview: preview2, plan };
|
|
41909
42121
|
}
|
|
41910
42122
|
function syncDirectory2(path) {
|
|
41911
|
-
const fd =
|
|
42123
|
+
const fd = openSync18(path, constants16.O_RDONLY | constants16.O_DIRECTORY | constants16.O_NOFOLLOW);
|
|
41912
42124
|
try {
|
|
41913
42125
|
fsyncSync7(fd);
|
|
41914
42126
|
} finally {
|
|
41915
|
-
|
|
42127
|
+
closeSync18(fd);
|
|
41916
42128
|
}
|
|
41917
42129
|
}
|
|
41918
42130
|
function exclusive2(path, bytes) {
|
|
41919
|
-
const fd =
|
|
42131
|
+
const fd = openSync18(path, constants16.O_CREAT | constants16.O_EXCL | constants16.O_WRONLY | constants16.O_NOFOLLOW, 384);
|
|
41920
42132
|
try {
|
|
41921
42133
|
writeFileSync12(fd, bytes);
|
|
41922
42134
|
fsyncSync7(fd);
|
|
41923
42135
|
} finally {
|
|
41924
|
-
|
|
42136
|
+
closeSync18(fd);
|
|
41925
42137
|
}
|
|
41926
42138
|
syncDirectory2(dirname23(path));
|
|
41927
42139
|
}
|
|
41928
|
-
async function
|
|
42140
|
+
async function planRegistration(options, dependencies = {}) {
|
|
41929
42141
|
const selected = { ...options }, before = inspect2(selected);
|
|
41930
42142
|
try {
|
|
41931
|
-
|
|
42143
|
+
if (selected.readiness)
|
|
42144
|
+
need9(nativeReadinessCommandVerified(before.preview.command), "guard_failed");
|
|
42145
|
+
else
|
|
42146
|
+
await (dependencies.verify ?? verifyNativeSafetyCommand)(before.preview.command, before.home);
|
|
41932
42147
|
} catch {
|
|
41933
42148
|
throw new NativeRegistrationError("guard_failed");
|
|
41934
42149
|
}
|
|
41935
42150
|
need9(inspect2(selected).plan.planDigest === before.plan.planDigest, "settings_changed");
|
|
41936
42151
|
return before.plan;
|
|
41937
42152
|
}
|
|
41938
|
-
async function
|
|
42153
|
+
async function applyRegistration(options, dependencies = {}) {
|
|
41939
42154
|
const selected = { ...options };
|
|
41940
42155
|
need9(/^[a-f0-9]{64}$/.test(selected.expectedPlanDigest), "input_invalid");
|
|
41941
|
-
const plan = await
|
|
42156
|
+
const plan = await planRegistration(selected, dependencies);
|
|
41942
42157
|
need9(plan.planDigest === selected.expectedPlanDigest, "settings_changed");
|
|
41943
42158
|
const before = inspect2(selected);
|
|
41944
42159
|
need9(before.plan.planDigest === plan.planDigest, "settings_changed");
|
|
@@ -41946,8 +42161,8 @@ async function applyNativeSafetyRegistration(options, dependencies = {}) {
|
|
|
41946
42161
|
return { ok: true, changed: false, ...plan };
|
|
41947
42162
|
const operationId = randomUUID5();
|
|
41948
42163
|
let directory2 = before.home;
|
|
41949
|
-
for (const part of [".hasna", "hooks", "native", "trash-registration", operationId]) {
|
|
41950
|
-
directory2 =
|
|
42164
|
+
for (const part of [".hasna", "hooks", "native", selected.readiness ? "trash-readiness-registration" : "trash-registration", operationId]) {
|
|
42165
|
+
directory2 = join27(directory2, part);
|
|
41951
42166
|
try {
|
|
41952
42167
|
mkdirSync14(directory2, { mode: 448 });
|
|
41953
42168
|
syncDirectory2(dirname23(directory2));
|
|
@@ -41958,12 +42173,12 @@ async function applyNativeSafetyRegistration(options, dependencies = {}) {
|
|
|
41958
42173
|
safeDirectory3(directory2);
|
|
41959
42174
|
}
|
|
41960
42175
|
if (before.before.raw !== null)
|
|
41961
|
-
exclusive2(
|
|
41962
|
-
exclusive2(
|
|
42176
|
+
exclusive2(join27(directory2, "settings.before"), before.before.raw);
|
|
42177
|
+
exclusive2(join27(directory2, "intent.json"), JSON.stringify({ operationId, plan }) + `
|
|
41963
42178
|
`);
|
|
41964
42179
|
try {
|
|
41965
42180
|
need9(inspect2(selected).plan.planDigest === plan.planDigest, "settings_changed");
|
|
41966
|
-
const result = installHook("trash-guard", { target: selected.target, overwrite: true, expectedSettingsSha256: plan.beforeSHA256 });
|
|
42181
|
+
const result = selected.readiness ? installNativeReadinessRegistration(selected.target, plan.beforeSHA256) : installHook("trash-guard", { target: selected.target, overwrite: true, expectedSettingsSha256: plan.beforeSHA256 });
|
|
41967
42182
|
need9(result.success, "apply_outcome_uncertain");
|
|
41968
42183
|
const after = inspect2(selected);
|
|
41969
42184
|
need9(after.before.sha256 === plan.desiredSHA256 && after.plan.resolvedPath === plan.resolvedPath && after.plan.action === "unchanged" && after.plan.commandSHA256 === plan.commandSHA256, "apply_outcome_uncertain");
|
|
@@ -41973,16 +42188,19 @@ async function applyNativeSafetyRegistration(options, dependencies = {}) {
|
|
|
41973
42188
|
operationId,
|
|
41974
42189
|
planDigest: plan.planDigest,
|
|
41975
42190
|
settingsSHA256: after.before.sha256,
|
|
41976
|
-
guardVerified:
|
|
42191
|
+
guardVerified: !selected.readiness,
|
|
42192
|
+
...selected.readiness ? { readinessRunnerVerified: true } : {},
|
|
41977
42193
|
nativeAdoptionVerified: false
|
|
41978
42194
|
};
|
|
41979
|
-
exclusive2(
|
|
42195
|
+
exclusive2(join27(directory2, "receipt.json"), JSON.stringify(receipt) + `
|
|
41980
42196
|
`);
|
|
41981
42197
|
return receipt;
|
|
41982
42198
|
} catch {
|
|
41983
42199
|
throw new NativeRegistrationError("apply_outcome_uncertain", operationId);
|
|
41984
42200
|
}
|
|
41985
42201
|
}
|
|
42202
|
+
var planNativeSafetyRegistration = (options, dependencies = {}) => planRegistration({ target: options.target }, dependencies);
|
|
42203
|
+
var applyNativeSafetyRegistration = (options, dependencies = {}) => applyRegistration({ target: options.target, expectedPlanDigest: options.expectedPlanDigest }, dependencies);
|
|
41986
42204
|
|
|
41987
42205
|
// src/index.ts
|
|
41988
42206
|
function installHookForProject(name, options = {}) {
|
|
@@ -42005,11 +42223,11 @@ async function runHook(name, input2, options = {}) {
|
|
|
42005
42223
|
throw new Error(`Hook '${name}' not found`);
|
|
42006
42224
|
const hookScript = getHookPath(name) + "/src/hook.ts";
|
|
42007
42225
|
const resolvedScript = custom3?.scriptPath;
|
|
42008
|
-
const script = resolvedScript ?? (
|
|
42226
|
+
const script = resolvedScript ?? (existsSync16(hookScript) ? hookScript : undefined);
|
|
42009
42227
|
if (!script)
|
|
42010
42228
|
throw new Error(`Hook script not found: ${name}`);
|
|
42011
42229
|
const { sha256Of: sha256Of2, checkScriptHash: checkScriptHash2 } = await Promise.resolve().then(() => (init_store(), exports_store));
|
|
42012
|
-
const content =
|
|
42230
|
+
const content = readFileSync25(script);
|
|
42013
42231
|
const check2 = checkScriptHash2(name, sha256Of2(content));
|
|
42014
42232
|
if (!check2.ok) {
|
|
42015
42233
|
throw new Error(`Hook '${name}' script changed since it was trusted (sha256 ${check2.expected} != ${check2.actual}). Run 'hooks trust ${name}' to trust the new content.`);
|