@hasna/hooks 0.11.2 → 0.11.4
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 +6 -1
- package/bin/hooks-mcp.js +65 -63
- package/bin/index.js +382 -376
- package/bin/native-readiness-entry.js +3 -1
- package/dist/index.js +63 -63
- package/dist/native-readiness.d.ts +3 -0
- package/dist/native-readiness.js +8 -2
- package/package.json +1 -1
|
@@ -143,6 +143,8 @@ function runReadinessProcess(command, args, options) {
|
|
|
143
143
|
var import_node_fs = require("node:fs");
|
|
144
144
|
var import_node_os = require("node:os");
|
|
145
145
|
var import_node_path = require("node:path");
|
|
146
|
+
var TRASH_READINESS_MAX_TIMEOUT_MS = 60000;
|
|
147
|
+
var TRASH_READINESS_NATIVE_TIMEOUT_SECONDS = TRASH_READINESS_MAX_TIMEOUT_MS / 1000 + 5;
|
|
146
148
|
|
|
147
149
|
class ReadinessError extends Error {
|
|
148
150
|
code;
|
|
@@ -171,7 +173,7 @@ function inside(home, path) {
|
|
|
171
173
|
function parseTrashReadinessBinding(input, provider, home) {
|
|
172
174
|
const b = input;
|
|
173
175
|
need(isReadinessProvider(provider) && absolute(home), "binding_invalid");
|
|
174
|
-
need(keys(b, ["schema", "provider", "home", "runtime", "trash", "plan", "timeoutMs"]) && b.schema === "hasna.hooks.trash-readiness/v1" && b.provider === provider && b.home === home && pin(b.runtime) && pin(b.plan) && keys(b.trash, ["root", "version", "cliSha256"]) && absolute(b.trash.root) && typeof b.trash.version === "string" && b.trash.version.length <= 128 && /^\d+\.\d+\.\d+$/.test(b.trash.version) && digest(b.trash.cliSha256) && Number.isInteger(b.timeoutMs) && b.timeoutMs >= 1000 && b.timeoutMs <=
|
|
176
|
+
need(keys(b, ["schema", "provider", "home", "runtime", "trash", "plan", "timeoutMs"]) && b.schema === "hasna.hooks.trash-readiness/v1" && b.provider === provider && b.home === home && pin(b.runtime) && pin(b.plan) && keys(b.trash, ["root", "version", "cliSha256"]) && absolute(b.trash.root) && typeof b.trash.version === "string" && b.trash.version.length <= 128 && /^\d+\.\d+\.\d+$/.test(b.trash.version) && digest(b.trash.cliSha256) && Number.isInteger(b.timeoutMs) && b.timeoutMs >= 1000 && b.timeoutMs <= TRASH_READINESS_MAX_TIMEOUT_MS && inside(home, b.plan.path) && inside(home, b.trash.root), "binding_invalid");
|
|
175
177
|
return structuredClone(b);
|
|
176
178
|
}
|
|
177
179
|
function trashReadinessBindingPath(provider, home = import_node_os.homedir()) {
|
package/dist/index.js
CHANGED
|
@@ -7547,6 +7547,66 @@ import { join as join10, dirname as dirname7, basename as basename3, resolve as
|
|
|
7547
7547
|
import { homedir as homedir6 } from "os";
|
|
7548
7548
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7549
7549
|
|
|
7550
|
+
// src/lib/readiness-process.ts
|
|
7551
|
+
var supervisor = String.raw`
|
|
7552
|
+
const {spawn}=require("node:child_process");
|
|
7553
|
+
const spec=JSON.parse(process.argv.at(-1));
|
|
7554
|
+
// This runtime was created detached and is its group's live leader. Its
|
|
7555
|
+
// independent timer and parent pipe remain effective if the observer exits
|
|
7556
|
+
// or its event loop stops responding. Only this freshly created tree is hit.
|
|
7557
|
+
const cleanup=()=>{try{process.kill(-process.pid,"SIGKILL")}catch{process.exit(1)}};
|
|
7558
|
+
setTimeout(cleanup,spec.timeout);
|
|
7559
|
+
process.stdin.once("end",cleanup);
|
|
7560
|
+
process.stdin.once("error",cleanup);
|
|
7561
|
+
process.stdin.on("data",cleanup);
|
|
7562
|
+
process.stdin.resume();
|
|
7563
|
+
for(const signal of ["SIGTERM","SIGINT","SIGHUP"])process.once(signal,cleanup);
|
|
7564
|
+
let done=false, out=Buffer.alloc(0), err=Buffer.alloc(0);
|
|
7565
|
+
function report(status,error){
|
|
7566
|
+
if(done)return; done=true;
|
|
7567
|
+
process.stdout.write(JSON.stringify({status,error,stdout:out.toString("utf8"),stderr:err.toString("utf8")})+"\n");
|
|
7568
|
+
}
|
|
7569
|
+
const child=spawn(spec.command,spec.args,{stdio:["ignore","pipe","pipe"],env:process.env});
|
|
7570
|
+
for(const [stream,name] of [[child.stdout,"stdout"],[child.stderr,"stderr"]]){
|
|
7571
|
+
stream.on("data",bytes=>{
|
|
7572
|
+
if(done)return;
|
|
7573
|
+
const before=name==="stdout"?out:err;
|
|
7574
|
+
if(before.length+bytes.length>spec.maxBuffer){report(null,{code:"ENOBUFS"});return}
|
|
7575
|
+
if(name==="stdout")out=Buffer.concat([out,bytes]);else err=Buffer.concat([err,bytes]);
|
|
7576
|
+
});
|
|
7577
|
+
stream.on("error",()=>report(null,{code:"ECHILD"}));
|
|
7578
|
+
}
|
|
7579
|
+
child.on("error",()=>report(null,{code:"ECHILD"}));
|
|
7580
|
+
// A successful observation includes complete streams. A nested client that
|
|
7581
|
+
// keeps them open after its parent exits is a failure requiring group cleanup.
|
|
7582
|
+
child.on("exit",()=>setTimeout(()=>report(null,{code:"ECHILD"}),100));
|
|
7583
|
+
child.on("close",(status,signal)=>report(status,signal?{code:"ECHILD"}:undefined));
|
|
7584
|
+
`;
|
|
7585
|
+
|
|
7586
|
+
// src/native-readiness.ts
|
|
7587
|
+
var TRASH_READINESS_MAX_TIMEOUT_MS = 60000;
|
|
7588
|
+
var TRASH_READINESS_NATIVE_TIMEOUT_SECONDS = TRASH_READINESS_MAX_TIMEOUT_MS / 1000 + 5;
|
|
7589
|
+
function isReadinessProvider(value) {
|
|
7590
|
+
return value === "claude" || value === "codex" || value === "sumi";
|
|
7591
|
+
}
|
|
7592
|
+
var safeFailureCodes = new Set([
|
|
7593
|
+
"unsupported_platform",
|
|
7594
|
+
"station_detection_failed",
|
|
7595
|
+
"station_mismatch",
|
|
7596
|
+
"hosted_unavailable",
|
|
7597
|
+
"authentication_refused",
|
|
7598
|
+
"station_setup_required",
|
|
7599
|
+
"invalid_status",
|
|
7600
|
+
"deadline_exceeded",
|
|
7601
|
+
"retention_mismatch",
|
|
7602
|
+
"managed_instructions_refused",
|
|
7603
|
+
"removal_instruction_missing",
|
|
7604
|
+
"instructions_changed",
|
|
7605
|
+
"native_config_changed",
|
|
7606
|
+
"guard_refused",
|
|
7607
|
+
"owner_refused"
|
|
7608
|
+
]);
|
|
7609
|
+
|
|
7550
7610
|
// src/lib/resolve.ts
|
|
7551
7611
|
import { dirname as dirname2, join as join6 } from "path";
|
|
7552
7612
|
import { existsSync as existsSync5 } from "fs";
|
|
@@ -7774,66 +7834,6 @@ import { createHash as createHash2 } from "crypto";
|
|
|
7774
7834
|
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
7835
|
import { dirname as dirname4, isAbsolute as isAbsolute3, join as join8 } from "path";
|
|
7776
7836
|
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
7837
|
var quote2 = (value) => "'" + value.replaceAll("'", "'\\''") + "'";
|
|
7838
7838
|
var bootstrap = String.raw`/* hasna-native-readiness-v1 */ (() => {
|
|
7839
7839
|
const fs = require("node:fs"), path = require("node:path"), crypto = require("node:crypto");
|
|
@@ -39769,7 +39769,7 @@ function registerHook(name, scope = "global", target = "claude", profile, mement
|
|
|
39769
39769
|
name,
|
|
39770
39770
|
event: "SessionStart",
|
|
39771
39771
|
matcher: "^(startup|resume|clear|fork)$",
|
|
39772
|
-
timeoutSeconds:
|
|
39772
|
+
timeoutSeconds: TRASH_READINESS_NATIVE_TIMEOUT_SECONDS,
|
|
39773
39773
|
displayName: "Trash Readiness",
|
|
39774
39774
|
description: "Read-only native startup readiness",
|
|
39775
39775
|
version: "0.1.0",
|
|
@@ -41351,7 +41351,7 @@ async function verifyNativeSafetyCommand(command, home = homedir7()) {
|
|
|
41351
41351
|
}
|
|
41352
41352
|
|
|
41353
41353
|
// src/lib/codex-safety-check.ts
|
|
41354
|
-
var versions = new Set(["codex-cli 0.153.0", "codex-cli 0.154.0", "codex-cli 0.155.0", "codex-cli 0.155.1", "codex-cli 0.156.1", "codex-cli 0.157.0", "codex-cli 0.157.1"]);
|
|
41354
|
+
var versions = new Set(["codex-cli 0.153.0", "codex-cli 0.154.0", "codex-cli 0.154.0-alpha.6.1", "codex-cli 0.155.0", "codex-cli 0.155.1", "codex-cli 0.156.1", "codex-cli 0.157.0", "codex-cli 0.157.1"]);
|
|
41355
41355
|
var matcher = "^(Bash|apply_patch|ApplyPatch|functions\\.apply_patch)$";
|
|
41356
41356
|
|
|
41357
41357
|
class CodexSafetyError extends Error {
|
|
@@ -41563,7 +41563,7 @@ function selectCodexSafety(discovery, configuration, home, cwd, featureEnabled,
|
|
|
41563
41563
|
need5(selected.length !== 0, "guard_missing");
|
|
41564
41564
|
need5(selected.length === 1, "guard_ambiguous");
|
|
41565
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 ?
|
|
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 ? TRASH_READINESS_NATIVE_TIMEOUT_SECONDS : 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");
|
|
41567
41567
|
need5(typeof hook.enabled === "boolean" && ["trusted", "untrusted", "modified"].includes(hook.trustStatus), "guard_definition_invalid");
|
|
41568
41568
|
if (requireReady) {
|
|
41569
41569
|
need5(hook.enabled === true, "guard_disabled");
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { type ReadinessChildResult, type ReadinessProcessOptions } from "./lib/readiness-process";
|
|
2
2
|
export type ReadinessProvider = "claude" | "codex" | "sumi";
|
|
3
|
+
export declare const TRASH_READINESS_DEFAULT_TIMEOUT_MS = 45000;
|
|
4
|
+
export declare const TRASH_READINESS_MAX_TIMEOUT_MS = 60000;
|
|
5
|
+
export declare const TRASH_READINESS_NATIVE_TIMEOUT_SECONDS: number;
|
|
3
6
|
type Pin = {
|
|
4
7
|
path: string;
|
|
5
8
|
sha256: string;
|
package/dist/native-readiness.js
CHANGED
|
@@ -144,6 +144,9 @@ function runReadinessProcess(command, args, options) {
|
|
|
144
144
|
import { closeSync, constants, fstatSync, lstatSync, openSync, readSync, realpathSync } from "fs";
|
|
145
145
|
import { homedir } from "os";
|
|
146
146
|
import { dirname, isAbsolute, join, relative, resolve } from "path";
|
|
147
|
+
var TRASH_READINESS_DEFAULT_TIMEOUT_MS = 45000;
|
|
148
|
+
var TRASH_READINESS_MAX_TIMEOUT_MS = 60000;
|
|
149
|
+
var TRASH_READINESS_NATIVE_TIMEOUT_SECONDS = TRASH_READINESS_MAX_TIMEOUT_MS / 1000 + 5;
|
|
147
150
|
|
|
148
151
|
class ReadinessError extends Error {
|
|
149
152
|
code;
|
|
@@ -172,7 +175,7 @@ function inside(home, path) {
|
|
|
172
175
|
function parseTrashReadinessBinding(input, provider, home) {
|
|
173
176
|
const b = input;
|
|
174
177
|
need(isReadinessProvider(provider) && absolute(home), "binding_invalid");
|
|
175
|
-
need(keys(b, ["schema", "provider", "home", "runtime", "trash", "plan", "timeoutMs"]) && b.schema === "hasna.hooks.trash-readiness/v1" && b.provider === provider && b.home === home && pin(b.runtime) && pin(b.plan) && keys(b.trash, ["root", "version", "cliSha256"]) && absolute(b.trash.root) && typeof b.trash.version === "string" && b.trash.version.length <= 128 && /^\d+\.\d+\.\d+$/.test(b.trash.version) && digest(b.trash.cliSha256) && Number.isInteger(b.timeoutMs) && b.timeoutMs >= 1000 && b.timeoutMs <=
|
|
178
|
+
need(keys(b, ["schema", "provider", "home", "runtime", "trash", "plan", "timeoutMs"]) && b.schema === "hasna.hooks.trash-readiness/v1" && b.provider === provider && b.home === home && pin(b.runtime) && pin(b.plan) && keys(b.trash, ["root", "version", "cliSha256"]) && absolute(b.trash.root) && typeof b.trash.version === "string" && b.trash.version.length <= 128 && /^\d+\.\d+\.\d+$/.test(b.trash.version) && digest(b.trash.cliSha256) && Number.isInteger(b.timeoutMs) && b.timeoutMs >= 1000 && b.timeoutMs <= TRASH_READINESS_MAX_TIMEOUT_MS && inside(home, b.plan.path) && inside(home, b.trash.root), "binding_invalid");
|
|
176
179
|
return structuredClone(b);
|
|
177
180
|
}
|
|
178
181
|
function trashReadinessBindingPath(provider, home = homedir()) {
|
|
@@ -324,5 +327,8 @@ export {
|
|
|
324
327
|
parseTrashReadinessBinding,
|
|
325
328
|
isReadinessProvider,
|
|
326
329
|
handleReadinessSessionStart,
|
|
327
|
-
checkTrashReadinessBinding
|
|
330
|
+
checkTrashReadinessBinding,
|
|
331
|
+
TRASH_READINESS_NATIVE_TIMEOUT_SECONDS,
|
|
332
|
+
TRASH_READINESS_MAX_TIMEOUT_MS,
|
|
333
|
+
TRASH_READINESS_DEFAULT_TIMEOUT_MS
|
|
328
334
|
};
|
package/package.json
CHANGED