@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
package/README.md
CHANGED
|
@@ -46,7 +46,12 @@ hooks safety startup check --target codex
|
|
|
46
46
|
|
|
47
47
|
The binding schema is `hasna.hooks.trash-readiness/v1`. It contains the exact
|
|
48
48
|
provider and home, `runtime:{path,sha256}`, `trash:{root,version,cliSha256}`,
|
|
49
|
-
`plan:{path,sha256}`, and `timeoutMs` (1000–
|
|
49
|
+
`plan:{path,sha256}`, and `timeoutMs` (1000–60000). Use 45000 for a normal
|
|
50
|
+
whole-startup budget, configurable within those bounds. Existing bindings retain
|
|
51
|
+
their explicit budget until a guarded update. Claude and Codex registrations
|
|
52
|
+
allow 65 seconds for the observer's 60-second maximum plus runner overhead.
|
|
53
|
+
Authentication, station identity, source/hash checks and terminal timeout remain
|
|
54
|
+
mandatory; the separate removal guard's deadlines are unchanged. Paths are absolute; the
|
|
50
55
|
package and plan must be inside that home. The installed binding lives at
|
|
51
56
|
`~/.hasna/hooks/native/trash-readiness/<provider>.json`. Updating it requires its
|
|
52
57
|
current digest; the writer preserves an immutable predecessor, intent and
|
package/bin/hooks-mcp.js
CHANGED
|
@@ -2690,13 +2690,75 @@ import { fileURLToPath as fileURLToPath3 } from "url";
|
|
|
2690
2690
|
|
|
2691
2691
|
// src/lib/installer.ts
|
|
2692
2692
|
init_registry();
|
|
2693
|
-
init_resolve();
|
|
2694
|
-
init_manifest();
|
|
2695
2693
|
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
2694
|
import { join as join10, dirname as dirname7, basename as basename3, resolve as resolve7 } from "path";
|
|
2697
2695
|
import { homedir as homedir6 } from "os";
|
|
2698
2696
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2699
2697
|
|
|
2698
|
+
// src/lib/readiness-process.ts
|
|
2699
|
+
var supervisor = String.raw`
|
|
2700
|
+
const {spawn}=require("node:child_process");
|
|
2701
|
+
const spec=JSON.parse(process.argv.at(-1));
|
|
2702
|
+
// This runtime was created detached and is its group's live leader. Its
|
|
2703
|
+
// independent timer and parent pipe remain effective if the observer exits
|
|
2704
|
+
// or its event loop stops responding. Only this freshly created tree is hit.
|
|
2705
|
+
const cleanup=()=>{try{process.kill(-process.pid,"SIGKILL")}catch{process.exit(1)}};
|
|
2706
|
+
setTimeout(cleanup,spec.timeout);
|
|
2707
|
+
process.stdin.once("end",cleanup);
|
|
2708
|
+
process.stdin.once("error",cleanup);
|
|
2709
|
+
process.stdin.on("data",cleanup);
|
|
2710
|
+
process.stdin.resume();
|
|
2711
|
+
for(const signal of ["SIGTERM","SIGINT","SIGHUP"])process.once(signal,cleanup);
|
|
2712
|
+
let done=false, out=Buffer.alloc(0), err=Buffer.alloc(0);
|
|
2713
|
+
function report(status,error){
|
|
2714
|
+
if(done)return; done=true;
|
|
2715
|
+
process.stdout.write(JSON.stringify({status,error,stdout:out.toString("utf8"),stderr:err.toString("utf8")})+"\n");
|
|
2716
|
+
}
|
|
2717
|
+
const child=spawn(spec.command,spec.args,{stdio:["ignore","pipe","pipe"],env:process.env});
|
|
2718
|
+
for(const [stream,name] of [[child.stdout,"stdout"],[child.stderr,"stderr"]]){
|
|
2719
|
+
stream.on("data",bytes=>{
|
|
2720
|
+
if(done)return;
|
|
2721
|
+
const before=name==="stdout"?out:err;
|
|
2722
|
+
if(before.length+bytes.length>spec.maxBuffer){report(null,{code:"ENOBUFS"});return}
|
|
2723
|
+
if(name==="stdout")out=Buffer.concat([out,bytes]);else err=Buffer.concat([err,bytes]);
|
|
2724
|
+
});
|
|
2725
|
+
stream.on("error",()=>report(null,{code:"ECHILD"}));
|
|
2726
|
+
}
|
|
2727
|
+
child.on("error",()=>report(null,{code:"ECHILD"}));
|
|
2728
|
+
// A successful observation includes complete streams. A nested client that
|
|
2729
|
+
// keeps them open after its parent exits is a failure requiring group cleanup.
|
|
2730
|
+
child.on("exit",()=>setTimeout(()=>report(null,{code:"ECHILD"}),100));
|
|
2731
|
+
child.on("close",(status,signal)=>report(status,signal?{code:"ECHILD"}:undefined));
|
|
2732
|
+
`;
|
|
2733
|
+
|
|
2734
|
+
// src/native-readiness.ts
|
|
2735
|
+
var TRASH_READINESS_MAX_TIMEOUT_MS = 60000;
|
|
2736
|
+
var TRASH_READINESS_NATIVE_TIMEOUT_SECONDS = TRASH_READINESS_MAX_TIMEOUT_MS / 1000 + 5;
|
|
2737
|
+
function isReadinessProvider(value) {
|
|
2738
|
+
return value === "claude" || value === "codex" || value === "sumi";
|
|
2739
|
+
}
|
|
2740
|
+
var safeFailureCodes = new Set([
|
|
2741
|
+
"unsupported_platform",
|
|
2742
|
+
"station_detection_failed",
|
|
2743
|
+
"station_mismatch",
|
|
2744
|
+
"hosted_unavailable",
|
|
2745
|
+
"authentication_refused",
|
|
2746
|
+
"station_setup_required",
|
|
2747
|
+
"invalid_status",
|
|
2748
|
+
"deadline_exceeded",
|
|
2749
|
+
"retention_mismatch",
|
|
2750
|
+
"managed_instructions_refused",
|
|
2751
|
+
"removal_instruction_missing",
|
|
2752
|
+
"instructions_changed",
|
|
2753
|
+
"native_config_changed",
|
|
2754
|
+
"guard_refused",
|
|
2755
|
+
"owner_refused"
|
|
2756
|
+
]);
|
|
2757
|
+
|
|
2758
|
+
// src/lib/installer.ts
|
|
2759
|
+
init_resolve();
|
|
2760
|
+
init_manifest();
|
|
2761
|
+
|
|
2700
2762
|
// src/lib/native-safety.ts
|
|
2701
2763
|
import { createHash } from "crypto";
|
|
2702
2764
|
import { closeSync, constants, existsSync as existsSync4, fstatSync, lstatSync, openSync, readFileSync as readFileSync2, realpathSync as realpathSync2 } from "fs";
|
|
@@ -2836,66 +2898,6 @@ import { createHash as createHash2 } from "crypto";
|
|
|
2836
2898
|
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
2899
|
import { dirname as dirname4, isAbsolute as isAbsolute3, join as join6 } from "path";
|
|
2838
2900
|
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
2901
|
var quote2 = (value) => "'" + value.replaceAll("'", "'\\''") + "'";
|
|
2900
2902
|
var bootstrap = String.raw`/* hasna-native-readiness-v1 */ (() => {
|
|
2901
2903
|
const fs = require("node:fs"), path = require("node:path"), crypto = require("node:crypto");
|
|
@@ -34967,7 +34969,7 @@ function registerHook(name, scope = "global", target = "claude", profile, mement
|
|
|
34967
34969
|
name,
|
|
34968
34970
|
event: "SessionStart",
|
|
34969
34971
|
matcher: "^(startup|resume|clear|fork)$",
|
|
34970
|
-
timeoutSeconds:
|
|
34972
|
+
timeoutSeconds: TRASH_READINESS_NATIVE_TIMEOUT_SECONDS,
|
|
34971
34973
|
displayName: "Trash Readiness",
|
|
34972
34974
|
description: "Read-only native startup readiness",
|
|
34973
34975
|
version: "0.1.0",
|