@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/bin/hooks-mcp.js
CHANGED
|
@@ -1130,9 +1130,9 @@ var init_migrations = __esm(() => {
|
|
|
1130
1130
|
});
|
|
1131
1131
|
|
|
1132
1132
|
// src/db/legacy-import.ts
|
|
1133
|
-
import { existsSync as
|
|
1134
|
-
import { join as
|
|
1135
|
-
import { homedir as
|
|
1133
|
+
import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync4 } from "fs";
|
|
1134
|
+
import { join as join7 } from "path";
|
|
1135
|
+
import { homedir as homedir4 } from "os";
|
|
1136
1136
|
function ensureMetaTable(db) {
|
|
1137
1137
|
db.exec(`
|
|
1138
1138
|
CREATE TABLE IF NOT EXISTS _meta (
|
|
@@ -1155,7 +1155,7 @@ function nanoid() {
|
|
|
1155
1155
|
function importJsonlFile(db, filePath) {
|
|
1156
1156
|
let count = 0;
|
|
1157
1157
|
try {
|
|
1158
|
-
const lines =
|
|
1158
|
+
const lines = readFileSync4(filePath, "utf-8").split(`
|
|
1159
1159
|
`).filter(Boolean);
|
|
1160
1160
|
for (const line of lines) {
|
|
1161
1161
|
try {
|
|
@@ -1181,7 +1181,7 @@ function importJsonlFile(db, filePath) {
|
|
|
1181
1181
|
function importErrorsLog(db, filePath) {
|
|
1182
1182
|
let count = 0;
|
|
1183
1183
|
try {
|
|
1184
|
-
const lines =
|
|
1184
|
+
const lines = readFileSync4(filePath, "utf-8").split(`
|
|
1185
1185
|
`).filter(Boolean);
|
|
1186
1186
|
const linePattern = /^\[(.+?)\]\s+(?:\[session:(\S+)\]\s+)?(.+?)\s+\u2014\s+(.+)$/;
|
|
1187
1187
|
for (const line of lines) {
|
|
@@ -1206,25 +1206,25 @@ function importErrorsLog(db, filePath) {
|
|
|
1206
1206
|
} catch {}
|
|
1207
1207
|
return count;
|
|
1208
1208
|
}
|
|
1209
|
-
function runLegacyImport(db, homeDir =
|
|
1209
|
+
function runLegacyImport(db, homeDir = homedir4()) {
|
|
1210
1210
|
try {
|
|
1211
1211
|
if (isAlreadyDone(db))
|
|
1212
1212
|
return;
|
|
1213
1213
|
let total = 0;
|
|
1214
|
-
const claudeProjectsDir =
|
|
1215
|
-
if (
|
|
1214
|
+
const claudeProjectsDir = join7(homeDir, ".claude", "projects");
|
|
1215
|
+
if (existsSync6(claudeProjectsDir)) {
|
|
1216
1216
|
try {
|
|
1217
1217
|
const projectDirs = readdirSync2(claudeProjectsDir);
|
|
1218
1218
|
for (const dir of projectDirs) {
|
|
1219
|
-
const projectDir =
|
|
1219
|
+
const projectDir = join7(claudeProjectsDir, dir);
|
|
1220
1220
|
try {
|
|
1221
1221
|
const files = readdirSync2(projectDir);
|
|
1222
1222
|
for (const file of files) {
|
|
1223
1223
|
if (file.match(/^session-log-\d{4}-\d{2}-\d{2}\.jsonl$/)) {
|
|
1224
|
-
total += importJsonlFile(db,
|
|
1224
|
+
total += importJsonlFile(db, join7(projectDir, file));
|
|
1225
1225
|
}
|
|
1226
1226
|
if (file === "errors.log") {
|
|
1227
|
-
total += importErrorsLog(db,
|
|
1227
|
+
total += importErrorsLog(db, join7(projectDir, file));
|
|
1228
1228
|
}
|
|
1229
1229
|
}
|
|
1230
1230
|
} catch {}
|
|
@@ -1271,8 +1271,8 @@ __export(exports_db, {
|
|
|
1271
1271
|
allowLocalStore: () => allowLocalStore
|
|
1272
1272
|
});
|
|
1273
1273
|
import { Database } from "bun:sqlite";
|
|
1274
|
-
import { existsSync as
|
|
1275
|
-
import { join as
|
|
1274
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync2, cpSync } from "fs";
|
|
1275
|
+
import { join as join8 } from "path";
|
|
1276
1276
|
function refuseLocalStore(message) {
|
|
1277
1277
|
localStoreRefusal = message;
|
|
1278
1278
|
}
|
|
@@ -1287,8 +1287,8 @@ function localStoreRefusalMessage() {
|
|
|
1287
1287
|
}
|
|
1288
1288
|
function resolveDataDir() {
|
|
1289
1289
|
const effective = getEffectiveDataRoot();
|
|
1290
|
-
const oldDir =
|
|
1291
|
-
if (!
|
|
1290
|
+
const oldDir = join8(getHomeDir(), ".hooks");
|
|
1291
|
+
if (!existsSync7(effective) && existsSync7(oldDir)) {
|
|
1292
1292
|
mkdirSync2(effective, { recursive: true });
|
|
1293
1293
|
cpSync(oldDir, effective, { recursive: true });
|
|
1294
1294
|
}
|
|
@@ -1299,11 +1299,11 @@ function getDbPath() {
|
|
|
1299
1299
|
if (explicitDb)
|
|
1300
1300
|
return explicitDb;
|
|
1301
1301
|
const dataDir2 = resolveDataDir();
|
|
1302
|
-
return
|
|
1302
|
+
return join8(dataDir2, "hooks.db");
|
|
1303
1303
|
}
|
|
1304
1304
|
function ensureDir(dbPath) {
|
|
1305
1305
|
const dir = dbPath.substring(0, dbPath.lastIndexOf("/"));
|
|
1306
|
-
if (dir && !
|
|
1306
|
+
if (dir && !existsSync7(dir)) {
|
|
1307
1307
|
mkdirSync2(dir, { recursive: true });
|
|
1308
1308
|
}
|
|
1309
1309
|
}
|
|
@@ -1313,7 +1313,7 @@ function getDb() {
|
|
|
1313
1313
|
if (instance)
|
|
1314
1314
|
return instance;
|
|
1315
1315
|
const dbPath = getDbPath();
|
|
1316
|
-
const isNew = dbPath === ":memory:" || !
|
|
1316
|
+
const isNew = dbPath === ":memory:" || !existsSync7(dbPath);
|
|
1317
1317
|
ensureDir(dbPath);
|
|
1318
1318
|
instance = new Database(dbPath);
|
|
1319
1319
|
instance.exec("PRAGMA busy_timeout=5000");
|
|
@@ -1495,9 +1495,9 @@ function parseEnvFile(text3) {
|
|
|
1495
1495
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
|
|
1496
1496
|
continue;
|
|
1497
1497
|
let value = withoutExport.slice(equals + 1).trim();
|
|
1498
|
-
const
|
|
1499
|
-
if (
|
|
1500
|
-
if (value.length < 2 || !value.endsWith(
|
|
1498
|
+
const quote4 = value[0];
|
|
1499
|
+
if (quote4 === '"' || quote4 === "'") {
|
|
1500
|
+
if (value.length < 2 || !value.endsWith(quote4)) {
|
|
1501
1501
|
unusable.add(key);
|
|
1502
1502
|
continue;
|
|
1503
1503
|
}
|
|
@@ -2230,11 +2230,11 @@ var init_local_opt_in = __esm(() => {
|
|
|
2230
2230
|
|
|
2231
2231
|
// src/lib/hook-env.ts
|
|
2232
2232
|
import { dirname as dirname11, isAbsolute as isAbsolute15 } from "path";
|
|
2233
|
-
import { existsSync as
|
|
2233
|
+
import { existsSync as existsSync13, realpathSync as realpathSync7, statSync } from "fs";
|
|
2234
2234
|
import { tmpdir } from "os";
|
|
2235
2235
|
function isDeniedEnvName(name) {
|
|
2236
2236
|
const upper = name.toUpperCase();
|
|
2237
|
-
if (DENY_PREFIXES.some((
|
|
2237
|
+
if (DENY_PREFIXES.some((prefix3) => upper.startsWith(prefix3)))
|
|
2238
2238
|
return true;
|
|
2239
2239
|
if (DENY_SUFFIXES.some((suffix) => upper.endsWith(suffix)))
|
|
2240
2240
|
return true;
|
|
@@ -2243,7 +2243,7 @@ function isDeniedEnvName(name) {
|
|
|
2243
2243
|
function isInterpreterInjectionEnvName(name) {
|
|
2244
2244
|
if (INTERPRETER_INJECTION.has(name))
|
|
2245
2245
|
return true;
|
|
2246
|
-
return INTERPRETER_INJECTION_PREFIXES.some((
|
|
2246
|
+
return INTERPRETER_INJECTION_PREFIXES.some((prefix3) => name.startsWith(prefix3));
|
|
2247
2247
|
}
|
|
2248
2248
|
function isBashFunctionEnvName(name) {
|
|
2249
2249
|
return name.startsWith("BASH_FUNC_");
|
|
@@ -2278,7 +2278,7 @@ function isUnsafePathEntry(entry, home) {
|
|
|
2278
2278
|
}
|
|
2279
2279
|
function sanitizeHookPath(pathValue, home) {
|
|
2280
2280
|
const baseline = PATH_BASELINE.slice();
|
|
2281
|
-
if (process.platform === "darwin" &&
|
|
2281
|
+
if (process.platform === "darwin" && existsSync13("/opt/homebrew/bin"))
|
|
2282
2282
|
baseline.push("/opt/homebrew/bin");
|
|
2283
2283
|
const bunDir = dirname11(process.execPath);
|
|
2284
2284
|
const seen = new Set;
|
|
@@ -2405,7 +2405,7 @@ __export(exports_run, {
|
|
|
2405
2405
|
});
|
|
2406
2406
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
2407
2407
|
import { rmSync as rmSync2, writeFileSync as writeFileSync10 } from "fs";
|
|
2408
|
-
import { basename as basename5, dirname as dirname17, isAbsolute as isAbsolute17, join as
|
|
2408
|
+
import { basename as basename5, dirname as dirname17, isAbsolute as isAbsolute17, join as join12 } from "path";
|
|
2409
2409
|
function extensionOf(scriptPath) {
|
|
2410
2410
|
const base = basename5(scriptPath);
|
|
2411
2411
|
const dot = base.lastIndexOf(".");
|
|
@@ -2476,7 +2476,7 @@ async function readPipeWithDeadline(stream, deadlineMs) {
|
|
|
2476
2476
|
async function executeVerifiedScript(options) {
|
|
2477
2477
|
const scriptDir = dirname17(options.scriptPath);
|
|
2478
2478
|
const interpreter = interpreterFor(options.name, options.scriptPath, options.content);
|
|
2479
|
-
const tempPath =
|
|
2479
|
+
const tempPath = join12(scriptDir, `.hooks-verified-${randomBytes2(12).toString("hex")}.${interpreter.tempExt}`);
|
|
2480
2480
|
let proc = null;
|
|
2481
2481
|
let timedOut = false;
|
|
2482
2482
|
const timeoutTimer = options.timeout ? setTimeout(() => {
|
|
@@ -2684,17 +2684,17 @@ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
|
2684
2684
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2685
2685
|
import { z as z3 } from "zod";
|
|
2686
2686
|
init_registry();
|
|
2687
|
-
import { existsSync as
|
|
2688
|
-
import { join as
|
|
2687
|
+
import { existsSync as existsSync14, readFileSync as readFileSync9 } from "fs";
|
|
2688
|
+
import { join as join13, dirname as dirname19 } from "path";
|
|
2689
2689
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2690
2690
|
|
|
2691
2691
|
// src/lib/installer.ts
|
|
2692
2692
|
init_registry();
|
|
2693
2693
|
init_resolve();
|
|
2694
2694
|
init_manifest();
|
|
2695
|
-
import { existsSync as
|
|
2695
|
+
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";
|
|
2696
2696
|
import { join as join10, dirname as dirname7, basename as basename3, resolve as resolve7 } from "path";
|
|
2697
|
-
import { homedir as
|
|
2697
|
+
import { homedir as homedir6 } from "os";
|
|
2698
2698
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2699
2699
|
|
|
2700
2700
|
// src/lib/native-safety.ts
|
|
@@ -2831,6 +2831,159 @@ function installedNativeSafetyCommand(name) {
|
|
|
2831
2831
|
return buildNativeSafetyCommand({ name, runtime, runtimeSHA256: pinEligibleFile(runtime, 200000000, true), worker: canonicalWorker, workerSHA256: pinEligibleFile(canonicalWorker, 256000), home, path: `${dirname3(runtime)}:${join5(home, ".bun/bin")}:/usr/local/bin:/usr/bin:/bin` });
|
|
2832
2832
|
}
|
|
2833
2833
|
|
|
2834
|
+
// src/lib/native-readiness-command.ts
|
|
2835
|
+
import { createHash as createHash2 } from "crypto";
|
|
2836
|
+
import { constants as constants2, closeSync as closeSync2, existsSync as existsSync5, fstatSync as fstatSync2, lstatSync as lstatSync2, openSync as openSync2, readFileSync as readFileSync3, realpathSync as realpathSync3 } from "fs";
|
|
2837
|
+
import { dirname as dirname4, isAbsolute as isAbsolute3, join as join6 } from "path";
|
|
2838
|
+
import { homedir as homedir3 } from "os";
|
|
2839
|
+
|
|
2840
|
+
// src/lib/readiness-process.ts
|
|
2841
|
+
var supervisor = String.raw`
|
|
2842
|
+
const {spawn}=require("node:child_process");
|
|
2843
|
+
const spec=JSON.parse(process.argv.at(-1));
|
|
2844
|
+
// This runtime was created detached and is its group's live leader. Its
|
|
2845
|
+
// independent timer and parent pipe remain effective if the observer exits
|
|
2846
|
+
// or its event loop stops responding. Only this freshly created tree is hit.
|
|
2847
|
+
const cleanup=()=>{try{process.kill(-process.pid,"SIGKILL")}catch{process.exit(1)}};
|
|
2848
|
+
setTimeout(cleanup,spec.timeout);
|
|
2849
|
+
process.stdin.once("end",cleanup);
|
|
2850
|
+
process.stdin.once("error",cleanup);
|
|
2851
|
+
process.stdin.on("data",cleanup);
|
|
2852
|
+
process.stdin.resume();
|
|
2853
|
+
for(const signal of ["SIGTERM","SIGINT","SIGHUP"])process.once(signal,cleanup);
|
|
2854
|
+
let done=false, out=Buffer.alloc(0), err=Buffer.alloc(0);
|
|
2855
|
+
function report(status,error){
|
|
2856
|
+
if(done)return; done=true;
|
|
2857
|
+
process.stdout.write(JSON.stringify({status,error,stdout:out.toString("utf8"),stderr:err.toString("utf8")})+"\n");
|
|
2858
|
+
}
|
|
2859
|
+
const child=spawn(spec.command,spec.args,{stdio:["ignore","pipe","pipe"],env:process.env});
|
|
2860
|
+
for(const [stream,name] of [[child.stdout,"stdout"],[child.stderr,"stderr"]]){
|
|
2861
|
+
stream.on("data",bytes=>{
|
|
2862
|
+
if(done)return;
|
|
2863
|
+
const before=name==="stdout"?out:err;
|
|
2864
|
+
if(before.length+bytes.length>spec.maxBuffer){report(null,{code:"ENOBUFS"});return}
|
|
2865
|
+
if(name==="stdout")out=Buffer.concat([out,bytes]);else err=Buffer.concat([err,bytes]);
|
|
2866
|
+
});
|
|
2867
|
+
stream.on("error",()=>report(null,{code:"ECHILD"}));
|
|
2868
|
+
}
|
|
2869
|
+
child.on("error",()=>report(null,{code:"ECHILD"}));
|
|
2870
|
+
// A successful observation includes complete streams. A nested client that
|
|
2871
|
+
// keeps them open after its parent exits is a failure requiring group cleanup.
|
|
2872
|
+
child.on("exit",()=>setTimeout(()=>report(null,{code:"ECHILD"}),100));
|
|
2873
|
+
child.on("close",(status,signal)=>report(status,signal?{code:"ECHILD"}:undefined));
|
|
2874
|
+
`;
|
|
2875
|
+
|
|
2876
|
+
// src/native-readiness.ts
|
|
2877
|
+
function isReadinessProvider(value) {
|
|
2878
|
+
return value === "claude" || value === "codex" || value === "sumi";
|
|
2879
|
+
}
|
|
2880
|
+
var safeFailureCodes = new Set([
|
|
2881
|
+
"unsupported_platform",
|
|
2882
|
+
"station_detection_failed",
|
|
2883
|
+
"station_mismatch",
|
|
2884
|
+
"hosted_unavailable",
|
|
2885
|
+
"authentication_refused",
|
|
2886
|
+
"station_setup_required",
|
|
2887
|
+
"invalid_status",
|
|
2888
|
+
"deadline_exceeded",
|
|
2889
|
+
"retention_mismatch",
|
|
2890
|
+
"managed_instructions_refused",
|
|
2891
|
+
"removal_instruction_missing",
|
|
2892
|
+
"instructions_changed",
|
|
2893
|
+
"native_config_changed",
|
|
2894
|
+
"guard_refused",
|
|
2895
|
+
"owner_refused"
|
|
2896
|
+
]);
|
|
2897
|
+
|
|
2898
|
+
// src/lib/native-readiness-command.ts
|
|
2899
|
+
var quote2 = (value) => "'" + value.replaceAll("'", "'\\''") + "'";
|
|
2900
|
+
var bootstrap = String.raw`/* hasna-native-readiness-v1 */ (() => {
|
|
2901
|
+
const fs = require("node:fs"), path = require("node:path"), crypto = require("node:crypto");
|
|
2902
|
+
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");
|
|
2903
|
+
try {
|
|
2904
|
+
const b = JSON.parse(Buffer.from(process.argv[1],"base64url").toString("utf8"));
|
|
2905
|
+
if (!["claude","codex","sumi"].includes(b.provider) || process.argv[2] !== b.provider || process.env.HOME !== b.home) throw Error();
|
|
2906
|
+
const check = (file, expected, maximum, executable) => {
|
|
2907
|
+
if (!path.isAbsolute(file) || fs.realpathSync(file) !== file) throw Error();
|
|
2908
|
+
for (let p = path.dirname(file);; p = path.dirname(p)) {
|
|
2909
|
+
const s = fs.lstatSync(p);
|
|
2910
|
+
if (!s.isDirectory() || ![0,process.getuid()].includes(s.uid) || s.mode & 0o022) throw Error();
|
|
2911
|
+
if (p === path.dirname(p)) break;
|
|
2912
|
+
}
|
|
2913
|
+
const fd = fs.openSync(file,fs.constants.O_RDONLY|fs.constants.O_NOFOLLOW|fs.constants.O_NONBLOCK);
|
|
2914
|
+
try {
|
|
2915
|
+
const s = fs.fstatSync(fd);
|
|
2916
|
+
if (!s.isFile() || ![0,process.getuid()].includes(s.uid) || s.mode & 0o022 || s.size > maximum || executable && !(s.mode & 0o100)) throw Error();
|
|
2917
|
+
const bytes = Buffer.alloc(s.size+1); let length=0;
|
|
2918
|
+
while (length<bytes.length) { const n=fs.readSync(fd,bytes,length,bytes.length-length,null); if(!n)break; length+=n; }
|
|
2919
|
+
const after=fs.fstatSync(fd);
|
|
2920
|
+
if(length!==s.size || after.size!==s.size || after.mtimeMs!==s.mtimeMs || after.ctimeMs!==s.ctimeMs)throw Error();
|
|
2921
|
+
const content=bytes.subarray(0,length);
|
|
2922
|
+
if(crypto.createHash("sha256").update(content).digest("hex")!==expected)throw Error();
|
|
2923
|
+
return content;
|
|
2924
|
+
} finally { fs.closeSync(fd); }
|
|
2925
|
+
};
|
|
2926
|
+
check(b.runtime,b.runtimeSHA256,200000000,true);
|
|
2927
|
+
const bytes=check(b.worker,b.workerSHA256,256000,false);
|
|
2928
|
+
new Function("require","process",bytes.toString("utf8"))(require,process);
|
|
2929
|
+
} catch { failed(); }
|
|
2930
|
+
})();`;
|
|
2931
|
+
var prefix2 = "/usr/bin/env -i ";
|
|
2932
|
+
function valid(b) {
|
|
2933
|
+
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)))
|
|
2934
|
+
throw new Error("Invalid readiness command binding");
|
|
2935
|
+
}
|
|
2936
|
+
function buildNativeReadinessCommand(binding) {
|
|
2937
|
+
valid(binding);
|
|
2938
|
+
const encoded = Buffer.from(JSON.stringify(binding)).toString("base64url");
|
|
2939
|
+
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(" ");
|
|
2940
|
+
}
|
|
2941
|
+
function nativeReadinessCommandBinding(command) {
|
|
2942
|
+
if (!command.startsWith(prefix2) || !command.includes(quote2(bootstrap)))
|
|
2943
|
+
return;
|
|
2944
|
+
const tail = command.slice(command.indexOf(quote2(bootstrap)) + quote2(bootstrap).length);
|
|
2945
|
+
const match = /^ '([A-Za-z0-9_-]{1,16384})' '(claude|codex|sumi)'$/.exec(tail);
|
|
2946
|
+
if (!match)
|
|
2947
|
+
return;
|
|
2948
|
+
try {
|
|
2949
|
+
const b = JSON.parse(Buffer.from(match[1], "base64url").toString("utf8"));
|
|
2950
|
+
return buildNativeReadinessCommand(b) === command ? b : undefined;
|
|
2951
|
+
} catch {
|
|
2952
|
+
return;
|
|
2953
|
+
}
|
|
2954
|
+
}
|
|
2955
|
+
function pin(path, maximum, executable) {
|
|
2956
|
+
if (realpathSync3(path) !== path)
|
|
2957
|
+
throw new Error("Readiness runner requires canonical files");
|
|
2958
|
+
for (let p = dirname4(path);; p = dirname4(p)) {
|
|
2959
|
+
const s = lstatSync2(p);
|
|
2960
|
+
if (!s.isDirectory() || ![0, process.getuid?.()].includes(s.uid) || s.mode & 18)
|
|
2961
|
+
throw new Error("Readiness runner requires safe ancestry");
|
|
2962
|
+
if (p === dirname4(p))
|
|
2963
|
+
break;
|
|
2964
|
+
}
|
|
2965
|
+
const fd = openSync2(path, constants2.O_RDONLY | constants2.O_NOFOLLOW | constants2.O_NONBLOCK);
|
|
2966
|
+
try {
|
|
2967
|
+
const s = fstatSync2(fd);
|
|
2968
|
+
if (!s.isFile() || ![0, process.getuid?.()].includes(s.uid) || s.mode & 18 || s.size > maximum || executable && !(s.mode & 64))
|
|
2969
|
+
throw new Error("Readiness runner requires safe regular files");
|
|
2970
|
+
const bytes = readFileSync3(fd);
|
|
2971
|
+
if (bytes.length > maximum)
|
|
2972
|
+
throw new Error("Readiness runner is too large");
|
|
2973
|
+
return createHash2("sha256").update(bytes).digest("hex");
|
|
2974
|
+
} finally {
|
|
2975
|
+
closeSync2(fd);
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
function installedNativeReadinessCommand(provider) {
|
|
2979
|
+
const candidates = [join6(import.meta.dir, "../../bin/native-readiness-entry.js"), join6(import.meta.dir, "../bin/native-readiness-entry.js"), join6(import.meta.dir, "native-readiness-entry.js")];
|
|
2980
|
+
const selected = candidates.find(existsSync5);
|
|
2981
|
+
if (!selected)
|
|
2982
|
+
throw new Error("Build or install the native readiness runner first");
|
|
2983
|
+
const runtime = realpathSync3(process.execPath), worker = realpathSync3(selected);
|
|
2984
|
+
return buildNativeReadinessCommand({ provider, home: realpathSync3(homedir3()), runtime, runtimeSHA256: pin(runtime, 200000000, true), worker, workerSHA256: pin(worker, 256000, false) });
|
|
2985
|
+
}
|
|
2986
|
+
|
|
2834
2987
|
// src/lib/registration.ts
|
|
2835
2988
|
init_resolve();
|
|
2836
2989
|
|
|
@@ -2898,6 +3051,8 @@ function registrationName(command) {
|
|
|
2898
3051
|
const native = nativeSafetyRegistration(command);
|
|
2899
3052
|
if (native)
|
|
2900
3053
|
return native;
|
|
3054
|
+
if (nativeReadinessCommandBinding(command))
|
|
3055
|
+
return "trash-readiness";
|
|
2901
3056
|
if (/^hooks run mementos-context(?:\s|$)/.test(command.trim())) {
|
|
2902
3057
|
try {
|
|
2903
3058
|
retainedMementosRegistration([command.trim()]);
|
|
@@ -2964,13 +3119,13 @@ function hookRegisteredInSettings(settings, name, eventSpec, hookMatcher) {
|
|
|
2964
3119
|
}
|
|
2965
3120
|
|
|
2966
3121
|
// src/lib/store.ts
|
|
2967
|
-
import { createHash as
|
|
2968
|
-
import { existsSync as
|
|
3122
|
+
import { createHash as createHash3 } from "crypto";
|
|
3123
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync2, renameSync } from "fs";
|
|
2969
3124
|
init_db();
|
|
2970
3125
|
init_config();
|
|
2971
3126
|
import { randomBytes } from "crypto";
|
|
2972
3127
|
function sha256Of(input) {
|
|
2973
|
-
return
|
|
3128
|
+
return createHash3("sha256").update(input).digest("hex");
|
|
2974
3129
|
}
|
|
2975
3130
|
function storeDb() {
|
|
2976
3131
|
return isLocalStoreRefused() ? null : getDb();
|
|
@@ -3026,11 +3181,11 @@ function assertLockShape(parsed, path) {
|
|
|
3026
3181
|
}
|
|
3027
3182
|
function readLock() {
|
|
3028
3183
|
const path = getLockPath();
|
|
3029
|
-
if (!
|
|
3184
|
+
if (!existsSync8(path))
|
|
3030
3185
|
return { hooks: {} };
|
|
3031
3186
|
let raw;
|
|
3032
3187
|
try {
|
|
3033
|
-
raw =
|
|
3188
|
+
raw = readFileSync5(path, "utf-8");
|
|
3034
3189
|
} catch {
|
|
3035
3190
|
throw new LockFileError(`hooks.lock (${path}) could not be read. Repair: fix permissions or move the file aside and run 'hooks sync'.`);
|
|
3036
3191
|
}
|
|
@@ -3067,15 +3222,15 @@ function getPinnedHook(name) {
|
|
|
3067
3222
|
function checkScriptHash(name, actual) {
|
|
3068
3223
|
const db = storeDb();
|
|
3069
3224
|
const record = db ? getHookRecord(db, name) : null;
|
|
3070
|
-
const
|
|
3071
|
-
const expected = record?.sha256 ??
|
|
3225
|
+
const pin2 = getPinnedHook(name);
|
|
3226
|
+
const expected = record?.sha256 ?? pin2?.sha256 ?? null;
|
|
3072
3227
|
if (expected !== null && expected !== actual) {
|
|
3073
3228
|
return { ok: false, pinned: true, expected, actual, name };
|
|
3074
3229
|
}
|
|
3075
3230
|
if (expected === null) {
|
|
3076
3231
|
const now2 = new Date().toISOString();
|
|
3077
|
-
const version =
|
|
3078
|
-
const source =
|
|
3232
|
+
const version = pin2?.version ?? record?.version ?? "0.0.0";
|
|
3233
|
+
const source = pin2?.source ?? record?.source_type ?? "local";
|
|
3079
3234
|
if (db) {
|
|
3080
3235
|
upsertHookRecord(db, {
|
|
3081
3236
|
name,
|
|
@@ -3092,9 +3247,9 @@ function checkScriptHash(name, actual) {
|
|
|
3092
3247
|
if (db) {
|
|
3093
3248
|
upsertHookRecord(db, {
|
|
3094
3249
|
name,
|
|
3095
|
-
version: record?.version ??
|
|
3250
|
+
version: record?.version ?? pin2?.version ?? "0.0.0",
|
|
3096
3251
|
sha256: expected,
|
|
3097
|
-
source_type: record?.source_type ??
|
|
3252
|
+
source_type: record?.source_type ?? pin2?.source ?? "local",
|
|
3098
3253
|
last_verified_at: now
|
|
3099
3254
|
});
|
|
3100
3255
|
}
|
|
@@ -3102,16 +3257,16 @@ function checkScriptHash(name, actual) {
|
|
|
3102
3257
|
}
|
|
3103
3258
|
|
|
3104
3259
|
// src/lib/codex-settings.ts
|
|
3105
|
-
import { closeSync as
|
|
3106
|
-
import { dirname as
|
|
3107
|
-
import { createHash as
|
|
3260
|
+
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";
|
|
3261
|
+
import { dirname as dirname5 } from "path";
|
|
3262
|
+
import { createHash as createHash4, randomUUID } from "crypto";
|
|
3108
3263
|
var snapshots = new WeakMap;
|
|
3109
3264
|
function current(path) {
|
|
3110
3265
|
try {
|
|
3111
|
-
const stat =
|
|
3266
|
+
const stat = lstatSync3(path);
|
|
3112
3267
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 1048576)
|
|
3113
3268
|
throw new Error("invalid_file");
|
|
3114
|
-
return
|
|
3269
|
+
return readFileSync6(path, "utf8");
|
|
3115
3270
|
} catch (error) {
|
|
3116
3271
|
if (error.code === "ENOENT")
|
|
3117
3272
|
return null;
|
|
@@ -3142,7 +3297,7 @@ function readCodexSettings(path) {
|
|
|
3142
3297
|
function assertSettingsPreimage(raw, expected) {
|
|
3143
3298
|
if (expected === undefined)
|
|
3144
3299
|
return;
|
|
3145
|
-
if (!/^(?:absent|[a-f0-9]{64})$/.test(expected) || (raw === null ? "absent" :
|
|
3300
|
+
if (!/^(?:absent|[a-f0-9]{64})$/.test(expected) || (raw === null ? "absent" : createHash4("sha256").update(raw).digest("hex")) !== expected) {
|
|
3146
3301
|
throw new Error("Hook settings changed since planning; no settings were written.");
|
|
3147
3302
|
}
|
|
3148
3303
|
}
|
|
@@ -3156,11 +3311,11 @@ function writeCodexSettings(path, settings, expectedSha256) {
|
|
|
3156
3311
|
` });
|
|
3157
3312
|
}
|
|
3158
3313
|
function writeHookSettingsPreimage(path, settings, before) {
|
|
3159
|
-
mkdirSync4(
|
|
3314
|
+
mkdirSync4(dirname5(path), { recursive: true, mode: 448 });
|
|
3160
3315
|
const lock = `${path}.hooks-write.lock`;
|
|
3161
3316
|
let fd;
|
|
3162
3317
|
try {
|
|
3163
|
-
fd =
|
|
3318
|
+
fd = openSync3(lock, "wx", 384);
|
|
3164
3319
|
} catch {
|
|
3165
3320
|
throw new Error("Codex settings are being edited; retry after the active writer finishes.");
|
|
3166
3321
|
}
|
|
@@ -3175,23 +3330,23 @@ function writeHookSettingsPreimage(path, settings, before) {
|
|
|
3175
3330
|
throw new Error("Hook settings changed since reading; re-read before retrying.");
|
|
3176
3331
|
renameSync2(temp, path);
|
|
3177
3332
|
} finally {
|
|
3178
|
-
if (
|
|
3333
|
+
if (existsSync9(temp))
|
|
3179
3334
|
unlinkSync(temp);
|
|
3180
|
-
|
|
3335
|
+
closeSync3(fd);
|
|
3181
3336
|
unlinkSync(lock);
|
|
3182
3337
|
}
|
|
3183
3338
|
}
|
|
3184
3339
|
|
|
3185
3340
|
// src/lib/claude-skills-coordination.ts
|
|
3186
|
-
import { createHash as
|
|
3341
|
+
import { createHash as createHash6 } from "crypto";
|
|
3187
3342
|
|
|
3188
3343
|
// ../../node_modules/.bun/@hasna+skills@0.9.17+a00d3064559565bb/node_modules/@hasna/skills/dist/index.js
|
|
3189
|
-
import { existsSync as
|
|
3190
|
-
import { homedir as
|
|
3191
|
-
import { join as
|
|
3344
|
+
import { existsSync as existsSync10 } from "fs";
|
|
3345
|
+
import { homedir as homedir5 } from "os";
|
|
3346
|
+
import { join as join9, resolve as resolve3 } from "path";
|
|
3192
3347
|
import { homedir as pathsResolverHomedir2 } from "os";
|
|
3193
3348
|
import { join as pathsResolverJoin2 } from "path";
|
|
3194
|
-
import { createHash as
|
|
3349
|
+
import { createHash as createHash5 } from "crypto";
|
|
3195
3350
|
import { existsSync as existsSync52, readdirSync as readdirSync3, readFileSync as readFileSync42, statSync as statSync3 } from "fs";
|
|
3196
3351
|
import { join as join52, sep as sep2 } from "path";
|
|
3197
3352
|
import { basename as basename2, isAbsolute as isAbsolute22, resolve as resolve22 } from "path";
|
|
@@ -3199,23 +3354,23 @@ import { createHash as createHash16 } from "crypto";
|
|
|
3199
3354
|
import { lstatSync as lstatSync20, readFileSync as readFileSync16, readdirSync as readdirSync9, realpathSync as realpathSync4 } from "fs";
|
|
3200
3355
|
import { basename as basename8, dirname as dirname16, isAbsolute as isAbsolute14, join as join26, resolve as resolve14, sep as sep5 } from "path";
|
|
3201
3356
|
import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
|
|
3202
|
-
import { dirname as dirname32, isAbsolute as
|
|
3203
|
-
import { lstatSync as
|
|
3357
|
+
import { dirname as dirname32, isAbsolute as isAbsolute32, join as join72, resolve as resolve32 } from "path";
|
|
3358
|
+
import { lstatSync as lstatSync32, readFileSync as readFileSync62, opendirSync } from "fs";
|
|
3204
3359
|
import { createHash as createHash22 } from "crypto";
|
|
3205
3360
|
import { lstatSync as lstatSync4, opendirSync as opendirSync2 } from "fs";
|
|
3206
3361
|
import { dirname as dirname42, isAbsolute as isAbsolute4, join as join82, resolve as resolve4 } from "path";
|
|
3207
3362
|
import { createHash as createHash32 } from "crypto";
|
|
3208
|
-
import { closeSync as
|
|
3209
|
-
import { dirname as
|
|
3363
|
+
import { closeSync as closeSync4, constants as constants3, fstatSync as fstatSync3, lstatSync as lstatSync5, openSync as openSync4, readSync } from "fs";
|
|
3364
|
+
import { dirname as dirname52, isAbsolute as isAbsolute5, resolve as resolve5 } from "path";
|
|
3210
3365
|
import { createHash as createHash42 } from "crypto";
|
|
3211
3366
|
import { lstatSync as lstatSync6, readlinkSync } from "fs";
|
|
3212
|
-
import { dirname as dirname6, isAbsolute as isAbsolute6, join as
|
|
3367
|
+
import { dirname as dirname6, isAbsolute as isAbsolute6, join as join92, parse, resolve as resolve6, sep as sep22 } from "path";
|
|
3213
3368
|
import { closeSync as closeSync12, constants as constants9, fstatSync as fstatSync12, lstatSync as lstatSync17, openSync as openSync12, opendirSync as opendirSync5, readSync as readSync8 } from "fs";
|
|
3214
3369
|
import { basename as basename6, dirname as dirname13, isAbsolute as isAbsolute12, join as join25, resolve as resolve12 } from "path";
|
|
3215
3370
|
import { isAbsolute as isAbsolute11, join as join23, resolve as resolve11 } from "path";
|
|
3216
3371
|
import { createRequire } from "module";
|
|
3217
3372
|
import { createHash as createHash10, randomUUID as randomUUID2 } from "crypto";
|
|
3218
|
-
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
|
|
3373
|
+
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";
|
|
3219
3374
|
import { dirname as dirname10, join as join16, resolve as resolve9, relative as relative4, isAbsolute as isAbsolute9, sep as sep4 } from "path";
|
|
3220
3375
|
import { homedir as homedir22 } from "os";
|
|
3221
3376
|
import { closeSync as closeSync8, constants as constants5, fstatSync as fstatSync8, lstatSync as lstatSync11, openSync as openSync8, readSync as readSync4 } from "fs";
|
|
@@ -3516,8 +3671,8 @@ var require_directives = __commonJS((exports) => {
|
|
|
3516
3671
|
if (parts.length < 2)
|
|
3517
3672
|
return false;
|
|
3518
3673
|
}
|
|
3519
|
-
const [handle,
|
|
3520
|
-
this.tags[handle] =
|
|
3674
|
+
const [handle, prefix3] = parts;
|
|
3675
|
+
this.tags[handle] = prefix3;
|
|
3521
3676
|
return true;
|
|
3522
3677
|
}
|
|
3523
3678
|
case "%YAML": {
|
|
@@ -3561,10 +3716,10 @@ var require_directives = __commonJS((exports) => {
|
|
|
3561
3716
|
const [, handle, suffix] = source.match(/^(.*!)([^!]*)$/s);
|
|
3562
3717
|
if (!suffix)
|
|
3563
3718
|
onError(`The ${source} tag has no suffix`);
|
|
3564
|
-
const
|
|
3565
|
-
if (
|
|
3719
|
+
const prefix3 = this.tags[handle];
|
|
3720
|
+
if (prefix3) {
|
|
3566
3721
|
try {
|
|
3567
|
-
return
|
|
3722
|
+
return prefix3 + decodeURIComponent(suffix);
|
|
3568
3723
|
} catch (error) {
|
|
3569
3724
|
onError(String(error));
|
|
3570
3725
|
return null;
|
|
@@ -3576,9 +3731,9 @@ var require_directives = __commonJS((exports) => {
|
|
|
3576
3731
|
return null;
|
|
3577
3732
|
}
|
|
3578
3733
|
tagString(tag) {
|
|
3579
|
-
for (const [handle,
|
|
3580
|
-
if (tag.startsWith(
|
|
3581
|
-
return handle + escapeTagName(tag.substring(
|
|
3734
|
+
for (const [handle, prefix3] of Object.entries(this.tags)) {
|
|
3735
|
+
if (tag.startsWith(prefix3))
|
|
3736
|
+
return handle + escapeTagName(tag.substring(prefix3.length));
|
|
3582
3737
|
}
|
|
3583
3738
|
return tag[0] === "!" ? tag : `!<${tag}>`;
|
|
3584
3739
|
}
|
|
@@ -3595,11 +3750,11 @@ var require_directives = __commonJS((exports) => {
|
|
|
3595
3750
|
tagNames = Object.keys(tags);
|
|
3596
3751
|
} else
|
|
3597
3752
|
tagNames = [];
|
|
3598
|
-
for (const [handle,
|
|
3599
|
-
if (handle === "!!" &&
|
|
3753
|
+
for (const [handle, prefix3] of tagEntries) {
|
|
3754
|
+
if (handle === "!!" && prefix3 === "tag:yaml.org,2002:")
|
|
3600
3755
|
continue;
|
|
3601
|
-
if (!doc || tagNames.some((tn) => tn.startsWith(
|
|
3602
|
-
lines.push(`%TAG ${handle} ${
|
|
3756
|
+
if (!doc || tagNames.some((tn) => tn.startsWith(prefix3)))
|
|
3757
|
+
lines.push(`%TAG ${handle} ${prefix3}`);
|
|
3603
3758
|
}
|
|
3604
3759
|
return lines.join(`
|
|
3605
3760
|
`);
|
|
@@ -3630,14 +3785,14 @@ var require_anchors = __commonJS((exports) => {
|
|
|
3630
3785
|
});
|
|
3631
3786
|
return anchors;
|
|
3632
3787
|
}
|
|
3633
|
-
function findNewAnchor(
|
|
3788
|
+
function findNewAnchor(prefix3, exclude) {
|
|
3634
3789
|
for (let i = 1;; ++i) {
|
|
3635
|
-
const name = `${
|
|
3790
|
+
const name = `${prefix3}${i}`;
|
|
3636
3791
|
if (!exclude.has(name))
|
|
3637
3792
|
return name;
|
|
3638
3793
|
}
|
|
3639
3794
|
}
|
|
3640
|
-
function createNodeAnchors(doc,
|
|
3795
|
+
function createNodeAnchors(doc, prefix3) {
|
|
3641
3796
|
const aliasObjects = [];
|
|
3642
3797
|
const sourceObjects = new Map;
|
|
3643
3798
|
let prevAnchors = null;
|
|
@@ -3645,7 +3800,7 @@ var require_anchors = __commonJS((exports) => {
|
|
|
3645
3800
|
onAnchor: (source) => {
|
|
3646
3801
|
aliasObjects.push(source);
|
|
3647
3802
|
prevAnchors ?? (prevAnchors = anchorNames(doc));
|
|
3648
|
-
const anchor = findNewAnchor(
|
|
3803
|
+
const anchor = findNewAnchor(prefix3, prevAnchors);
|
|
3649
3804
|
prevAnchors.add(anchor);
|
|
3650
3805
|
return anchor;
|
|
3651
3806
|
},
|
|
@@ -5470,10 +5625,10 @@ var require_int = __commonJS((exports) => {
|
|
|
5470
5625
|
var stringifyNumber = require_stringifyNumber();
|
|
5471
5626
|
var intIdentify = (value) => typeof value === "bigint" || Number.isInteger(value);
|
|
5472
5627
|
var intResolve = (str, offset, radix, { intAsBigInt }) => intAsBigInt ? BigInt(str) : parseInt(str.substring(offset), radix);
|
|
5473
|
-
function intStringify(node, radix,
|
|
5628
|
+
function intStringify(node, radix, prefix3) {
|
|
5474
5629
|
const { value } = node;
|
|
5475
5630
|
if (intIdentify(value) && value >= 0)
|
|
5476
|
-
return
|
|
5631
|
+
return prefix3 + value.toString(radix);
|
|
5477
5632
|
return stringifyNumber.stringifyNumber(node);
|
|
5478
5633
|
}
|
|
5479
5634
|
var intOct = {
|
|
@@ -5884,11 +6039,11 @@ var require_int2 = __commonJS((exports) => {
|
|
|
5884
6039
|
const n = parseInt(str, radix);
|
|
5885
6040
|
return sign === "-" ? -1 * n : n;
|
|
5886
6041
|
}
|
|
5887
|
-
function intStringify(node, radix,
|
|
6042
|
+
function intStringify(node, radix, prefix3) {
|
|
5888
6043
|
const { value } = node;
|
|
5889
6044
|
if (intIdentify(value)) {
|
|
5890
6045
|
const str = value.toString(radix);
|
|
5891
|
-
return value < 0 ? "-" +
|
|
6046
|
+
return value < 0 ? "-" + prefix3 + str.substr(1) : prefix3 + str;
|
|
5892
6047
|
}
|
|
5893
6048
|
return stringifyNumber.stringifyNumber(node);
|
|
5894
6049
|
}
|
|
@@ -8802,9 +8957,9 @@ var require_lexer = __commonJS((exports) => {
|
|
|
8802
8957
|
}
|
|
8803
8958
|
}
|
|
8804
8959
|
*parseQuotedScalar() {
|
|
8805
|
-
const
|
|
8806
|
-
let end = this.buffer.indexOf(
|
|
8807
|
-
if (
|
|
8960
|
+
const quote3 = this.charAt(0);
|
|
8961
|
+
let end = this.buffer.indexOf(quote3, this.pos + 1);
|
|
8962
|
+
if (quote3 === "'") {
|
|
8808
8963
|
while (end !== -1 && this.buffer[end + 1] === "'")
|
|
8809
8964
|
end = this.buffer.indexOf("'", end + 2);
|
|
8810
8965
|
} else {
|
|
@@ -10089,10 +10244,10 @@ var SKILLS_HOME_ENV = "SKILLS_HOME";
|
|
|
10089
10244
|
var DEFAULT_SQLITE_FILENAME = "server.db";
|
|
10090
10245
|
var GLOBAL_CONFIG_FILENAME = "config.json";
|
|
10091
10246
|
function effectiveHome() {
|
|
10092
|
-
return process.env["HOME"] || process.env["USERPROFILE"] ||
|
|
10247
|
+
return process.env["HOME"] || process.env["USERPROFILE"] || homedir5() || "/tmp";
|
|
10093
10248
|
}
|
|
10094
10249
|
function legacyDataRoot() {
|
|
10095
|
-
return
|
|
10250
|
+
return join9(effectiveHome(), ".hasna", "skills");
|
|
10096
10251
|
}
|
|
10097
10252
|
function resolverDataRoot(home = effectiveHome(), env) {
|
|
10098
10253
|
return dataDir2({ app: "skills", home, env });
|
|
@@ -10101,7 +10256,7 @@ function adoptResolverDataRoot2(resolved, env = process.env) {
|
|
|
10101
10256
|
const dataOverride = env.HASNA_DATA_HOME;
|
|
10102
10257
|
if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
|
|
10103
10258
|
return true;
|
|
10104
|
-
return
|
|
10259
|
+
return existsSync10(join9(resolved, DEFAULT_SQLITE_FILENAME)) || existsSync10(join9(resolved, GLOBAL_CONFIG_FILENAME));
|
|
10105
10260
|
}
|
|
10106
10261
|
function exactDataRoot() {
|
|
10107
10262
|
for (const key of [DATA_DIR_ENV, HASNA_SKILLS_HOME_ENV, SKILLS_HOME_ENV]) {
|
|
@@ -10335,7 +10490,7 @@ function* bundleHashParts(files) {
|
|
|
10335
10490
|
yield new TextEncoder().encode("\x00");
|
|
10336
10491
|
}
|
|
10337
10492
|
function hashBundleFiles(files) {
|
|
10338
|
-
const hash =
|
|
10493
|
+
const hash = createHash5(CONTENT_HASH_ALGORITHM);
|
|
10339
10494
|
for (const part of bundleHashParts(files))
|
|
10340
10495
|
hash.update(part);
|
|
10341
10496
|
return hash.digest("hex");
|
|
@@ -10790,10 +10945,10 @@ function quoted(value) {
|
|
|
10790
10945
|
throw new Error("Invalid Hermes Skills command");
|
|
10791
10946
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
10792
10947
|
}
|
|
10793
|
-
function hermesHookDefinitions(
|
|
10794
|
-
if (!
|
|
10948
|
+
function hermesHookDefinitions(supervisor2) {
|
|
10949
|
+
if (!isAbsolute32(supervisor2.runtime) || !isAbsolute32(supervisor2.path))
|
|
10795
10950
|
throw new Error("Expected absolute Hermes supervisor paths");
|
|
10796
|
-
const entry = (event) => ({ command: `${quoted(
|
|
10951
|
+
const entry = (event) => ({ command: `${quoted(supervisor2.runtime)} ${quoted(supervisor2.path)} --event ${event}`, timeout: 15, ...event === "pre_tool_call" ? { fail_closed: true } : {} });
|
|
10797
10952
|
return { pre_llm_call: entry("pre_llm_call"), pre_tool_call: entry("pre_tool_call") };
|
|
10798
10953
|
}
|
|
10799
10954
|
function assertHermesEnvironment(home) {
|
|
@@ -10804,30 +10959,30 @@ function assertHermesEnvironment(home) {
|
|
|
10804
10959
|
throw new Error(`NATIVE_SKILL_DRIFT: Hermes ${variable} requires a dedicated discovery adapter; unset it before using this adapter`);
|
|
10805
10960
|
}
|
|
10806
10961
|
const selected = process.env.HERMES_HOME?.trim();
|
|
10807
|
-
if (selected && (!
|
|
10962
|
+
if (selected && (!isAbsolute32(selected) || resolve32(selected) !== join72(resolve32(home), ".hermes")))
|
|
10808
10963
|
throw new Error("NATIVE_SKILL_DRIFT: custom Hermes homes/profiles require their own reviewed bridge");
|
|
10809
10964
|
if (process.env.HERMES_ENABLE_PROJECT_PLUGINS?.trim() && !["0", "false", "no", "off"].includes(process.env.HERMES_ENABLE_PROJECT_PLUGINS.trim().toLowerCase()))
|
|
10810
10965
|
throw new Error("NATIVE_SKILL_DRIFT: Hermes project plugins require a dedicated discovery review");
|
|
10811
10966
|
}
|
|
10812
10967
|
function read(path) {
|
|
10813
10968
|
for (let current2 = path;; current2 = dirname32(current2)) {
|
|
10814
|
-
if (
|
|
10969
|
+
if (lstatSync32(current2, { throwIfNoEntry: false })?.isSymbolicLink())
|
|
10815
10970
|
throw new Error("NATIVE_SKILL_DRIFT: symlink Hermes protection input");
|
|
10816
10971
|
if (dirname32(current2) === current2)
|
|
10817
10972
|
break;
|
|
10818
10973
|
}
|
|
10819
|
-
const stat =
|
|
10974
|
+
const stat = lstatSync32(path, { throwIfNoEntry: false });
|
|
10820
10975
|
if (!stat)
|
|
10821
10976
|
return null;
|
|
10822
10977
|
if (!stat.isFile() || stat.size > 1024 * 1024)
|
|
10823
10978
|
throw new Error("NATIVE_SKILL_DRIFT: invalid Hermes protection input");
|
|
10824
|
-
return
|
|
10979
|
+
return readFileSync62(path, "utf8");
|
|
10825
10980
|
}
|
|
10826
|
-
function assertHermesProtection(home, config, command, profile,
|
|
10981
|
+
function assertHermesProtection(home, config, command, profile, supervisor2) {
|
|
10827
10982
|
assertHermesEnvironment(home);
|
|
10828
10983
|
if (read(join72(home, ".hermes", HERMES_OPT_OUT)) === null)
|
|
10829
10984
|
throw new Error("NATIVE_SKILL_DRIFT: Hermes bundled skill reseeding is not disabled; run skills hook install");
|
|
10830
|
-
if (!
|
|
10985
|
+
if (!supervisor2 || !isAbsolute32(supervisor2.path) || supervisor2.runtime !== process.execPath || read(supervisor2.path) !== renderHermesSupervisor(command, profile))
|
|
10831
10986
|
throw new Error("NATIVE_SKILL_DRIFT: Hermes owned supervisor changed; run skills hook install");
|
|
10832
10987
|
const raw = read(join72(home, ".hermes/shell-hooks-allowlist.json"));
|
|
10833
10988
|
let trust;
|
|
@@ -10836,7 +10991,7 @@ function assertHermesProtection(home, config, command, profile, supervisor) {
|
|
|
10836
10991
|
} catch {
|
|
10837
10992
|
throw new Error("NATIVE_SKILL_DRIFT: invalid Hermes native hook trust");
|
|
10838
10993
|
}
|
|
10839
|
-
for (const [event, entry] of Object.entries(hermesHookDefinitions(
|
|
10994
|
+
for (const [event, entry] of Object.entries(hermesHookDefinitions(supervisor2))) {
|
|
10840
10995
|
if (!Array.isArray(config.hooks?.[event]) || config.hooks[event].filter((actual) => sameEntry(actual, entry)).length !== 1)
|
|
10841
10996
|
throw new Error("NATIVE_SKILL_DRIFT: Hermes required native hooks changed; run skills hook install");
|
|
10842
10997
|
if (!Array.isArray(trust?.approvals) || !trust.approvals.some((approval) => object2(approval) && approval.event === event && approval.command === entry.command))
|
|
@@ -10854,7 +11009,7 @@ function assertHermesProtection(home, config, command, profile, supervisor) {
|
|
|
10854
11009
|
function assertNoHermesLegacyShadow(root) {
|
|
10855
11010
|
let entries = 0;
|
|
10856
11011
|
for (let cursor = resolve32(root);; cursor = dirname32(cursor)) {
|
|
10857
|
-
if (
|
|
11012
|
+
if (lstatSync32(cursor, { throwIfNoEntry: false })?.isSymbolicLink())
|
|
10858
11013
|
throw new Error("NATIVE_SKILL_DRIFT: symlink Hermes legacy discovery root");
|
|
10859
11014
|
if (dirname32(cursor) === cursor)
|
|
10860
11015
|
break;
|
|
@@ -10862,7 +11017,7 @@ function assertNoHermesLegacyShadow(root) {
|
|
|
10862
11017
|
function scan(path, depth) {
|
|
10863
11018
|
if (++entries > 1e4 || depth > 64)
|
|
10864
11019
|
throw new Error("NATIVE_SKILL_DRIFT: Hermes legacy discovery limit exceeded; review the root before continuing");
|
|
10865
|
-
const stat =
|
|
11020
|
+
const stat = lstatSync32(path, { throwIfNoEntry: false });
|
|
10866
11021
|
if (!stat)
|
|
10867
11022
|
return;
|
|
10868
11023
|
if (stat.isSymbolicLink())
|
|
@@ -10872,9 +11027,9 @@ function assertNoHermesLegacyShadow(root) {
|
|
|
10872
11027
|
throw new Error("NATIVE_SKILL_DRIFT: unsupported Hermes legacy discovery input");
|
|
10873
11028
|
return;
|
|
10874
11029
|
}
|
|
10875
|
-
if (
|
|
11030
|
+
if (lstatSync32(join72(path, "skills-cli.md"), { throwIfNoEntry: false }))
|
|
10876
11031
|
throw new Error("NATIVE_SKILL_DRIFT: legacy Hermes skills-cli.md can shadow the owned bridge; preserve and retire it before continuing");
|
|
10877
|
-
const hasDescriptor = Boolean(
|
|
11032
|
+
const hasDescriptor = Boolean(lstatSync32(join72(path, "SKILL.md"), { throwIfNoEntry: false }));
|
|
10878
11033
|
const directory = opendirSync(path);
|
|
10879
11034
|
try {
|
|
10880
11035
|
for (let child = directory.readSync();child; child = directory.readSync()) {
|
|
@@ -11007,10 +11162,10 @@ function discoveryByteBudget() {
|
|
|
11007
11162
|
function safe2(path) {
|
|
11008
11163
|
if (typeof path !== "string" || !isAbsolute5(path) || path !== resolve5(path) || path.length > AGENT_POLICY_LIMITS.pathCharacters || path.includes("\x00"))
|
|
11009
11164
|
throw new Error("Expected a canonical absolute raw discovery path");
|
|
11010
|
-
for (let cursor = path;; cursor =
|
|
11165
|
+
for (let cursor = path;; cursor = dirname52(cursor)) {
|
|
11011
11166
|
if (lstatSync5(cursor, { throwIfNoEntry: false })?.isSymbolicLink())
|
|
11012
11167
|
throw new Error("Refusing symlink raw discovery input");
|
|
11013
|
-
if (
|
|
11168
|
+
if (dirname52(cursor) === cursor)
|
|
11014
11169
|
break;
|
|
11015
11170
|
}
|
|
11016
11171
|
}
|
|
@@ -11038,9 +11193,9 @@ function hashRawDiscoveryFile(path, budget, changes) {
|
|
|
11038
11193
|
throw new Error("Unsupported or oversized raw discovery input");
|
|
11039
11194
|
if (initial.size > budget.remaining)
|
|
11040
11195
|
throw new Error("Raw discovery aggregate byte limit exceeded");
|
|
11041
|
-
const fd =
|
|
11196
|
+
const fd = openSync4(path, constants3.O_RDONLY | constants3.O_NOFOLLOW | constants3.O_NONBLOCK);
|
|
11042
11197
|
try {
|
|
11043
|
-
const opened =
|
|
11198
|
+
const opened = fstatSync3(fd);
|
|
11044
11199
|
if (!opened.isFile() || !same2(initial, opened))
|
|
11045
11200
|
throw new Error("Raw discovery input changed during open");
|
|
11046
11201
|
const hash = createHash32("sha256"), buffer = Buffer.alloc(64 * 1024);
|
|
@@ -11055,13 +11210,13 @@ function hashRawDiscoveryFile(path, budget, changes) {
|
|
|
11055
11210
|
consume(budget, count);
|
|
11056
11211
|
hash.update(buffer.subarray(0, count));
|
|
11057
11212
|
}
|
|
11058
|
-
const final =
|
|
11213
|
+
const final = fstatSync3(fd), current2 = lstatSync5(path, { throwIfNoEntry: false });
|
|
11059
11214
|
safe2(path);
|
|
11060
11215
|
if (!current2?.isFile() || !same2(opened, final) || !same2(final, current2) || bytes !== final.size)
|
|
11061
11216
|
throw new Error("Raw discovery input changed during read");
|
|
11062
11217
|
return hash.digest("hex");
|
|
11063
11218
|
} finally {
|
|
11064
|
-
|
|
11219
|
+
closeSync4(fd);
|
|
11065
11220
|
}
|
|
11066
11221
|
}
|
|
11067
11222
|
function identity2(stat, mutable = false) {
|
|
@@ -11107,7 +11262,7 @@ function trace(path) {
|
|
|
11107
11262
|
cursor = dirname6(cursor);
|
|
11108
11263
|
continue;
|
|
11109
11264
|
}
|
|
11110
|
-
const candidate =
|
|
11265
|
+
const candidate = join92(cursor, component);
|
|
11111
11266
|
if (candidate.length > AGENT_POLICY_LIMITS.pathCharacters)
|
|
11112
11267
|
throw new Error("Discovery resolved path length limit exceeded");
|
|
11113
11268
|
const stat = lstatSync6(candidate, { bigint: true, throwIfNoEntry: false });
|
|
@@ -12117,7 +12272,7 @@ function assertSafePath(path) {
|
|
|
12117
12272
|
}
|
|
12118
12273
|
function readRootAlias(home, agent) {
|
|
12119
12274
|
assertSafePath(home);
|
|
12120
|
-
const realHome =
|
|
12275
|
+
const realHome = realpathSync32(home), alias = join16(realHome, `.${agent}`);
|
|
12121
12276
|
const stat = lstatSync14(alias, { throwIfNoEntry: false, bigint: true });
|
|
12122
12277
|
if (!stat?.isSymbolicLink())
|
|
12123
12278
|
throw new Error(`Agent root alias changed after planning: ${alias}`);
|
|
@@ -12126,7 +12281,7 @@ function readRootAlias(home, agent) {
|
|
|
12126
12281
|
const targetStat = lstatSync14(targetPath, { throwIfNoEntry: false });
|
|
12127
12282
|
if (!targetStat?.isDirectory())
|
|
12128
12283
|
throw new Error(`Agent root alias must target an existing directory: ${alias}`);
|
|
12129
|
-
const target =
|
|
12284
|
+
const target = realpathSync32(targetPath), inside = relative4(realHome, target);
|
|
12130
12285
|
if (!inside || inside === ".." || inside.startsWith(`..${sep4}`) || isAbsolute9(inside))
|
|
12131
12286
|
throw new Error(`Agent root alias target must be inside the real home: ${alias}`);
|
|
12132
12287
|
return { agent, home: realHome, alias, target, link, aliasIdentity: `${stat.dev}:${stat.ino}:${stat.ctimeNs}`, targetIdentity: `${targetStat.dev}:${targetStat.ino}` };
|
|
@@ -12660,10 +12815,10 @@ function assertManagedAgentBridge(agent, options = {}) {
|
|
|
12660
12815
|
if (options.profileId !== undefined && options.profileId !== profile)
|
|
12661
12816
|
throw new Error("NATIVE_SKILL_DRIFT: the hook selection profile differs from its managed binding; restart the native client after reviewing skills hook install");
|
|
12662
12817
|
if (agent === "hermes") {
|
|
12663
|
-
const
|
|
12664
|
-
if (
|
|
12818
|
+
const supervisor2 = binding.supervisors?.hermes;
|
|
12819
|
+
if (supervisor2?.path !== join16(dataDir22, "agent-hooks", "hermes.js") || supervisor2?.sha256 !== sha(renderHermesSupervisor(command, profile)))
|
|
12665
12820
|
throw new Error("NATIVE_SKILL_DRIFT: Hermes supervisor binding changed; run skills hook install");
|
|
12666
|
-
assertHermesProtection(home, config, command, profile,
|
|
12821
|
+
assertHermesProtection(home, config, command, profile, supervisor2);
|
|
12667
12822
|
} else if (agent === "opencode") {
|
|
12668
12823
|
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))
|
|
12669
12824
|
throw new Error("NATIVE_SKILL_DRIFT: OpenCode bridge protection changed; run skills hook install");
|
|
@@ -12955,9 +13110,9 @@ function snapshotPluginTree(root, options = {}) {
|
|
|
12955
13110
|
const pins = directoryNames(absolute);
|
|
12956
13111
|
pluginNeed(pins.length <= 1024, "Native plugin pin count exceeds its limit");
|
|
12957
13112
|
directories.push({ path: absolute, stat: entryStat, names: pins });
|
|
12958
|
-
for (const
|
|
12959
|
-
pluginNeed(/^[1-9][0-9]{0,9}$/.test(
|
|
12960
|
-
const pinPath = join21(absolute,
|
|
13113
|
+
for (const pin2 of pins) {
|
|
13114
|
+
pluginNeed(/^[1-9][0-9]{0,9}$/.test(pin2) && Number(pin2) <= 2147483647, "Unrecognized native plugin pin name");
|
|
13115
|
+
const pinPath = join21(absolute, pin2), pinStat = stat(pinPath);
|
|
12961
13116
|
pluginNeed(pinStat, "Native plugin pin disappeared");
|
|
12962
13117
|
const bytes2 = readBytes(pinPath, 4096, "native-pin");
|
|
12963
13118
|
let value;
|
|
@@ -12967,7 +13122,7 @@ function snapshotPluginTree(root, options = {}) {
|
|
|
12967
13122
|
pluginRefusal("Malformed native plugin process pin");
|
|
12968
13123
|
}
|
|
12969
13124
|
pluginKeys(value, ["pid", "procStart", "procStartFt"]);
|
|
12970
|
-
pluginNeed(value.pid === Number(
|
|
13125
|
+
pluginNeed(value.pid === Number(pin2), "Native plugin pin identity differs from its name");
|
|
12971
13126
|
for (const key of ["procStart", "procStartFt"])
|
|
12972
13127
|
pluginNeed(value[key] === undefined || typeof value[key] === "string" && value[key].length <= 128 && !/[\x00-\x1f\x7f]/.test(value[key]), "Invalid native plugin process-start witness");
|
|
12973
13128
|
same3(pinStat, stat(pinPath));
|
|
@@ -13175,10 +13330,10 @@ function validatePlan(value) {
|
|
|
13175
13330
|
pluginNeed(observedEvidenceDigest(plan.observation) === plan.evidenceDigest, "Plugin receipt observed evidence changed");
|
|
13176
13331
|
pluginNeed(identityDigest(plan) === plan.planDigest, "Plugin receipt immutable identity changed");
|
|
13177
13332
|
}
|
|
13178
|
-
var
|
|
13333
|
+
var quote3 = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
13179
13334
|
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"';
|
|
13180
13335
|
function pluginResolverCommand(binding) {
|
|
13181
|
-
return `${
|
|
13336
|
+
return `${quote3("/bin/sh")} -eu -c ${quote3(resolverGuard)} plugin-resolver ${quote3(binding.target.resolver.executable)} ${quote3(binding.target.resolver.digest)} ${quote3(bindingId(binding))}`;
|
|
13182
13337
|
}
|
|
13183
13338
|
function pluginBindingPath(storeRoot, id) {
|
|
13184
13339
|
pluginNeed(/^[a-f0-9]{64}$/.test(id), "Invalid plugin binding identifier");
|
|
@@ -16905,11 +17060,11 @@ function isValidCIDRv6(value) {
|
|
|
16905
17060
|
const parts = value.split("/");
|
|
16906
17061
|
if (parts.length !== 2)
|
|
16907
17062
|
return false;
|
|
16908
|
-
const [address,
|
|
16909
|
-
if (!
|
|
17063
|
+
const [address, prefix3] = parts;
|
|
17064
|
+
if (!prefix3)
|
|
16910
17065
|
return false;
|
|
16911
|
-
const prefixNum = Number(
|
|
16912
|
-
if (`${prefixNum}` !==
|
|
17066
|
+
const prefixNum = Number(prefix3);
|
|
17067
|
+
if (`${prefixNum}` !== prefix3)
|
|
16913
17068
|
return false;
|
|
16914
17069
|
if (prefixNum < 0 || prefixNum > 128)
|
|
16915
17070
|
return false;
|
|
@@ -26791,8 +26946,8 @@ function generateStringFormatCheck(doc, ctx, def, accessor) {
|
|
|
26791
26946
|
doc.write(`if (!${accessor}.includes(${esc(def.includes)})) return INVALID;`);
|
|
26792
26947
|
break;
|
|
26793
26948
|
case "starts_with": {
|
|
26794
|
-
const
|
|
26795
|
-
doc.write(`if (${accessor}.slice(0, ${
|
|
26949
|
+
const prefix3 = def.prefix;
|
|
26950
|
+
doc.write(`if (${accessor}.slice(0, ${prefix3.length}) !== ${esc(prefix3)}) return INVALID;`);
|
|
26796
26951
|
break;
|
|
26797
26952
|
}
|
|
26798
26953
|
case "ends_with": {
|
|
@@ -27525,8 +27680,8 @@ function generateDiscriminatedUnionCheck(doc, ctx, def, accessor) {
|
|
|
27525
27680
|
claimed.add(value);
|
|
27526
27681
|
}
|
|
27527
27682
|
const conditions = Array.from(values, (value) => literalEquality(ctx, discVar, value));
|
|
27528
|
-
const
|
|
27529
|
-
doc.write(`${
|
|
27683
|
+
const prefix3 = firstBranch ? "if" : "else if";
|
|
27684
|
+
doc.write(`${prefix3} (${conditions.join(" || ")}) {`);
|
|
27530
27685
|
doc.indented((d) => {
|
|
27531
27686
|
const branchOutput = generateCheck(d, ctx, option, accessor);
|
|
27532
27687
|
d.write(`${outputVar} = ${branchOutput};`);
|
|
@@ -28380,12 +28535,12 @@ function _includes(includes, params) {
|
|
|
28380
28535
|
includes
|
|
28381
28536
|
});
|
|
28382
28537
|
}
|
|
28383
|
-
function _startsWith(
|
|
28538
|
+
function _startsWith(prefix3, params) {
|
|
28384
28539
|
return new $ZodCheckStartsWith({
|
|
28385
28540
|
check: "string_format",
|
|
28386
28541
|
format: "starts_with",
|
|
28387
28542
|
...normalizeParams(params),
|
|
28388
|
-
prefix:
|
|
28543
|
+
prefix: prefix3
|
|
28389
28544
|
});
|
|
28390
28545
|
}
|
|
28391
28546
|
function _endsWith(suffix, params) {
|
|
@@ -34353,7 +34508,7 @@ function coordinateClaudeSettings(before, replacement, options) {
|
|
|
34353
34508
|
const plan = planClaudeHookEventsUpdate({
|
|
34354
34509
|
...options,
|
|
34355
34510
|
events: selected,
|
|
34356
|
-
expectedSettingsSha256: before === null ? "absent" :
|
|
34511
|
+
expectedSettingsSha256: before === null ? "absent" : createHash6("sha256").update(before).digest("hex"),
|
|
34357
34512
|
replacement
|
|
34358
34513
|
});
|
|
34359
34514
|
if (!plan)
|
|
@@ -34364,7 +34519,7 @@ function coordinateClaudeSettings(before, replacement, options) {
|
|
|
34364
34519
|
|
|
34365
34520
|
// src/lib/installer.ts
|
|
34366
34521
|
var __dirname2 = dirname7(fileURLToPath2(import.meta.url));
|
|
34367
|
-
var HOOKS_DIR =
|
|
34522
|
+
var HOOKS_DIR = existsSync11(join10(__dirname2, "..", "..", "hooks", "hook-gitguard")) ? join10(__dirname2, "..", "..", "hooks") : join10(__dirname2, "..", "hooks");
|
|
34368
34523
|
function normalizeHookName(name) {
|
|
34369
34524
|
return name.startsWith("hook-") ? name : `hook-${name}`;
|
|
34370
34525
|
}
|
|
@@ -34452,11 +34607,11 @@ function getSettingsPath(scope = "global", target = "claude", codewithConfigPath
|
|
|
34452
34607
|
return globalOverride;
|
|
34453
34608
|
const dir = getTargetSettingsDir(target);
|
|
34454
34609
|
if (target === "codex")
|
|
34455
|
-
return join10(scope === "project" ? process.cwd() :
|
|
34610
|
+
return join10(scope === "project" ? process.cwd() : homedir6(), dir, "hooks.json");
|
|
34456
34611
|
if (scope === "project") {
|
|
34457
34612
|
return target === "codewith" ? join10(process.cwd(), dir, "config.toml") : join10(process.cwd(), dir, "settings.json");
|
|
34458
34613
|
}
|
|
34459
|
-
return target === "codewith" ? join10(
|
|
34614
|
+
return target === "codewith" ? join10(homedir6(), dir, "config.toml") : join10(homedir6(), dir, "settings.json");
|
|
34460
34615
|
}
|
|
34461
34616
|
function getHookPath(name) {
|
|
34462
34617
|
const shortName = shortHookName(name);
|
|
@@ -34466,7 +34621,7 @@ function getHookPath(name) {
|
|
|
34466
34621
|
return join10(HOOKS_DIR, normalizeHookName(shortName));
|
|
34467
34622
|
}
|
|
34468
34623
|
function hookExists(name) {
|
|
34469
|
-
return
|
|
34624
|
+
return existsSync11(getHookPath(name));
|
|
34470
34625
|
}
|
|
34471
34626
|
var settingsPreimages = new WeakMap;
|
|
34472
34627
|
function readSettings2(scope = "global", target = "claude") {
|
|
@@ -34474,7 +34629,7 @@ function readSettings2(scope = "global", target = "claude") {
|
|
|
34474
34629
|
if (target === "codex")
|
|
34475
34630
|
return readCodexSettings(path);
|
|
34476
34631
|
try {
|
|
34477
|
-
if (
|
|
34632
|
+
if (existsSync11(path)) {
|
|
34478
34633
|
const text3 = readFileSync7(path, "utf-8"), value = JSON.parse(text3);
|
|
34479
34634
|
settingsPreimages.set(value, { path, text: text3 });
|
|
34480
34635
|
return value;
|
|
@@ -34483,14 +34638,14 @@ function readSettings2(scope = "global", target = "claude") {
|
|
|
34483
34638
|
console.warn("[hooks] Cannot read hook settings; repair the selected file before installation.");
|
|
34484
34639
|
}
|
|
34485
34640
|
const empty = {};
|
|
34486
|
-
if (!
|
|
34641
|
+
if (!existsSync11(path))
|
|
34487
34642
|
settingsPreimages.set(empty, { path, text: null });
|
|
34488
34643
|
return empty;
|
|
34489
34644
|
}
|
|
34490
34645
|
function settingsIdentity(path) {
|
|
34491
|
-
if (
|
|
34646
|
+
if (existsSync11(path))
|
|
34492
34647
|
return realpathSync5(path);
|
|
34493
|
-
return
|
|
34648
|
+
return existsSync11(dirname7(path)) ? join10(realpathSync5(dirname7(path)), basename3(path)) : resolve7(path);
|
|
34494
34649
|
}
|
|
34495
34650
|
function writeSettings(settings, scope = "global", target = "claude", expectedSettingsSha256) {
|
|
34496
34651
|
const path = getSettingsPath(scope, target);
|
|
@@ -34502,21 +34657,21 @@ function writeSettings(settings, scope = "global", target = "claude", expectedSe
|
|
|
34502
34657
|
throw new Error("Hook settings require a valid captured preimage.");
|
|
34503
34658
|
assertSettingsPreimage(captured.text, expectedSettingsSha256);
|
|
34504
34659
|
}
|
|
34505
|
-
if (target === "claude" && settingsIdentity(path) === settingsIdentity(join10(
|
|
34660
|
+
if (target === "claude" && settingsIdentity(path) === settingsIdentity(join10(homedir6(), ".claude/settings.json"))) {
|
|
34506
34661
|
const before = settingsPreimages.get(settings);
|
|
34507
34662
|
if (!before || before.path !== path)
|
|
34508
34663
|
throw new Error("Claude settings require a valid captured preimage before installation.");
|
|
34509
|
-
const current2 =
|
|
34664
|
+
const current2 = existsSync11(path) ? readFileSync7(path, "utf8") : null;
|
|
34510
34665
|
if (current2 !== before.text)
|
|
34511
34666
|
throw new Error("Claude settings changed during installation; no settings were written.");
|
|
34512
34667
|
if (coordinateClaudeSettings(before.text, JSON.stringify(settings, null, 2) + `
|
|
34513
|
-
`, { home:
|
|
34668
|
+
`, { home: homedir6() }))
|
|
34514
34669
|
return;
|
|
34515
34670
|
}
|
|
34516
34671
|
if (expectedSettingsSha256 !== undefined)
|
|
34517
34672
|
return writeHookSettingsPreimage(path, settings, captured.text);
|
|
34518
34673
|
const dir = dirname7(path);
|
|
34519
|
-
if (!
|
|
34674
|
+
if (!existsSync11(dir)) {
|
|
34520
34675
|
mkdirSync6(dir, { recursive: true });
|
|
34521
34676
|
}
|
|
34522
34677
|
writeFileSync5(path, JSON.stringify(settings, null, 2) + `
|
|
@@ -34615,7 +34770,7 @@ function buildCodewithTomlFragment(name, profile, mementos) {
|
|
|
34615
34770
|
function readCodewithConfig(scope, configPath) {
|
|
34616
34771
|
const path = getSettingsPath(scope, "codewith", configPath);
|
|
34617
34772
|
try {
|
|
34618
|
-
return
|
|
34773
|
+
return existsSync11(path) ? readFileSync7(path, "utf-8") : "";
|
|
34619
34774
|
} catch {
|
|
34620
34775
|
return "";
|
|
34621
34776
|
}
|
|
@@ -34623,9 +34778,9 @@ function readCodewithConfig(scope, configPath) {
|
|
|
34623
34778
|
function appendCodewithFragment(fragment, scope, configPath) {
|
|
34624
34779
|
const path = getSettingsPath(scope, "codewith", configPath);
|
|
34625
34780
|
const dir = dirname7(path);
|
|
34626
|
-
if (!
|
|
34781
|
+
if (!existsSync11(dir))
|
|
34627
34782
|
mkdirSync6(dir, { recursive: true });
|
|
34628
|
-
const existing =
|
|
34783
|
+
const existing = existsSync11(path) ? readFileSync7(path, "utf-8") : "";
|
|
34629
34784
|
const sep3 = existing.trim() ? `
|
|
34630
34785
|
|
|
34631
34786
|
` : "";
|
|
@@ -34805,7 +34960,20 @@ function installHook(name, options = {}) {
|
|
|
34805
34960
|
return installForTarget(name, scope, overwrite, target, profile, codewithMode, codewithConfigPath, mementos, expectedSettingsSha256);
|
|
34806
34961
|
}
|
|
34807
34962
|
function registerHook(name, scope = "global", target = "claude", profile, mementos, write = writeSettings) {
|
|
34808
|
-
const
|
|
34963
|
+
const readiness = name === "trash-readiness";
|
|
34964
|
+
if (readiness && (target === "gemini" || profile || scope !== "global"))
|
|
34965
|
+
throw new Error("Unsupported readiness target");
|
|
34966
|
+
const meta3 = readiness ? {
|
|
34967
|
+
name,
|
|
34968
|
+
event: "SessionStart",
|
|
34969
|
+
matcher: "^(startup|resume|clear|fork)$",
|
|
34970
|
+
timeoutSeconds: 20,
|
|
34971
|
+
displayName: "Trash Readiness",
|
|
34972
|
+
description: "Read-only native startup readiness",
|
|
34973
|
+
version: "0.1.0",
|
|
34974
|
+
category: "Environment",
|
|
34975
|
+
tags: ["trash", "startup"]
|
|
34976
|
+
} : isNativeSafetyName(name) ? getHook(name) : resolveHookMeta(name);
|
|
34809
34977
|
if (!meta3)
|
|
34810
34978
|
return;
|
|
34811
34979
|
const eventKeys = getHookEvents(meta3).map((event) => {
|
|
@@ -34822,18 +34990,20 @@ function registerHook(name, scope = "global", target = "claude", profile, mement
|
|
|
34822
34990
|
const settings = readSettings2(scope, target);
|
|
34823
34991
|
if (!settings.hooks)
|
|
34824
34992
|
settings.hooks = {};
|
|
34825
|
-
if (isNativeSafetyName(name)) {
|
|
34993
|
+
if (isNativeSafetyName(name) || readiness) {
|
|
34826
34994
|
for (const groups of Object.values(settings.hooks))
|
|
34827
34995
|
for (const group of groups)
|
|
34828
34996
|
for (const hook of group.hooks ?? []) {
|
|
34829
34997
|
if (typeof hook.command === "string" && /(?:^|\s)hasna-native-safety-v\d+(?:\s|$)/.test(hook.command) && !registrationName(hook.command)) {
|
|
34830
34998
|
throw new Error("Unknown managed safety definition; reconcile its exact version before replacing it.");
|
|
34831
34999
|
}
|
|
35000
|
+
if (readiness && typeof hook.command === "string" && hook.command.includes("hasna-native-readiness-v") && registrationName(hook.command) !== "trash-readiness")
|
|
35001
|
+
throw new Error("Unknown managed readiness definition");
|
|
34832
35002
|
}
|
|
34833
35003
|
}
|
|
34834
35004
|
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;
|
|
34835
|
-
const hookCommand = isNativeSafetyName(name) ? installedNativeSafetyCommand(name) : (profile ? `hooks run ${name} --profile ${profile}` : `hooks run ${name}`) + mementosCommandSuffix(mementos ?? retained);
|
|
34836
|
-
if (target === "codex" || target === "claude" && isNativeSafetyName(name)) {
|
|
35005
|
+
const hookCommand = readiness ? installedNativeReadinessCommand(target) : isNativeSafetyName(name) ? installedNativeSafetyCommand(name) : (profile ? `hooks run ${name} --profile ${profile}` : `hooks run ${name}`) + mementosCommandSuffix(mementos ?? retained);
|
|
35006
|
+
if (target === "codex" || target === "claude" && (isNativeSafetyName(name) || readiness)) {
|
|
34837
35007
|
const positions = [];
|
|
34838
35008
|
for (const [event, groups] of Object.entries(settings.hooks)) {
|
|
34839
35009
|
for (const entry of groups) {
|
|
@@ -34843,8 +35013,8 @@ function registerHook(name, scope = "global", target = "claude", profile, mement
|
|
|
34843
35013
|
if (match && (match[1] ?? match[2]) === name || registrationName(command) === name) {
|
|
34844
35014
|
if (registrationName(command) !== name)
|
|
34845
35015
|
throw new Error("Unrecognized Codex registration; reconcile it before updating this hook.");
|
|
34846
|
-
if (isNativeSafetyName(name) && entry.hooks[index].async !== undefined && entry.hooks[index].async !== false)
|
|
34847
|
-
throw new Error("Asynchronous
|
|
35016
|
+
if ((isNativeSafetyName(name) || readiness) && (entry.hooks[index].async !== undefined && entry.hooks[index].async !== false || entry.hooks[index].asyncRewake === true))
|
|
35017
|
+
throw new Error("Asynchronous managed native hooks are refused; synchronous registration is required");
|
|
34848
35018
|
positions.push({ event, entry, index });
|
|
34849
35019
|
}
|
|
34850
35020
|
}
|
|
@@ -34964,12 +35134,12 @@ function removeHookForTarget(name, scope, target) {
|
|
|
34964
35134
|
|
|
34965
35135
|
// src/lib/profiles.ts
|
|
34966
35136
|
init_app_home();
|
|
34967
|
-
import { existsSync as
|
|
34968
|
-
import { join as
|
|
35137
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync8, readdirSync as readdirSync8, rmSync, cpSync as cpSync2 } from "fs";
|
|
35138
|
+
import { join as join11 } from "path";
|
|
34969
35139
|
function getProfilesDir() {
|
|
34970
|
-
const newDir =
|
|
34971
|
-
const oldDir =
|
|
34972
|
-
if (!
|
|
35140
|
+
const newDir = join11(getEffectiveDataRoot(), "profiles");
|
|
35141
|
+
const oldDir = join11(getHomeDir(), ".hooks", "profiles");
|
|
35142
|
+
if (!existsSync12(newDir) && existsSync12(oldDir)) {
|
|
34973
35143
|
mkdirSync9(newDir, { recursive: true });
|
|
34974
35144
|
cpSync2(oldDir, newDir, { recursive: true });
|
|
34975
35145
|
}
|
|
@@ -34977,12 +35147,12 @@ function getProfilesDir() {
|
|
|
34977
35147
|
}
|
|
34978
35148
|
function ensureProfilesDir() {
|
|
34979
35149
|
const dir = getProfilesDir();
|
|
34980
|
-
if (!
|
|
35150
|
+
if (!existsSync12(dir)) {
|
|
34981
35151
|
mkdirSync9(dir, { recursive: true });
|
|
34982
35152
|
}
|
|
34983
35153
|
}
|
|
34984
35154
|
function profilePath(id) {
|
|
34985
|
-
return
|
|
35155
|
+
return join11(getProfilesDir(), `${id}.json`);
|
|
34986
35156
|
}
|
|
34987
35157
|
function shortUuid() {
|
|
34988
35158
|
return crypto.randomUUID().slice(0, 8);
|
|
@@ -35008,23 +35178,23 @@ function createProfile(input2) {
|
|
|
35008
35178
|
function getProfile(id) {
|
|
35009
35179
|
const path = profilePath(id);
|
|
35010
35180
|
try {
|
|
35011
|
-
if (!
|
|
35181
|
+
if (!existsSync12(path))
|
|
35012
35182
|
return null;
|
|
35013
|
-
return JSON.parse(
|
|
35183
|
+
return JSON.parse(readFileSync8(path, "utf-8"));
|
|
35014
35184
|
} catch {
|
|
35015
35185
|
return null;
|
|
35016
35186
|
}
|
|
35017
35187
|
}
|
|
35018
35188
|
function listProfiles() {
|
|
35019
35189
|
const dir = getProfilesDir();
|
|
35020
|
-
if (!
|
|
35190
|
+
if (!existsSync12(dir))
|
|
35021
35191
|
return [];
|
|
35022
35192
|
try {
|
|
35023
35193
|
const files = readdirSync8(dir).filter((f) => f.endsWith(".json"));
|
|
35024
35194
|
const profiles = [];
|
|
35025
35195
|
for (const file2 of files) {
|
|
35026
35196
|
try {
|
|
35027
|
-
const content =
|
|
35197
|
+
const content = readFileSync8(join11(dir, file2), "utf-8");
|
|
35028
35198
|
profiles.push(JSON.parse(content));
|
|
35029
35199
|
} catch {}
|
|
35030
35200
|
}
|
|
@@ -35563,9 +35733,9 @@ var __dirname3 = dirname19(fileURLToPath3(import.meta.url));
|
|
|
35563
35733
|
var pkg = { name: "@hasna/hooks", version: "0.0.0" };
|
|
35564
35734
|
try {
|
|
35565
35735
|
for (const rel of ["../../package.json", "../package.json", "../../../package.json"]) {
|
|
35566
|
-
const p =
|
|
35567
|
-
if (
|
|
35568
|
-
pkg = JSON.parse(
|
|
35736
|
+
const p = join13(__dirname3, rel);
|
|
35737
|
+
if (existsSync14(p)) {
|
|
35738
|
+
pkg = JSON.parse(readFileSync9(p, "utf-8"));
|
|
35569
35739
|
break;
|
|
35570
35740
|
}
|
|
35571
35741
|
}
|
|
@@ -35574,10 +35744,10 @@ async function runVerifiedHook(name, stdin, timeoutMs) {
|
|
|
35574
35744
|
const { resolveHook: resolveHook2, resolveScriptPath: resolveScriptPath2 } = await Promise.resolve().then(() => (init_resolve(), exports_resolve));
|
|
35575
35745
|
const resolved = resolveHook2(name);
|
|
35576
35746
|
const hookScript = resolveScriptPath2(name);
|
|
35577
|
-
if (!resolved || !hookScript || !
|
|
35747
|
+
if (!resolved || !hookScript || !existsSync14(hookScript)) {
|
|
35578
35748
|
throw new Error(`Hook '${name}' not found`);
|
|
35579
35749
|
}
|
|
35580
|
-
const content =
|
|
35750
|
+
const content = readFileSync9(hookScript);
|
|
35581
35751
|
const sha4 = sha256Of(content);
|
|
35582
35752
|
const check2 = checkScriptHash(name, sha4);
|
|
35583
35753
|
if (!check2.ok) {
|
|
@@ -35783,7 +35953,7 @@ function createHooksServer() {
|
|
|
35783
35953
|
const settingsPath = getSettingsPath(scope);
|
|
35784
35954
|
const issues = [];
|
|
35785
35955
|
const healthy = [];
|
|
35786
|
-
const settingsExist =
|
|
35956
|
+
const settingsExist = existsSync14(settingsPath);
|
|
35787
35957
|
if (!settingsExist) {
|
|
35788
35958
|
issues.push({ hook: "(settings)", issue: `${settingsPath} not found`, severity: "warning" });
|
|
35789
35959
|
}
|
|
@@ -35793,7 +35963,7 @@ function createHooksServer() {
|
|
|
35793
35963
|
const meta3 = custom2 ? resolveHookMeta(name) : getHook(name);
|
|
35794
35964
|
let hookHealthy = true;
|
|
35795
35965
|
if (custom2) {
|
|
35796
|
-
if (!
|
|
35966
|
+
if (!existsSync14(custom2.scriptPath)) {
|
|
35797
35967
|
issues.push({ hook: name, issue: `Custom hook script missing: ${custom2.scriptPath}`, severity: "error" });
|
|
35798
35968
|
hookHealthy = false;
|
|
35799
35969
|
}
|
|
@@ -35803,14 +35973,14 @@ function createHooksServer() {
|
|
|
35803
35973
|
continue;
|
|
35804
35974
|
}
|
|
35805
35975
|
const hookDir = getHookPath(name);
|
|
35806
|
-
if (!
|
|
35976
|
+
if (!existsSync14(join13(hookDir, "src", "hook.ts"))) {
|
|
35807
35977
|
issues.push({ hook: name, issue: "Missing src/hook.ts in package", severity: "error" });
|
|
35808
35978
|
hookHealthy = false;
|
|
35809
35979
|
}
|
|
35810
35980
|
}
|
|
35811
35981
|
if (meta3 && settingsExist) {
|
|
35812
35982
|
try {
|
|
35813
|
-
const settings = JSON.parse(
|
|
35983
|
+
const settings = JSON.parse(readFileSync9(settingsPath, "utf-8"));
|
|
35814
35984
|
if (!hookRegisteredInSettings(settings, name, meta3.event, meta3.matcher)) {
|
|
35815
35985
|
const eventName = meta3.event.split(":")[0];
|
|
35816
35986
|
issues.push({ hook: name, issue: `Not registered under correct event (${eventName})`, severity: "error" });
|
|
@@ -35847,10 +36017,10 @@ function createHooksServer() {
|
|
|
35847
36017
|
return { content: [{ type: "text", text: JSON.stringify({ error: `Hook '${name}' not found` }) }] };
|
|
35848
36018
|
}
|
|
35849
36019
|
const hookPath = getHookPath(name);
|
|
35850
|
-
const readmePath =
|
|
36020
|
+
const readmePath = join13(hookPath, "README.md");
|
|
35851
36021
|
let readme = "";
|
|
35852
|
-
if (
|
|
35853
|
-
readme =
|
|
36022
|
+
if (existsSync14(readmePath)) {
|
|
36023
|
+
readme = readFileSync9(readmePath, "utf-8");
|
|
35854
36024
|
}
|
|
35855
36025
|
if (verbose) {
|
|
35856
36026
|
return { content: [{ type: "text", text: JSON.stringify({ ...meta3, readme }) }] };
|
|
@@ -36033,7 +36203,7 @@ function createHooksServer() {
|
|
|
36033
36203
|
const ctx = {
|
|
36034
36204
|
scope,
|
|
36035
36205
|
settings_path: settingsPath,
|
|
36036
|
-
settings_exists:
|
|
36206
|
+
settings_exists: existsSync14(settingsPath),
|
|
36037
36207
|
registered_hooks: hooks,
|
|
36038
36208
|
hook_count: hooks.length,
|
|
36039
36209
|
healthy,
|
|
@@ -36179,8 +36349,8 @@ function createHooksServer() {
|
|
|
36179
36349
|
const settingsPath = getSettingsPath(scope);
|
|
36180
36350
|
let settings = {};
|
|
36181
36351
|
try {
|
|
36182
|
-
if (
|
|
36183
|
-
settings = JSON.parse(
|
|
36352
|
+
if (existsSync14(settingsPath))
|
|
36353
|
+
settings = JSON.parse(readFileSync9(settingsPath, "utf-8"));
|
|
36184
36354
|
} catch {}
|
|
36185
36355
|
if (!settings.hooks)
|
|
36186
36356
|
settings.hooks = {};
|
|
@@ -36202,8 +36372,8 @@ function createHooksServer() {
|
|
|
36202
36372
|
const settingsPath = getSettingsPath(scope);
|
|
36203
36373
|
let settings = {};
|
|
36204
36374
|
try {
|
|
36205
|
-
if (
|
|
36206
|
-
settings = JSON.parse(
|
|
36375
|
+
if (existsSync14(settingsPath))
|
|
36376
|
+
settings = JSON.parse(readFileSync9(settingsPath, "utf-8"));
|
|
36207
36377
|
} catch {}
|
|
36208
36378
|
if (settings.hooks?.__disabled) {
|
|
36209
36379
|
settings.hooks.__disabled = settings.hooks.__disabled.filter((n) => n !== name);
|