@hasna/hooks 0.10.2 → 0.10.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 +41 -0
- package/bin/hooks-mcp.js +6 -3
- package/bin/index.js +1232 -319
- package/bin/native-safety-entry.js +325 -80
- package/bin/serve.js +9 -9
- package/dist/index.js +16 -13
- package/dist/lib/native-safety.d.ts +1 -0
- package/dist/native-safety.d.ts +58 -0
- package/dist/native-safety.js +363 -0
- package/dist/sdk/index.js +1 -1
- package/hooks/hook-trash-guard/src/binary.ts +158 -0
- package/hooks/hook-trash-guard/src/hook.ts +10 -40
- package/hooks/hook-workspace-repos-guard/README.md +30 -8
- package/hooks/hook-workspace-repos-guard/src/hook.ts +181 -42
- package/hooks/native-safety-entry.ts +2 -2
- package/package.json +6 -2
- package/scripts/validate-package.ts +12 -3
package/bin/index.js
CHANGED
|
@@ -47,7 +47,7 @@ var __export = (target, all) => {
|
|
|
47
47
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
48
48
|
var __require = import.meta.require;
|
|
49
49
|
|
|
50
|
-
//
|
|
50
|
+
// ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/error.js
|
|
51
51
|
var require_error = __commonJS((exports) => {
|
|
52
52
|
class CommanderError extends Error {
|
|
53
53
|
constructor(exitCode, code, message) {
|
|
@@ -71,7 +71,7 @@ var require_error = __commonJS((exports) => {
|
|
|
71
71
|
exports.InvalidArgumentError = InvalidArgumentError;
|
|
72
72
|
});
|
|
73
73
|
|
|
74
|
-
//
|
|
74
|
+
// ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/argument.js
|
|
75
75
|
var require_argument = __commonJS((exports) => {
|
|
76
76
|
var { InvalidArgumentError } = require_error();
|
|
77
77
|
|
|
@@ -150,7 +150,7 @@ var require_argument = __commonJS((exports) => {
|
|
|
150
150
|
exports.humanReadableArgName = humanReadableArgName;
|
|
151
151
|
});
|
|
152
152
|
|
|
153
|
-
//
|
|
153
|
+
// ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/help.js
|
|
154
154
|
var require_help = __commonJS((exports) => {
|
|
155
155
|
var { humanReadableArgName } = require_argument();
|
|
156
156
|
|
|
@@ -399,7 +399,7 @@ var require_help = __commonJS((exports) => {
|
|
|
399
399
|
exports.Help = Help;
|
|
400
400
|
});
|
|
401
401
|
|
|
402
|
-
//
|
|
402
|
+
// ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/option.js
|
|
403
403
|
var require_option = __commonJS((exports) => {
|
|
404
404
|
var { InvalidArgumentError } = require_error();
|
|
405
405
|
|
|
@@ -550,7 +550,7 @@ var require_option = __commonJS((exports) => {
|
|
|
550
550
|
exports.DualOptions = DualOptions;
|
|
551
551
|
});
|
|
552
552
|
|
|
553
|
-
//
|
|
553
|
+
// ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/suggestSimilar.js
|
|
554
554
|
var require_suggestSimilar = __commonJS((exports) => {
|
|
555
555
|
var maxDistance = 3;
|
|
556
556
|
function editDistance(a, b) {
|
|
@@ -623,7 +623,7 @@ var require_suggestSimilar = __commonJS((exports) => {
|
|
|
623
623
|
exports.suggestSimilar = suggestSimilar;
|
|
624
624
|
});
|
|
625
625
|
|
|
626
|
-
//
|
|
626
|
+
// ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/command.js
|
|
627
627
|
var require_command = __commonJS((exports) => {
|
|
628
628
|
var EventEmitter = __require("events").EventEmitter;
|
|
629
629
|
var childProcess = __require("child_process");
|
|
@@ -1866,7 +1866,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1866
1866
|
exports.Command = Command;
|
|
1867
1867
|
});
|
|
1868
1868
|
|
|
1869
|
-
//
|
|
1869
|
+
// ../../node_modules/.bun/commander@12.1.0/node_modules/commander/index.js
|
|
1870
1870
|
var require_commander = __commonJS((exports) => {
|
|
1871
1871
|
var { Argument } = require_argument();
|
|
1872
1872
|
var { Command } = require_command();
|
|
@@ -1887,76 +1887,76 @@ var require_commander = __commonJS((exports) => {
|
|
|
1887
1887
|
});
|
|
1888
1888
|
|
|
1889
1889
|
// src/lib/app-home.ts
|
|
1890
|
-
import { existsSync as
|
|
1890
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1891
1891
|
import { homedir as homedir2 } from "os";
|
|
1892
|
-
import { join as
|
|
1893
|
-
import { homedir as
|
|
1894
|
-
import { join as
|
|
1895
|
-
function
|
|
1892
|
+
import { join as join3, resolve as resolve2 } from "path";
|
|
1893
|
+
import { homedir as pathsResolverHomedir2 } from "os";
|
|
1894
|
+
import { join as pathsResolverJoin2 } from "path";
|
|
1895
|
+
function pathsResolverAssertApp2(app) {
|
|
1896
1896
|
if (typeof app !== "string" || app.length === 0) {
|
|
1897
1897
|
throw new TypeError("paths: app must be a non-empty string");
|
|
1898
1898
|
}
|
|
1899
|
-
if (!
|
|
1899
|
+
if (!PATHS_RESOLVER_APP_SLUG_RE2.test(app)) {
|
|
1900
1900
|
throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
|
|
1901
1901
|
}
|
|
1902
1902
|
}
|
|
1903
|
-
function
|
|
1904
|
-
if (!Object.keys(
|
|
1905
|
-
throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(
|
|
1903
|
+
function pathsResolverAssertKind2(kind) {
|
|
1904
|
+
if (!Object.keys(PATHS_RESOLVER_KIND_ENV2).includes(kind)) {
|
|
1905
|
+
throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(PATHS_RESOLVER_KIND_ENV2).join(", ")}`);
|
|
1906
1906
|
}
|
|
1907
1907
|
}
|
|
1908
|
-
function
|
|
1909
|
-
|
|
1908
|
+
function pathsResolverBaseDir2(kind, options) {
|
|
1909
|
+
pathsResolverAssertKind2(kind);
|
|
1910
1910
|
const env = options.env ?? process.env;
|
|
1911
|
-
const override = env[
|
|
1911
|
+
const override = env[PATHS_RESOLVER_KIND_ENV2[kind]];
|
|
1912
1912
|
if (typeof override === "string" && override.length > 0)
|
|
1913
1913
|
return override;
|
|
1914
|
-
const home = options.home ??
|
|
1914
|
+
const home = options.home ?? pathsResolverHomedir2();
|
|
1915
1915
|
const platform = options.platform ?? process.platform;
|
|
1916
1916
|
if (platform === "darwin") {
|
|
1917
1917
|
switch (kind) {
|
|
1918
1918
|
case "config":
|
|
1919
1919
|
case "data":
|
|
1920
|
-
return
|
|
1920
|
+
return pathsResolverJoin2(home, "Library", "Application Support", "Hasna");
|
|
1921
1921
|
case "cache":
|
|
1922
|
-
return
|
|
1922
|
+
return pathsResolverJoin2(home, "Library", "Caches", "Hasna");
|
|
1923
1923
|
case "state":
|
|
1924
|
-
return
|
|
1924
|
+
return pathsResolverJoin2(home, "Library", "Logs", "Hasna");
|
|
1925
1925
|
}
|
|
1926
1926
|
}
|
|
1927
1927
|
switch (kind) {
|
|
1928
1928
|
case "config":
|
|
1929
|
-
return
|
|
1929
|
+
return pathsResolverJoin2(home, ".config", "hasna");
|
|
1930
1930
|
case "data":
|
|
1931
|
-
return
|
|
1931
|
+
return pathsResolverJoin2(home, ".local", "share", "hasna");
|
|
1932
1932
|
case "state":
|
|
1933
|
-
return
|
|
1933
|
+
return pathsResolverJoin2(home, ".local", "state", "hasna");
|
|
1934
1934
|
case "cache":
|
|
1935
|
-
return
|
|
1935
|
+
return pathsResolverJoin2(home, ".cache", "hasna");
|
|
1936
1936
|
}
|
|
1937
1937
|
}
|
|
1938
|
-
function
|
|
1939
|
-
|
|
1940
|
-
const appSegment = options.internal === true ?
|
|
1941
|
-
return
|
|
1938
|
+
function pathsResolverResolve2(kind, options) {
|
|
1939
|
+
pathsResolverAssertApp2(options.app);
|
|
1940
|
+
const appSegment = options.internal === true ? pathsResolverJoin2("internal", options.app) : options.app;
|
|
1941
|
+
return pathsResolverJoin2(pathsResolverBaseDir2(kind, options), appSegment);
|
|
1942
1942
|
}
|
|
1943
|
-
function
|
|
1944
|
-
return
|
|
1943
|
+
function dataDir2(options) {
|
|
1944
|
+
return pathsResolverResolve2("data", options);
|
|
1945
1945
|
}
|
|
1946
1946
|
function getHomeDir(env = process.env) {
|
|
1947
1947
|
return env.HOME || env.USERPROFILE || homedir2();
|
|
1948
1948
|
}
|
|
1949
1949
|
function getResolverDataRoot(env = process.env) {
|
|
1950
|
-
return
|
|
1950
|
+
return dataDir2({ app: "hooks", home: getHomeDir(env), env });
|
|
1951
1951
|
}
|
|
1952
1952
|
function getLegacyDataRoot(env = process.env) {
|
|
1953
|
-
return
|
|
1953
|
+
return join3(getHomeDir(env), ".hasna", "hooks");
|
|
1954
1954
|
}
|
|
1955
1955
|
function adoptResolverDataRoot(resolved, env = process.env) {
|
|
1956
1956
|
const dataOverride = env.HASNA_DATA_HOME;
|
|
1957
1957
|
if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
|
|
1958
1958
|
return true;
|
|
1959
|
-
return
|
|
1959
|
+
return existsSync3(join3(resolved, STORE_MARKER));
|
|
1960
1960
|
}
|
|
1961
1961
|
function getExplicitDataDir(env = process.env) {
|
|
1962
1962
|
const dir = env.HASNA_HOOKS_DATA_DIR ?? env.HOOKS_DATA_DIR;
|
|
@@ -1967,7 +1967,7 @@ function getExplicitDataDir(env = process.env) {
|
|
|
1967
1967
|
function getExactDataRoot(env = process.env) {
|
|
1968
1968
|
const dir = env.HASNA_HOOKS_HOME?.trim() || env.HOOKS_HOME?.trim();
|
|
1969
1969
|
if (dir)
|
|
1970
|
-
return
|
|
1970
|
+
return resolve2(dir);
|
|
1971
1971
|
return;
|
|
1972
1972
|
}
|
|
1973
1973
|
function getEffectiveDataRoot(env = process.env) {
|
|
@@ -1978,13 +1978,13 @@ function getEffectiveDataRoot(env = process.env) {
|
|
|
1978
1978
|
if (exact)
|
|
1979
1979
|
return exact;
|
|
1980
1980
|
const resolved = getResolverDataRoot(env);
|
|
1981
|
-
return adoptResolverDataRoot(resolved, env) ?
|
|
1981
|
+
return adoptResolverDataRoot(resolved, env) ? resolve2(resolved) : getLegacyDataRoot(env);
|
|
1982
1982
|
}
|
|
1983
1983
|
function getReportedDbPath(env = process.env) {
|
|
1984
1984
|
const explicit = getExplicitDbPath(env);
|
|
1985
1985
|
if (explicit)
|
|
1986
1986
|
return explicit;
|
|
1987
|
-
return
|
|
1987
|
+
return join3(getEffectiveDataRoot(env), "hooks.db");
|
|
1988
1988
|
}
|
|
1989
1989
|
function getExplicitDbPath(env = process.env) {
|
|
1990
1990
|
const db = env.HASNA_HOOKS_DB_PATH ?? env.HOOKS_DB_PATH;
|
|
@@ -1992,15 +1992,15 @@ function getExplicitDbPath(env = process.env) {
|
|
|
1992
1992
|
return db.trim();
|
|
1993
1993
|
return;
|
|
1994
1994
|
}
|
|
1995
|
-
var
|
|
1995
|
+
var PATHS_RESOLVER_KIND_ENV2, PATHS_RESOLVER_APP_SLUG_RE2, STORE_MARKER = "hooks.db";
|
|
1996
1996
|
var init_app_home = __esm(() => {
|
|
1997
|
-
|
|
1997
|
+
PATHS_RESOLVER_KIND_ENV2 = {
|
|
1998
1998
|
config: "HASNA_CONFIG_HOME",
|
|
1999
1999
|
data: "HASNA_DATA_HOME",
|
|
2000
2000
|
state: "HASNA_STATE_HOME",
|
|
2001
2001
|
cache: "HASNA_CACHE_HOME"
|
|
2002
2002
|
};
|
|
2003
|
-
|
|
2003
|
+
PATHS_RESOLVER_APP_SLUG_RE2 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
2004
2004
|
});
|
|
2005
2005
|
|
|
2006
2006
|
// src/lib/registry.ts
|
|
@@ -2571,7 +2571,7 @@ var init_registry = __esm(() => {
|
|
|
2571
2571
|
];
|
|
2572
2572
|
});
|
|
2573
2573
|
|
|
2574
|
-
//
|
|
2574
|
+
// ../../node_modules/.bun/cli-spinners@2.9.2/node_modules/cli-spinners/spinners.json
|
|
2575
2575
|
var require_spinners = __commonJS((exports, module) => {
|
|
2576
2576
|
module.exports = {
|
|
2577
2577
|
dots: {
|
|
@@ -4197,7 +4197,7 @@ var require_spinners = __commonJS((exports, module) => {
|
|
|
4197
4197
|
};
|
|
4198
4198
|
});
|
|
4199
4199
|
|
|
4200
|
-
//
|
|
4200
|
+
// ../../node_modules/.bun/cli-spinners@2.9.2/node_modules/cli-spinners/index.js
|
|
4201
4201
|
var require_cli_spinners = __commonJS((exports, module) => {
|
|
4202
4202
|
var spinners = Object.assign({}, require_spinners());
|
|
4203
4203
|
var spinnersList = Object.keys(spinners);
|
|
@@ -4212,18 +4212,18 @@ var require_cli_spinners = __commonJS((exports, module) => {
|
|
|
4212
4212
|
});
|
|
4213
4213
|
|
|
4214
4214
|
// src/config.ts
|
|
4215
|
-
import { join as
|
|
4215
|
+
import { join as join5 } from "path";
|
|
4216
4216
|
function getHooksDataDir() {
|
|
4217
4217
|
return getEffectiveDataRoot();
|
|
4218
4218
|
}
|
|
4219
4219
|
function getCustomHooksDir() {
|
|
4220
|
-
return
|
|
4220
|
+
return join5(getHooksDataDir(), "hooks");
|
|
4221
4221
|
}
|
|
4222
4222
|
function getLockPath() {
|
|
4223
4223
|
const explicit = process.env.HASNA_HOOKS_LOCK_PATH ?? process.env.HOOKS_LOCK_PATH;
|
|
4224
4224
|
if (explicit)
|
|
4225
4225
|
return explicit;
|
|
4226
|
-
return
|
|
4226
|
+
return join5(getHooksDataDir(), "hooks.lock");
|
|
4227
4227
|
}
|
|
4228
4228
|
var init_config = __esm(() => {
|
|
4229
4229
|
init_app_home();
|
|
@@ -4253,8 +4253,8 @@ __export(exports_manifest, {
|
|
|
4253
4253
|
ScriptContainmentError: () => ScriptContainmentError,
|
|
4254
4254
|
HOOK_NAME_RE: () => HOOK_NAME_RE
|
|
4255
4255
|
});
|
|
4256
|
-
import { existsSync as
|
|
4257
|
-
import { basename, dirname, isAbsolute, join as
|
|
4256
|
+
import { existsSync as existsSync5, mkdirSync, readFileSync, readdirSync, realpathSync, writeFileSync } from "fs";
|
|
4257
|
+
import { basename, dirname, isAbsolute, join as join6, normalize, resolve as resolve3, sep } from "path";
|
|
4258
4258
|
import { z } from "zod";
|
|
4259
4259
|
function parseManifest(json) {
|
|
4260
4260
|
const parsed = JSON.parse(json);
|
|
@@ -4270,8 +4270,8 @@ function isWithin(child, parent) {
|
|
|
4270
4270
|
return child === parent || child.startsWith(parent + sep);
|
|
4271
4271
|
}
|
|
4272
4272
|
function assertContainedLexical(target, root, raw) {
|
|
4273
|
-
const lexical =
|
|
4274
|
-
const rootLexical =
|
|
4273
|
+
const lexical = resolve3(target);
|
|
4274
|
+
const rootLexical = resolve3(root);
|
|
4275
4275
|
if (!isWithin(lexical, rootLexical)) {
|
|
4276
4276
|
throw new ScriptContainmentError(target, root, raw);
|
|
4277
4277
|
}
|
|
@@ -4280,14 +4280,14 @@ function assertContainedLexical(target, root, raw) {
|
|
|
4280
4280
|
function realpathNearest(path) {
|
|
4281
4281
|
const tail = [];
|
|
4282
4282
|
let cur = path;
|
|
4283
|
-
while (!
|
|
4283
|
+
while (!existsSync5(cur)) {
|
|
4284
4284
|
const parent = dirname(cur);
|
|
4285
4285
|
if (parent === cur)
|
|
4286
4286
|
break;
|
|
4287
4287
|
tail.unshift(basename(cur));
|
|
4288
4288
|
cur = parent;
|
|
4289
4289
|
}
|
|
4290
|
-
return
|
|
4290
|
+
return join6(realpathSync(cur), ...tail);
|
|
4291
4291
|
}
|
|
4292
4292
|
function assertContainedReal(root, target, raw) {
|
|
4293
4293
|
const rootReal = realpathSync(root);
|
|
@@ -4320,25 +4320,25 @@ function resolveScript(manifest, manifestDir) {
|
|
|
4320
4320
|
if (isInlineScript(manifest))
|
|
4321
4321
|
return { path: scriptRelFor(manifest), content: manifest.script };
|
|
4322
4322
|
const rel = scriptRelFor(manifest);
|
|
4323
|
-
const resolved = assertContained(
|
|
4324
|
-
if (!
|
|
4323
|
+
const resolved = assertContained(join6(manifestDir, rel), manifestDir, manifest.script);
|
|
4324
|
+
if (!existsSync5(resolved)) {
|
|
4325
4325
|
throw new Error(`manifest script file not found: ${resolved}`);
|
|
4326
4326
|
}
|
|
4327
4327
|
return { path: rel, content: readFileSync(resolved, "utf-8") };
|
|
4328
4328
|
}
|
|
4329
4329
|
function customHookDir(name) {
|
|
4330
|
-
return
|
|
4330
|
+
return join6(getCustomHooksDir(), shortManifestName(name));
|
|
4331
4331
|
}
|
|
4332
4332
|
function readCustomManifest(name) {
|
|
4333
4333
|
const dir = customHookDir(name);
|
|
4334
|
-
const manifestPath =
|
|
4335
|
-
if (!
|
|
4334
|
+
const manifestPath = join6(dir, "manifest.json");
|
|
4335
|
+
if (!existsSync5(manifestPath))
|
|
4336
4336
|
return;
|
|
4337
4337
|
try {
|
|
4338
4338
|
const manifest = parseManifest(readFileSync(manifestPath, "utf-8"));
|
|
4339
4339
|
const script = resolveScript(manifest, dir);
|
|
4340
4340
|
const scriptIsInline = isInlineScript(manifest);
|
|
4341
|
-
return { manifest, scriptPath:
|
|
4341
|
+
return { manifest, scriptPath: join6(dir, script.path), scriptContent: script.content, scriptIsInline };
|
|
4342
4342
|
} catch (err) {
|
|
4343
4343
|
if (err instanceof ScriptContainmentError)
|
|
4344
4344
|
throw err;
|
|
@@ -4347,7 +4347,7 @@ function readCustomManifest(name) {
|
|
|
4347
4347
|
}
|
|
4348
4348
|
function listCustomHooks() {
|
|
4349
4349
|
const dir = getCustomHooksDir();
|
|
4350
|
-
if (!
|
|
4350
|
+
if (!existsSync5(dir))
|
|
4351
4351
|
return [];
|
|
4352
4352
|
const results = [];
|
|
4353
4353
|
for (const entry of readdirSafe(dir)) {
|
|
@@ -4362,7 +4362,7 @@ function writeCustomHook(name, manifest, scriptContent, scriptRelPath) {
|
|
|
4362
4362
|
if (isAbsolute(scriptRelPath)) {
|
|
4363
4363
|
throw new ScriptContainmentError(scriptRelPath, dir);
|
|
4364
4364
|
}
|
|
4365
|
-
const scriptPath = scriptRelPath.includes("/") ?
|
|
4365
|
+
const scriptPath = scriptRelPath.includes("/") ? join6(dir, scriptRelPath) : join6(dir, scriptRelPath);
|
|
4366
4366
|
assertContainedLexical(scriptPath, dir, scriptRelPath);
|
|
4367
4367
|
mkdirSync(dir, { recursive: true });
|
|
4368
4368
|
assertContainedReal(dir, scriptPath, scriptRelPath);
|
|
@@ -4370,7 +4370,7 @@ function writeCustomHook(name, manifest, scriptContent, scriptRelPath) {
|
|
|
4370
4370
|
if (scriptDir !== dir)
|
|
4371
4371
|
mkdirSync(scriptDir, { recursive: true });
|
|
4372
4372
|
writeFileSync(scriptPath, scriptContent, "utf-8");
|
|
4373
|
-
writeFileSync(
|
|
4373
|
+
writeFileSync(join6(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + `
|
|
4374
4374
|
`, "utf-8");
|
|
4375
4375
|
return { dir, scriptPath };
|
|
4376
4376
|
}
|
|
@@ -4414,14 +4414,14 @@ __export(exports_resolve, {
|
|
|
4414
4414
|
resolveHook: () => resolveHook,
|
|
4415
4415
|
resolveBundledHooksDir: () => resolveBundledHooksDir
|
|
4416
4416
|
});
|
|
4417
|
-
import { dirname as dirname2, join as
|
|
4418
|
-
import { existsSync as
|
|
4417
|
+
import { dirname as dirname2, join as join7 } from "path";
|
|
4418
|
+
import { existsSync as existsSync6 } from "fs";
|
|
4419
4419
|
import { fileURLToPath } from "url";
|
|
4420
4420
|
function resolveBundledHooksDir(moduleDir = dirname2(fileURLToPath(import.meta.url))) {
|
|
4421
|
-
const sourceLayout =
|
|
4422
|
-
if (
|
|
4421
|
+
const sourceLayout = join7(moduleDir, "..", "..", "hooks");
|
|
4422
|
+
if (existsSync6(join7(sourceLayout, "hook-gitguard")))
|
|
4423
4423
|
return sourceLayout;
|
|
4424
|
-
return
|
|
4424
|
+
return join7(moduleDir, "..", "hooks");
|
|
4425
4425
|
}
|
|
4426
4426
|
function resolveHookDir(name) {
|
|
4427
4427
|
const custom = readCustomManifest(name);
|
|
@@ -4432,11 +4432,11 @@ function resolveHookDir(name) {
|
|
|
4432
4432
|
return;
|
|
4433
4433
|
const short = shortManifestName(name);
|
|
4434
4434
|
const bundledBase = resolveBundledHooksDir();
|
|
4435
|
-
const direct =
|
|
4436
|
-
if (
|
|
4435
|
+
const direct = join7(bundledBase, short);
|
|
4436
|
+
if (existsSync6(direct))
|
|
4437
4437
|
return direct;
|
|
4438
|
-
const prefixed =
|
|
4439
|
-
return
|
|
4438
|
+
const prefixed = join7(bundledBase, `hook-${short}`);
|
|
4439
|
+
return existsSync6(prefixed) ? prefixed : undefined;
|
|
4440
4440
|
}
|
|
4441
4441
|
function resolveScriptPath(name) {
|
|
4442
4442
|
const custom = readCustomManifest(name);
|
|
@@ -4445,8 +4445,8 @@ function resolveScriptPath(name) {
|
|
|
4445
4445
|
const dir = resolveHookDir(name);
|
|
4446
4446
|
if (!dir)
|
|
4447
4447
|
return;
|
|
4448
|
-
const script =
|
|
4449
|
-
return
|
|
4448
|
+
const script = join7(dir, "src", "hook.ts");
|
|
4449
|
+
return existsSync6(script) ? script : undefined;
|
|
4450
4450
|
}
|
|
4451
4451
|
function resolveHookMeta(name) {
|
|
4452
4452
|
const custom = readCustomManifest(name);
|
|
@@ -4503,8 +4503,8 @@ var init_resolve = __esm(() => {
|
|
|
4503
4503
|
|
|
4504
4504
|
// src/lib/native-safety.ts
|
|
4505
4505
|
import { createHash } from "crypto";
|
|
4506
|
-
import { closeSync, constants, existsSync as
|
|
4507
|
-
import { dirname as dirname3, isAbsolute as isAbsolute2, join as
|
|
4506
|
+
import { closeSync, constants, existsSync as existsSync7, fstatSync, lstatSync, openSync, readFileSync as readFileSync2, realpathSync as realpathSync2 } from "fs";
|
|
4507
|
+
import { dirname as dirname3, isAbsolute as isAbsolute2, join as join8 } from "path";
|
|
4508
4508
|
import { homedir as homedir3 } from "os";
|
|
4509
4509
|
function isNativeSafetyName(name) {
|
|
4510
4510
|
return name === "trash-guard" || name === "workspace-repos-guard";
|
|
@@ -4519,6 +4519,9 @@ function buildNativeSafetyCommand(binding) {
|
|
|
4519
4519
|
return prefix + [encoded, binding.runtime, binding.home, binding.path].map(quote).join(" ");
|
|
4520
4520
|
}
|
|
4521
4521
|
function nativeSafetyRegistration(command) {
|
|
4522
|
+
return nativeSafetyCommandBinding(command)?.name;
|
|
4523
|
+
}
|
|
4524
|
+
function nativeSafetyCommandBinding(command) {
|
|
4522
4525
|
if (!command.startsWith(prefix))
|
|
4523
4526
|
return;
|
|
4524
4527
|
const encoded = command.slice(prefix.length).match(/^'([A-Za-z0-9_-]+)' /)?.[1];
|
|
@@ -4526,7 +4529,7 @@ function nativeSafetyRegistration(command) {
|
|
|
4526
4529
|
return;
|
|
4527
4530
|
try {
|
|
4528
4531
|
const binding = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
|
|
4529
|
-
return buildNativeSafetyCommand(binding) === command ? binding
|
|
4532
|
+
return buildNativeSafetyCommand(binding) === command ? binding : undefined;
|
|
4530
4533
|
} catch {
|
|
4531
4534
|
return;
|
|
4532
4535
|
}
|
|
@@ -4558,12 +4561,12 @@ function pinEligibleFile(file, maximum, executable = false) {
|
|
|
4558
4561
|
function installedNativeSafetyCommand(name) {
|
|
4559
4562
|
const home = realpathSync2(homedir3());
|
|
4560
4563
|
const runtime = realpathSync2(process.execPath);
|
|
4561
|
-
const candidates = [
|
|
4562
|
-
const worker = candidates.find(
|
|
4564
|
+
const candidates = [join8(import.meta.dir, "../../bin/native-safety-entry.js"), join8(import.meta.dir, "../bin/native-safety-entry.js"), join8(import.meta.dir, "native-safety-entry.js")];
|
|
4565
|
+
const worker = candidates.find(existsSync7);
|
|
4563
4566
|
if (!worker)
|
|
4564
4567
|
throw new Error("Bundled native safety runner is missing; build or reinstall @hasna/hooks before installing its guards.");
|
|
4565
4568
|
const canonicalWorker = realpathSync2(worker);
|
|
4566
|
-
return buildNativeSafetyCommand({ name, runtime, runtimeSHA256: pinEligibleFile(runtime, 200000000, true), worker: canonicalWorker, workerSHA256: pinEligibleFile(canonicalWorker, 256000), home, path: `${dirname3(runtime)}:${
|
|
4569
|
+
return buildNativeSafetyCommand({ name, runtime, runtimeSHA256: pinEligibleFile(runtime, 200000000, true), worker: canonicalWorker, workerSHA256: pinEligibleFile(canonicalWorker, 256000), home, path: `${dirname3(runtime)}:${join8(home, ".bun/bin")}:/usr/local/bin:/usr/bin:/bin` });
|
|
4567
4570
|
}
|
|
4568
4571
|
var supervisorSource, quote = (value) => "'" + value.replaceAll("'", "'\\''") + "'", program2, shell, prefix;
|
|
4569
4572
|
var init_native_safety = __esm(() => {
|
|
@@ -5010,8 +5013,8 @@ var init_migrations = __esm(() => {
|
|
|
5010
5013
|
});
|
|
5011
5014
|
|
|
5012
5015
|
// src/db/legacy-import.ts
|
|
5013
|
-
import { existsSync as
|
|
5014
|
-
import { join as
|
|
5016
|
+
import { existsSync as existsSync8, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
5017
|
+
import { join as join9 } from "path";
|
|
5015
5018
|
import { homedir as homedir4 } from "os";
|
|
5016
5019
|
function ensureMetaTable(db) {
|
|
5017
5020
|
db.exec(`
|
|
@@ -5091,20 +5094,20 @@ function runLegacyImport(db, homeDir = homedir4()) {
|
|
|
5091
5094
|
if (isAlreadyDone(db))
|
|
5092
5095
|
return;
|
|
5093
5096
|
let total = 0;
|
|
5094
|
-
const claudeProjectsDir =
|
|
5095
|
-
if (
|
|
5097
|
+
const claudeProjectsDir = join9(homeDir, ".claude", "projects");
|
|
5098
|
+
if (existsSync8(claudeProjectsDir)) {
|
|
5096
5099
|
try {
|
|
5097
5100
|
const projectDirs = readdirSync2(claudeProjectsDir);
|
|
5098
5101
|
for (const dir of projectDirs) {
|
|
5099
|
-
const projectDir =
|
|
5102
|
+
const projectDir = join9(claudeProjectsDir, dir);
|
|
5100
5103
|
try {
|
|
5101
5104
|
const files = readdirSync2(projectDir);
|
|
5102
5105
|
for (const file of files) {
|
|
5103
5106
|
if (file.match(/^session-log-\d{4}-\d{2}-\d{2}\.jsonl$/)) {
|
|
5104
|
-
total += importJsonlFile(db,
|
|
5107
|
+
total += importJsonlFile(db, join9(projectDir, file));
|
|
5105
5108
|
}
|
|
5106
5109
|
if (file === "errors.log") {
|
|
5107
|
-
total += importErrorsLog(db,
|
|
5110
|
+
total += importErrorsLog(db, join9(projectDir, file));
|
|
5108
5111
|
}
|
|
5109
5112
|
}
|
|
5110
5113
|
} catch {}
|
|
@@ -5151,8 +5154,8 @@ __export(exports_db, {
|
|
|
5151
5154
|
allowLocalStore: () => allowLocalStore
|
|
5152
5155
|
});
|
|
5153
5156
|
import { Database } from "bun:sqlite";
|
|
5154
|
-
import { existsSync as
|
|
5155
|
-
import { join as
|
|
5157
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync2, cpSync } from "fs";
|
|
5158
|
+
import { join as join10 } from "path";
|
|
5156
5159
|
function refuseLocalStore(message) {
|
|
5157
5160
|
localStoreRefusal = message;
|
|
5158
5161
|
}
|
|
@@ -5167,8 +5170,8 @@ function localStoreRefusalMessage() {
|
|
|
5167
5170
|
}
|
|
5168
5171
|
function resolveDataDir() {
|
|
5169
5172
|
const effective = getEffectiveDataRoot();
|
|
5170
|
-
const oldDir =
|
|
5171
|
-
if (!
|
|
5173
|
+
const oldDir = join10(getHomeDir(), ".hooks");
|
|
5174
|
+
if (!existsSync9(effective) && existsSync9(oldDir)) {
|
|
5172
5175
|
mkdirSync2(effective, { recursive: true });
|
|
5173
5176
|
cpSync(oldDir, effective, { recursive: true });
|
|
5174
5177
|
}
|
|
@@ -5178,12 +5181,12 @@ function getDbPath() {
|
|
|
5178
5181
|
const explicitDb = process.env.HASNA_HOOKS_DB_PATH ?? process.env.HOOKS_DB_PATH;
|
|
5179
5182
|
if (explicitDb)
|
|
5180
5183
|
return explicitDb;
|
|
5181
|
-
const
|
|
5182
|
-
return
|
|
5184
|
+
const dataDir3 = resolveDataDir();
|
|
5185
|
+
return join10(dataDir3, "hooks.db");
|
|
5183
5186
|
}
|
|
5184
5187
|
function ensureDir(dbPath) {
|
|
5185
5188
|
const dir = dbPath.substring(0, dbPath.lastIndexOf("/"));
|
|
5186
|
-
if (dir && !
|
|
5189
|
+
if (dir && !existsSync9(dir)) {
|
|
5187
5190
|
mkdirSync2(dir, { recursive: true });
|
|
5188
5191
|
}
|
|
5189
5192
|
}
|
|
@@ -5193,7 +5196,7 @@ function getDb() {
|
|
|
5193
5196
|
if (instance)
|
|
5194
5197
|
return instance;
|
|
5195
5198
|
const dbPath = getDbPath();
|
|
5196
|
-
const isNew = dbPath === ":memory:" || !
|
|
5199
|
+
const isNew = dbPath === ":memory:" || !existsSync9(dbPath);
|
|
5197
5200
|
ensureDir(dbPath);
|
|
5198
5201
|
instance = new Database(dbPath);
|
|
5199
5202
|
instance.exec("PRAGMA busy_timeout=5000");
|
|
@@ -5256,7 +5259,7 @@ __export(exports_store, {
|
|
|
5256
5259
|
LockFileError: () => LockFileError
|
|
5257
5260
|
});
|
|
5258
5261
|
import { createHash as createHash2 } from "crypto";
|
|
5259
|
-
import { existsSync as
|
|
5262
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync2, renameSync } from "fs";
|
|
5260
5263
|
import { readFile as readFile2 } from "fs/promises";
|
|
5261
5264
|
import { randomBytes } from "crypto";
|
|
5262
5265
|
function sha256Of(input) {
|
|
@@ -5321,7 +5324,7 @@ function assertLockShape(parsed, path) {
|
|
|
5321
5324
|
}
|
|
5322
5325
|
function readLock() {
|
|
5323
5326
|
const path = getLockPath();
|
|
5324
|
-
if (!
|
|
5327
|
+
if (!existsSync10(path))
|
|
5325
5328
|
return { hooks: {} };
|
|
5326
5329
|
let raw;
|
|
5327
5330
|
try {
|
|
@@ -5455,7 +5458,7 @@ var init_store = __esm(() => {
|
|
|
5455
5458
|
});
|
|
5456
5459
|
|
|
5457
5460
|
// src/lib/codex-settings.ts
|
|
5458
|
-
import { closeSync as closeSync2, existsSync as
|
|
5461
|
+
import { closeSync as closeSync2, existsSync as existsSync11, lstatSync as lstatSync2, mkdirSync as mkdirSync4, openSync as openSync2, readFileSync as readFileSync5, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
|
|
5459
5462
|
import { dirname as dirname4 } from "path";
|
|
5460
5463
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
5461
5464
|
function current(path) {
|
|
@@ -5515,7 +5518,7 @@ function writeCodexSettings(path, settings) {
|
|
|
5515
5518
|
renameSync2(temp, path);
|
|
5516
5519
|
snapshots.set(settings, { path, raw: next });
|
|
5517
5520
|
} finally {
|
|
5518
|
-
if (
|
|
5521
|
+
if (existsSync11(temp))
|
|
5519
5522
|
unlinkSync(temp);
|
|
5520
5523
|
closeSync2(fd);
|
|
5521
5524
|
unlinkSync(lock);
|
|
@@ -5526,12 +5529,12 @@ var init_codex_settings = __esm(() => {
|
|
|
5526
5529
|
snapshots = new WeakMap;
|
|
5527
5530
|
});
|
|
5528
5531
|
|
|
5529
|
-
//
|
|
5530
|
-
import { existsSync as
|
|
5532
|
+
// ../../node_modules/.bun/@hasna+skills@0.9.14+a00d3064559565bb/node_modules/@hasna/skills/dist/index.js
|
|
5533
|
+
import { existsSync as existsSync12 } from "fs";
|
|
5531
5534
|
import { homedir as homedir5 } from "os";
|
|
5532
|
-
import { join as
|
|
5533
|
-
import { homedir as
|
|
5534
|
-
import { join as
|
|
5535
|
+
import { join as join11, resolve as resolve4 } from "path";
|
|
5536
|
+
import { homedir as pathsResolverHomedir3 } from "os";
|
|
5537
|
+
import { join as pathsResolverJoin3 } from "path";
|
|
5535
5538
|
import { createHash as createHash3 } from "crypto";
|
|
5536
5539
|
import { existsSync as existsSync52, readdirSync as readdirSync3, readFileSync as readFileSync42, statSync as statSync3 } from "fs";
|
|
5537
5540
|
import { join as join52, sep as sep2 } from "path";
|
|
@@ -5543,7 +5546,7 @@ import { dirname as dirname32, isAbsolute as isAbsolute3, join as join72, resolv
|
|
|
5543
5546
|
import { lstatSync as lstatSync3, readFileSync as readFileSync6, opendirSync } from "fs";
|
|
5544
5547
|
import { createHash as createHash22 } from "crypto";
|
|
5545
5548
|
import { lstatSync as lstatSync4, opendirSync as opendirSync2 } from "fs";
|
|
5546
|
-
import { dirname as dirname42, isAbsolute as isAbsolute4, join as join82, resolve as
|
|
5549
|
+
import { dirname as dirname42, isAbsolute as isAbsolute4, join as join82, resolve as resolve42 } from "path";
|
|
5547
5550
|
import { createHash as createHash32 } from "crypto";
|
|
5548
5551
|
import { closeSync as closeSync3, constants as constants2, fstatSync as fstatSync2, lstatSync as lstatSync5, openSync as openSync3, readSync } from "fs";
|
|
5549
5552
|
import { dirname as dirname5, isAbsolute as isAbsolute5, resolve as resolve5 } from "path";
|
|
@@ -5553,7 +5556,7 @@ import { dirname as dirname6, isAbsolute as isAbsolute6, join as join92, parse,
|
|
|
5553
5556
|
import { closeSync as closeSync12, constants as constants9, fstatSync as fstatSync12, lstatSync as lstatSync17, openSync as openSync12, opendirSync as opendirSync5, readSync as readSync8 } from "fs";
|
|
5554
5557
|
import { basename as basename6, dirname as dirname13, isAbsolute as isAbsolute12, join as join25, resolve as resolve12 } from "path";
|
|
5555
5558
|
import { isAbsolute as isAbsolute11, join as join23, resolve as resolve11 } from "path";
|
|
5556
|
-
import { createRequire } from "module";
|
|
5559
|
+
import { createRequire as createRequire2 } from "module";
|
|
5557
5560
|
import { createHash as createHash10, randomUUID as randomUUID22 } from "crypto";
|
|
5558
5561
|
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 realpathSync3, 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";
|
|
5559
5562
|
import { dirname as dirname10, join as join16, resolve as resolve9, relative as relative4, isAbsolute as isAbsolute9, sep as sep4 } from "path";
|
|
@@ -5581,11 +5584,11 @@ import { createHash as createHash20 } from "crypto";
|
|
|
5581
5584
|
function __exportSetter2(name, newValue) {
|
|
5582
5585
|
this[name] = __returnValue2.bind(null, newValue);
|
|
5583
5586
|
}
|
|
5584
|
-
function
|
|
5587
|
+
function pathsResolverAssertApp3(app) {
|
|
5585
5588
|
if (typeof app !== "string" || app.length === 0) {
|
|
5586
5589
|
throw new TypeError("paths: app must be a non-empty string");
|
|
5587
5590
|
}
|
|
5588
|
-
if (!
|
|
5591
|
+
if (!PATHS_RESOLVER_APP_SLUG_RE3.test(app)) {
|
|
5589
5592
|
throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
|
|
5590
5593
|
}
|
|
5591
5594
|
}
|
|
@@ -5594,35 +5597,35 @@ function pathsResolverDataBaseDir(options) {
|
|
|
5594
5597
|
const override = env[PATHS_RESOLVER_DATA_ENV];
|
|
5595
5598
|
if (typeof override === "string" && override.length > 0)
|
|
5596
5599
|
return override;
|
|
5597
|
-
const home = options.home ??
|
|
5600
|
+
const home = options.home ?? pathsResolverHomedir3();
|
|
5598
5601
|
const platform = options.platform ?? process.platform;
|
|
5599
|
-
return platform === "darwin" ?
|
|
5602
|
+
return platform === "darwin" ? pathsResolverJoin3(home, "Library", "Application Support", "Hasna") : pathsResolverJoin3(home, ".local", "share", "hasna");
|
|
5600
5603
|
}
|
|
5601
|
-
function
|
|
5602
|
-
|
|
5603
|
-
const appSegment = options.internal === true ?
|
|
5604
|
-
return
|
|
5604
|
+
function dataDir3(options) {
|
|
5605
|
+
pathsResolverAssertApp3(options.app);
|
|
5606
|
+
const appSegment = options.internal === true ? pathsResolverJoin3("internal", options.app) : options.app;
|
|
5607
|
+
return pathsResolverJoin3(pathsResolverDataBaseDir(options), appSegment);
|
|
5605
5608
|
}
|
|
5606
|
-
function
|
|
5609
|
+
function effectiveHome2() {
|
|
5607
5610
|
return process.env["HOME"] || process.env["USERPROFILE"] || homedir5() || "/tmp";
|
|
5608
5611
|
}
|
|
5609
5612
|
function legacyDataRoot() {
|
|
5610
|
-
return
|
|
5613
|
+
return join11(effectiveHome2(), ".hasna", "skills");
|
|
5611
5614
|
}
|
|
5612
|
-
function resolverDataRoot(home =
|
|
5613
|
-
return
|
|
5615
|
+
function resolverDataRoot(home = effectiveHome2(), env) {
|
|
5616
|
+
return dataDir3({ app: "skills", home, env });
|
|
5614
5617
|
}
|
|
5615
5618
|
function adoptResolverDataRoot2(resolved, env = process.env) {
|
|
5616
5619
|
const dataOverride = env.HASNA_DATA_HOME;
|
|
5617
5620
|
if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
|
|
5618
5621
|
return true;
|
|
5619
|
-
return
|
|
5622
|
+
return existsSync12(join11(resolved, DEFAULT_SQLITE_FILENAME)) || existsSync12(join11(resolved, GLOBAL_CONFIG_FILENAME));
|
|
5620
5623
|
}
|
|
5621
5624
|
function exactDataRoot() {
|
|
5622
5625
|
for (const key of [DATA_DIR_ENV, HASNA_SKILLS_HOME_ENV, SKILLS_HOME_ENV]) {
|
|
5623
5626
|
const dir = process.env[key]?.trim();
|
|
5624
5627
|
if (dir)
|
|
5625
|
-
return
|
|
5628
|
+
return resolve4(dir);
|
|
5626
5629
|
}
|
|
5627
5630
|
return;
|
|
5628
5631
|
}
|
|
@@ -5631,7 +5634,7 @@ function getDataRoot() {
|
|
|
5631
5634
|
if (exact)
|
|
5632
5635
|
return exact;
|
|
5633
5636
|
const resolved = resolverDataRoot();
|
|
5634
|
-
return adoptResolverDataRoot2(resolved) ?
|
|
5637
|
+
return adoptResolverDataRoot2(resolved) ? resolve4(resolved) : resolve4(legacyDataRoot());
|
|
5635
5638
|
}
|
|
5636
5639
|
function getDataDirReadOnly() {
|
|
5637
5640
|
return getDataRoot();
|
|
@@ -6253,7 +6256,7 @@ function need(value, message) {
|
|
|
6253
6256
|
throw new Error(message);
|
|
6254
6257
|
}
|
|
6255
6258
|
function safe(path) {
|
|
6256
|
-
need(typeof path === "string" && path.length <= AGENT_POLICY_LIMITS.pathCharacters && isAbsolute4(path) &&
|
|
6259
|
+
need(typeof path === "string" && path.length <= AGENT_POLICY_LIMITS.pathCharacters && isAbsolute4(path) && resolve42(path) === path && !path.includes("\x00"), "Expected a canonical absolute discovery directory path");
|
|
6257
6260
|
for (let cursor = path;; cursor = dirname42(cursor)) {
|
|
6258
6261
|
need(!lstatSync4(cursor, { throwIfNoEntry: false })?.isSymbolicLink(), "Refusing symlink native discovery directory");
|
|
6259
6262
|
if (dirname42(cursor) === cursor)
|
|
@@ -15487,7 +15490,7 @@ var __defProp2, __commonJS2 = (cb, mod) => () => (mod || cb((mod = { exports: {}
|
|
|
15487
15490
|
configurable: true,
|
|
15488
15491
|
set: __exportSetter2.bind(all, name)
|
|
15489
15492
|
});
|
|
15490
|
-
}, __require2, require_identity, require_visit, require_directives, require_anchors, require_applyReviver, require_toJS, require_Node, require_Alias, require_Scalar, require_createNode, require_Collection, require_stringifyComment, require_foldFlowLines, require_stringifyString, require_stringify, require_stringifyPair, require_log, require_merge, require_addPairToJSMap, require_Pair, require_stringifyCollection, require_YAMLMap, require_map, require_YAMLSeq, require_seq, require_string, require_null, require_bool, require_stringifyNumber, require_float, require_int, require_schema, require_schema2, require_binary, require_pairs, require_omap, require_bool2, require_float2, require_int2, require_set, require_timestamp, require_schema3, require_tags, require_Schema, require_stringifyDocument, require_Document, require_errors, require_resolve_props, require_util_contains_newline, require_util_flow_indent_check, require_util_map_includes, require_resolve_block_map, require_resolve_block_seq, require_resolve_end, require_resolve_flow_collection, require_compose_collection, require_resolve_block_scalar, require_resolve_flow_scalar, require_compose_scalar, require_util_empty_scalar_position, require_compose_node, require_compose_doc, require_composer, require_cst_scalar, require_cst_stringify, require_cst_visit, require_cst, require_lexer, require_line_counter, require_parser, require_public_api, RETIRED_CONFIG_KEY_REASONS, PATHS_RESOLVER_DATA_ENV = "HASNA_DATA_HOME",
|
|
15493
|
+
}, __require2, require_identity, require_visit, require_directives, require_anchors, require_applyReviver, require_toJS, require_Node, require_Alias, require_Scalar, require_createNode, require_Collection, require_stringifyComment, require_foldFlowLines, require_stringifyString, require_stringify, require_stringifyPair, require_log, require_merge, require_addPairToJSMap, require_Pair, require_stringifyCollection, require_YAMLMap, require_map, require_YAMLSeq, require_seq, require_string, require_null, require_bool, require_stringifyNumber, require_float, require_int, require_schema, require_schema2, require_binary, require_pairs, require_omap, require_bool2, require_float2, require_int2, require_set, require_timestamp, require_schema3, require_tags, require_Schema, require_stringifyDocument, require_Document, require_errors, require_resolve_props, require_util_contains_newline, require_util_flow_indent_check, require_util_map_includes, require_resolve_block_map, require_resolve_block_seq, require_resolve_end, require_resolve_flow_collection, require_compose_collection, require_resolve_block_scalar, require_resolve_flow_scalar, require_compose_scalar, require_util_empty_scalar_position, require_compose_node, require_compose_doc, require_composer, require_cst_scalar, require_cst_stringify, require_cst_visit, require_cst, require_lexer, require_line_counter, require_parser, require_public_api, RETIRED_CONFIG_KEY_REASONS, PATHS_RESOLVER_DATA_ENV = "HASNA_DATA_HOME", PATHS_RESOLVER_APP_SLUG_RE3, DATA_DIR_ENV = "HASNA_SKILLS_DIR", HASNA_SKILLS_HOME_ENV = "HASNA_SKILLS_HOME", SKILLS_HOME_ENV = "SKILLS_HOME", DEFAULT_SQLITE_FILENAME = "server.db", GLOBAL_CONFIG_FILENAME = "config.json", SKILLS, HOSTED_RUNTIMES, HOSTED_SOURCES, HOSTED_METADATA_SET_EMPTY_ERROR, SLUG = "[^,{}/]+", BRACE_SOURCE_EXCLUSION, SINGLE_SOURCE_EXCLUSION, RESERVED_SKILL_ENTRIES, KNOWN_TOP_LEVEL_ENTRIES, VALID_PROVENANCE_SOURCES, CONTENT_HASH_ALGORITHM = "sha256", HASH_EXCLUDE_DIRS, HASH_COVERAGE, CONTENT_HASH_LIMITS, typedArrayPrototype, byteLengthOf, bufferOf, PORTABLE_SKILL_STANDARD = "hasna.skill.v1", SKILL_RUNTIMES, SKILL_SANDBOX_MODES, SKILL_SYSTEM_DEPS_ALLOWLIST, SEMVER_PATTERN2 = "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$", CONTENT_HASH_PATTERN = "^[a-f0-9]{64}$", ENV_REFERENCE_PATTERN = "^[A-Z0-9_]+$", MAX_RUNTIME_TIMEOUT_SECONDS = 900, ANY_SEGMENT_COPY_EXCLUDES, FIRST_SEGMENT_COPY_EXCLUDES, NATIVE_SKILL_ROOTS, INTEGRATION_AGENTS, AGENT_ADAPTERS, AGENT_POLICY_LIMITS, composer, Document, Schema, errors, Alias, identity, Pair, Scalar, YAMLMap, YAMLSeq, cst, lexer, lineCounter, parser, publicApi, visit, $Composer, $Document, $Schema, $YAMLError, $YAMLParseError, $YAMLWarning, $Alias, $isAlias, $isCollection, $isDocument, $isMap, $isNode, $isPair, $isScalar, $isSeq, $Pair, $Scalar, $YAMLMap, $YAMLSeq, $Lexer, $LineCounter, $Parser, $parse, $parseAllDocuments, $parseDocument, $stringify, $visit, $visitAsync, HERMES_OPT_OUT = ".no-bundled-skills", package_default, MAX_PACKAGE_BYTES2, MAX_CREDENTIAL_FILE_BYTES2, INSPECT_CUSTOM2, CREDENTIAL_SEAL2, AMBIENT_ENVIRONMENT2, requireSecretsSdk2, IDEMPOTENT_METHODS2, AUTHORITY_OVERRIDE_HEADERS2, SKILLS_APP = "skills", ENV_KEYS, SKILLS_API_URL_ENV_KEYS, SKILLS_API_KEY_ENV_KEYS, SKILLS_API_URL_ENV, SKILLS_API_KEY_ENV, SkillsFleetCredentialError, MAX_REMOTE_FILE_BYTES, MAX_PROFILE_SELECTIONS = 4096, MAX_PROFILE_DOCUMENT_BYTES, MAX_SKILL_SESSION_ID_CHARS = 256, PROFILE_SESSION_ENVELOPE_BYTES, MAX_RESOLVED_PROFILE_BYTES, BLOCK = 512, ANY_SEGMENT_EXCLUDES, ROOT_EXCLUDES, TOOL_SIDECAR_FILENAMES, CREDENTIAL_FILENAMES, ENV_TEMPLATE_NAMES, NON_DOTENV_EXTENSIONS, SKILL_BUNDLE_INSPECTION_LIMITS, SkillBundleInspectionError, SKILL_VERSION_PATTERN, SkillSelectionError, MAX_CACHED_PROFILE_AGE_MS, CLI_BRIDGE_NAME = "skills-cli", CLI_BRIDGE_VERSION = 1, markdown = `---
|
|
15491
15494
|
name: skills-cli
|
|
15492
15495
|
description: Discover, load, author, and run versioned skills through the Skills CLI when a task needs reusable instructions or an executable skill.
|
|
15493
15496
|
---
|
|
@@ -29705,7 +29708,7 @@ ${end.comment}` : end.comment;
|
|
|
29705
29708
|
mode: "Deployment modes were removed: a Skills client either resolves a credential " + "or it does not, and that is the whole of it.",
|
|
29706
29709
|
apiUrl: "The service address is no longer kept in this app's config file: it is read from " + "HASNA_SKILLS_API_URL, then the macOS Keychain item hasna.credentials.skills.api-url, " + "then ~/.hasna/skills/config/credentials, then the fleet gateway."
|
|
29707
29710
|
};
|
|
29708
|
-
|
|
29711
|
+
PATHS_RESOLVER_APP_SLUG_RE3 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
29709
29712
|
SKILLS = [];
|
|
29710
29713
|
HOSTED_RUNTIMES = new Set(["hosted"]);
|
|
29711
29714
|
HOSTED_SOURCES = new Set(["remote", "private-hosted"]);
|
|
@@ -30032,14 +30035,14 @@ ${end.comment}` : end.comment;
|
|
|
30032
30035
|
url: "https://github.com/hasna/apps/issues"
|
|
30033
30036
|
}
|
|
30034
30037
|
};
|
|
30035
|
-
|
|
30036
|
-
|
|
30037
|
-
|
|
30038
|
-
|
|
30039
|
-
|
|
30040
|
-
|
|
30041
|
-
|
|
30042
|
-
|
|
30038
|
+
MAX_PACKAGE_BYTES2 = 1024 * 1024;
|
|
30039
|
+
MAX_CREDENTIAL_FILE_BYTES2 = 64 * 1024;
|
|
30040
|
+
INSPECT_CUSTOM2 = Symbol.for("nodejs.util.inspect.custom");
|
|
30041
|
+
CREDENTIAL_SEAL2 = Symbol.for("hasna:contracts:sealedCredential");
|
|
30042
|
+
AMBIENT_ENVIRONMENT2 = Symbol.for("hasna:contracts:ambientClientEnvironment");
|
|
30043
|
+
requireSecretsSdk2 = createRequire2(import.meta.url);
|
|
30044
|
+
IDEMPOTENT_METHODS2 = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
|
|
30045
|
+
AUTHORITY_OVERRIDE_HEADERS2 = new Set([
|
|
30043
30046
|
"host",
|
|
30044
30047
|
":authority",
|
|
30045
30048
|
"forwarded",
|
|
@@ -36495,8 +36498,8 @@ __export(exports_installer, {
|
|
|
36495
36498
|
detectRewriteConflict: () => detectRewriteConflict,
|
|
36496
36499
|
buildCodewithTomlFragment: () => buildCodewithTomlFragment
|
|
36497
36500
|
});
|
|
36498
|
-
import { existsSync as
|
|
36499
|
-
import { join as
|
|
36501
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync7, readdirSync as readdirSync4, realpathSync as realpathSync5, rmdirSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
36502
|
+
import { join as join12, dirname as dirname7, basename as basename3, resolve as resolve7 } from "path";
|
|
36500
36503
|
import { homedir as homedir6 } from "os";
|
|
36501
36504
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
36502
36505
|
function normalizeHookName(name) {
|
|
@@ -36544,28 +36547,28 @@ function getSettingsPath(scope = "global", target = "claude", codewithConfigPath
|
|
|
36544
36547
|
return globalOverride;
|
|
36545
36548
|
const dir = getTargetSettingsDir(target);
|
|
36546
36549
|
if (target === "codex")
|
|
36547
|
-
return
|
|
36550
|
+
return join12(scope === "project" ? process.cwd() : homedir6(), dir, "hooks.json");
|
|
36548
36551
|
if (scope === "project") {
|
|
36549
|
-
return target === "codewith" ?
|
|
36552
|
+
return target === "codewith" ? join12(process.cwd(), dir, "config.toml") : join12(process.cwd(), dir, "settings.json");
|
|
36550
36553
|
}
|
|
36551
|
-
return target === "codewith" ?
|
|
36554
|
+
return target === "codewith" ? join12(homedir6(), dir, "config.toml") : join12(homedir6(), dir, "settings.json");
|
|
36552
36555
|
}
|
|
36553
36556
|
function getHookPath(name) {
|
|
36554
36557
|
const shortName = shortHookName(name);
|
|
36555
36558
|
const resolved = resolveHookDir(shortName);
|
|
36556
36559
|
if (resolved)
|
|
36557
36560
|
return resolved;
|
|
36558
|
-
return
|
|
36561
|
+
return join12(HOOKS_DIR, normalizeHookName(shortName));
|
|
36559
36562
|
}
|
|
36560
36563
|
function hookExists(name) {
|
|
36561
|
-
return
|
|
36564
|
+
return existsSync13(getHookPath(name));
|
|
36562
36565
|
}
|
|
36563
36566
|
function readSettings2(scope = "global", target = "claude") {
|
|
36564
36567
|
const path = getSettingsPath(scope, target);
|
|
36565
36568
|
if (target === "codex")
|
|
36566
36569
|
return readCodexSettings(path);
|
|
36567
36570
|
try {
|
|
36568
|
-
if (
|
|
36571
|
+
if (existsSync13(path)) {
|
|
36569
36572
|
const text3 = readFileSync7(path, "utf-8"), value = JSON.parse(text3);
|
|
36570
36573
|
settingsPreimages.set(value, { path, text: text3 });
|
|
36571
36574
|
return value;
|
|
@@ -36574,24 +36577,24 @@ function readSettings2(scope = "global", target = "claude") {
|
|
|
36574
36577
|
console.warn(`[hooks] Failed to read settings at ${path}: ${error61 instanceof Error ? error61.message : String(error61)}`);
|
|
36575
36578
|
}
|
|
36576
36579
|
const empty = {};
|
|
36577
|
-
if (!
|
|
36580
|
+
if (!existsSync13(path))
|
|
36578
36581
|
settingsPreimages.set(empty, { path, text: null });
|
|
36579
36582
|
return empty;
|
|
36580
36583
|
}
|
|
36581
36584
|
function settingsIdentity(path) {
|
|
36582
|
-
if (
|
|
36585
|
+
if (existsSync13(path))
|
|
36583
36586
|
return realpathSync5(path);
|
|
36584
|
-
return
|
|
36587
|
+
return existsSync13(dirname7(path)) ? join12(realpathSync5(dirname7(path)), basename3(path)) : resolve7(path);
|
|
36585
36588
|
}
|
|
36586
36589
|
function writeSettings(settings, scope = "global", target = "claude") {
|
|
36587
36590
|
const path = getSettingsPath(scope, target);
|
|
36588
36591
|
if (target === "codex")
|
|
36589
36592
|
return writeCodexSettings(path, settings);
|
|
36590
|
-
if (target === "claude" && settingsIdentity(path) === settingsIdentity(
|
|
36593
|
+
if (target === "claude" && settingsIdentity(path) === settingsIdentity(join12(homedir6(), ".claude/settings.json"))) {
|
|
36591
36594
|
const before = settingsPreimages.get(settings);
|
|
36592
36595
|
if (!before || before.path !== path)
|
|
36593
36596
|
throw new Error("Claude settings require a valid captured preimage before installation.");
|
|
36594
|
-
const current2 =
|
|
36597
|
+
const current2 = existsSync13(path) ? readFileSync7(path, "utf8") : null;
|
|
36595
36598
|
if (current2 !== before.text)
|
|
36596
36599
|
throw new Error("Claude settings changed during installation; no settings were written.");
|
|
36597
36600
|
if (coordinateClaudeSettings(before.text, JSON.stringify(settings, null, 2) + `
|
|
@@ -36599,7 +36602,7 @@ function writeSettings(settings, scope = "global", target = "claude") {
|
|
|
36599
36602
|
return;
|
|
36600
36603
|
}
|
|
36601
36604
|
const dir = dirname7(path);
|
|
36602
|
-
if (!
|
|
36605
|
+
if (!existsSync13(dir)) {
|
|
36603
36606
|
mkdirSync6(dir, { recursive: true });
|
|
36604
36607
|
}
|
|
36605
36608
|
writeFileSync5(path, JSON.stringify(settings, null, 2) + `
|
|
@@ -36698,7 +36701,7 @@ function buildCodewithTomlFragment(name, profile, mementos) {
|
|
|
36698
36701
|
function readCodewithConfig(scope, configPath) {
|
|
36699
36702
|
const path = getSettingsPath(scope, "codewith", configPath);
|
|
36700
36703
|
try {
|
|
36701
|
-
return
|
|
36704
|
+
return existsSync13(path) ? readFileSync7(path, "utf-8") : "";
|
|
36702
36705
|
} catch {
|
|
36703
36706
|
return "";
|
|
36704
36707
|
}
|
|
@@ -36706,9 +36709,9 @@ function readCodewithConfig(scope, configPath) {
|
|
|
36706
36709
|
function appendCodewithFragment(fragment, scope, configPath) {
|
|
36707
36710
|
const path = getSettingsPath(scope, "codewith", configPath);
|
|
36708
36711
|
const dir = dirname7(path);
|
|
36709
|
-
if (!
|
|
36712
|
+
if (!existsSync13(dir))
|
|
36710
36713
|
mkdirSync6(dir, { recursive: true });
|
|
36711
|
-
const existing =
|
|
36714
|
+
const existing = existsSync13(path) ? readFileSync7(path, "utf-8") : "";
|
|
36712
36715
|
const sep3 = existing.trim() ? `
|
|
36713
36716
|
|
|
36714
36717
|
` : "";
|
|
@@ -37046,7 +37049,7 @@ function removeHookForTarget(name, scope, target) {
|
|
|
37046
37049
|
}
|
|
37047
37050
|
function readSettingsAt(path) {
|
|
37048
37051
|
try {
|
|
37049
|
-
if (!
|
|
37052
|
+
if (!existsSync13(path))
|
|
37050
37053
|
return;
|
|
37051
37054
|
const raw = readFileSync7(path, "utf-8");
|
|
37052
37055
|
const settings = JSON.parse(raw);
|
|
@@ -37172,7 +37175,7 @@ function codewithHasHookEntryInText(configText, name) {
|
|
|
37172
37175
|
}
|
|
37173
37176
|
function codewithHasHookEntry(name, scope = "global") {
|
|
37174
37177
|
const path = getSettingsPath(scope, "codewith");
|
|
37175
|
-
if (!
|
|
37178
|
+
if (!existsSync13(path))
|
|
37176
37179
|
return false;
|
|
37177
37180
|
try {
|
|
37178
37181
|
return codewithHasHookEntryInText(readFileSync7(path, "utf-8"), name);
|
|
@@ -37184,12 +37187,12 @@ function removeStoreDirFailClosed(dir) {
|
|
|
37184
37187
|
const entries = readdirSync4(dir, { withFileTypes: true });
|
|
37185
37188
|
for (const entry of entries) {
|
|
37186
37189
|
if (entry.isDirectory()) {
|
|
37187
|
-
removeStoreDirFailClosed(
|
|
37190
|
+
removeStoreDirFailClosed(join12(dir, entry.name));
|
|
37188
37191
|
}
|
|
37189
37192
|
}
|
|
37190
37193
|
for (const entry of entries) {
|
|
37191
37194
|
if (!entry.isDirectory()) {
|
|
37192
|
-
unlinkSync2(
|
|
37195
|
+
unlinkSync2(join12(dir, entry.name));
|
|
37193
37196
|
}
|
|
37194
37197
|
}
|
|
37195
37198
|
rmdirSync(dir);
|
|
@@ -37220,7 +37223,7 @@ function uninstallHook(name, scope = "global", target = "claude") {
|
|
|
37220
37223
|
const registrationsRemaining = [];
|
|
37221
37224
|
if (target === "codewith" || target === "all") {
|
|
37222
37225
|
const codewithPath = getSettingsPath(scope, "codewith");
|
|
37223
|
-
if (
|
|
37226
|
+
if (existsSync13(codewithPath)) {
|
|
37224
37227
|
const before = readFileSync7(codewithPath, "utf-8");
|
|
37225
37228
|
const after = removeCodewithHookEntry(before, shortName);
|
|
37226
37229
|
if (after.removed) {
|
|
@@ -37271,7 +37274,7 @@ var init_installer = __esm(() => {
|
|
|
37271
37274
|
init_native_safety();
|
|
37272
37275
|
init_claude_skills_coordination();
|
|
37273
37276
|
__dirname2 = dirname7(fileURLToPath2(import.meta.url));
|
|
37274
|
-
HOOKS_DIR =
|
|
37277
|
+
HOOKS_DIR = existsSync13(join12(__dirname2, "..", "..", "hooks", "hook-gitguard")) ? join12(__dirname2, "..", "..", "hooks") : join12(__dirname2, "..", "hooks");
|
|
37275
37278
|
EVENT_MAP = {
|
|
37276
37279
|
claude: {
|
|
37277
37280
|
PreToolUse: "PreToolUse",
|
|
@@ -37385,25 +37388,25 @@ var init_redact = __esm(() => {
|
|
|
37385
37388
|
});
|
|
37386
37389
|
|
|
37387
37390
|
// src/lib/profiles.ts
|
|
37388
|
-
import { existsSync as
|
|
37389
|
-
import { join as
|
|
37391
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync11, readFileSync as readFileSync14, writeFileSync as writeFileSync10, readdirSync as readdirSync8, rmSync, cpSync as cpSync2 } from "fs";
|
|
37392
|
+
import { join as join19 } from "path";
|
|
37390
37393
|
function getProfilesDir() {
|
|
37391
|
-
const newDir =
|
|
37392
|
-
const oldDir =
|
|
37393
|
-
if (!
|
|
37394
|
-
|
|
37394
|
+
const newDir = join19(getEffectiveDataRoot(), "profiles");
|
|
37395
|
+
const oldDir = join19(getHomeDir(), ".hooks", "profiles");
|
|
37396
|
+
if (!existsSync15(newDir) && existsSync15(oldDir)) {
|
|
37397
|
+
mkdirSync11(newDir, { recursive: true });
|
|
37395
37398
|
cpSync2(oldDir, newDir, { recursive: true });
|
|
37396
37399
|
}
|
|
37397
37400
|
return newDir;
|
|
37398
37401
|
}
|
|
37399
37402
|
function ensureProfilesDir() {
|
|
37400
37403
|
const dir = getProfilesDir();
|
|
37401
|
-
if (!
|
|
37402
|
-
|
|
37404
|
+
if (!existsSync15(dir)) {
|
|
37405
|
+
mkdirSync11(dir, { recursive: true });
|
|
37403
37406
|
}
|
|
37404
37407
|
}
|
|
37405
37408
|
function profilePath(id) {
|
|
37406
|
-
return
|
|
37409
|
+
return join19(getProfilesDir(), `${id}.json`);
|
|
37407
37410
|
}
|
|
37408
37411
|
function shortUuid() {
|
|
37409
37412
|
return crypto.randomUUID().slice(0, 8);
|
|
@@ -37422,30 +37425,30 @@ function createProfile(input2) {
|
|
|
37422
37425
|
if (input2.name) {
|
|
37423
37426
|
profile.name = input2.name;
|
|
37424
37427
|
}
|
|
37425
|
-
|
|
37428
|
+
writeFileSync10(profilePath(id), JSON.stringify(profile, null, 2) + `
|
|
37426
37429
|
`);
|
|
37427
37430
|
return profile;
|
|
37428
37431
|
}
|
|
37429
37432
|
function getProfile(id) {
|
|
37430
37433
|
const path = profilePath(id);
|
|
37431
37434
|
try {
|
|
37432
|
-
if (!
|
|
37435
|
+
if (!existsSync15(path))
|
|
37433
37436
|
return null;
|
|
37434
|
-
return JSON.parse(
|
|
37437
|
+
return JSON.parse(readFileSync14(path, "utf-8"));
|
|
37435
37438
|
} catch {
|
|
37436
37439
|
return null;
|
|
37437
37440
|
}
|
|
37438
37441
|
}
|
|
37439
37442
|
function listProfiles() {
|
|
37440
37443
|
const dir = getProfilesDir();
|
|
37441
|
-
if (!
|
|
37444
|
+
if (!existsSync15(dir))
|
|
37442
37445
|
return [];
|
|
37443
37446
|
try {
|
|
37444
37447
|
const files = readdirSync8(dir).filter((f) => f.endsWith(".json"));
|
|
37445
37448
|
const profiles = [];
|
|
37446
37449
|
for (const file2 of files) {
|
|
37447
37450
|
try {
|
|
37448
|
-
const content =
|
|
37451
|
+
const content = readFileSync14(join19(dir, file2), "utf-8");
|
|
37449
37452
|
profiles.push(JSON.parse(content));
|
|
37450
37453
|
} catch {}
|
|
37451
37454
|
}
|
|
@@ -37459,7 +37462,7 @@ function touchProfile(id) {
|
|
|
37459
37462
|
if (!profile)
|
|
37460
37463
|
return;
|
|
37461
37464
|
profile.last_seen_at = new Date().toISOString();
|
|
37462
|
-
|
|
37465
|
+
writeFileSync10(profilePath(id), JSON.stringify(profile, null, 2) + `
|
|
37463
37466
|
`);
|
|
37464
37467
|
}
|
|
37465
37468
|
function exportProfiles() {
|
|
@@ -37475,11 +37478,11 @@ function importProfiles(profiles) {
|
|
|
37475
37478
|
continue;
|
|
37476
37479
|
}
|
|
37477
37480
|
const path = profilePath(profile.agent_id);
|
|
37478
|
-
if (
|
|
37481
|
+
if (existsSync15(path)) {
|
|
37479
37482
|
skipped++;
|
|
37480
37483
|
continue;
|
|
37481
37484
|
}
|
|
37482
|
-
|
|
37485
|
+
writeFileSync10(path, JSON.stringify(profile, null, 2) + `
|
|
37483
37486
|
`);
|
|
37484
37487
|
imported++;
|
|
37485
37488
|
}
|
|
@@ -37489,18 +37492,18 @@ var init_profiles = __esm(() => {
|
|
|
37489
37492
|
init_app_home();
|
|
37490
37493
|
});
|
|
37491
37494
|
|
|
37492
|
-
//
|
|
37493
|
-
import { isIP } from "net";
|
|
37494
|
-
import { spawnSync } from "child_process";
|
|
37495
|
+
// ../../node_modules/.bun/@hasna+contracts@1.2.2+944b86d8024e79fa/node_modules/@hasna/contracts/dist/client/transport.js
|
|
37496
|
+
import { isIP as isIP2 } from "net";
|
|
37497
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
37495
37498
|
import { closeSync as closeSync22, fstatSync as fstatSync22, openSync as openSync22, readFileSync as readFileSync22 } from "fs";
|
|
37496
37499
|
import { O_NOFOLLOW as O_NOFOLLOW2, O_NONBLOCK as O_NONBLOCK2, O_RDONLY as O_RDONLY2 } from "constants";
|
|
37497
37500
|
import { hostname as osHostname } from "os";
|
|
37498
|
-
import { isAbsolute as
|
|
37499
|
-
import { createRequire as
|
|
37501
|
+
import { isAbsolute as isAbsolute17, join as join22 } from "path";
|
|
37502
|
+
import { createRequire as createRequire3 } from "module";
|
|
37500
37503
|
import { fileURLToPath as fileURLToPath22 } from "url";
|
|
37501
|
-
import { closeSync as
|
|
37504
|
+
import { closeSync as closeSync6, fstatSync as fstatSync5, lstatSync as lstatSync10, openSync as openSync6, readFileSync as readFileSync15, realpathSync as realpathSync9, statSync as statSync2 } from "fs";
|
|
37502
37505
|
import { O_NOFOLLOW, O_NONBLOCK, O_RDONLY } from "constants";
|
|
37503
|
-
import { basename as basename5, dirname as
|
|
37506
|
+
import { basename as basename5, dirname as dirname19, join as join20, relative, sep as sep3 } from "path";
|
|
37504
37507
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "url";
|
|
37505
37508
|
function envToken2(name) {
|
|
37506
37509
|
return name.toUpperCase().replace(/-/g, "_");
|
|
@@ -37574,16 +37577,16 @@ function o(e2, r2, t2) {
|
|
|
37574
37577
|
}
|
|
37575
37578
|
}
|
|
37576
37579
|
function entryPoint(directory2) {
|
|
37577
|
-
const root =
|
|
37578
|
-
const fd =
|
|
37580
|
+
const root = realpathSync9(directory2);
|
|
37581
|
+
const fd = openSync6(join20(root, "package.json"), O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
|
|
37579
37582
|
let pkg;
|
|
37580
37583
|
try {
|
|
37581
|
-
const stat2 =
|
|
37582
|
-
if (!stat2.isFile() || stat2.size >
|
|
37584
|
+
const stat2 = fstatSync5(fd);
|
|
37585
|
+
if (!stat2.isFile() || stat2.size > MAX_PACKAGE_BYTES3)
|
|
37583
37586
|
throw new Error("Invalid Secrets package metadata");
|
|
37584
|
-
pkg = JSON.parse(
|
|
37587
|
+
pkg = JSON.parse(readFileSync15(fd, "utf8"));
|
|
37585
37588
|
} finally {
|
|
37586
|
-
|
|
37589
|
+
closeSync6(fd);
|
|
37587
37590
|
}
|
|
37588
37591
|
if (!pkg || typeof pkg !== "object" || !("name" in pkg) || pkg.name !== PACKAGE) {
|
|
37589
37592
|
throw new Error("Invalid Secrets package identity");
|
|
@@ -37595,21 +37598,21 @@ function entryPoint(directory2) {
|
|
|
37595
37598
|
if (/[\\%?#\0]/.test(target) || parts.some((part) => !part || part === "." || part === ".." || part === "node_modules")) {
|
|
37596
37599
|
throw new Error("Invalid Secrets import export");
|
|
37597
37600
|
}
|
|
37598
|
-
const file2 =
|
|
37601
|
+
const file2 = realpathSync9(join20(root, ...parts));
|
|
37599
37602
|
const within = relative(root, file2);
|
|
37600
|
-
if (!within || within === ".." || within.startsWith(`..${sep3}`) || !
|
|
37603
|
+
if (!within || within === ".." || within.startsWith(`..${sep3}`) || !statSync2(file2).isFile()) {
|
|
37601
37604
|
throw new Error("Secrets import export escapes its package or is not a file");
|
|
37602
37605
|
}
|
|
37603
37606
|
return pathToFileURL(file2).href;
|
|
37604
37607
|
}
|
|
37605
37608
|
function resolveInstalledSecrets(parentUrl) {
|
|
37606
|
-
let directory2 =
|
|
37609
|
+
let directory2 = dirname19(fileURLToPath3(parentUrl));
|
|
37607
37610
|
for (;; ) {
|
|
37608
37611
|
if (basename5(directory2) !== "node_modules") {
|
|
37609
|
-
const candidate =
|
|
37612
|
+
const candidate = join20(directory2, "node_modules", PACKAGE);
|
|
37610
37613
|
let absent = false;
|
|
37611
37614
|
try {
|
|
37612
|
-
|
|
37615
|
+
lstatSync10(candidate);
|
|
37613
37616
|
} catch (error61) {
|
|
37614
37617
|
if (error61.code !== "ENOENT")
|
|
37615
37618
|
throw error61;
|
|
@@ -37618,7 +37621,7 @@ function resolveInstalledSecrets(parentUrl) {
|
|
|
37618
37621
|
if (!absent)
|
|
37619
37622
|
return entryPoint(candidate);
|
|
37620
37623
|
}
|
|
37621
|
-
const parent =
|
|
37624
|
+
const parent = dirname19(directory2);
|
|
37622
37625
|
if (parent === directory2)
|
|
37623
37626
|
throw new Error("Secrets SDK is not installed for this consumer");
|
|
37624
37627
|
directory2 = parent;
|
|
@@ -37630,7 +37633,7 @@ function homeDir(env) {
|
|
|
37630
37633
|
}
|
|
37631
37634
|
function absoluteOverride(env, key) {
|
|
37632
37635
|
const value = env[key]?.trim();
|
|
37633
|
-
return value &&
|
|
37636
|
+
return value && isAbsolute17(value) ? value : null;
|
|
37634
37637
|
}
|
|
37635
37638
|
function hasnaHomeDir(env) {
|
|
37636
37639
|
const override = absoluteOverride(env, HASNA_HOME_ENV_KEY);
|
|
@@ -37726,7 +37729,7 @@ function readAppConfigFile(path) {
|
|
|
37726
37729
|
const uid2 = process.getuid?.() ?? process.geteuid?.();
|
|
37727
37730
|
if (uid2 !== undefined && before.uid !== uid2)
|
|
37728
37731
|
unsafe("the file is not owned by the current user");
|
|
37729
|
-
if (before.size >
|
|
37732
|
+
if (before.size > MAX_CREDENTIAL_FILE_BYTES3)
|
|
37730
37733
|
unsafe("the file exceeds the size limit");
|
|
37731
37734
|
const bytes = readFileSync22(fd);
|
|
37732
37735
|
const after = fstatSync22(fd);
|
|
@@ -37818,13 +37821,13 @@ function sealCredential(fields) {
|
|
|
37818
37821
|
configurable: false
|
|
37819
37822
|
});
|
|
37820
37823
|
}
|
|
37821
|
-
Object.defineProperty(sealed,
|
|
37824
|
+
Object.defineProperty(sealed, INSPECT_CUSTOM3, {
|
|
37822
37825
|
value: () => ({ ...visible, apiKey: "[redacted]" }),
|
|
37823
37826
|
enumerable: false,
|
|
37824
37827
|
writable: false,
|
|
37825
37828
|
configurable: false
|
|
37826
37829
|
});
|
|
37827
|
-
Object.defineProperty(sealed,
|
|
37830
|
+
Object.defineProperty(sealed, CREDENTIAL_SEAL3, {
|
|
37828
37831
|
value: true,
|
|
37829
37832
|
enumerable: false,
|
|
37830
37833
|
writable: false,
|
|
@@ -37855,10 +37858,10 @@ function firstEnvValue(env, keys) {
|
|
|
37855
37858
|
return null;
|
|
37856
37859
|
}
|
|
37857
37860
|
function isAmbientEnvironment(env) {
|
|
37858
|
-
return env === process.env || env[
|
|
37861
|
+
return env === process.env || env[AMBIENT_ENVIRONMENT3] === true;
|
|
37859
37862
|
}
|
|
37860
37863
|
function defaultKeychainRunner(argv) {
|
|
37861
|
-
const result =
|
|
37864
|
+
const result = spawnSync2(KEYCHAIN_SECURITY_BIN, [...argv], {
|
|
37862
37865
|
encoding: "utf8",
|
|
37863
37866
|
stdio: ["ignore", "pipe", "pipe"],
|
|
37864
37867
|
timeout: KEYCHAIN_SPAWN_TIMEOUT_MS
|
|
@@ -37949,7 +37952,7 @@ function snapshotClientEnvironment(name, env) {
|
|
|
37949
37952
|
snapshot2[key] = descriptor.value;
|
|
37950
37953
|
}
|
|
37951
37954
|
if (ambient) {
|
|
37952
|
-
Object.defineProperty(snapshot2,
|
|
37955
|
+
Object.defineProperty(snapshot2, AMBIENT_ENVIRONMENT3, {
|
|
37953
37956
|
value: true,
|
|
37954
37957
|
enumerable: false,
|
|
37955
37958
|
writable: false,
|
|
@@ -38122,7 +38125,7 @@ async function completePointerCredential(name, pointerResolution, env = process.
|
|
|
38122
38125
|
let secretsSdk;
|
|
38123
38126
|
try {
|
|
38124
38127
|
const sdkUrl = resolveInstalledSecrets(import.meta.url);
|
|
38125
|
-
secretsSdk =
|
|
38128
|
+
secretsSdk = requireSecretsSdk3(fileURLToPath22(sdkUrl));
|
|
38126
38129
|
} catch {
|
|
38127
38130
|
throw new CredentialResolutionError(name, `${pointerEnvKey} names vault item '${vaultKey}', but the secrets SDK (@hasna/secrets) is not installed ` + `in this process. A vault pointer is TERMINAL: install @hasna/secrets to resolve it, or unset ${pointerEnvKey}.`, [pointerEnvKey]);
|
|
38128
38131
|
}
|
|
@@ -38206,7 +38209,7 @@ function canonicalAuthorityHostname(authority) {
|
|
|
38206
38209
|
}
|
|
38207
38210
|
assertCanonicalPort(portSuffix.slice(1));
|
|
38208
38211
|
}
|
|
38209
|
-
if (
|
|
38212
|
+
if (isIP2(rawHostname.slice(1, -1)) !== 6) {
|
|
38210
38213
|
throw new Error("API URL authority must contain a canonical IPv6 literal.");
|
|
38211
38214
|
}
|
|
38212
38215
|
} else {
|
|
@@ -38222,7 +38225,7 @@ function canonicalAuthorityHostname(authority) {
|
|
|
38222
38225
|
} else {
|
|
38223
38226
|
rawHostname = authority;
|
|
38224
38227
|
}
|
|
38225
|
-
const ipVersion =
|
|
38228
|
+
const ipVersion = isIP2(rawHostname);
|
|
38226
38229
|
const numericAddressParts = rawHostname.split(".");
|
|
38227
38230
|
const looksLikeNonCanonicalIpv4 = numericAddressParts.every((part) => /^(?:0x[0-9a-f]+|[0-9]+)$/i.test(part));
|
|
38228
38231
|
if (ipVersion !== 4 && looksLikeNonCanonicalIpv4 || ipVersion !== 4 && !isValidDnsDomain(rawHostname.toLowerCase())) {
|
|
@@ -38358,9 +38361,9 @@ function credentialDiskSourcesForMessage(name, env) {
|
|
|
38358
38361
|
const paths = credentialDiskSources(name, env);
|
|
38359
38362
|
return paths.length > 0 ? paths.join(" or ") : "<no HOME or HASNA_HOME set in this environment, so no credential file was consulted>";
|
|
38360
38363
|
}
|
|
38361
|
-
var CREDENTIAL_PROFILE_ENV_KEY = "HASNA_PROFILE", PACKAGE = "@hasna/secrets",
|
|
38364
|
+
var CREDENTIAL_PROFILE_ENV_KEY = "HASNA_PROFILE", PACKAGE = "@hasna/secrets", MAX_PACKAGE_BYTES3, CredentialResolutionError, CredentialFileUnsafeError, HASNA_HOME_ENV_KEY = "HASNA_HOME", HASNA_CONFIG_HOME_ENV_KEY = "HASNA_CONFIG_HOME", KEYCHAIN_STATION_ENV_KEY = "HASNA_STATION", HASNA_HOME_DIR = ".hasna", CONFIG_SUBDIR = "config", CREDENTIALS_FILE = "credentials", KEYCHAIN_SECURITY_BIN = "/usr/bin/security", KEYCHAIN_SERVICE_PREFIX = "hasna.credentials", KEYCHAIN_ITEM_NOT_FOUND_STATUS = 44, KEYCHAIN_SPAWN_TIMEOUT_MS = 1e4, MAX_CREDENTIAL_FILE_BYTES3, SAFE_APP_SLUG, SAFE_PROFILE, ILLEGAL_IN_HEADER_VALUE, VAULT_POINTER_SHAPE, CREDENTIAL_SHAPED_KEY, INSPECT_CUSTOM3, CREDENTIAL_SEAL3, AMBIENT_ENVIRONMENT3, requireSecretsSdk3, DEFAULT_FLEET_GATEWAY_ORIGIN = "https://api.hasna.com", DEFAULT_AUTHORITY_SOURCE = "default", ASCII_CONTROL_PATTERN, DNS_LABEL_PATTERN, ClientTransportConfigurationError, IDEMPOTENT_METHODS3, AUTHORITY_OVERRIDE_HEADERS3;
|
|
38362
38365
|
var init_transport = __esm(() => {
|
|
38363
|
-
|
|
38366
|
+
MAX_PACKAGE_BYTES3 = 1024 * 1024;
|
|
38364
38367
|
CredentialResolutionError = class CredentialResolutionError extends Error {
|
|
38365
38368
|
appName;
|
|
38366
38369
|
attempted;
|
|
@@ -38379,16 +38382,16 @@ var init_transport = __esm(() => {
|
|
|
38379
38382
|
this.path = path;
|
|
38380
38383
|
}
|
|
38381
38384
|
};
|
|
38382
|
-
|
|
38385
|
+
MAX_CREDENTIAL_FILE_BYTES3 = 64 * 1024;
|
|
38383
38386
|
SAFE_APP_SLUG = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
38384
38387
|
SAFE_PROFILE = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
|
|
38385
38388
|
ILLEGAL_IN_HEADER_VALUE = /[^\t\x20-\x7e]/;
|
|
38386
38389
|
VAULT_POINTER_SHAPE = /^[a-z0-9][a-z0-9-]*(?:\/[a-z0-9][a-z0-9-_.]*){2,}$/;
|
|
38387
38390
|
CREDENTIAL_SHAPED_KEY = /(?:^|_)(?:API_KEY|KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTH)(?:_|$)/;
|
|
38388
|
-
|
|
38389
|
-
|
|
38390
|
-
|
|
38391
|
-
|
|
38391
|
+
INSPECT_CUSTOM3 = Symbol.for("nodejs.util.inspect.custom");
|
|
38392
|
+
CREDENTIAL_SEAL3 = Symbol.for("hasna:contracts:sealedCredential");
|
|
38393
|
+
AMBIENT_ENVIRONMENT3 = Symbol.for("hasna:contracts:ambientClientEnvironment");
|
|
38394
|
+
requireSecretsSdk3 = createRequire3(import.meta.url);
|
|
38392
38395
|
ASCII_CONTROL_PATTERN = /[\u0000-\u001f\u007f]/;
|
|
38393
38396
|
DNS_LABEL_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
38394
38397
|
ClientTransportConfigurationError = class ClientTransportConfigurationError extends Error {
|
|
@@ -38401,8 +38404,8 @@ var init_transport = __esm(() => {
|
|
|
38401
38404
|
this.sources = Object.freeze([...sources]);
|
|
38402
38405
|
}
|
|
38403
38406
|
};
|
|
38404
|
-
|
|
38405
|
-
|
|
38407
|
+
IDEMPOTENT_METHODS3 = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
|
|
38408
|
+
AUTHORITY_OVERRIDE_HEADERS3 = new Set([
|
|
38406
38409
|
"host",
|
|
38407
38410
|
":authority",
|
|
38408
38411
|
"forwarded",
|
|
@@ -38737,8 +38740,8 @@ var init_db_writer = __esm(() => {
|
|
|
38737
38740
|
});
|
|
38738
38741
|
|
|
38739
38742
|
// src/lib/hook-env.ts
|
|
38740
|
-
import { dirname as
|
|
38741
|
-
import { existsSync as
|
|
38743
|
+
import { dirname as dirname20, isAbsolute as isAbsolute18 } from "path";
|
|
38744
|
+
import { existsSync as existsSync16, realpathSync as realpathSync10, statSync as statSync4 } from "fs";
|
|
38742
38745
|
import { tmpdir } from "os";
|
|
38743
38746
|
function isDeniedEnvName(name) {
|
|
38744
38747
|
const upper = name.toUpperCase();
|
|
@@ -38757,7 +38760,7 @@ function isBashFunctionEnvName(name) {
|
|
|
38757
38760
|
return name.startsWith("BASH_FUNC_");
|
|
38758
38761
|
}
|
|
38759
38762
|
function isUnsafePathEntry(entry, home) {
|
|
38760
|
-
if (entry === "" || !
|
|
38763
|
+
if (entry === "" || !isAbsolute18(entry))
|
|
38761
38764
|
return true;
|
|
38762
38765
|
if (home && (entry === home || entry.startsWith(home + "/")))
|
|
38763
38766
|
return true;
|
|
@@ -38767,7 +38770,7 @@ function isUnsafePathEntry(entry, home) {
|
|
|
38767
38770
|
return true;
|
|
38768
38771
|
const canonical4 = (path) => {
|
|
38769
38772
|
try {
|
|
38770
|
-
return
|
|
38773
|
+
return realpathSync10(path);
|
|
38771
38774
|
} catch {
|
|
38772
38775
|
return path;
|
|
38773
38776
|
}
|
|
@@ -38779,16 +38782,16 @@ function isUnsafePathEntry(entry, home) {
|
|
|
38779
38782
|
return true;
|
|
38780
38783
|
}
|
|
38781
38784
|
try {
|
|
38782
|
-
if ((
|
|
38785
|
+
if ((statSync4(entry).mode & 2) !== 0)
|
|
38783
38786
|
return true;
|
|
38784
38787
|
} catch {}
|
|
38785
38788
|
return false;
|
|
38786
38789
|
}
|
|
38787
38790
|
function sanitizeHookPath(pathValue, home) {
|
|
38788
38791
|
const baseline = PATH_BASELINE.slice();
|
|
38789
|
-
if (process.platform === "darwin" &&
|
|
38792
|
+
if (process.platform === "darwin" && existsSync16("/opt/homebrew/bin"))
|
|
38790
38793
|
baseline.push("/opt/homebrew/bin");
|
|
38791
|
-
const bunDir =
|
|
38794
|
+
const bunDir = dirname20(process.execPath);
|
|
38792
38795
|
const seen = new Set;
|
|
38793
38796
|
const out = [];
|
|
38794
38797
|
for (const dir of baseline) {
|
|
@@ -38912,8 +38915,8 @@ __export(exports_run, {
|
|
|
38912
38915
|
HookTimeoutError: () => HookTimeoutError
|
|
38913
38916
|
});
|
|
38914
38917
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
38915
|
-
import { rmSync as rmSync2, writeFileSync as
|
|
38916
|
-
import { basename as basename9, dirname as
|
|
38918
|
+
import { rmSync as rmSync2, writeFileSync as writeFileSync11 } from "fs";
|
|
38919
|
+
import { basename as basename9, dirname as dirname21, isAbsolute as isAbsolute19, join as join24 } from "path";
|
|
38917
38920
|
function extensionOf(scriptPath) {
|
|
38918
38921
|
const base = basename9(scriptPath);
|
|
38919
38922
|
const dot = base.lastIndexOf(".");
|
|
@@ -38982,9 +38985,9 @@ async function readPipeWithDeadline(stream, deadlineMs) {
|
|
|
38982
38985
|
return chunks.join("");
|
|
38983
38986
|
}
|
|
38984
38987
|
async function executeVerifiedScript(options) {
|
|
38985
|
-
const scriptDir =
|
|
38988
|
+
const scriptDir = dirname21(options.scriptPath);
|
|
38986
38989
|
const interpreter = interpreterFor(options.name, options.scriptPath, options.content);
|
|
38987
|
-
const tempPath =
|
|
38990
|
+
const tempPath = join24(scriptDir, `.hooks-verified-${randomBytes2(12).toString("hex")}.${interpreter.tempExt}`);
|
|
38988
38991
|
let proc = null;
|
|
38989
38992
|
let timedOut = false;
|
|
38990
38993
|
const timeoutTimer = options.timeout ? setTimeout(() => {
|
|
@@ -38992,8 +38995,8 @@ async function executeVerifiedScript(options) {
|
|
|
38992
38995
|
killGroup(proc, { leaderAlive: proc !== null && proc.exitCode === null });
|
|
38993
38996
|
}, options.timeout) : null;
|
|
38994
38997
|
try {
|
|
38995
|
-
|
|
38996
|
-
proc = Bun.spawn([...interpreter.command,
|
|
38998
|
+
writeFileSync11(tempPath, options.content, { flag: "wx", mode: 384 });
|
|
38999
|
+
proc = Bun.spawn([...interpreter.command, isAbsolute19(tempPath) ? tempPath : `./${tempPath}`, ...options.args ?? []], {
|
|
38997
39000
|
stdin: new Response(options.stdin),
|
|
38998
39001
|
stdout: "pipe",
|
|
38999
39002
|
stderr: "pipe",
|
|
@@ -39075,15 +39078,15 @@ __export(exports_custom_install, {
|
|
|
39075
39078
|
installCustomSource: () => installCustomSource,
|
|
39076
39079
|
ensureCustomDir: () => ensureCustomDir
|
|
39077
39080
|
});
|
|
39078
|
-
import { existsSync as
|
|
39079
|
-
import { join as
|
|
39081
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync12, mkdtempSync, readFileSync as readFileSync17, rmSync as rmSync4 } from "fs";
|
|
39082
|
+
import { join as join27 } from "path";
|
|
39080
39083
|
import { tmpdir as tmpdir2 } from "os";
|
|
39081
39084
|
function manifestAt(dir) {
|
|
39082
|
-
const path =
|
|
39083
|
-
if (!
|
|
39085
|
+
const path = join27(dir, "manifest.json");
|
|
39086
|
+
if (!existsSync17(path))
|
|
39084
39087
|
return;
|
|
39085
39088
|
try {
|
|
39086
|
-
return parseManifest(
|
|
39089
|
+
return parseManifest(readFileSync17(path, "utf-8"));
|
|
39087
39090
|
} catch {
|
|
39088
39091
|
return;
|
|
39089
39092
|
}
|
|
@@ -39102,8 +39105,8 @@ function writeFromManifest(manifest, srcDir, source, kind) {
|
|
|
39102
39105
|
return { name, version: manifest.version, kind, source, dir, scriptPath };
|
|
39103
39106
|
}
|
|
39104
39107
|
async function cloneToTemp(gitUrl) {
|
|
39105
|
-
const tmp = mkdtempSync(
|
|
39106
|
-
const proc = Bun.spawn(["git", "clone", "--depth", "1", "--quiet", gitUrl,
|
|
39108
|
+
const tmp = mkdtempSync(join27(tmpdir2(), "hooks-install-"));
|
|
39109
|
+
const proc = Bun.spawn(["git", "clone", "--depth", "1", "--quiet", gitUrl, join27(tmp, "repo")], {
|
|
39107
39110
|
stdout: "pipe",
|
|
39108
39111
|
stderr: "pipe"
|
|
39109
39112
|
});
|
|
@@ -39116,7 +39119,7 @@ async function cloneToTemp(gitUrl) {
|
|
|
39116
39119
|
rmSync4(tmp, { recursive: true, force: true });
|
|
39117
39120
|
throw new Error(`git clone failed (${exitCode}): ${stderr.trim()}`);
|
|
39118
39121
|
}
|
|
39119
|
-
return
|
|
39122
|
+
return join27(tmp, "repo");
|
|
39120
39123
|
}
|
|
39121
39124
|
function isGitUrl(value) {
|
|
39122
39125
|
return value.startsWith("git@") || value.startsWith("ssh://") || value.startsWith("file://") || value.endsWith(".git");
|
|
@@ -39126,7 +39129,7 @@ function isCustomSource(value) {
|
|
|
39126
39129
|
return true;
|
|
39127
39130
|
if (value.startsWith("http://") || value.startsWith("https://"))
|
|
39128
39131
|
return true;
|
|
39129
|
-
return
|
|
39132
|
+
return existsSync17(value);
|
|
39130
39133
|
}
|
|
39131
39134
|
async function installCustomSource(source) {
|
|
39132
39135
|
if (isGitUrl(source)) {
|
|
@@ -39140,11 +39143,11 @@ async function installCustomSource(source) {
|
|
|
39140
39143
|
if (source.startsWith("http://") || source.startsWith("https://")) {
|
|
39141
39144
|
return installManifestUrl(source);
|
|
39142
39145
|
}
|
|
39143
|
-
if (!
|
|
39146
|
+
if (!existsSync17(source)) {
|
|
39144
39147
|
throw new Error(`No such local path: ${source}`);
|
|
39145
39148
|
}
|
|
39146
39149
|
if (source.endsWith("manifest.json")) {
|
|
39147
|
-
const manifest = parseManifest(
|
|
39150
|
+
const manifest = parseManifest(readFileSync17(source, "utf-8"));
|
|
39148
39151
|
return writeFromManifest(manifest, source.substring(0, source.lastIndexOf("/")), source, "local");
|
|
39149
39152
|
}
|
|
39150
39153
|
return copyManifestDir(source, source, "local");
|
|
@@ -39175,7 +39178,7 @@ async function installManifestUrl(url2) {
|
|
|
39175
39178
|
}
|
|
39176
39179
|
function ensureCustomDir() {
|
|
39177
39180
|
const dir = getCustomHooksDir();
|
|
39178
|
-
|
|
39181
|
+
mkdirSync12(dir, { recursive: true });
|
|
39179
39182
|
return dir;
|
|
39180
39183
|
}
|
|
39181
39184
|
var init_custom_install = __esm(() => {
|
|
@@ -39193,13 +39196,13 @@ __export(exports_sync, {
|
|
|
39193
39196
|
fetchPinnedHook: () => fetchPinnedHook,
|
|
39194
39197
|
commitSyncArtifacts: () => commitSyncArtifacts
|
|
39195
39198
|
});
|
|
39196
|
-
import { createHash as
|
|
39197
|
-
import { readFileSync as
|
|
39199
|
+
import { createHash as createHash12 } from "crypto";
|
|
39200
|
+
import { readFileSync as readFileSync19 } from "fs";
|
|
39198
39201
|
function sha256OfText(text3) {
|
|
39199
|
-
return
|
|
39202
|
+
return createHash12("sha256").update(text3).digest("hex");
|
|
39200
39203
|
}
|
|
39201
39204
|
function sha256OfPath(path) {
|
|
39202
|
-
return
|
|
39205
|
+
return createHash12("sha256").update(readFileSync19(path)).digest("hex");
|
|
39203
39206
|
}
|
|
39204
39207
|
function collectBundledCatalog() {
|
|
39205
39208
|
const out = [];
|
|
@@ -40354,13 +40357,13 @@ __export(exports_serve, {
|
|
|
40354
40357
|
SERVE_HOST: () => SERVE_HOST,
|
|
40355
40358
|
DEFAULT_SERVE_PORT: () => DEFAULT_SERVE_PORT
|
|
40356
40359
|
});
|
|
40357
|
-
import { readFileSync as
|
|
40358
|
-
import { dirname as
|
|
40360
|
+
import { readFileSync as readFileSync20 } from "fs";
|
|
40361
|
+
import { dirname as dirname22, join as join29 } from "path";
|
|
40359
40362
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
40360
40363
|
function packageVersion() {
|
|
40361
40364
|
try {
|
|
40362
|
-
const pkgPath =
|
|
40363
|
-
return JSON.parse(
|
|
40365
|
+
const pkgPath = join29(dirname22(fileURLToPath4(import.meta.url)), "..", "package.json");
|
|
40366
|
+
return JSON.parse(readFileSync20(pkgPath, "utf-8")).version || "0.0.0";
|
|
40364
40367
|
} catch {
|
|
40365
40368
|
return "0.0.0";
|
|
40366
40369
|
}
|
|
@@ -40660,17 +40663,17 @@ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
|
40660
40663
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
40661
40664
|
import { z as z3 } from "zod";
|
|
40662
40665
|
import { createServer } from "http";
|
|
40663
|
-
import { existsSync as
|
|
40664
|
-
import { join as
|
|
40666
|
+
import { existsSync as existsSync18, readFileSync as readFileSync21 } from "fs";
|
|
40667
|
+
import { join as join30, dirname as dirname23 } from "path";
|
|
40665
40668
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
40666
40669
|
async function runVerifiedHook(name, stdin, timeoutMs) {
|
|
40667
40670
|
const { resolveHook: resolveHook2, resolveScriptPath: resolveScriptPath2 } = await Promise.resolve().then(() => (init_resolve(), exports_resolve));
|
|
40668
40671
|
const resolved = resolveHook2(name);
|
|
40669
40672
|
const hookScript = resolveScriptPath2(name);
|
|
40670
|
-
if (!resolved || !hookScript || !
|
|
40673
|
+
if (!resolved || !hookScript || !existsSync18(hookScript)) {
|
|
40671
40674
|
throw new Error(`Hook '${name}' not found`);
|
|
40672
40675
|
}
|
|
40673
|
-
const content =
|
|
40676
|
+
const content = readFileSync21(hookScript);
|
|
40674
40677
|
const sha4 = sha256Of(content);
|
|
40675
40678
|
const check2 = checkScriptHash(name, sha4);
|
|
40676
40679
|
if (!check2.ok) {
|
|
@@ -40875,7 +40878,7 @@ function createHooksServer() {
|
|
|
40875
40878
|
const settingsPath = getSettingsPath(scope);
|
|
40876
40879
|
const issues = [];
|
|
40877
40880
|
const healthy = [];
|
|
40878
|
-
const settingsExist =
|
|
40881
|
+
const settingsExist = existsSync18(settingsPath);
|
|
40879
40882
|
if (!settingsExist) {
|
|
40880
40883
|
issues.push({ hook: "(settings)", issue: `${settingsPath} not found`, severity: "warning" });
|
|
40881
40884
|
}
|
|
@@ -40885,7 +40888,7 @@ function createHooksServer() {
|
|
|
40885
40888
|
const meta3 = custom2 ? resolveHookMeta(name) : getHook(name);
|
|
40886
40889
|
let hookHealthy = true;
|
|
40887
40890
|
if (custom2) {
|
|
40888
|
-
if (!
|
|
40891
|
+
if (!existsSync18(custom2.scriptPath)) {
|
|
40889
40892
|
issues.push({ hook: name, issue: `Custom hook script missing: ${custom2.scriptPath}`, severity: "error" });
|
|
40890
40893
|
hookHealthy = false;
|
|
40891
40894
|
}
|
|
@@ -40895,14 +40898,14 @@ function createHooksServer() {
|
|
|
40895
40898
|
continue;
|
|
40896
40899
|
}
|
|
40897
40900
|
const hookDir = getHookPath(name);
|
|
40898
|
-
if (!
|
|
40901
|
+
if (!existsSync18(join30(hookDir, "src", "hook.ts"))) {
|
|
40899
40902
|
issues.push({ hook: name, issue: "Missing src/hook.ts in package", severity: "error" });
|
|
40900
40903
|
hookHealthy = false;
|
|
40901
40904
|
}
|
|
40902
40905
|
}
|
|
40903
40906
|
if (meta3 && settingsExist) {
|
|
40904
40907
|
try {
|
|
40905
|
-
const settings = JSON.parse(
|
|
40908
|
+
const settings = JSON.parse(readFileSync21(settingsPath, "utf-8"));
|
|
40906
40909
|
if (!hookRegisteredInSettings(settings, name, meta3.event, meta3.matcher)) {
|
|
40907
40910
|
const eventName = meta3.event.split(":")[0];
|
|
40908
40911
|
issues.push({ hook: name, issue: `Not registered under correct event (${eventName})`, severity: "error" });
|
|
@@ -40939,10 +40942,10 @@ function createHooksServer() {
|
|
|
40939
40942
|
return { content: [{ type: "text", text: JSON.stringify({ error: `Hook '${name}' not found` }) }] };
|
|
40940
40943
|
}
|
|
40941
40944
|
const hookPath = getHookPath(name);
|
|
40942
|
-
const readmePath =
|
|
40945
|
+
const readmePath = join30(hookPath, "README.md");
|
|
40943
40946
|
let readme = "";
|
|
40944
|
-
if (
|
|
40945
|
-
readme =
|
|
40947
|
+
if (existsSync18(readmePath)) {
|
|
40948
|
+
readme = readFileSync21(readmePath, "utf-8");
|
|
40946
40949
|
}
|
|
40947
40950
|
if (verbose) {
|
|
40948
40951
|
return { content: [{ type: "text", text: JSON.stringify({ ...meta3, readme }) }] };
|
|
@@ -41125,7 +41128,7 @@ function createHooksServer() {
|
|
|
41125
41128
|
const ctx = {
|
|
41126
41129
|
scope,
|
|
41127
41130
|
settings_path: settingsPath,
|
|
41128
|
-
settings_exists:
|
|
41131
|
+
settings_exists: existsSync18(settingsPath),
|
|
41129
41132
|
registered_hooks: hooks,
|
|
41130
41133
|
hook_count: hooks.length,
|
|
41131
41134
|
healthy,
|
|
@@ -41271,8 +41274,8 @@ function createHooksServer() {
|
|
|
41271
41274
|
const settingsPath = getSettingsPath(scope);
|
|
41272
41275
|
let settings = {};
|
|
41273
41276
|
try {
|
|
41274
|
-
if (
|
|
41275
|
-
settings = JSON.parse(
|
|
41277
|
+
if (existsSync18(settingsPath))
|
|
41278
|
+
settings = JSON.parse(readFileSync21(settingsPath, "utf-8"));
|
|
41276
41279
|
} catch {}
|
|
41277
41280
|
if (!settings.hooks)
|
|
41278
41281
|
settings.hooks = {};
|
|
@@ -41294,8 +41297,8 @@ function createHooksServer() {
|
|
|
41294
41297
|
const settingsPath = getSettingsPath(scope);
|
|
41295
41298
|
let settings = {};
|
|
41296
41299
|
try {
|
|
41297
|
-
if (
|
|
41298
|
-
settings = JSON.parse(
|
|
41300
|
+
if (existsSync18(settingsPath))
|
|
41301
|
+
settings = JSON.parse(readFileSync21(settingsPath, "utf-8"));
|
|
41299
41302
|
} catch {}
|
|
41300
41303
|
if (settings.hooks?.__disabled) {
|
|
41301
41304
|
settings.hooks.__disabled = settings.hooks.__disabled.filter((n2) => n2 !== name);
|
|
@@ -41665,13 +41668,13 @@ var init_server = __esm(() => {
|
|
|
41665
41668
|
init_redact();
|
|
41666
41669
|
init_authority();
|
|
41667
41670
|
init_storage();
|
|
41668
|
-
__dirname3 =
|
|
41671
|
+
__dirname3 = dirname23(fileURLToPath5(import.meta.url));
|
|
41669
41672
|
pkg = { name: "@hasna/hooks", version: "0.0.0" };
|
|
41670
41673
|
try {
|
|
41671
41674
|
for (const rel of ["../../package.json", "../package.json", "../../../package.json"]) {
|
|
41672
|
-
const p =
|
|
41673
|
-
if (
|
|
41674
|
-
pkg = JSON.parse(
|
|
41675
|
+
const p = join30(__dirname3, rel);
|
|
41676
|
+
if (existsSync18(p)) {
|
|
41677
|
+
pkg = JSON.parse(readFileSync21(p, "utf-8"));
|
|
41675
41678
|
break;
|
|
41676
41679
|
}
|
|
41677
41680
|
}
|
|
@@ -41739,16 +41742,25 @@ function startMcpHttpServer(options) {
|
|
|
41739
41742
|
var DEFAULT_MCP_HTTP_PORT = 8847, MCP_HTTP_HOST = "127.0.0.1", MCP_SERVICE_NAME = "hooks";
|
|
41740
41743
|
var init_http = () => {};
|
|
41741
41744
|
|
|
41742
|
-
// ../
|
|
41745
|
+
// ../events/dist/commander.js
|
|
41743
41746
|
import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
41744
41747
|
import { Buffer as Buffer2 } from "buffer";
|
|
41748
|
+
import { existsSync as existsSync2 } from "fs";
|
|
41749
|
+
import { join as join2 } from "path";
|
|
41745
41750
|
import { existsSync } from "fs";
|
|
41746
41751
|
import { homedir } from "os";
|
|
41747
|
-
import { join } from "path";
|
|
41752
|
+
import { join, resolve } from "path";
|
|
41753
|
+
import { homedir as pathsResolverHomedir } from "os";
|
|
41754
|
+
import { join as pathsResolverJoin } from "path";
|
|
41748
41755
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
41756
|
+
import { lookup as dnsLookup } from "dns/promises";
|
|
41757
|
+
import { isIP } from "net";
|
|
41749
41758
|
import { randomUUID } from "crypto";
|
|
41750
41759
|
import { spawn } from "child_process";
|
|
41760
|
+
import { request as nodeHttpRequest } from "http";
|
|
41761
|
+
import { request as nodeHttpsRequest } from "https";
|
|
41751
41762
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
41763
|
+
import { createRequire } from "module";
|
|
41752
41764
|
function getPathValue(input, path) {
|
|
41753
41765
|
return path.split(".").reduce((value, part) => {
|
|
41754
41766
|
if (value && typeof value === "object" && part in value) {
|
|
@@ -41844,13 +41856,103 @@ function channelMatchesEvent(channel, event) {
|
|
|
41844
41856
|
return true;
|
|
41845
41857
|
return channel.filters.some((filter) => eventMatchesFilter(event, filter));
|
|
41846
41858
|
}
|
|
41859
|
+
var PATHS_RESOLVER_KIND_ENV = {
|
|
41860
|
+
config: "HASNA_CONFIG_HOME",
|
|
41861
|
+
data: "HASNA_DATA_HOME",
|
|
41862
|
+
state: "HASNA_STATE_HOME",
|
|
41863
|
+
cache: "HASNA_CACHE_HOME"
|
|
41864
|
+
};
|
|
41865
|
+
var PATHS_RESOLVER_APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
41866
|
+
function pathsResolverAssertApp(app) {
|
|
41867
|
+
if (typeof app !== "string" || app.length === 0) {
|
|
41868
|
+
throw new TypeError("paths: app must be a non-empty string");
|
|
41869
|
+
}
|
|
41870
|
+
if (!PATHS_RESOLVER_APP_SLUG_RE.test(app)) {
|
|
41871
|
+
throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
|
|
41872
|
+
}
|
|
41873
|
+
}
|
|
41874
|
+
function pathsResolverAssertKind(kind) {
|
|
41875
|
+
if (!Object.keys(PATHS_RESOLVER_KIND_ENV).includes(kind)) {
|
|
41876
|
+
throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(PATHS_RESOLVER_KIND_ENV).join(", ")}`);
|
|
41877
|
+
}
|
|
41878
|
+
}
|
|
41879
|
+
function pathsResolverBaseDir(kind, options) {
|
|
41880
|
+
pathsResolverAssertKind(kind);
|
|
41881
|
+
const env = options.env ?? process.env;
|
|
41882
|
+
const override = env[PATHS_RESOLVER_KIND_ENV[kind]];
|
|
41883
|
+
if (typeof override === "string" && override.length > 0)
|
|
41884
|
+
return override;
|
|
41885
|
+
const home = options.home ?? pathsResolverHomedir();
|
|
41886
|
+
const platform = options.platform ?? process.platform;
|
|
41887
|
+
if (platform === "darwin") {
|
|
41888
|
+
switch (kind) {
|
|
41889
|
+
case "config":
|
|
41890
|
+
case "data":
|
|
41891
|
+
return pathsResolverJoin(home, "Library", "Application Support", "Hasna");
|
|
41892
|
+
case "cache":
|
|
41893
|
+
return pathsResolverJoin(home, "Library", "Caches", "Hasna");
|
|
41894
|
+
case "state":
|
|
41895
|
+
return pathsResolverJoin(home, "Library", "Logs", "Hasna");
|
|
41896
|
+
}
|
|
41897
|
+
}
|
|
41898
|
+
switch (kind) {
|
|
41899
|
+
case "config":
|
|
41900
|
+
return pathsResolverJoin(home, ".config", "hasna");
|
|
41901
|
+
case "data":
|
|
41902
|
+
return pathsResolverJoin(home, ".local", "share", "hasna");
|
|
41903
|
+
case "state":
|
|
41904
|
+
return pathsResolverJoin(home, ".local", "state", "hasna");
|
|
41905
|
+
case "cache":
|
|
41906
|
+
return pathsResolverJoin(home, ".cache", "hasna");
|
|
41907
|
+
}
|
|
41908
|
+
}
|
|
41909
|
+
function pathsResolverResolve(kind, options) {
|
|
41910
|
+
pathsResolverAssertApp(options.app);
|
|
41911
|
+
const appSegment = options.internal === true ? pathsResolverJoin("internal", options.app) : options.app;
|
|
41912
|
+
return pathsResolverJoin(pathsResolverBaseDir(kind, options), appSegment);
|
|
41913
|
+
}
|
|
41914
|
+
function dataDir(options) {
|
|
41915
|
+
return pathsResolverResolve("data", options);
|
|
41916
|
+
}
|
|
41847
41917
|
var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
|
|
41848
41918
|
var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
|
|
41919
|
+
var EVENTS_STORE_SENTINEL_FILE = "events.json";
|
|
41920
|
+
function effectiveHome() {
|
|
41921
|
+
return process.env["HOME"] || process.env["USERPROFILE"] || homedir();
|
|
41922
|
+
}
|
|
41923
|
+
function legacyHomeDir() {
|
|
41924
|
+
return join(effectiveHome(), ".hasna", "events");
|
|
41925
|
+
}
|
|
41926
|
+
function resolverHome() {
|
|
41927
|
+
return dataDir({ app: "events", home: effectiveHome() || undefined });
|
|
41928
|
+
}
|
|
41929
|
+
function adoptResolverHome(resolved, env = process.env) {
|
|
41930
|
+
const dataOverride = env.HASNA_DATA_HOME;
|
|
41931
|
+
if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
|
|
41932
|
+
return true;
|
|
41933
|
+
return existsSync(join(resolved, EVENTS_STORE_SENTINEL_FILE));
|
|
41934
|
+
}
|
|
41935
|
+
function exactEventsHome() {
|
|
41936
|
+
const dir = process.env[HASNA_EVENTS_DIR_ENV];
|
|
41937
|
+
if (dir && dir.trim())
|
|
41938
|
+
return dir.trim();
|
|
41939
|
+
const home = process.env[HASNA_EVENTS_HOME_ENV];
|
|
41940
|
+
if (home && home.trim())
|
|
41941
|
+
return home.trim();
|
|
41942
|
+
return;
|
|
41943
|
+
}
|
|
41944
|
+
function getEventsHome() {
|
|
41945
|
+
const exact = exactEventsHome();
|
|
41946
|
+
if (exact)
|
|
41947
|
+
return resolve(exact);
|
|
41948
|
+
const resolved = resolverHome();
|
|
41949
|
+
return adoptResolverHome(resolved) ? resolve(resolved) : resolve(legacyHomeDir());
|
|
41950
|
+
}
|
|
41849
41951
|
var LOCAL_JSON_EVENT_CURSOR_PREFIX = "local-json-v1:";
|
|
41850
41952
|
var DEFAULT_EVENT_PAGE_LIMIT = 100;
|
|
41851
41953
|
var MAX_EVENT_PAGE_LIMIT = 1000;
|
|
41852
41954
|
function getEventsDataDir(override) {
|
|
41853
|
-
return override ||
|
|
41955
|
+
return override || getEventsHome();
|
|
41854
41956
|
}
|
|
41855
41957
|
function getActiveEventsDirEnv() {
|
|
41856
41958
|
if (process.env[HASNA_EVENTS_DIR_ENV])
|
|
@@ -41866,12 +41968,12 @@ class JsonEventsStore {
|
|
|
41866
41968
|
channelsPath;
|
|
41867
41969
|
eventsPath;
|
|
41868
41970
|
deliveriesPath;
|
|
41869
|
-
constructor(
|
|
41870
|
-
this.dataDir =
|
|
41871
|
-
this.runtime = localJsonRuntime(
|
|
41872
|
-
this.channelsPath =
|
|
41873
|
-
this.eventsPath =
|
|
41874
|
-
this.deliveriesPath =
|
|
41971
|
+
constructor(dataDir2 = getEventsDataDir()) {
|
|
41972
|
+
this.dataDir = dataDir2;
|
|
41973
|
+
this.runtime = localJsonRuntime(dataDir2);
|
|
41974
|
+
this.channelsPath = join2(dataDir2, "channels.json");
|
|
41975
|
+
this.eventsPath = join2(dataDir2, "events.json");
|
|
41976
|
+
this.deliveriesPath = join2(dataDir2, "deliveries.json");
|
|
41875
41977
|
}
|
|
41876
41978
|
async init() {
|
|
41877
41979
|
await mkdir(this.dataDir, { recursive: true, mode: 448 });
|
|
@@ -41988,7 +42090,7 @@ class JsonEventsStore {
|
|
|
41988
42090
|
};
|
|
41989
42091
|
}
|
|
41990
42092
|
async ensureArrayFile(path) {
|
|
41991
|
-
if (!
|
|
42093
|
+
if (!existsSync2(path)) {
|
|
41992
42094
|
await writeFile(path, `[]
|
|
41993
42095
|
`, { encoding: "utf-8", mode: 384 });
|
|
41994
42096
|
}
|
|
@@ -42018,7 +42120,7 @@ class JsonEventsStore {
|
|
|
42018
42120
|
});
|
|
42019
42121
|
}
|
|
42020
42122
|
}
|
|
42021
|
-
function localJsonRuntime(
|
|
42123
|
+
function localJsonRuntime(dataDir2 = getEventsDataDir()) {
|
|
42022
42124
|
return {
|
|
42023
42125
|
mode: "local-files",
|
|
42024
42126
|
name: "json-events-store",
|
|
@@ -42031,7 +42133,7 @@ function localJsonRuntime(dataDir = getEventsDataDir()) {
|
|
|
42031
42133
|
durable: true,
|
|
42032
42134
|
idempotency: "best-effort-local",
|
|
42033
42135
|
replayCursors: true,
|
|
42034
|
-
description: `Local JSON files in ${
|
|
42136
|
+
description: `Local JSON files in ${dataDir2}; no SQLite, Postgres, S3, or AWS runtime is configured by this store.`
|
|
42035
42137
|
};
|
|
42036
42138
|
}
|
|
42037
42139
|
function encodeLocalJsonEventCursor(offset, options = {}) {
|
|
@@ -42095,8 +42197,8 @@ function assertCursorFilter(name, cursorValue, optionValue) {
|
|
|
42095
42197
|
function findEventByIdentity(events, identity) {
|
|
42096
42198
|
return events.find((event) => identity.id !== undefined && event.id === identity.id || identity.dedupeKey !== undefined && event.dedupeKey === identity.dedupeKey);
|
|
42097
42199
|
}
|
|
42098
|
-
async function getEventsStatus(
|
|
42099
|
-
const store = new JsonEventsStore(
|
|
42200
|
+
async function getEventsStatus(dataDir2) {
|
|
42201
|
+
const store = new JsonEventsStore(dataDir2);
|
|
42100
42202
|
await store.init();
|
|
42101
42203
|
const [channels, events, deliveries] = await Promise.all([
|
|
42102
42204
|
store.listChannels(),
|
|
@@ -42138,9 +42240,9 @@ async function getEventsStatus(dataDir) {
|
|
|
42138
42240
|
}
|
|
42139
42241
|
};
|
|
42140
42242
|
}
|
|
42141
|
-
function statusFile(
|
|
42142
|
-
const path =
|
|
42143
|
-
return { path, exists:
|
|
42243
|
+
function statusFile(dataDir2, fileName, records) {
|
|
42244
|
+
const path = join2(dataDir2, fileName);
|
|
42245
|
+
return { path, exists: existsSync2(path), records };
|
|
42144
42246
|
}
|
|
42145
42247
|
var DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
|
|
42146
42248
|
function buildSignatureBase(timestamp, body) {
|
|
@@ -42150,6 +42252,214 @@ function signPayload(secret, timestamp, body) {
|
|
|
42150
42252
|
const digest = createHmac("sha256", secret).update(buildSignatureBase(timestamp, body)).digest("hex");
|
|
42151
42253
|
return `sha256=${digest}`;
|
|
42152
42254
|
}
|
|
42255
|
+
var DEFAULT_MAX_REDIRECTS = 5;
|
|
42256
|
+
var IPV4_PRIVATE_RANGES = [
|
|
42257
|
+
[0, 16777215],
|
|
42258
|
+
[167772160, 184549375],
|
|
42259
|
+
[1681915904, 1686110207],
|
|
42260
|
+
[2130706432, 2147483647],
|
|
42261
|
+
[2851995648, 2852061183],
|
|
42262
|
+
[2886729728, 2887778303],
|
|
42263
|
+
[3221225472, 3221225727],
|
|
42264
|
+
[3221225984, 3221226239],
|
|
42265
|
+
[3227017984, 3227018239],
|
|
42266
|
+
[3232235520, 3232301055],
|
|
42267
|
+
[3323068416, 3323199487],
|
|
42268
|
+
[3325256704, 3325256959],
|
|
42269
|
+
[3405803776, 3405804031],
|
|
42270
|
+
[3758096384, 4294967295]
|
|
42271
|
+
];
|
|
42272
|
+
var IPV6_SPECIAL_PREFIXES = [
|
|
42273
|
+
{ groups: [0, 0, 0, 0, 0, 0, 0, 0], bits: 128 },
|
|
42274
|
+
{ groups: [0, 0, 0, 0, 0, 0, 0, 1], bits: 128 },
|
|
42275
|
+
{ groups: [0, 0, 0, 0, 0, 65535, 0, 0], bits: 96 },
|
|
42276
|
+
{ groups: [100, 65435, 0, 0, 0, 0, 0, 0], bits: 96 },
|
|
42277
|
+
{ groups: [256, 0, 0, 0, 0, 0, 0, 0], bits: 64 },
|
|
42278
|
+
{ groups: [8193, 0, 0, 0, 0, 0, 0, 0], bits: 32 },
|
|
42279
|
+
{ groups: [8193, 2, 0, 0, 0, 0, 0, 0], bits: 48 },
|
|
42280
|
+
{ groups: [8193, 16, 0, 0, 0, 0, 0, 0], bits: 28 },
|
|
42281
|
+
{ groups: [8193, 3512, 0, 0, 0, 0, 0, 0], bits: 32 },
|
|
42282
|
+
{ groups: [8194, 0, 0, 0, 0, 0, 0, 0], bits: 16 },
|
|
42283
|
+
{ groups: [16383, 0, 0, 0, 0, 0, 0, 0], bits: 20 },
|
|
42284
|
+
{ groups: [64512, 0, 0, 0, 0, 0, 0, 0], bits: 7 },
|
|
42285
|
+
{ groups: [65152, 0, 0, 0, 0, 0, 0, 0], bits: 10 },
|
|
42286
|
+
{ groups: [65216, 0, 0, 0, 0, 0, 0, 0], bits: 10 },
|
|
42287
|
+
{ groups: [65280, 0, 0, 0, 0, 0, 0, 0], bits: 8 }
|
|
42288
|
+
];
|
|
42289
|
+
function isPrivateAddress(address) {
|
|
42290
|
+
const normalized = stripZoneId(address);
|
|
42291
|
+
const version = isIP(normalized);
|
|
42292
|
+
if (version === 4) {
|
|
42293
|
+
const integer = ipv4ToInt(normalized);
|
|
42294
|
+
if (integer === undefined)
|
|
42295
|
+
return true;
|
|
42296
|
+
return IPV4_PRIVATE_RANGES.some(([low, high]) => integer >= low && integer <= high);
|
|
42297
|
+
}
|
|
42298
|
+
if (version === 6) {
|
|
42299
|
+
const groups = ipv6Groups(normalized);
|
|
42300
|
+
if (!groups)
|
|
42301
|
+
return true;
|
|
42302
|
+
for (const prefix of IPV6_SPECIAL_PREFIXES) {
|
|
42303
|
+
if (!ipv6MatchesPrefix(groups, prefix.groups, prefix.bits))
|
|
42304
|
+
continue;
|
|
42305
|
+
if (prefix.bits === 96 && groups[5] === 65535) {
|
|
42306
|
+
return isPrivateAddress(ipv4IntToString(groups[6] << 16 | groups[7]));
|
|
42307
|
+
}
|
|
42308
|
+
if (prefix.bits === 16 && groups[0] === 8194) {
|
|
42309
|
+
return isPrivateAddress(ipv4IntToString(groups[1] << 16 | groups[2]));
|
|
42310
|
+
}
|
|
42311
|
+
return true;
|
|
42312
|
+
}
|
|
42313
|
+
return false;
|
|
42314
|
+
}
|
|
42315
|
+
return true;
|
|
42316
|
+
}
|
|
42317
|
+
async function resolveWebhookTarget(url, policy = {}) {
|
|
42318
|
+
const hostname = normalizeHostname(url.hostname);
|
|
42319
|
+
const allowlist = (policy.allowPrivateHosts ?? []).map((entry) => normalizeHostname(entry.toLowerCase()));
|
|
42320
|
+
if (allowlist.includes(hostname)) {
|
|
42321
|
+
const version2 = isIP(hostname);
|
|
42322
|
+
if (version2 === 4 || version2 === 6) {
|
|
42323
|
+
return { hostname, addresses: [hostname] };
|
|
42324
|
+
}
|
|
42325
|
+
const lookup2 = policy.lookup ?? defaultTargetLookup;
|
|
42326
|
+
let resolved2;
|
|
42327
|
+
try {
|
|
42328
|
+
resolved2 = await lookup2(hostname);
|
|
42329
|
+
} catch {
|
|
42330
|
+
throw new Error(`Webhook target ${hostname} could not be resolved`);
|
|
42331
|
+
}
|
|
42332
|
+
if (!Array.isArray(resolved2) || resolved2.length === 0) {
|
|
42333
|
+
throw new Error(`Webhook target ${hostname} resolved to no addresses`);
|
|
42334
|
+
}
|
|
42335
|
+
const addresses = resolved2.map((entry) => normalizeHostname(entry.address));
|
|
42336
|
+
return { hostname, addresses };
|
|
42337
|
+
}
|
|
42338
|
+
const version = isIP(hostname);
|
|
42339
|
+
if (version === 4 || version === 6) {
|
|
42340
|
+
if (isPrivateAddress(hostname)) {
|
|
42341
|
+
throw new Error(`Webhook target ${hostname} is a private or special-use address`);
|
|
42342
|
+
}
|
|
42343
|
+
return { hostname, addresses: [hostname] };
|
|
42344
|
+
}
|
|
42345
|
+
const lookup = policy.lookup ?? defaultTargetLookup;
|
|
42346
|
+
let resolved;
|
|
42347
|
+
try {
|
|
42348
|
+
resolved = await lookup(hostname);
|
|
42349
|
+
} catch {
|
|
42350
|
+
throw new Error(`Webhook target ${hostname} could not be resolved`);
|
|
42351
|
+
}
|
|
42352
|
+
if (!Array.isArray(resolved) || resolved.length === 0) {
|
|
42353
|
+
throw new Error(`Webhook target ${hostname} resolved to no addresses`);
|
|
42354
|
+
}
|
|
42355
|
+
const allowed = [];
|
|
42356
|
+
for (const entry of resolved) {
|
|
42357
|
+
const address = normalizeHostname(entry.address);
|
|
42358
|
+
if (isPrivateAddress(address)) {
|
|
42359
|
+
if (allowlist.includes(address)) {
|
|
42360
|
+
allowed.push(address);
|
|
42361
|
+
continue;
|
|
42362
|
+
}
|
|
42363
|
+
throw new Error(`Webhook target ${hostname} resolves to private or special-use address ${address}`);
|
|
42364
|
+
}
|
|
42365
|
+
allowed.push(address);
|
|
42366
|
+
}
|
|
42367
|
+
if (allowed.length === 0) {
|
|
42368
|
+
throw new Error(`Webhook target ${hostname} resolved to no public addresses`);
|
|
42369
|
+
}
|
|
42370
|
+
return { hostname, addresses: allowed };
|
|
42371
|
+
}
|
|
42372
|
+
function normalizeMaxRedirects(value) {
|
|
42373
|
+
if (value === undefined)
|
|
42374
|
+
return DEFAULT_MAX_REDIRECTS;
|
|
42375
|
+
if (!Number.isInteger(value) || value < 0)
|
|
42376
|
+
throw new Error("webhookTargetPolicy.maxRedirects must be a non-negative integer");
|
|
42377
|
+
return value;
|
|
42378
|
+
}
|
|
42379
|
+
var defaultTargetLookup = async (hostname) => {
|
|
42380
|
+
return dnsLookup(hostname, { all: true, verbatim: false });
|
|
42381
|
+
};
|
|
42382
|
+
function normalizeHostname(hostname) {
|
|
42383
|
+
const lower = hostname.toLowerCase();
|
|
42384
|
+
if (lower.startsWith("[") && lower.endsWith("]"))
|
|
42385
|
+
return lower.slice(1, -1);
|
|
42386
|
+
return lower;
|
|
42387
|
+
}
|
|
42388
|
+
function stripZoneId(address) {
|
|
42389
|
+
const percent = address.indexOf("%");
|
|
42390
|
+
return percent === -1 ? address : address.slice(0, percent);
|
|
42391
|
+
}
|
|
42392
|
+
function ipv4ToInt(address) {
|
|
42393
|
+
const parts = address.split(".");
|
|
42394
|
+
if (parts.length !== 4)
|
|
42395
|
+
return;
|
|
42396
|
+
let value = 0;
|
|
42397
|
+
for (const part of parts) {
|
|
42398
|
+
if (!/^\d{1,3}$/.test(part))
|
|
42399
|
+
return;
|
|
42400
|
+
const octet = Number(part);
|
|
42401
|
+
if (octet > 255)
|
|
42402
|
+
return;
|
|
42403
|
+
value = value << 8 | octet;
|
|
42404
|
+
}
|
|
42405
|
+
return value >>> 0;
|
|
42406
|
+
}
|
|
42407
|
+
function ipv4IntToString(integer) {
|
|
42408
|
+
return [
|
|
42409
|
+
integer >>> 24 & 255,
|
|
42410
|
+
integer >>> 16 & 255,
|
|
42411
|
+
integer >>> 8 & 255,
|
|
42412
|
+
integer & 255
|
|
42413
|
+
].join(".");
|
|
42414
|
+
}
|
|
42415
|
+
function ipv6Groups(address) {
|
|
42416
|
+
const raw = stripZoneId(address);
|
|
42417
|
+
const doubleColon = raw.indexOf("::");
|
|
42418
|
+
const headText = doubleColon === -1 ? raw : raw.slice(0, doubleColon);
|
|
42419
|
+
const tailText = doubleColon === -1 ? "" : raw.slice(doubleColon + 2);
|
|
42420
|
+
const parseGroups = (text) => {
|
|
42421
|
+
if (text === "")
|
|
42422
|
+
return [];
|
|
42423
|
+
const out = [];
|
|
42424
|
+
for (const part of text.split(":")) {
|
|
42425
|
+
if (part.includes(".")) {
|
|
42426
|
+
const v4 = ipv4ToInt(part);
|
|
42427
|
+
if (v4 === undefined)
|
|
42428
|
+
return;
|
|
42429
|
+
out.push(v4 >>> 16 & 65535, v4 & 65535);
|
|
42430
|
+
} else {
|
|
42431
|
+
if (!/^[0-9a-fA-F]{1,4}$/.test(part))
|
|
42432
|
+
return;
|
|
42433
|
+
out.push(parseInt(part, 16));
|
|
42434
|
+
}
|
|
42435
|
+
}
|
|
42436
|
+
return out;
|
|
42437
|
+
};
|
|
42438
|
+
const head = parseGroups(headText);
|
|
42439
|
+
if (!head)
|
|
42440
|
+
return;
|
|
42441
|
+
const tail = parseGroups(tailText);
|
|
42442
|
+
if (!tail)
|
|
42443
|
+
return;
|
|
42444
|
+
const total = head.length + tail.length;
|
|
42445
|
+
if (doubleColon === -1) {
|
|
42446
|
+
return total === 8 ? head : undefined;
|
|
42447
|
+
}
|
|
42448
|
+
if (total >= 8)
|
|
42449
|
+
return;
|
|
42450
|
+
return [...head, ...new Array(8 - total).fill(0), ...tail];
|
|
42451
|
+
}
|
|
42452
|
+
function ipv6MatchesPrefix(groups, prefixGroups, prefixBits) {
|
|
42453
|
+
let remaining = prefixBits;
|
|
42454
|
+
for (let index = 0;index < prefixGroups.length && remaining > 0; index += 1) {
|
|
42455
|
+
const take = Math.min(16, remaining);
|
|
42456
|
+
const mask = 65535 << 16 - take & 65535;
|
|
42457
|
+
if ((groups[index] & mask) !== (prefixGroups[index] & mask))
|
|
42458
|
+
return false;
|
|
42459
|
+
remaining -= take;
|
|
42460
|
+
}
|
|
42461
|
+
return true;
|
|
42462
|
+
}
|
|
42153
42463
|
function now() {
|
|
42154
42464
|
return new Date().toISOString();
|
|
42155
42465
|
}
|
|
@@ -42180,9 +42490,18 @@ function buildWebhookRequest(event, channel, options = {}) {
|
|
|
42180
42490
|
}
|
|
42181
42491
|
return { body, headers };
|
|
42182
42492
|
}
|
|
42493
|
+
function normalizeWebhookUrl(raw) {
|
|
42494
|
+
const url = new URL(raw);
|
|
42495
|
+
if (url.username !== "" || url.password !== "") {
|
|
42496
|
+
url.username = "";
|
|
42497
|
+
url.password = "";
|
|
42498
|
+
}
|
|
42499
|
+
return url.toString();
|
|
42500
|
+
}
|
|
42183
42501
|
async function dispatchWebhook(event, channel, options = {}) {
|
|
42184
42502
|
if (!channel.webhook)
|
|
42185
42503
|
throw new Error(`Channel ${channel.id} has no webhook config`);
|
|
42504
|
+
const webhookUrl = normalizeWebhookUrl(channel.webhook.url);
|
|
42186
42505
|
const startedAt = now();
|
|
42187
42506
|
let secret = channel.webhook.secret;
|
|
42188
42507
|
if (channel.webhook.secretRef) {
|
|
@@ -42199,10 +42518,14 @@ async function dispatchWebhook(event, channel, options = {}) {
|
|
|
42199
42518
|
}
|
|
42200
42519
|
const timestamp = (options.now?.() ?? new Date).toISOString();
|
|
42201
42520
|
const { body, headers } = buildWebhookRequest(event, channel, { secret, timestamp });
|
|
42521
|
+
const validateTargets = options.webhookTargetPolicy !== undefined || options.fetchImpl === undefined;
|
|
42522
|
+
if (validateTargets) {
|
|
42523
|
+
return dispatchValidatedWebhook(event, channel, { body, headers, startedAt, options });
|
|
42524
|
+
}
|
|
42202
42525
|
const controller = new AbortController;
|
|
42203
42526
|
const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
|
|
42204
42527
|
try {
|
|
42205
|
-
const response = await (options.fetchImpl ?? fetch)(
|
|
42528
|
+
const response = await (options.fetchImpl ?? fetch)(webhookUrl, {
|
|
42206
42529
|
method: "POST",
|
|
42207
42530
|
headers,
|
|
42208
42531
|
body,
|
|
@@ -42230,6 +42553,130 @@ async function dispatchWebhook(event, channel, options = {}) {
|
|
|
42230
42553
|
clearTimeout(timeout);
|
|
42231
42554
|
}
|
|
42232
42555
|
}
|
|
42556
|
+
function isRedirectStatus(status) {
|
|
42557
|
+
return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
|
|
42558
|
+
}
|
|
42559
|
+
function redirectKeepsBody(status) {
|
|
42560
|
+
return status === 307 || status === 308;
|
|
42561
|
+
}
|
|
42562
|
+
async function pinnedNativeRequest(target, addresses, method, headers, body, signal, tls) {
|
|
42563
|
+
const isHttps = target.protocol === "https:";
|
|
42564
|
+
if (!isHttps && target.protocol !== "http:") {
|
|
42565
|
+
throw new Error(`Webhook target uses unsupported protocol ${target.protocol}`);
|
|
42566
|
+
}
|
|
42567
|
+
const defaultPort = isHttps ? 443 : 80;
|
|
42568
|
+
const port = target.port ? Number(target.port) : defaultPort;
|
|
42569
|
+
const requestOptions = {
|
|
42570
|
+
hostname: target.hostname,
|
|
42571
|
+
port,
|
|
42572
|
+
path: `${target.pathname}${target.search}`,
|
|
42573
|
+
method,
|
|
42574
|
+
headers,
|
|
42575
|
+
...tls?.ca ? { ca: tls.ca } : {},
|
|
42576
|
+
lookup: (hostname, _options, callback) => {
|
|
42577
|
+
const entries = addresses.map((address) => ({
|
|
42578
|
+
address,
|
|
42579
|
+
family: address.includes(":") ? 6 : 4
|
|
42580
|
+
}));
|
|
42581
|
+
callback(null, entries);
|
|
42582
|
+
}
|
|
42583
|
+
};
|
|
42584
|
+
return new Promise((resolve2, reject) => {
|
|
42585
|
+
const request = isHttps ? nodeHttpsRequest(requestOptions, onResponse) : nodeHttpRequest(requestOptions, onResponse);
|
|
42586
|
+
const onAbort = () => {
|
|
42587
|
+
const error = new Error("The operation was aborted.");
|
|
42588
|
+
error.name = "AbortError";
|
|
42589
|
+
request.destroy(error);
|
|
42590
|
+
};
|
|
42591
|
+
if (signal.aborted)
|
|
42592
|
+
onAbort();
|
|
42593
|
+
else
|
|
42594
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
42595
|
+
request.on("error", reject);
|
|
42596
|
+
if (body !== undefined)
|
|
42597
|
+
request.write(body);
|
|
42598
|
+
request.end();
|
|
42599
|
+
function onResponse(response) {
|
|
42600
|
+
const chunks = [];
|
|
42601
|
+
response.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
|
|
42602
|
+
response.on("error", reject);
|
|
42603
|
+
response.on("end", () => {
|
|
42604
|
+
const headersRecord = {};
|
|
42605
|
+
for (const [name, value] of Object.entries(response.headers)) {
|
|
42606
|
+
if (typeof value === "string")
|
|
42607
|
+
headersRecord[name] = value;
|
|
42608
|
+
else if (Array.isArray(value))
|
|
42609
|
+
headersRecord[name] = value.join(", ");
|
|
42610
|
+
}
|
|
42611
|
+
resolve2(new Response(Buffer.concat(chunks), { status: response.statusCode ?? 200, headers: headersRecord }));
|
|
42612
|
+
});
|
|
42613
|
+
}
|
|
42614
|
+
});
|
|
42615
|
+
}
|
|
42616
|
+
async function dispatchValidatedWebhook(event, channel, input) {
|
|
42617
|
+
const { body, headers, startedAt, options } = input;
|
|
42618
|
+
const webhook = channel.webhook;
|
|
42619
|
+
if (!webhook)
|
|
42620
|
+
throw new Error(`Channel ${channel.id} has no webhook config`);
|
|
42621
|
+
const policy = options.webhookTargetPolicy ?? {};
|
|
42622
|
+
const maxRedirects = normalizeMaxRedirects(policy.maxRedirects);
|
|
42623
|
+
const controller = new AbortController;
|
|
42624
|
+
const timeout = setTimeout(() => controller.abort(), webhook.timeoutMs ?? 15000);
|
|
42625
|
+
try {
|
|
42626
|
+
let target = new URL(normalizeWebhookUrl(webhook.url));
|
|
42627
|
+
let requestHeaders = headers;
|
|
42628
|
+
let method = "POST";
|
|
42629
|
+
let requestBody = body;
|
|
42630
|
+
let redirectsFollowed = 0;
|
|
42631
|
+
for (;; ) {
|
|
42632
|
+
const resolved = await resolveWebhookTarget(target, policy).catch((error) => {
|
|
42633
|
+
throw new Error(`Webhook target rejected by SSRF guard: ${error.message}`);
|
|
42634
|
+
});
|
|
42635
|
+
const response = options.fetchImpl ? await options.fetchImpl(target, {
|
|
42636
|
+
method,
|
|
42637
|
+
headers: requestHeaders,
|
|
42638
|
+
body: requestBody,
|
|
42639
|
+
signal: controller.signal,
|
|
42640
|
+
redirect: "manual"
|
|
42641
|
+
}) : await pinnedNativeRequest(target, resolved.addresses, method, requestHeaders, requestBody, controller.signal, options.tls);
|
|
42642
|
+
const location = response.headers.get("location");
|
|
42643
|
+
if (isRedirectStatus(response.status) && location) {
|
|
42644
|
+
if (redirectsFollowed >= maxRedirects) {
|
|
42645
|
+
return failedAttempt(startedAt, `Webhook target exceeded ${maxRedirects} redirects`);
|
|
42646
|
+
}
|
|
42647
|
+
redirectsFollowed += 1;
|
|
42648
|
+
const next = new URL(location, target);
|
|
42649
|
+
target = next;
|
|
42650
|
+
if (!redirectKeepsBody(response.status)) {
|
|
42651
|
+
method = "GET";
|
|
42652
|
+
requestBody = undefined;
|
|
42653
|
+
requestHeaders = Object.fromEntries(Object.entries(requestHeaders).filter(([name]) => name.toLowerCase() !== "content-type" && name.toLowerCase() !== "content-length"));
|
|
42654
|
+
}
|
|
42655
|
+
continue;
|
|
42656
|
+
}
|
|
42657
|
+
const responseBody = truncate(await response.text());
|
|
42658
|
+
return {
|
|
42659
|
+
attempt: 1,
|
|
42660
|
+
status: response.ok ? "success" : "failed",
|
|
42661
|
+
startedAt,
|
|
42662
|
+
completedAt: now(),
|
|
42663
|
+
responseStatus: response.status,
|
|
42664
|
+
responseBody,
|
|
42665
|
+
error: response.ok ? undefined : `Webhook returned HTTP ${response.status}`
|
|
42666
|
+
};
|
|
42667
|
+
}
|
|
42668
|
+
} catch (error) {
|
|
42669
|
+
return {
|
|
42670
|
+
attempt: 1,
|
|
42671
|
+
status: "failed",
|
|
42672
|
+
startedAt,
|
|
42673
|
+
completedAt: now(),
|
|
42674
|
+
error: error instanceof Error ? error.message : String(error)
|
|
42675
|
+
};
|
|
42676
|
+
} finally {
|
|
42677
|
+
clearTimeout(timeout);
|
|
42678
|
+
}
|
|
42679
|
+
}
|
|
42233
42680
|
function failedAttempt(startedAt, error) {
|
|
42234
42681
|
return {
|
|
42235
42682
|
attempt: 1,
|
|
@@ -42258,7 +42705,7 @@ async function dispatchCommand(event, channel) {
|
|
|
42258
42705
|
HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
|
|
42259
42706
|
HASNA_EVENT_JSON: eventJson
|
|
42260
42707
|
};
|
|
42261
|
-
return new Promise((
|
|
42708
|
+
return new Promise((resolve2) => {
|
|
42262
42709
|
const child = spawn(channel.command.command, channel.command.args ?? [], {
|
|
42263
42710
|
cwd: channel.command.cwd,
|
|
42264
42711
|
env,
|
|
@@ -42276,7 +42723,7 @@ async function dispatchCommand(event, channel) {
|
|
|
42276
42723
|
});
|
|
42277
42724
|
child.on("error", (error) => {
|
|
42278
42725
|
clearTimeout(timeout);
|
|
42279
|
-
|
|
42726
|
+
resolve2({
|
|
42280
42727
|
attempt: 1,
|
|
42281
42728
|
status: "failed",
|
|
42282
42729
|
startedAt,
|
|
@@ -42289,7 +42736,7 @@ async function dispatchCommand(event, channel) {
|
|
|
42289
42736
|
child.on("close", (code, signal) => {
|
|
42290
42737
|
clearTimeout(timeout);
|
|
42291
42738
|
const success = code === 0;
|
|
42292
|
-
|
|
42739
|
+
resolve2({
|
|
42293
42740
|
attempt: 1,
|
|
42294
42741
|
status: success ? "success" : "failed",
|
|
42295
42742
|
startedAt,
|
|
@@ -42411,6 +42858,22 @@ function setPath(input, path, replacement) {
|
|
|
42411
42858
|
if (last && last in cursor)
|
|
42412
42859
|
cursor[last] = replacement;
|
|
42413
42860
|
}
|
|
42861
|
+
var MAX_PACKAGE_BYTES = 1024 * 1024;
|
|
42862
|
+
var MAX_CREDENTIAL_FILE_BYTES = 64 * 1024;
|
|
42863
|
+
var INSPECT_CUSTOM = Symbol.for("nodejs.util.inspect.custom");
|
|
42864
|
+
var CREDENTIAL_SEAL = Symbol.for("hasna:contracts:sealedCredential");
|
|
42865
|
+
var AMBIENT_ENVIRONMENT = Symbol.for("hasna:contracts:ambientClientEnvironment");
|
|
42866
|
+
var requireSecretsSdk = createRequire(import.meta.url);
|
|
42867
|
+
var IDEMPOTENT_METHODS = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
|
|
42868
|
+
var AUTHORITY_OVERRIDE_HEADERS = new Set([
|
|
42869
|
+
"host",
|
|
42870
|
+
":authority",
|
|
42871
|
+
"forwarded",
|
|
42872
|
+
"x-forwarded-host",
|
|
42873
|
+
"x-original-host"
|
|
42874
|
+
]);
|
|
42875
|
+
var MAX_ENVELOPE_BYTES = 256 * 1024;
|
|
42876
|
+
var MAX_REQUEST_BYTES = MAX_ENVELOPE_BYTES * 2 + 8192;
|
|
42414
42877
|
function createEvent(input) {
|
|
42415
42878
|
return {
|
|
42416
42879
|
id: input.id ?? randomUUID2(),
|
|
@@ -42439,7 +42902,9 @@ class EventsClient {
|
|
|
42439
42902
|
this.transportOptions = {
|
|
42440
42903
|
fetchImpl: options.fetchImpl,
|
|
42441
42904
|
secretResolver: options.secretResolver,
|
|
42442
|
-
now: options.now
|
|
42905
|
+
now: options.now,
|
|
42906
|
+
tls: options.tls,
|
|
42907
|
+
webhookTargetPolicy: options.webhookTargetPolicy
|
|
42443
42908
|
};
|
|
42444
42909
|
this.catalog = options.catalog ?? defaultEventTypeCatalog;
|
|
42445
42910
|
this.validateCatalogTypes = options.validateCatalogTypes ?? false;
|
|
@@ -42726,6 +43191,13 @@ function parseMatcherExpression(value, label) {
|
|
|
42726
43191
|
negated: false
|
|
42727
43192
|
};
|
|
42728
43193
|
}
|
|
43194
|
+
function webhookTargetPolicyFromEnv() {
|
|
43195
|
+
const value = process.env.HASNA_EVENTS_ALLOW_PRIVATE_WEBHOOK_TARGETS;
|
|
43196
|
+
if (!value)
|
|
43197
|
+
return;
|
|
43198
|
+
const hosts = value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
43199
|
+
return hosts.length > 0 ? { allowPrivateHosts: hosts } : undefined;
|
|
43200
|
+
}
|
|
42729
43201
|
var DEFAULT_EVENT_LIST_LIMIT = 100;
|
|
42730
43202
|
function parseJsonObject(value, fallback) {
|
|
42731
43203
|
if (!value)
|
|
@@ -42751,7 +43223,7 @@ function parseHeaders(values) {
|
|
|
42751
43223
|
function createClient(options) {
|
|
42752
43224
|
if (options.createClient)
|
|
42753
43225
|
return options.createClient();
|
|
42754
|
-
return new EventsClient({ store: new JsonEventsStore(options.dataDir) });
|
|
43226
|
+
return new EventsClient({ store: new JsonEventsStore(options.dataDir), webhookTargetPolicy: webhookTargetPolicyFromEnv() });
|
|
42755
43227
|
}
|
|
42756
43228
|
function print(value, json, text) {
|
|
42757
43229
|
if (json)
|
|
@@ -42832,6 +43304,8 @@ function registerChannelCommands(program, options) {
|
|
|
42832
43304
|
metadata: parseJsonObject(actionOptions.metadata, {})
|
|
42833
43305
|
}, { honorFilters: actionOptions.honorFilters });
|
|
42834
43306
|
print(result, json, `${result.status}: ${result.channelId}`);
|
|
43307
|
+
if (result.status === "failed")
|
|
43308
|
+
process.exitCode = 1;
|
|
42835
43309
|
} catch (error) {
|
|
42836
43310
|
fail(error, json);
|
|
42837
43311
|
}
|
|
@@ -42924,7 +43398,7 @@ function replaySummary(events, deliveries, nextCursor) {
|
|
|
42924
43398
|
// src/cli/index.tsx
|
|
42925
43399
|
import { render } from "ink";
|
|
42926
43400
|
|
|
42927
|
-
//
|
|
43401
|
+
// ../../node_modules/.bun/commander@12.1.0/node_modules/commander/esm.mjs
|
|
42928
43402
|
var import__ = __toESM(require_commander(), 1);
|
|
42929
43403
|
var {
|
|
42930
43404
|
program,
|
|
@@ -42942,19 +43416,19 @@ var {
|
|
|
42942
43416
|
|
|
42943
43417
|
// src/cli/index.tsx
|
|
42944
43418
|
import chalk2 from "chalk";
|
|
42945
|
-
import { existsSync as
|
|
42946
|
-
import { join as
|
|
43419
|
+
import { existsSync as existsSync19, readFileSync as readFileSync23, rmSync as rmSync6 } from "fs";
|
|
43420
|
+
import { join as join31, dirname as dirname24 } from "path";
|
|
42947
43421
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
42948
43422
|
|
|
42949
43423
|
// src/cli/components/App.tsx
|
|
42950
43424
|
import { useState as useState7 } from "react";
|
|
42951
43425
|
import { Box as Box8, Text as Text10, useApp, useInput as useInput5 } from "ink";
|
|
42952
43426
|
|
|
42953
|
-
//
|
|
43427
|
+
// ../../node_modules/.bun/ink-select-input@6.2.0+fa875fef9f949ebd/node_modules/ink-select-input/build/Indicator.js
|
|
42954
43428
|
import React from "react";
|
|
42955
43429
|
import { Box, Text } from "ink";
|
|
42956
43430
|
|
|
42957
|
-
//
|
|
43431
|
+
// ../../node_modules/.bun/is-unicode-supported@2.1.0/node_modules/is-unicode-supported/index.js
|
|
42958
43432
|
import process2 from "process";
|
|
42959
43433
|
function isUnicodeSupported() {
|
|
42960
43434
|
const { env } = process2;
|
|
@@ -42965,7 +43439,7 @@ function isUnicodeSupported() {
|
|
|
42965
43439
|
return Boolean(env.WT_SESSION) || Boolean(env.TERMINUS_SUBLIME) || env.ConEmuTask === "{cmd::Cmder}" || TERM_PROGRAM === "Terminus-Sublime" || TERM_PROGRAM === "vscode" || TERM === "xterm-256color" || TERM === "alacritty" || TERM === "rxvt-unicode" || TERM === "rxvt-unicode-256color" || env.TERMINAL_EMULATOR === "JetBrains-JediTerm";
|
|
42966
43440
|
}
|
|
42967
43441
|
|
|
42968
|
-
//
|
|
43442
|
+
// ../../node_modules/.bun/figures@6.1.0/node_modules/figures/index.js
|
|
42969
43443
|
var common = {
|
|
42970
43444
|
circleQuestionMark: "(?)",
|
|
42971
43445
|
questionMarkPrefix: "(?)",
|
|
@@ -43241,23 +43715,23 @@ var figures = shouldUseMain ? mainSymbols : fallbackSymbols;
|
|
|
43241
43715
|
var figures_default = figures;
|
|
43242
43716
|
var replacements = Object.entries(specialMainSymbols);
|
|
43243
43717
|
|
|
43244
|
-
//
|
|
43718
|
+
// ../../node_modules/.bun/ink-select-input@6.2.0+fa875fef9f949ebd/node_modules/ink-select-input/build/Indicator.js
|
|
43245
43719
|
function Indicator({ isSelected = false }) {
|
|
43246
43720
|
return React.createElement(Box, { marginRight: 1 }, isSelected ? React.createElement(Text, { color: "blue" }, figures_default.pointer) : React.createElement(Text, null, " "));
|
|
43247
43721
|
}
|
|
43248
43722
|
var Indicator_default = Indicator;
|
|
43249
|
-
//
|
|
43723
|
+
// ../../node_modules/.bun/ink-select-input@6.2.0+fa875fef9f949ebd/node_modules/ink-select-input/build/Item.js
|
|
43250
43724
|
import * as React2 from "react";
|
|
43251
43725
|
import { Text as Text2 } from "ink";
|
|
43252
43726
|
function Item({ isSelected = false, label }) {
|
|
43253
43727
|
return React2.createElement(Text2, { color: isSelected ? "blue" : undefined }, label);
|
|
43254
43728
|
}
|
|
43255
43729
|
var Item_default = Item;
|
|
43256
|
-
//
|
|
43730
|
+
// ../../node_modules/.bun/ink-select-input@6.2.0+fa875fef9f949ebd/node_modules/ink-select-input/build/SelectInput.js
|
|
43257
43731
|
import { isDeepStrictEqual } from "util";
|
|
43258
43732
|
import React3, { useState, useEffect, useRef, useCallback } from "react";
|
|
43259
43733
|
|
|
43260
|
-
//
|
|
43734
|
+
// ../../node_modules/.bun/to-rotated@1.0.0/node_modules/to-rotated/index.js
|
|
43261
43735
|
function toRotated(array, steps) {
|
|
43262
43736
|
if (!Array.isArray(array)) {
|
|
43263
43737
|
throw new TypeError(`Expected an array, got \`${typeof array}\`.`);
|
|
@@ -43279,7 +43753,7 @@ function toRotated(array, steps) {
|
|
|
43279
43753
|
];
|
|
43280
43754
|
}
|
|
43281
43755
|
|
|
43282
|
-
//
|
|
43756
|
+
// ../../node_modules/.bun/ink-select-input@6.2.0+fa875fef9f949ebd/node_modules/ink-select-input/build/SelectInput.js
|
|
43283
43757
|
import { Box as Box2, useInput } from "ink";
|
|
43284
43758
|
function SelectInput({ items = [], isFocused = true, initialIndex = 0, indicatorComponent = Indicator_default, itemComponent = Item_default, limit: customLimit, onSelect, onHighlight }) {
|
|
43285
43759
|
const hasLimit = typeof customLimit === "number" && items.length > customLimit;
|
|
@@ -43575,7 +44049,7 @@ function HookSelect({
|
|
|
43575
44049
|
import { useState as useState4, useEffect as useEffect3 } from "react";
|
|
43576
44050
|
import { Box as Box6, Text as Text7, useInput as useInput4 } from "ink";
|
|
43577
44051
|
|
|
43578
|
-
//
|
|
44052
|
+
// ../../node_modules/.bun/ink-text-input@6.0.0+fa875fef9f949ebd/node_modules/ink-text-input/build/index.js
|
|
43579
44053
|
import React5, { useState as useState3, useEffect as useEffect2 } from "react";
|
|
43580
44054
|
import { Text as Text6, useInput as useInput3 } from "ink";
|
|
43581
44055
|
import chalk from "chalk";
|
|
@@ -43753,7 +44227,7 @@ function SearchView({
|
|
|
43753
44227
|
import { useState as useState6, useEffect as useEffect5 } from "react";
|
|
43754
44228
|
import { Box as Box7, Text as Text9 } from "ink";
|
|
43755
44229
|
|
|
43756
|
-
//
|
|
44230
|
+
// ../../node_modules/.bun/ink-spinner@5.0.0+fa875fef9f949ebd/node_modules/ink-spinner/build/index.js
|
|
43757
44231
|
var import_cli_spinners = __toESM(require_cli_spinners(), 1);
|
|
43758
44232
|
import React7, { useState as useState5, useEffect as useEffect4 } from "react";
|
|
43759
44233
|
import { Text as Text8 } from "ink";
|
|
@@ -44117,6 +44591,403 @@ function App({ initialHooks, overwrite = false }) {
|
|
|
44117
44591
|
|
|
44118
44592
|
// src/cli/index.tsx
|
|
44119
44593
|
init_native_safety();
|
|
44594
|
+
|
|
44595
|
+
// src/native-safety.ts
|
|
44596
|
+
init_native_safety();
|
|
44597
|
+
import { execFile } from "child_process";
|
|
44598
|
+
import { createHash as createHash7, randomUUID as randomUUID5 } from "crypto";
|
|
44599
|
+
import { closeSync as closeSync4, constants as constants3, existsSync as existsSync14, fstatSync as fstatSync3, fsyncSync, linkSync as linkSync3, lstatSync as lstatSync7, mkdirSync as mkdirSync9, openSync as openSync4, readFileSync as readFileSync9, realpathSync as realpathSync7, renameSync as renameSync3, unlinkSync as unlinkSync5, writeFileSync as writeFileSync8 } from "fs";
|
|
44600
|
+
import { homedir as homedir7 } from "os";
|
|
44601
|
+
import { dirname as dirname11, isAbsolute as isAbsolute7, join as join13, resolve as resolve15 } from "path";
|
|
44602
|
+
|
|
44603
|
+
class NativeSafetyError extends Error {
|
|
44604
|
+
code;
|
|
44605
|
+
reason;
|
|
44606
|
+
constructor(code2, reason) {
|
|
44607
|
+
super(`Native deletion safety refused (${code2}). Run the reviewed Hooks safety setup before using shell or patch tools.`);
|
|
44608
|
+
this.code = code2;
|
|
44609
|
+
this.reason = reason;
|
|
44610
|
+
this.name = "NativeSafetyError";
|
|
44611
|
+
}
|
|
44612
|
+
}
|
|
44613
|
+
var hash = (value) => createHash7("sha256").update(value).digest("hex");
|
|
44614
|
+
var filename = "sumi-trash-guard.json";
|
|
44615
|
+
var owner2 = (uid2) => uid2 === 0 || uid2 === process.getuid?.();
|
|
44616
|
+
function safeDirectory(path) {
|
|
44617
|
+
for (let cursor = path;; cursor = dirname11(cursor)) {
|
|
44618
|
+
const stat2 = lstatSync7(cursor);
|
|
44619
|
+
if (!stat2.isDirectory() || !owner2(stat2.uid) || stat2.mode & 18)
|
|
44620
|
+
throw new NativeSafetyError("binding_invalid");
|
|
44621
|
+
if (dirname11(cursor) === cursor)
|
|
44622
|
+
return;
|
|
44623
|
+
}
|
|
44624
|
+
}
|
|
44625
|
+
function canonicalHome(home) {
|
|
44626
|
+
if (!isAbsolute7(home) || home.includes("\x00") || realpathSync7(home) !== resolve15(home))
|
|
44627
|
+
throw new NativeSafetyError("binding_invalid");
|
|
44628
|
+
safeDirectory(home);
|
|
44629
|
+
return resolve15(home);
|
|
44630
|
+
}
|
|
44631
|
+
function readOwned(path) {
|
|
44632
|
+
safeDirectory(dirname11(path));
|
|
44633
|
+
const fd = openSync4(path, constants3.O_RDONLY | constants3.O_NOFOLLOW | constants3.O_NONBLOCK);
|
|
44634
|
+
try {
|
|
44635
|
+
const stat2 = fstatSync3(fd);
|
|
44636
|
+
if (!stat2.isFile() || !owner2(stat2.uid) || stat2.mode & 63 || stat2.size > 32768)
|
|
44637
|
+
throw new NativeSafetyError("binding_invalid");
|
|
44638
|
+
const value = readFileSync9(fd, "utf8");
|
|
44639
|
+
if (Buffer.byteLength(value) > 32768)
|
|
44640
|
+
throw new NativeSafetyError("binding_invalid");
|
|
44641
|
+
return value;
|
|
44642
|
+
} finally {
|
|
44643
|
+
closeSync4(fd);
|
|
44644
|
+
}
|
|
44645
|
+
}
|
|
44646
|
+
function parseRecord(text3, home) {
|
|
44647
|
+
const record3 = JSON.parse(text3);
|
|
44648
|
+
if (!record3 || Array.isArray(record3) || Object.keys(record3).sort().join() !== "command,harness,schema" || record3.schema !== "hasna.hooks.native-safety.v1" || record3.harness !== "sumi" || typeof record3.command !== "string")
|
|
44649
|
+
throw new NativeSafetyError("binding_invalid");
|
|
44650
|
+
const binding = nativeSafetyCommandBinding(record3.command);
|
|
44651
|
+
if (binding?.name !== "trash-guard" || binding.home !== home)
|
|
44652
|
+
throw new NativeSafetyError("binding_invalid");
|
|
44653
|
+
return record3;
|
|
44654
|
+
}
|
|
44655
|
+
function syncDirectory(path) {
|
|
44656
|
+
const fd = openSync4(path, constants3.O_RDONLY | constants3.O_DIRECTORY | constants3.O_NOFOLLOW);
|
|
44657
|
+
try {
|
|
44658
|
+
fsyncSync(fd);
|
|
44659
|
+
} finally {
|
|
44660
|
+
closeSync4(fd);
|
|
44661
|
+
}
|
|
44662
|
+
}
|
|
44663
|
+
function readNativeSafetyRegistration(home = homedir7()) {
|
|
44664
|
+
try {
|
|
44665
|
+
home = canonicalHome(home);
|
|
44666
|
+
const path = join13(home, ".hasna/hooks/native", filename), text3 = readOwned(path);
|
|
44667
|
+
return { record: parseRecord(text3, home), sha256: hash(text3), path };
|
|
44668
|
+
} catch (error61) {
|
|
44669
|
+
if (error61 instanceof NativeSafetyError)
|
|
44670
|
+
throw error61;
|
|
44671
|
+
throw new NativeSafetyError(error61?.code === "ENOENT" ? "binding_missing" : "binding_invalid");
|
|
44672
|
+
}
|
|
44673
|
+
}
|
|
44674
|
+
function registerNativeSafety(options = {}) {
|
|
44675
|
+
const home = canonicalHome(options.home ?? homedir7());
|
|
44676
|
+
const record3 = { schema: "hasna.hooks.native-safety.v1", harness: "sumi", command: options.command ?? installedNativeSafetyCommand("trash-guard") };
|
|
44677
|
+
const text3 = JSON.stringify(record3) + `
|
|
44678
|
+
`;
|
|
44679
|
+
parseRecord(text3, home);
|
|
44680
|
+
for (const dir of [join13(home, ".hasna"), join13(home, ".hasna/hooks"), join13(home, ".hasna/hooks/native")]) {
|
|
44681
|
+
try {
|
|
44682
|
+
mkdirSync9(dir, { mode: 448 });
|
|
44683
|
+
syncDirectory(dirname11(dir));
|
|
44684
|
+
} catch (error61) {
|
|
44685
|
+
if (error61.code !== "EEXIST")
|
|
44686
|
+
throw error61;
|
|
44687
|
+
}
|
|
44688
|
+
safeDirectory(dir);
|
|
44689
|
+
}
|
|
44690
|
+
const directory2 = join13(home, ".hasna/hooks/native"), path = join13(directory2, filename), lock = path + ".lock";
|
|
44691
|
+
let lockFD;
|
|
44692
|
+
try {
|
|
44693
|
+
lockFD = openSync4(lock, constants3.O_CREAT | constants3.O_EXCL | constants3.O_WRONLY, 384);
|
|
44694
|
+
} catch {
|
|
44695
|
+
throw new NativeSafetyError("binding_busy");
|
|
44696
|
+
}
|
|
44697
|
+
const lockIdentity = fstatSync3(lockFD);
|
|
44698
|
+
let temporaryIdentity;
|
|
44699
|
+
const temporary = join13(directory2, `.${filename}.${randomUUID5()}`);
|
|
44700
|
+
try {
|
|
44701
|
+
writeFileSync8(lockFD, JSON.stringify({ pid: process.pid, expectedSHA256: options.expectedSHA256 ?? "absent" }));
|
|
44702
|
+
fsyncSync(lockFD);
|
|
44703
|
+
syncDirectory(directory2);
|
|
44704
|
+
let before;
|
|
44705
|
+
try {
|
|
44706
|
+
before = readNativeSafetyRegistration(home);
|
|
44707
|
+
} catch (error61) {
|
|
44708
|
+
if (!(error61 instanceof NativeSafetyError) || error61.code !== "binding_missing")
|
|
44709
|
+
throw error61;
|
|
44710
|
+
}
|
|
44711
|
+
if (before?.sha256 === hash(text3))
|
|
44712
|
+
return { ok: true, changed: false, path, sha256: before.sha256 };
|
|
44713
|
+
if (before ? options.expectedSHA256 !== before.sha256 : options.expectedSHA256 !== undefined && options.expectedSHA256 !== "absent")
|
|
44714
|
+
throw new NativeSafetyError("binding_changed");
|
|
44715
|
+
if (before) {
|
|
44716
|
+
const backup = path + ".before-" + before.sha256;
|
|
44717
|
+
if (existsSync14(backup)) {
|
|
44718
|
+
if (hash(readOwned(backup)) !== before.sha256)
|
|
44719
|
+
throw new NativeSafetyError("binding_changed");
|
|
44720
|
+
} else {
|
|
44721
|
+
const original = readOwned(path);
|
|
44722
|
+
if (hash(original) !== before.sha256)
|
|
44723
|
+
throw new NativeSafetyError("binding_changed");
|
|
44724
|
+
const fd2 = openSync4(backup, constants3.O_CREAT | constants3.O_EXCL | constants3.O_WRONLY, 384);
|
|
44725
|
+
try {
|
|
44726
|
+
writeFileSync8(fd2, original);
|
|
44727
|
+
fsyncSync(fd2);
|
|
44728
|
+
} finally {
|
|
44729
|
+
closeSync4(fd2);
|
|
44730
|
+
}
|
|
44731
|
+
syncDirectory(directory2);
|
|
44732
|
+
}
|
|
44733
|
+
}
|
|
44734
|
+
const fd = openSync4(temporary, constants3.O_CREAT | constants3.O_EXCL | constants3.O_WRONLY, 384);
|
|
44735
|
+
try {
|
|
44736
|
+
temporaryIdentity = fstatSync3(fd);
|
|
44737
|
+
writeFileSync8(fd, text3);
|
|
44738
|
+
fsyncSync(fd);
|
|
44739
|
+
} finally {
|
|
44740
|
+
closeSync4(fd);
|
|
44741
|
+
}
|
|
44742
|
+
if (before) {
|
|
44743
|
+
if (hash(readOwned(path)) !== before.sha256)
|
|
44744
|
+
throw new NativeSafetyError("binding_changed");
|
|
44745
|
+
renameSync3(temporary, path);
|
|
44746
|
+
} else {
|
|
44747
|
+
linkSync3(temporary, path);
|
|
44748
|
+
unlinkSync5(temporary);
|
|
44749
|
+
}
|
|
44750
|
+
syncDirectory(directory2);
|
|
44751
|
+
const after = readNativeSafetyRegistration(home);
|
|
44752
|
+
if (after.sha256 !== hash(text3))
|
|
44753
|
+
throw new NativeSafetyError("binding_changed");
|
|
44754
|
+
return { ok: true, changed: true, path, sha256: after.sha256 };
|
|
44755
|
+
} finally {
|
|
44756
|
+
closeSync4(lockFD);
|
|
44757
|
+
const currentTemporary = lstatSync7(temporary, { throwIfNoEntry: false });
|
|
44758
|
+
if (temporaryIdentity && currentTemporary?.dev === temporaryIdentity.dev && currentTemporary.ino === temporaryIdentity.ino)
|
|
44759
|
+
unlinkSync5(temporary);
|
|
44760
|
+
const currentLock = lstatSync7(lock, { throwIfNoEntry: false });
|
|
44761
|
+
if (currentLock?.dev !== lockIdentity.dev || currentLock.ino !== lockIdentity.ino)
|
|
44762
|
+
throw new NativeSafetyError("binding_changed");
|
|
44763
|
+
unlinkSync5(lock);
|
|
44764
|
+
syncDirectory(directory2);
|
|
44765
|
+
}
|
|
44766
|
+
}
|
|
44767
|
+
async function evaluateRecord(event, record3, signal) {
|
|
44768
|
+
if (!event || event.hook_event_name !== "PreToolUse" || !["Bash", "apply_patch"].includes(event.tool_name) || typeof event.cwd !== "string" || !isAbsolute7(event.cwd) || !event.tool_input || typeof event.tool_input !== "object" || Array.isArray(event.tool_input))
|
|
44769
|
+
throw new NativeSafetyError("input_invalid");
|
|
44770
|
+
const input2 = JSON.stringify(event);
|
|
44771
|
+
if (Buffer.byteLength(input2) > 1048576)
|
|
44772
|
+
throw new NativeSafetyError("input_invalid");
|
|
44773
|
+
const output2 = await new Promise((accept, reject) => {
|
|
44774
|
+
const child = execFile("/bin/sh", ["-c", record3.command], { cwd: "/", env: {}, encoding: "utf8", timeout: 8000, maxBuffer: 1048576, signal }, (error61, stdout, stderr) => {
|
|
44775
|
+
if (error61 || stderr) {
|
|
44776
|
+
reject(new NativeSafetyError("guard_failed"));
|
|
44777
|
+
return;
|
|
44778
|
+
}
|
|
44779
|
+
accept(stdout);
|
|
44780
|
+
});
|
|
44781
|
+
child.stdin?.on("error", () => {});
|
|
44782
|
+
child.stdin?.end(input2);
|
|
44783
|
+
});
|
|
44784
|
+
if (!output2)
|
|
44785
|
+
return { decision: "continue" };
|
|
44786
|
+
try {
|
|
44787
|
+
const value = JSON.parse(output2);
|
|
44788
|
+
if (Object.keys(value).join() !== "hookSpecificOutput")
|
|
44789
|
+
throw new Error;
|
|
44790
|
+
const verdict = value.hookSpecificOutput;
|
|
44791
|
+
if (verdict.hookEventName !== "PreToolUse")
|
|
44792
|
+
throw new Error;
|
|
44793
|
+
if (verdict.permissionDecision === "deny" && Object.keys(verdict).sort().join() === "hookEventName,permissionDecision,permissionDecisionReason" && typeof verdict.permissionDecisionReason === "string" && verdict.permissionDecisionReason.trim())
|
|
44794
|
+
return { decision: "deny", reason: verdict.permissionDecisionReason };
|
|
44795
|
+
if (event.tool_name === "Bash" && verdict.permissionDecision === "allow" && Object.keys(verdict).sort().join() === "hookEventName,permissionDecision,updatedInput" && typeof verdict.updatedInput?.command === "string" && verdict.updatedInput.command.trim())
|
|
44796
|
+
return { decision: "rewrite", command: verdict.updatedInput.command };
|
|
44797
|
+
} catch {}
|
|
44798
|
+
throw new NativeSafetyError("guard_failed");
|
|
44799
|
+
}
|
|
44800
|
+
async function verifyNativeSafetyCommand(command, home = homedir7()) {
|
|
44801
|
+
home = canonicalHome(home);
|
|
44802
|
+
const record3 = parseRecord(JSON.stringify({ schema: "hasna.hooks.native-safety.v1", harness: "sumi", command }), home);
|
|
44803
|
+
const verdict = await evaluateRecord({ hook_event_name: "PreToolUse", tool_name: "Bash", cwd: home, tool_input: { command: "rm -- .hasna-trash-verification-only" } }, record3);
|
|
44804
|
+
if (verdict.decision !== "rewrite")
|
|
44805
|
+
throw new NativeSafetyError("guard_failed", verdict.decision === "deny" ? verdict.reason.slice(0, 512) : undefined);
|
|
44806
|
+
}
|
|
44807
|
+
async function verifyNativeSafetyRegistration(home = homedir7()) {
|
|
44808
|
+
const before = readNativeSafetyRegistration(home);
|
|
44809
|
+
await verifyNativeSafetyCommand(before.record.command, home);
|
|
44810
|
+
const after = readNativeSafetyRegistration(home);
|
|
44811
|
+
if (after.sha256 !== before.sha256)
|
|
44812
|
+
throw new NativeSafetyError("binding_changed");
|
|
44813
|
+
return { path: before.path, sha256: before.sha256 };
|
|
44814
|
+
}
|
|
44815
|
+
|
|
44816
|
+
// hooks/hook-trash-guard/src/binary.ts
|
|
44817
|
+
import { dirname as dirname17, isAbsolute as isAbsolute15, join as join17, resolve as resolve17 } from "path";
|
|
44818
|
+
import { accessSync, closeSync as closeSync5, constants as constants4, fstatSync as fstatSync4, lstatSync as lstatSync9, openSync as openSync5, readFileSync as readFileSync12, readlinkSync as readlinkSync3, readSync as readSync2, realpathSync as realpathSync8, statSync } from "fs";
|
|
44819
|
+
import { spawnSync } from "child_process";
|
|
44820
|
+
function trustedOwner(uid2) {
|
|
44821
|
+
return uid2 === 0 || uid2 === process.getuid?.();
|
|
44822
|
+
}
|
|
44823
|
+
function safeExecutableFile(path, systemBinAlias = false) {
|
|
44824
|
+
let current2 = path;
|
|
44825
|
+
for (let hop = 0;hop < 32; hop++) {
|
|
44826
|
+
if (!isAbsolute15(current2) || current2.length > 4096 || !safeBinaryDirectory("/"))
|
|
44827
|
+
return false;
|
|
44828
|
+
const parts = current2.split("/"), name = parts.pop();
|
|
44829
|
+
if (!name || name === "." || name === "..")
|
|
44830
|
+
return false;
|
|
44831
|
+
let parent = "/";
|
|
44832
|
+
for (const part of parts) {
|
|
44833
|
+
if (!part || part === ".")
|
|
44834
|
+
continue;
|
|
44835
|
+
if (part === "..") {
|
|
44836
|
+
parent = dirname17(parent);
|
|
44837
|
+
continue;
|
|
44838
|
+
}
|
|
44839
|
+
const next = join17(parent, part), info2 = lstatSync9(next);
|
|
44840
|
+
if (systemBinAlias && next === "/bin" && info2.isSymbolicLink() && info2.uid === 0) {
|
|
44841
|
+
const target = realpathSync8(next);
|
|
44842
|
+
if (!safeBinaryDirectory(target))
|
|
44843
|
+
return false;
|
|
44844
|
+
parent = target;
|
|
44845
|
+
continue;
|
|
44846
|
+
}
|
|
44847
|
+
if (!info2.isDirectory() || !trustedOwner(info2.uid) || info2.mode & 18)
|
|
44848
|
+
return false;
|
|
44849
|
+
parent = next;
|
|
44850
|
+
}
|
|
44851
|
+
const candidate = join17(parent, name), info = lstatSync9(candidate);
|
|
44852
|
+
if (!trustedOwner(info.uid))
|
|
44853
|
+
return false;
|
|
44854
|
+
if (info.isSymbolicLink()) {
|
|
44855
|
+
const target = readlinkSync3(candidate);
|
|
44856
|
+
current2 = isAbsolute15(target) ? target : parent + "/" + target;
|
|
44857
|
+
continue;
|
|
44858
|
+
}
|
|
44859
|
+
return info.isFile() && Boolean(info.mode & 73) && !(info.mode & 18);
|
|
44860
|
+
}
|
|
44861
|
+
return false;
|
|
44862
|
+
}
|
|
44863
|
+
function safeInterpreterSearchDirectory(path) {
|
|
44864
|
+
if (!isAbsolute15(path) || resolve17(path) !== (path.replace(/\/+$/, "") || "/"))
|
|
44865
|
+
return false;
|
|
44866
|
+
for (let cursor = resolve17(path);; cursor = dirname17(cursor)) {
|
|
44867
|
+
try {
|
|
44868
|
+
lstatSync9(cursor);
|
|
44869
|
+
return safeBinaryDirectory(cursor);
|
|
44870
|
+
} catch (error61) {
|
|
44871
|
+
if (error61.code !== "ENOENT")
|
|
44872
|
+
return false;
|
|
44873
|
+
}
|
|
44874
|
+
if (dirname17(cursor) === cursor)
|
|
44875
|
+
return false;
|
|
44876
|
+
}
|
|
44877
|
+
}
|
|
44878
|
+
function safeScriptInterpreter(executable, pathValue) {
|
|
44879
|
+
const fd = openSync5(executable, constants4.O_RDONLY | constants4.O_NONBLOCK | constants4.O_NOFOLLOW);
|
|
44880
|
+
let header;
|
|
44881
|
+
try {
|
|
44882
|
+
if (!fstatSync4(fd).isFile())
|
|
44883
|
+
return false;
|
|
44884
|
+
const bytes = Buffer.alloc(256), length = readSync2(fd, bytes, 0, bytes.length, 0);
|
|
44885
|
+
header = bytes.subarray(0, length).toString("utf8").split(`
|
|
44886
|
+
`)[0];
|
|
44887
|
+
} finally {
|
|
44888
|
+
closeSync5(fd);
|
|
44889
|
+
}
|
|
44890
|
+
const direct = /^#!(\/[A-Za-z0-9_./-]+)$/.exec(header);
|
|
44891
|
+
if (direct && direct[1] !== "/usr/bin/env")
|
|
44892
|
+
return safeExecutableFile(direct[1], true);
|
|
44893
|
+
if (header !== "#!/usr/bin/env bun" || !safeExecutableFile("/usr/bin/env", true))
|
|
44894
|
+
return false;
|
|
44895
|
+
for (const dir of pathValue.split(":").slice(0, 64)) {
|
|
44896
|
+
if (!safeInterpreterSearchDirectory(dir))
|
|
44897
|
+
return false;
|
|
44898
|
+
const interpreter = resolve17(dir, "bun");
|
|
44899
|
+
try {
|
|
44900
|
+
if (!statSync(interpreter).isFile())
|
|
44901
|
+
continue;
|
|
44902
|
+
accessSync(interpreter, constants4.X_OK);
|
|
44903
|
+
} catch {
|
|
44904
|
+
continue;
|
|
44905
|
+
}
|
|
44906
|
+
return safeExecutableFile(interpreter, true);
|
|
44907
|
+
}
|
|
44908
|
+
return false;
|
|
44909
|
+
}
|
|
44910
|
+
function safeBinaryDirectory(path) {
|
|
44911
|
+
let current2 = path;
|
|
44912
|
+
for (;; ) {
|
|
44913
|
+
const info = lstatSync9(current2);
|
|
44914
|
+
if (!info.isDirectory() || !trustedOwner(info.uid) || (info.mode & 18) !== 0)
|
|
44915
|
+
return false;
|
|
44916
|
+
const parent = dirname17(current2);
|
|
44917
|
+
if (parent === current2)
|
|
44918
|
+
return true;
|
|
44919
|
+
current2 = parent;
|
|
44920
|
+
}
|
|
44921
|
+
}
|
|
44922
|
+
function inspectTrashBinary(env = process.env) {
|
|
44923
|
+
const pathValue = env.PATH ?? "";
|
|
44924
|
+
const deadline = Date.now() + 2000;
|
|
44925
|
+
let failure = "missing";
|
|
44926
|
+
const reject = (code2) => {
|
|
44927
|
+
if (failure === "missing" || failure === "unrecognized_package" || code2.startsWith("unsafe_"))
|
|
44928
|
+
failure = code2;
|
|
44929
|
+
};
|
|
44930
|
+
for (const dir of pathValue.split(":").slice(0, 64)) {
|
|
44931
|
+
if (!isAbsolute15(dir) || Date.now() >= deadline)
|
|
44932
|
+
continue;
|
|
44933
|
+
const candidate = resolve17(dir, "trash");
|
|
44934
|
+
try {
|
|
44935
|
+
const stat2 = statSync(candidate);
|
|
44936
|
+
if (!stat2.isFile() || !trustedOwner(stat2.uid) || (stat2.mode & 73) === 0 || (stat2.mode & 18) !== 0) {
|
|
44937
|
+
reject("unsafe_executable");
|
|
44938
|
+
continue;
|
|
44939
|
+
}
|
|
44940
|
+
const executable = realpathSync8(candidate);
|
|
44941
|
+
if (!safeBinaryDirectory(resolve17(dir)) || !safeBinaryDirectory(dirname17(executable))) {
|
|
44942
|
+
reject("unsafe_directory");
|
|
44943
|
+
continue;
|
|
44944
|
+
}
|
|
44945
|
+
if (!safeExecutableFile(candidate)) {
|
|
44946
|
+
reject("unsafe_directory");
|
|
44947
|
+
continue;
|
|
44948
|
+
}
|
|
44949
|
+
const packageRoot = resolve17(dirname17(executable), "../..");
|
|
44950
|
+
const manifestPath = join17(packageRoot, "package.json");
|
|
44951
|
+
const manifestStat = statSync(manifestPath);
|
|
44952
|
+
if (!manifestStat.isFile() || !trustedOwner(manifestStat.uid) || manifestStat.size > 16384 || (manifestStat.mode & 18) !== 0 || lstatSync9(manifestPath).isSymbolicLink()) {
|
|
44953
|
+
reject("unsafe_manifest");
|
|
44954
|
+
continue;
|
|
44955
|
+
}
|
|
44956
|
+
const manifest = JSON.parse(readFileSync12(manifestPath, "utf8"));
|
|
44957
|
+
if (manifest.name !== "@hasna/trash" || typeof manifest.version !== "string" || typeof manifest.bin?.trash !== "string" || realpathSync8(resolve17(packageRoot, manifest.bin.trash)) !== executable) {
|
|
44958
|
+
reject("unrecognized_package");
|
|
44959
|
+
continue;
|
|
44960
|
+
}
|
|
44961
|
+
try {
|
|
44962
|
+
if (!safeScriptInterpreter(executable, pathValue)) {
|
|
44963
|
+
reject("unsafe_interpreter");
|
|
44964
|
+
continue;
|
|
44965
|
+
}
|
|
44966
|
+
} catch {
|
|
44967
|
+
reject("unsafe_interpreter");
|
|
44968
|
+
continue;
|
|
44969
|
+
}
|
|
44970
|
+
const probe = spawnSync(candidate, ["--identity"], {
|
|
44971
|
+
encoding: "utf8",
|
|
44972
|
+
timeout: Math.max(1, Math.min(500, deadline - Date.now())),
|
|
44973
|
+
maxBuffer: 2048,
|
|
44974
|
+
env: { PATH: pathValue },
|
|
44975
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
44976
|
+
});
|
|
44977
|
+
if (probe.error || probe.status !== 0) {
|
|
44978
|
+
reject("identity_failed");
|
|
44979
|
+
continue;
|
|
44980
|
+
}
|
|
44981
|
+
const identity3 = JSON.parse(probe.stdout);
|
|
44982
|
+
if (identity3.name === "@hasna/trash" && identity3.version === manifest.version && identity3.guardProtocol === "hasna.trash.guard.v1")
|
|
44983
|
+
return { ok: true, path: candidate, version: manifest.version };
|
|
44984
|
+
reject("unsupported_protocol");
|
|
44985
|
+
} catch {}
|
|
44986
|
+
}
|
|
44987
|
+
return { ok: false, code: failure };
|
|
44988
|
+
}
|
|
44989
|
+
|
|
44990
|
+
// src/cli/index.tsx
|
|
44120
44991
|
init_registry();
|
|
44121
44992
|
init_installer();
|
|
44122
44993
|
init_registration();
|
|
@@ -44132,9 +45003,9 @@ init_local_opt_in();
|
|
|
44132
45003
|
init_transport2();
|
|
44133
45004
|
init_db();
|
|
44134
45005
|
import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
|
|
44135
|
-
var __dirname4 =
|
|
44136
|
-
var pkgPath =
|
|
44137
|
-
var pkg2 = JSON.parse(
|
|
45006
|
+
var __dirname4 = dirname24(fileURLToPath6(import.meta.url));
|
|
45007
|
+
var pkgPath = existsSync19(join31(__dirname4, "..", "package.json")) ? join31(__dirname4, "..", "package.json") : join31(__dirname4, "..", "..", "package.json");
|
|
45008
|
+
var pkg2 = JSON.parse(readFileSync23(pkgPath, "utf-8"));
|
|
44138
45009
|
var program3 = new Command;
|
|
44139
45010
|
function resolveScope(options) {
|
|
44140
45011
|
if (options.project)
|
|
@@ -44214,7 +45085,7 @@ async function openLocalStoreOrExit(json3) {
|
|
|
44214
45085
|
function readToken(tokenFile) {
|
|
44215
45086
|
if (tokenFile) {
|
|
44216
45087
|
try {
|
|
44217
|
-
return
|
|
45088
|
+
return readFileSync23(tokenFile, "utf-8").trim();
|
|
44218
45089
|
} catch {
|
|
44219
45090
|
return;
|
|
44220
45091
|
}
|
|
@@ -44352,7 +45223,7 @@ program3.command("init").description("Register a new agent profile with a unique
|
|
|
44352
45223
|
if (profile.name) {
|
|
44353
45224
|
console.log(` ${chalk2.dim("Name:")} ${profile.name}`);
|
|
44354
45225
|
}
|
|
44355
|
-
console.log(` ${chalk2.dim("Profile:")} ${
|
|
45226
|
+
console.log(` ${chalk2.dim("Profile:")} ${join31(getProfilesDir(), `${profile.agent_id}.json`)}`);
|
|
44356
45227
|
console.log();
|
|
44357
45228
|
console.log(chalk2.dim(" Install hooks with this profile:"));
|
|
44358
45229
|
console.log(` hooks install gitguard --profile ${profile.agent_id}`);
|
|
@@ -44376,11 +45247,11 @@ program3.command("run").argument("<hook>", "Hook to run").option("--profile <id>
|
|
|
44376
45247
|
process.exit(1);
|
|
44377
45248
|
}
|
|
44378
45249
|
const hookScript = resolveScriptPath2(hook);
|
|
44379
|
-
if (!hookScript || !
|
|
45250
|
+
if (!hookScript || !existsSync19(hookScript)) {
|
|
44380
45251
|
console.error(JSON.stringify({ error: `Hook script not found: ${hookScript ?? hook}` }));
|
|
44381
45252
|
process.exit(1);
|
|
44382
45253
|
}
|
|
44383
|
-
const content =
|
|
45254
|
+
const content = readFileSync23(hookScript);
|
|
44384
45255
|
const sha4 = sha256Of2(content);
|
|
44385
45256
|
const check2 = checkScriptHash2(hook, sha4);
|
|
44386
45257
|
if (!check2.ok) {
|
|
@@ -44974,7 +45845,7 @@ program3.command("doctor").option("-g, --global", "Check global settings", false
|
|
|
44974
45845
|
});
|
|
44975
45846
|
}
|
|
44976
45847
|
const staleNames = new Set(stale.map((finding) => finding.hook));
|
|
44977
|
-
const settingsExist =
|
|
45848
|
+
const settingsExist = existsSync19(settingsPath);
|
|
44978
45849
|
if (!settingsExist) {
|
|
44979
45850
|
issues.push({ hook: "(settings)", issue: `${settingsPath} not found`, severity: "warning" });
|
|
44980
45851
|
}
|
|
@@ -44982,7 +45853,7 @@ program3.command("doctor").option("-g, --global", "Check global settings", false
|
|
|
44982
45853
|
let wiringCount = 0;
|
|
44983
45854
|
if (settingsExist) {
|
|
44984
45855
|
try {
|
|
44985
|
-
const settings = JSON.parse(
|
|
45856
|
+
const settings = JSON.parse(readFileSync23(settingsPath, "utf-8"));
|
|
44986
45857
|
wiringCount = countSettingsWiring(settings);
|
|
44987
45858
|
} catch {}
|
|
44988
45859
|
}
|
|
@@ -44993,7 +45864,7 @@ program3.command("doctor").option("-g, --global", "Check global settings", false
|
|
|
44993
45864
|
const meta3 = custom2 ? resolveHookMeta(name) : getHook(name);
|
|
44994
45865
|
let hookHealthy = true;
|
|
44995
45866
|
if (custom2) {
|
|
44996
|
-
if (!
|
|
45867
|
+
if (!existsSync19(custom2.scriptPath)) {
|
|
44997
45868
|
issues.push({ hook: name, issue: `Custom hook script missing: ${custom2.scriptPath}`, severity: "error" });
|
|
44998
45869
|
hookHealthy = false;
|
|
44999
45870
|
}
|
|
@@ -45004,15 +45875,15 @@ program3.command("doctor").option("-g, --global", "Check global settings", false
|
|
|
45004
45875
|
continue;
|
|
45005
45876
|
}
|
|
45006
45877
|
const hookDir = getHookPath(name);
|
|
45007
|
-
const hookScript =
|
|
45008
|
-
if (!
|
|
45878
|
+
const hookScript = join31(hookDir, "src", "hook.ts");
|
|
45879
|
+
if (!existsSync19(hookScript)) {
|
|
45009
45880
|
issues.push({ hook: name, issue: "Missing src/hook.ts in package", severity: "error" });
|
|
45010
45881
|
hookHealthy = false;
|
|
45011
45882
|
}
|
|
45012
45883
|
}
|
|
45013
45884
|
if (meta3 && settingsExist) {
|
|
45014
45885
|
try {
|
|
45015
|
-
const settings = JSON.parse(
|
|
45886
|
+
const settings = JSON.parse(readFileSync23(settingsPath, "utf-8"));
|
|
45016
45887
|
if (!hookRegisteredInSettings(settings, name, meta3.event, meta3.matcher)) {
|
|
45017
45888
|
const eventName = meta3.event.split(":")[0];
|
|
45018
45889
|
issues.push({ hook: name, issue: `Not registered under correct event (${eventName})`, severity: "error" });
|
|
@@ -45162,18 +46033,18 @@ program3.command("update").argument("[hooks...]", "Hooks to update (defaults to
|
|
|
45162
46033
|
const result = installHook(arg, { scope, overwrite: true });
|
|
45163
46034
|
const resolved = resolveHook2(arg);
|
|
45164
46035
|
if (result.success && resolved) {
|
|
45165
|
-
const
|
|
45166
|
-
setPinnedHook2(resolved.name, { version: resolved.version, sha256:
|
|
46036
|
+
const hash4 = await sha256File2(resolved.scriptPath);
|
|
46037
|
+
setPinnedHook2(resolved.name, { version: resolved.version, sha256: hash4, source: resolved.source });
|
|
45167
46038
|
if (!isLocalStoreRefused2()) {
|
|
45168
46039
|
upsertHookRecord2(getDb2(), {
|
|
45169
46040
|
name: resolved.name,
|
|
45170
46041
|
version: resolved.version,
|
|
45171
|
-
sha256:
|
|
46042
|
+
sha256: hash4,
|
|
45172
46043
|
source_type: resolved.source,
|
|
45173
46044
|
last_verified_at: new Date().toISOString()
|
|
45174
46045
|
});
|
|
45175
46046
|
}
|
|
45176
|
-
results2.push({ ...result, pinned: { version: resolved.version, sha256:
|
|
46047
|
+
results2.push({ ...result, pinned: { version: resolved.version, sha256: hash4 } });
|
|
45177
46048
|
continue;
|
|
45178
46049
|
}
|
|
45179
46050
|
results2.push(result);
|
|
@@ -45215,10 +46086,10 @@ program3.command("docs").argument("[hook]", "Hook name (shows general docs if om
|
|
|
45215
46086
|
return;
|
|
45216
46087
|
}
|
|
45217
46088
|
const hookPath = getHookPath(hook);
|
|
45218
|
-
const readmePath =
|
|
46089
|
+
const readmePath = join31(hookPath, "README.md");
|
|
45219
46090
|
let readme = "";
|
|
45220
|
-
if (
|
|
45221
|
-
readme =
|
|
46091
|
+
if (existsSync19(readmePath)) {
|
|
46092
|
+
readme = readFileSync23(readmePath, "utf-8");
|
|
45222
46093
|
}
|
|
45223
46094
|
if (options.json) {
|
|
45224
46095
|
console.log(JSON.stringify({ ...meta3, readme }));
|
|
@@ -45846,7 +46717,38 @@ program3.command("mcp").option("-s, --stdio", "Use stdio transport (one process
|
|
|
45846
46717
|
}
|
|
45847
46718
|
});
|
|
45848
46719
|
registerEventsCommands(program3, { source: "hooks" });
|
|
45849
|
-
program3.command("safety").description("
|
|
46720
|
+
var safety = program3.command("safety").description("Inspect and register bundled native deletion guards without a registry credential");
|
|
46721
|
+
safety.command("inspect").description("Check the installed Trash guard identity and filesystem safety without app credentials").action(() => {
|
|
46722
|
+
const result = inspectTrashBinary();
|
|
46723
|
+
console.log(JSON.stringify({ guard: "trash-guard", ...result }));
|
|
46724
|
+
if (!result.ok)
|
|
46725
|
+
process.exitCode = 1;
|
|
46726
|
+
});
|
|
46727
|
+
safety.command("install <guard>").option("--target <target>", "codex, claude or sumi", "codex").option("--scope <scope>", "global or project", "global").option("--overwrite", "Replace this guard in place; native trust is still required").option("--expected-binding-sha256 <sha256>", "Exact existing Sumi binding digest required for an upgrade").action(async (guard, options) => {
|
|
46728
|
+
if (options.target === "sumi") {
|
|
46729
|
+
try {
|
|
46730
|
+
if (guard !== "trash-guard" || options.scope !== "global" || options.overwrite)
|
|
46731
|
+
throw new NativeSafetyError("input_invalid");
|
|
46732
|
+
const command = installedNativeSafetyCommand("trash-guard");
|
|
46733
|
+
const binding = nativeSafetyCommandBinding(command);
|
|
46734
|
+
const installed = inspectTrashBinary({ PATH: binding.path });
|
|
46735
|
+
if (!installed.ok) {
|
|
46736
|
+
console.log(JSON.stringify({ ok: false, target: "sumi", code: installed.code }));
|
|
46737
|
+
process.exitCode = 1;
|
|
46738
|
+
return;
|
|
46739
|
+
}
|
|
46740
|
+
await verifyNativeSafetyCommand(command);
|
|
46741
|
+
const registered = registerNativeSafety({ command, expectedSHA256: options.expectedBindingSha256 });
|
|
46742
|
+
const verified = await verifyNativeSafetyRegistration();
|
|
46743
|
+
if (verified.sha256 !== registered.sha256)
|
|
46744
|
+
throw new NativeSafetyError("binding_changed");
|
|
46745
|
+
console.log(JSON.stringify({ target: "sumi", ...registered }));
|
|
46746
|
+
} catch (error61) {
|
|
46747
|
+
console.log(JSON.stringify({ ok: false, target: "sumi", code: error61 instanceof NativeSafetyError ? error61.code : "binding_invalid", ...error61 instanceof NativeSafetyError && error61.reason ? { reason: error61.reason } : {} }));
|
|
46748
|
+
process.exitCode = 1;
|
|
46749
|
+
}
|
|
46750
|
+
return;
|
|
46751
|
+
}
|
|
45850
46752
|
if (!isNativeSafetyName(guard) || !["codex", "claude"].includes(options.target) || !["global", "project"].includes(options.scope)) {
|
|
45851
46753
|
console.error("Unsupported bundled safety registration");
|
|
45852
46754
|
process.exitCode = 1;
|
|
@@ -45857,6 +46759,17 @@ program3.command("safety").description("Register bundled native deletion guards
|
|
|
45857
46759
|
if (!result.success)
|
|
45858
46760
|
process.exitCode = 1;
|
|
45859
46761
|
});
|
|
46762
|
+
safety.command("verify").requiredOption("--target <target>", "sumi").description("Verify a required native binding and its supervisor without running a file operation").action(async (options) => {
|
|
46763
|
+
try {
|
|
46764
|
+
if (options.target !== "sumi")
|
|
46765
|
+
throw new NativeSafetyError("input_invalid");
|
|
46766
|
+
const current2 = await verifyNativeSafetyRegistration();
|
|
46767
|
+
console.log(JSON.stringify({ ok: true, target: "sumi", sha256: current2.sha256, guardVerified: true, nativeAdoptionVerified: false }));
|
|
46768
|
+
} catch (error61) {
|
|
46769
|
+
console.log(JSON.stringify({ ok: false, target: "sumi", code: error61 instanceof NativeSafetyError ? error61.code : "guard_failed", ...error61 instanceof NativeSafetyError && error61.reason ? { reason: error61.reason } : {} }));
|
|
46770
|
+
process.exitCode = 1;
|
|
46771
|
+
}
|
|
46772
|
+
});
|
|
45860
46773
|
var API_INDEPENDENT_COMMANDS = new Set([
|
|
45861
46774
|
"safety",
|
|
45862
46775
|
"serve",
|