@hasna/hooks 0.6.8 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/index.js +886 -418
- package/bin/serve.js +613 -148
- package/dist/config.d.ts +7 -5
- package/dist/db/pg-migrations.d.ts +1 -1
- package/dist/index.js +788 -326
- package/dist/openapi.d.ts +216 -0
- package/dist/storage.js +34 -34
- package/hooks/codewith-native-common.test.ts +25 -25
- package/hooks/codewith-native-common.ts +1 -1
- package/hooks/hook-autoformat/package.json +1 -1
- package/hooks/hook-desktopnotify/package.json +1 -1
- package/hooks/hook-envsetup/package.json +1 -1
- package/hooks/hook-knowledge-context/README.md +1 -1
- package/hooks/hook-knowledge-context/src/hook.test.ts +1 -1
- package/hooks/hook-permissionguard/package.json +1 -1
- package/hooks/hook-protectfiles/package.json +1 -1
- package/hooks/hook-spiral-detector/LICENSE +191 -0
- package/hooks/hook-spiral-detector/README.md +23 -0
- package/hooks/hook-spiral-detector/package.json +12 -0
- package/hooks/hook-spiral-detector/src/hook.test.ts +72 -0
- package/hooks/hook-spiral-detector/src/hook.ts +93 -0
- package/hooks/hook-workspace-repos-guard/README.md +4 -3
- package/hooks/hook-workspace-repos-guard/src/hook.test.ts +7 -7
- package/hooks/hook-workspace-repos-guard/src/hook.ts +5 -3
- package/hooks/mention-context/src/hasna-mention-context.py +2 -2
- package/hooks/mention-context/src/hasna-mention-warm.py +1 -1
- package/hooks/pre-bash/README.md +1 -1
- package/hooks/prompt-guard/README.md +1 -1
- package/hooks/session-start/README.md +1 -1
- package/hooks/stop-sync/README.md +1 -1
- package/hooks/worktree-guard/README.md +1 -1
- package/package.json +10 -6
package/dist/index.js
CHANGED
|
@@ -49,7 +49,7 @@ function up(db) {
|
|
|
49
49
|
db.exec(idx);
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
-
var
|
|
52
|
+
var init__001_initial = __esm(() => {
|
|
53
53
|
init_schema();
|
|
54
54
|
});
|
|
55
55
|
|
|
@@ -78,7 +78,7 @@ function up2(db) {
|
|
|
78
78
|
throw err;
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
|
-
var
|
|
81
|
+
var init__002_session_events = __esm(() => {
|
|
82
82
|
init_schema();
|
|
83
83
|
});
|
|
84
84
|
|
|
@@ -107,7 +107,7 @@ function up3(db) {
|
|
|
107
107
|
throw err;
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
|
-
var
|
|
110
|
+
var init__003_user_prompt_submit_event = __esm(() => {
|
|
111
111
|
init_schema();
|
|
112
112
|
});
|
|
113
113
|
|
|
@@ -131,7 +131,7 @@ var CREATE_HOOKS_TABLE = `
|
|
|
131
131
|
last_verified_at TEXT
|
|
132
132
|
)
|
|
133
133
|
`, CREATE_HOOKS_INDEXES;
|
|
134
|
-
var
|
|
134
|
+
var init__004_hooks_table = __esm(() => {
|
|
135
135
|
CREATE_HOOKS_INDEXES = [
|
|
136
136
|
`CREATE INDEX IF NOT EXISTS idx_hooks_name ON hooks (name)`
|
|
137
137
|
];
|
|
@@ -162,7 +162,7 @@ function up5(db) {
|
|
|
162
162
|
throw err;
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
|
-
var
|
|
165
|
+
var init__005_subagent_start_event = __esm(() => {
|
|
166
166
|
init_schema();
|
|
167
167
|
});
|
|
168
168
|
|
|
@@ -194,11 +194,11 @@ function runMigrations(db) {
|
|
|
194
194
|
}
|
|
195
195
|
var MIGRATIONS;
|
|
196
196
|
var init_migrations = __esm(() => {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
197
|
+
init__001_initial();
|
|
198
|
+
init__002_session_events();
|
|
199
|
+
init__003_user_prompt_submit_event();
|
|
200
|
+
init__004_hooks_table();
|
|
201
|
+
init__005_subagent_start_event();
|
|
202
202
|
MIGRATIONS = [
|
|
203
203
|
{ version: "001_initial", up },
|
|
204
204
|
{ version: "002_session_events", up: up2 },
|
|
@@ -4296,113 +4296,113 @@ var init_types = __esm(() => {
|
|
|
4296
4296
|
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/external.js
|
|
4297
4297
|
var exports_external = {};
|
|
4298
4298
|
__export(exports_external, {
|
|
4299
|
-
|
|
4300
|
-
util: () => util,
|
|
4301
|
-
unknown: () => unknownType,
|
|
4302
|
-
union: () => unionType,
|
|
4303
|
-
undefined: () => undefinedType,
|
|
4304
|
-
tuple: () => tupleType,
|
|
4305
|
-
transformer: () => effectsType,
|
|
4306
|
-
symbol: () => symbolType,
|
|
4307
|
-
string: () => stringType,
|
|
4308
|
-
strictObject: () => strictObjectType,
|
|
4309
|
-
setErrorMap: () => setErrorMap,
|
|
4310
|
-
set: () => setType,
|
|
4311
|
-
record: () => recordType,
|
|
4312
|
-
quotelessJson: () => quotelessJson,
|
|
4313
|
-
promise: () => promiseType,
|
|
4314
|
-
preprocess: () => preprocessType,
|
|
4315
|
-
pipeline: () => pipelineType,
|
|
4316
|
-
ostring: () => ostring,
|
|
4317
|
-
optional: () => optionalType,
|
|
4318
|
-
onumber: () => onumber,
|
|
4319
|
-
oboolean: () => oboolean,
|
|
4320
|
-
objectUtil: () => objectUtil,
|
|
4321
|
-
object: () => objectType,
|
|
4322
|
-
number: () => numberType,
|
|
4323
|
-
nullable: () => nullableType,
|
|
4324
|
-
null: () => nullType,
|
|
4325
|
-
never: () => neverType,
|
|
4326
|
-
nativeEnum: () => nativeEnumType,
|
|
4327
|
-
nan: () => nanType,
|
|
4328
|
-
map: () => mapType,
|
|
4329
|
-
makeIssue: () => makeIssue,
|
|
4330
|
-
literal: () => literalType,
|
|
4331
|
-
lazy: () => lazyType,
|
|
4332
|
-
late: () => late,
|
|
4333
|
-
isValid: () => isValid,
|
|
4334
|
-
isDirty: () => isDirty,
|
|
4335
|
-
isAsync: () => isAsync,
|
|
4336
|
-
isAborted: () => isAborted,
|
|
4337
|
-
intersection: () => intersectionType,
|
|
4338
|
-
instanceof: () => instanceOfType,
|
|
4339
|
-
getParsedType: () => getParsedType,
|
|
4340
|
-
getErrorMap: () => getErrorMap,
|
|
4341
|
-
function: () => functionType,
|
|
4342
|
-
enum: () => enumType,
|
|
4343
|
-
effect: () => effectsType,
|
|
4344
|
-
discriminatedUnion: () => discriminatedUnionType,
|
|
4345
|
-
defaultErrorMap: () => en_default,
|
|
4346
|
-
datetimeRegex: () => datetimeRegex,
|
|
4347
|
-
date: () => dateType,
|
|
4348
|
-
custom: () => custom,
|
|
4349
|
-
coerce: () => coerce,
|
|
4350
|
-
boolean: () => booleanType,
|
|
4351
|
-
bigint: () => bigIntType,
|
|
4352
|
-
array: () => arrayType,
|
|
4353
|
-
any: () => anyType,
|
|
4354
|
-
addIssueToContext: () => addIssueToContext,
|
|
4355
|
-
ZodVoid: () => ZodVoid,
|
|
4356
|
-
ZodUnknown: () => ZodUnknown,
|
|
4357
|
-
ZodUnion: () => ZodUnion,
|
|
4358
|
-
ZodUndefined: () => ZodUndefined,
|
|
4359
|
-
ZodType: () => ZodType,
|
|
4360
|
-
ZodTuple: () => ZodTuple,
|
|
4361
|
-
ZodTransformer: () => ZodEffects,
|
|
4362
|
-
ZodSymbol: () => ZodSymbol,
|
|
4363
|
-
ZodString: () => ZodString,
|
|
4364
|
-
ZodSet: () => ZodSet,
|
|
4365
|
-
ZodSchema: () => ZodType,
|
|
4366
|
-
ZodRecord: () => ZodRecord,
|
|
4367
|
-
ZodReadonly: () => ZodReadonly,
|
|
4368
|
-
ZodPromise: () => ZodPromise,
|
|
4369
|
-
ZodPipeline: () => ZodPipeline,
|
|
4370
|
-
ZodParsedType: () => ZodParsedType,
|
|
4371
|
-
ZodOptional: () => ZodOptional,
|
|
4372
|
-
ZodObject: () => ZodObject,
|
|
4373
|
-
ZodNumber: () => ZodNumber,
|
|
4374
|
-
ZodNullable: () => ZodNullable,
|
|
4375
|
-
ZodNull: () => ZodNull,
|
|
4376
|
-
ZodNever: () => ZodNever,
|
|
4377
|
-
ZodNativeEnum: () => ZodNativeEnum,
|
|
4378
|
-
ZodNaN: () => ZodNaN,
|
|
4379
|
-
ZodMap: () => ZodMap,
|
|
4380
|
-
ZodLiteral: () => ZodLiteral,
|
|
4381
|
-
ZodLazy: () => ZodLazy,
|
|
4382
|
-
ZodIssueCode: () => ZodIssueCode,
|
|
4383
|
-
ZodIntersection: () => ZodIntersection,
|
|
4384
|
-
ZodFunction: () => ZodFunction,
|
|
4385
|
-
ZodFirstPartyTypeKind: () => ZodFirstPartyTypeKind,
|
|
4386
|
-
ZodError: () => ZodError,
|
|
4387
|
-
ZodEnum: () => ZodEnum,
|
|
4388
|
-
ZodEffects: () => ZodEffects,
|
|
4389
|
-
ZodDiscriminatedUnion: () => ZodDiscriminatedUnion,
|
|
4390
|
-
ZodDefault: () => ZodDefault,
|
|
4391
|
-
ZodDate: () => ZodDate,
|
|
4392
|
-
ZodCatch: () => ZodCatch,
|
|
4393
|
-
ZodBranded: () => ZodBranded,
|
|
4394
|
-
ZodBoolean: () => ZodBoolean,
|
|
4395
|
-
ZodBigInt: () => ZodBigInt,
|
|
4396
|
-
ZodArray: () => ZodArray,
|
|
4397
|
-
ZodAny: () => ZodAny,
|
|
4398
|
-
Schema: () => ZodType,
|
|
4399
|
-
ParseStatus: () => ParseStatus,
|
|
4400
|
-
OK: () => OK,
|
|
4401
|
-
NEVER: () => NEVER,
|
|
4402
|
-
INVALID: () => INVALID,
|
|
4403
|
-
EMPTY_PATH: () => EMPTY_PATH,
|
|
4299
|
+
BRAND: () => BRAND,
|
|
4404
4300
|
DIRTY: () => DIRTY,
|
|
4405
|
-
|
|
4301
|
+
EMPTY_PATH: () => EMPTY_PATH,
|
|
4302
|
+
INVALID: () => INVALID,
|
|
4303
|
+
NEVER: () => NEVER,
|
|
4304
|
+
OK: () => OK,
|
|
4305
|
+
ParseStatus: () => ParseStatus,
|
|
4306
|
+
Schema: () => ZodType,
|
|
4307
|
+
ZodAny: () => ZodAny,
|
|
4308
|
+
ZodArray: () => ZodArray,
|
|
4309
|
+
ZodBigInt: () => ZodBigInt,
|
|
4310
|
+
ZodBoolean: () => ZodBoolean,
|
|
4311
|
+
ZodBranded: () => ZodBranded,
|
|
4312
|
+
ZodCatch: () => ZodCatch,
|
|
4313
|
+
ZodDate: () => ZodDate,
|
|
4314
|
+
ZodDefault: () => ZodDefault,
|
|
4315
|
+
ZodDiscriminatedUnion: () => ZodDiscriminatedUnion,
|
|
4316
|
+
ZodEffects: () => ZodEffects,
|
|
4317
|
+
ZodEnum: () => ZodEnum,
|
|
4318
|
+
ZodError: () => ZodError,
|
|
4319
|
+
ZodFirstPartyTypeKind: () => ZodFirstPartyTypeKind,
|
|
4320
|
+
ZodFunction: () => ZodFunction,
|
|
4321
|
+
ZodIntersection: () => ZodIntersection,
|
|
4322
|
+
ZodIssueCode: () => ZodIssueCode,
|
|
4323
|
+
ZodLazy: () => ZodLazy,
|
|
4324
|
+
ZodLiteral: () => ZodLiteral,
|
|
4325
|
+
ZodMap: () => ZodMap,
|
|
4326
|
+
ZodNaN: () => ZodNaN,
|
|
4327
|
+
ZodNativeEnum: () => ZodNativeEnum,
|
|
4328
|
+
ZodNever: () => ZodNever,
|
|
4329
|
+
ZodNull: () => ZodNull,
|
|
4330
|
+
ZodNullable: () => ZodNullable,
|
|
4331
|
+
ZodNumber: () => ZodNumber,
|
|
4332
|
+
ZodObject: () => ZodObject,
|
|
4333
|
+
ZodOptional: () => ZodOptional,
|
|
4334
|
+
ZodParsedType: () => ZodParsedType,
|
|
4335
|
+
ZodPipeline: () => ZodPipeline,
|
|
4336
|
+
ZodPromise: () => ZodPromise,
|
|
4337
|
+
ZodReadonly: () => ZodReadonly,
|
|
4338
|
+
ZodRecord: () => ZodRecord,
|
|
4339
|
+
ZodSchema: () => ZodType,
|
|
4340
|
+
ZodSet: () => ZodSet,
|
|
4341
|
+
ZodString: () => ZodString,
|
|
4342
|
+
ZodSymbol: () => ZodSymbol,
|
|
4343
|
+
ZodTransformer: () => ZodEffects,
|
|
4344
|
+
ZodTuple: () => ZodTuple,
|
|
4345
|
+
ZodType: () => ZodType,
|
|
4346
|
+
ZodUndefined: () => ZodUndefined,
|
|
4347
|
+
ZodUnion: () => ZodUnion,
|
|
4348
|
+
ZodUnknown: () => ZodUnknown,
|
|
4349
|
+
ZodVoid: () => ZodVoid,
|
|
4350
|
+
addIssueToContext: () => addIssueToContext,
|
|
4351
|
+
any: () => anyType,
|
|
4352
|
+
array: () => arrayType,
|
|
4353
|
+
bigint: () => bigIntType,
|
|
4354
|
+
boolean: () => booleanType,
|
|
4355
|
+
coerce: () => coerce,
|
|
4356
|
+
custom: () => custom,
|
|
4357
|
+
date: () => dateType,
|
|
4358
|
+
datetimeRegex: () => datetimeRegex,
|
|
4359
|
+
defaultErrorMap: () => en_default,
|
|
4360
|
+
discriminatedUnion: () => discriminatedUnionType,
|
|
4361
|
+
effect: () => effectsType,
|
|
4362
|
+
enum: () => enumType,
|
|
4363
|
+
function: () => functionType,
|
|
4364
|
+
getErrorMap: () => getErrorMap,
|
|
4365
|
+
getParsedType: () => getParsedType,
|
|
4366
|
+
instanceof: () => instanceOfType,
|
|
4367
|
+
intersection: () => intersectionType,
|
|
4368
|
+
isAborted: () => isAborted,
|
|
4369
|
+
isAsync: () => isAsync,
|
|
4370
|
+
isDirty: () => isDirty,
|
|
4371
|
+
isValid: () => isValid,
|
|
4372
|
+
late: () => late,
|
|
4373
|
+
lazy: () => lazyType,
|
|
4374
|
+
literal: () => literalType,
|
|
4375
|
+
makeIssue: () => makeIssue,
|
|
4376
|
+
map: () => mapType,
|
|
4377
|
+
nan: () => nanType,
|
|
4378
|
+
nativeEnum: () => nativeEnumType,
|
|
4379
|
+
never: () => neverType,
|
|
4380
|
+
null: () => nullType,
|
|
4381
|
+
nullable: () => nullableType,
|
|
4382
|
+
number: () => numberType,
|
|
4383
|
+
object: () => objectType,
|
|
4384
|
+
objectUtil: () => objectUtil,
|
|
4385
|
+
oboolean: () => oboolean,
|
|
4386
|
+
onumber: () => onumber,
|
|
4387
|
+
optional: () => optionalType,
|
|
4388
|
+
ostring: () => ostring,
|
|
4389
|
+
pipeline: () => pipelineType,
|
|
4390
|
+
preprocess: () => preprocessType,
|
|
4391
|
+
promise: () => promiseType,
|
|
4392
|
+
quotelessJson: () => quotelessJson,
|
|
4393
|
+
record: () => recordType,
|
|
4394
|
+
set: () => setType,
|
|
4395
|
+
setErrorMap: () => setErrorMap,
|
|
4396
|
+
strictObject: () => strictObjectType,
|
|
4397
|
+
string: () => stringType,
|
|
4398
|
+
symbol: () => symbolType,
|
|
4399
|
+
transformer: () => effectsType,
|
|
4400
|
+
tuple: () => tupleType,
|
|
4401
|
+
undefined: () => undefinedType,
|
|
4402
|
+
union: () => unionType,
|
|
4403
|
+
unknown: () => unknownType,
|
|
4404
|
+
util: () => util,
|
|
4405
|
+
void: () => voidType
|
|
4406
4406
|
});
|
|
4407
4407
|
var init_external = __esm(() => {
|
|
4408
4408
|
init_errors();
|
|
@@ -4419,44 +4419,318 @@ var init_zod = __esm(() => {
|
|
|
4419
4419
|
init_external();
|
|
4420
4420
|
});
|
|
4421
4421
|
|
|
4422
|
-
//
|
|
4423
|
-
import {
|
|
4422
|
+
// ../contracts/dist/client/transport.js
|
|
4423
|
+
import { readFileSync as readFileSync2, statSync } from "fs";
|
|
4424
4424
|
import { join as join3 } from "path";
|
|
4425
|
+
function envToken(name) {
|
|
4426
|
+
return name.toUpperCase().replace(/-/g, "_");
|
|
4427
|
+
}
|
|
4428
|
+
function clientTransportEnvKeys(name) {
|
|
4429
|
+
const envSegment = envToken(name);
|
|
4430
|
+
return {
|
|
4431
|
+
apiUrlKeys: [`HASNA_${envSegment}_API_URL`, `${envSegment}_API_URL`],
|
|
4432
|
+
apiKeyKeys: [`HASNA_${envSegment}_API_KEY`, `${envSegment}_API_KEY`]
|
|
4433
|
+
};
|
|
4434
|
+
}
|
|
4435
|
+
function credentialOverrideEnvKey(name) {
|
|
4436
|
+
return `HASNA_${envToken(name)}_API_KEY_OVERRIDE`;
|
|
4437
|
+
}
|
|
4438
|
+
function homeDir(env) {
|
|
4439
|
+
const home = env.HOME?.trim();
|
|
4440
|
+
return home ? home : null;
|
|
4441
|
+
}
|
|
4442
|
+
function credentialDiskSources(name, env) {
|
|
4443
|
+
return profileDiskSources(name, env, null);
|
|
4444
|
+
}
|
|
4445
|
+
function profileDiskSources(name, env, profile) {
|
|
4446
|
+
const home = homeDir(env);
|
|
4447
|
+
if (!home || !SAFE_APP_SLUG.test(name))
|
|
4448
|
+
return [];
|
|
4449
|
+
const stem = profile ? `${name}.${profile}` : name;
|
|
4450
|
+
const configStem = profile ? `${name}-${profile}` : name;
|
|
4451
|
+
return [
|
|
4452
|
+
join3(home, HASNA_STATE_DIR, FLEET_CREDENTIAL_DIR, `${stem}.env`),
|
|
4453
|
+
join3(home, CONFIG_DIR, CONFIG_NAMESPACE, `${configStem}-cloud.env`)
|
|
4454
|
+
];
|
|
4455
|
+
}
|
|
4456
|
+
function parseEnvFile(text) {
|
|
4457
|
+
const values = new Map;
|
|
4458
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
4459
|
+
const line = rawLine.trim();
|
|
4460
|
+
if (line.length === 0 || line.startsWith("#"))
|
|
4461
|
+
continue;
|
|
4462
|
+
const withoutExport = line.startsWith("export ") ? line.slice("export ".length).trim() : line;
|
|
4463
|
+
const equals = withoutExport.indexOf("=");
|
|
4464
|
+
if (equals <= 0)
|
|
4465
|
+
continue;
|
|
4466
|
+
const key = withoutExport.slice(0, equals).trim();
|
|
4467
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
|
|
4468
|
+
continue;
|
|
4469
|
+
let value = withoutExport.slice(equals + 1).trim();
|
|
4470
|
+
const quote = value[0];
|
|
4471
|
+
if (quote === '"' || quote === "'") {
|
|
4472
|
+
if (value.length < 2 || !value.endsWith(quote))
|
|
4473
|
+
continue;
|
|
4474
|
+
value = value.slice(1, -1);
|
|
4475
|
+
}
|
|
4476
|
+
if (value.length === 0)
|
|
4477
|
+
continue;
|
|
4478
|
+
values.set(key, value);
|
|
4479
|
+
}
|
|
4480
|
+
return values;
|
|
4481
|
+
}
|
|
4482
|
+
function readAppConfigFile(path) {
|
|
4483
|
+
let text;
|
|
4484
|
+
try {
|
|
4485
|
+
const stats = statSync(path);
|
|
4486
|
+
if (!stats.isFile() || stats.size > MAX_CREDENTIAL_FILE_BYTES)
|
|
4487
|
+
return null;
|
|
4488
|
+
text = readFileSync2(path, "utf8");
|
|
4489
|
+
} catch {
|
|
4490
|
+
return null;
|
|
4491
|
+
}
|
|
4492
|
+
return parseEnvFile(text);
|
|
4493
|
+
}
|
|
4494
|
+
function readCredentialFile(path, apiKeyKeys) {
|
|
4495
|
+
const values = readAppConfigFile(path);
|
|
4496
|
+
if (!values)
|
|
4497
|
+
return null;
|
|
4498
|
+
for (const key of apiKeyKeys) {
|
|
4499
|
+
const value = values.get(key)?.trim();
|
|
4500
|
+
if (value)
|
|
4501
|
+
return value;
|
|
4502
|
+
}
|
|
4503
|
+
return null;
|
|
4504
|
+
}
|
|
4505
|
+
function assertUsableCredential(appName, source, value) {
|
|
4506
|
+
if (!ILLEGAL_IN_HEADER_VALUE.test(value))
|
|
4507
|
+
return;
|
|
4508
|
+
throw new CredentialResolutionError(appName, `The credential from ${source} contains characters that cannot be sent in an HTTP header ` + `(a control character or non-ASCII byte). A file written with CR-only line endings is the usual ` + `cause. Rewrite that credential file with one LF-terminated KEY=value line. ` + `The value is not shown here, and is deliberately never logged.`, [source]);
|
|
4509
|
+
}
|
|
4510
|
+
function sealCredential(fields) {
|
|
4511
|
+
const { apiKey } = fields;
|
|
4512
|
+
const visible = {
|
|
4513
|
+
tier: fields.tier,
|
|
4514
|
+
source: fields.source,
|
|
4515
|
+
deliberate: fields.deliberate,
|
|
4516
|
+
deprecated: fields.deprecated,
|
|
4517
|
+
diskCandidates: Object.freeze([...fields.diskCandidates]),
|
|
4518
|
+
warning: fields.warning
|
|
4519
|
+
};
|
|
4520
|
+
const sealed = { ...visible };
|
|
4521
|
+
Object.defineProperty(sealed, "apiKey", {
|
|
4522
|
+
value: apiKey,
|
|
4523
|
+
enumerable: false,
|
|
4524
|
+
writable: false,
|
|
4525
|
+
configurable: false
|
|
4526
|
+
});
|
|
4527
|
+
Object.defineProperty(sealed, INSPECT_CUSTOM, {
|
|
4528
|
+
value: () => ({ ...visible, apiKey: "[redacted]" }),
|
|
4529
|
+
enumerable: false,
|
|
4530
|
+
writable: false,
|
|
4531
|
+
configurable: false
|
|
4532
|
+
});
|
|
4533
|
+
Object.defineProperty(sealed, CREDENTIAL_SEAL, {
|
|
4534
|
+
value: true,
|
|
4535
|
+
enumerable: false,
|
|
4536
|
+
writable: false,
|
|
4537
|
+
configurable: false
|
|
4538
|
+
});
|
|
4539
|
+
return Object.freeze(sealed);
|
|
4540
|
+
}
|
|
4541
|
+
function firstEnvValue(env, keys) {
|
|
4542
|
+
for (const key of keys) {
|
|
4543
|
+
const value = env[key]?.trim();
|
|
4544
|
+
if (value)
|
|
4545
|
+
return { key, value };
|
|
4546
|
+
}
|
|
4547
|
+
return null;
|
|
4548
|
+
}
|
|
4549
|
+
function deprecationNotified() {
|
|
4550
|
+
const host = globalThis;
|
|
4551
|
+
const existing = host[DEPRECATION_REGISTRY];
|
|
4552
|
+
if (existing instanceof Set)
|
|
4553
|
+
return existing;
|
|
4554
|
+
const created = new Set;
|
|
4555
|
+
host[DEPRECATION_REGISTRY] = created;
|
|
4556
|
+
return created;
|
|
4557
|
+
}
|
|
4558
|
+
function defaultDeprecationSink(message) {
|
|
4559
|
+
if (typeof process !== "undefined" && process.stderr) {
|
|
4560
|
+
process.stderr.write(`${message}
|
|
4561
|
+
`);
|
|
4562
|
+
}
|
|
4563
|
+
}
|
|
4564
|
+
function resolveCredential(name, env, options = {}) {
|
|
4565
|
+
const { apiKeyKeys } = clientTransportEnvKeys(name);
|
|
4566
|
+
const diskPaths = credentialDiskSources(name, env);
|
|
4567
|
+
const explicitKey = options.apiKey?.trim();
|
|
4568
|
+
if (explicitKey) {
|
|
4569
|
+
assertUsableCredential(name, "the explicit apiKey argument", explicitKey);
|
|
4570
|
+
return sealCredential({
|
|
4571
|
+
apiKey: explicitKey,
|
|
4572
|
+
tier: "argument",
|
|
4573
|
+
source: "explicit apiKey argument",
|
|
4574
|
+
deliberate: true,
|
|
4575
|
+
deprecated: false,
|
|
4576
|
+
diskCandidates: diskPaths,
|
|
4577
|
+
warning: null
|
|
4578
|
+
});
|
|
4579
|
+
}
|
|
4580
|
+
const overrideKeyName = credentialOverrideEnvKey(name);
|
|
4581
|
+
const overrideRaw = env[overrideKeyName];
|
|
4582
|
+
if (overrideRaw !== undefined) {
|
|
4583
|
+
const override = overrideRaw.trim();
|
|
4584
|
+
if (!override) {
|
|
4585
|
+
throw new CredentialResolutionError(name, `${overrideKeyName} is set but empty. It is a deliberate override, so it is not resolved around: ` + `either give it a real key or unset it to fall back to the credential on disk.`, [overrideKeyName]);
|
|
4586
|
+
}
|
|
4587
|
+
assertUsableCredential(name, overrideKeyName, override);
|
|
4588
|
+
return sealCredential({
|
|
4589
|
+
apiKey: override,
|
|
4590
|
+
tier: "override",
|
|
4591
|
+
source: overrideKeyName,
|
|
4592
|
+
deliberate: true,
|
|
4593
|
+
deprecated: false,
|
|
4594
|
+
diskCandidates: diskPaths,
|
|
4595
|
+
warning: null
|
|
4596
|
+
});
|
|
4597
|
+
}
|
|
4598
|
+
const profile = options.profile?.trim() || env[CREDENTIAL_PROFILE_ENV_KEY]?.trim();
|
|
4599
|
+
if (profile) {
|
|
4600
|
+
const profileSource = options.profile?.trim() ? "explicit profile argument" : CREDENTIAL_PROFILE_ENV_KEY;
|
|
4601
|
+
if (!SAFE_PROFILE.test(profile)) {
|
|
4602
|
+
throw new CredentialResolutionError(name, `Profile name from ${profileSource} is not usable in a path. ` + `Use letters, digits, dot, dash, or underscore.`, [profileSource]);
|
|
4603
|
+
}
|
|
4604
|
+
const paths = profileDiskSources(name, env, profile);
|
|
4605
|
+
for (const path of paths) {
|
|
4606
|
+
const value = readCredentialFile(path, apiKeyKeys);
|
|
4607
|
+
if (value) {
|
|
4608
|
+
assertUsableCredential(name, path, value);
|
|
4609
|
+
return sealCredential({
|
|
4610
|
+
apiKey: value,
|
|
4611
|
+
tier: "profile",
|
|
4612
|
+
source: path,
|
|
4613
|
+
deliberate: true,
|
|
4614
|
+
deprecated: false,
|
|
4615
|
+
diskCandidates: paths,
|
|
4616
|
+
warning: null
|
|
4617
|
+
});
|
|
4618
|
+
}
|
|
4619
|
+
}
|
|
4620
|
+
throw new CredentialResolutionError(name, `Profile '${profile}' (from ${profileSource}) has no ${apiKeyKeys[0]} for '${name}'. ` + `Looked in: ${paths.join(", ") || "<no HOME in this environment>"}. ` + `A profile names WHICH identity to use, so it is never resolved around \u2014 ` + `create the profile's credential file or unset ${CREDENTIAL_PROFILE_ENV_KEY}.`, paths);
|
|
4621
|
+
}
|
|
4622
|
+
const diskHits = diskPaths.map((path) => ({ path, value: readCredentialFile(path, apiKeyKeys) })).filter((hit) => hit.value !== null);
|
|
4623
|
+
if (diskHits.length > 0) {
|
|
4624
|
+
const winner = diskHits[0];
|
|
4625
|
+
assertUsableCredential(name, winner.path, winner.value);
|
|
4626
|
+
const divergentSources = [
|
|
4627
|
+
...diskHits.slice(1).filter((hit) => hit.value !== winner.value).map((hit) => hit.path),
|
|
4628
|
+
...(() => {
|
|
4629
|
+
const legacyHit = firstEnvValue(env, apiKeyKeys);
|
|
4630
|
+
return legacyHit && legacyHit.value !== winner.value ? [legacyHit.key] : [];
|
|
4631
|
+
})()
|
|
4632
|
+
];
|
|
4633
|
+
const warning = divergentSources.length > 0 ? `Credential sources disagree for '${name}': ${winner.path} and ` + `${divergentSources.join(", ")} hold different keys. ${winner.path} wins, because a file on ` + `disk is re-read on every call while an environment variable is a snapshot. Reconcile them \u2014 ` + `a rotation that updated only one leaves the other to fail 401 wherever it is loaded first.` : null;
|
|
4634
|
+
return sealCredential({
|
|
4635
|
+
apiKey: winner.value,
|
|
4636
|
+
tier: "disk",
|
|
4637
|
+
source: winner.path,
|
|
4638
|
+
deliberate: false,
|
|
4639
|
+
deprecated: false,
|
|
4640
|
+
diskCandidates: diskPaths,
|
|
4641
|
+
warning
|
|
4642
|
+
});
|
|
4643
|
+
}
|
|
4644
|
+
const legacy = firstEnvValue(env, apiKeyKeys);
|
|
4645
|
+
if (legacy) {
|
|
4646
|
+
assertUsableCredential(name, legacy.key, legacy.value);
|
|
4647
|
+
const where = diskPaths.length > 0 ? `Put the current key in ${diskPaths[0]} \u2014 it is re-read on every call, so rotations take effect immediately.` : `This environment has no HOME, so no credential file could be consulted at all; the disk tier is ` + `unavailable here and this process will keep using the environment snapshot.`;
|
|
4648
|
+
const message = `[${name}] DEPRECATED: the API key came from ${legacy.key} in this process's environment. ` + `Environment variables are a snapshot taken when this process started, so a shell that started ` + `before a key rotation keeps using the old key until it exits. ${where}`;
|
|
4649
|
+
const sink = options.onDeprecation ?? defaultDeprecationSink;
|
|
4650
|
+
const notified = deprecationNotified();
|
|
4651
|
+
if (!notified.has(name)) {
|
|
4652
|
+
notified.add(name);
|
|
4653
|
+
sink(message);
|
|
4654
|
+
}
|
|
4655
|
+
return sealCredential({
|
|
4656
|
+
apiKey: legacy.value,
|
|
4657
|
+
tier: "legacy-env",
|
|
4658
|
+
source: legacy.key,
|
|
4659
|
+
deliberate: false,
|
|
4660
|
+
deprecated: true,
|
|
4661
|
+
diskCandidates: diskPaths,
|
|
4662
|
+
warning: message
|
|
4663
|
+
});
|
|
4664
|
+
}
|
|
4665
|
+
return null;
|
|
4666
|
+
}
|
|
4667
|
+
var CREDENTIAL_PROFILE_ENV_KEY = "HASNA_PROFILE", CredentialResolutionError, HASNA_STATE_DIR = ".hasna", FLEET_CREDENTIAL_DIR = "cloud", CONFIG_DIR = ".config", CONFIG_NAMESPACE = "hasna", MAX_CREDENTIAL_FILE_BYTES, SAFE_APP_SLUG, SAFE_PROFILE, ILLEGAL_IN_HEADER_VALUE, INSPECT_CUSTOM, CREDENTIAL_SEAL, DEPRECATION_REGISTRY, IDEMPOTENT_METHODS, AUTHORITY_OVERRIDE_HEADERS;
|
|
4668
|
+
var init_transport = __esm(() => {
|
|
4669
|
+
CredentialResolutionError = class CredentialResolutionError extends Error {
|
|
4670
|
+
appName;
|
|
4671
|
+
attempted;
|
|
4672
|
+
constructor(appName, message, attempted) {
|
|
4673
|
+
super(message);
|
|
4674
|
+
this.name = "CredentialResolutionError";
|
|
4675
|
+
this.appName = appName;
|
|
4676
|
+
this.attempted = attempted;
|
|
4677
|
+
}
|
|
4678
|
+
};
|
|
4679
|
+
MAX_CREDENTIAL_FILE_BYTES = 64 * 1024;
|
|
4680
|
+
SAFE_APP_SLUG = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
4681
|
+
SAFE_PROFILE = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
|
|
4682
|
+
ILLEGAL_IN_HEADER_VALUE = /[^\t\x20-\x7e]/;
|
|
4683
|
+
INSPECT_CUSTOM = Symbol.for("nodejs.util.inspect.custom");
|
|
4684
|
+
CREDENTIAL_SEAL = Symbol.for("hasna:contracts:sealedCredential");
|
|
4685
|
+
DEPRECATION_REGISTRY = Symbol.for("hasna:contracts:credentialDeprecationNotices");
|
|
4686
|
+
IDEMPOTENT_METHODS = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
|
|
4687
|
+
AUTHORITY_OVERRIDE_HEADERS = new Set([
|
|
4688
|
+
"host",
|
|
4689
|
+
":authority",
|
|
4690
|
+
"forwarded",
|
|
4691
|
+
"x-forwarded-host",
|
|
4692
|
+
"x-original-host"
|
|
4693
|
+
]);
|
|
4694
|
+
});
|
|
4695
|
+
|
|
4696
|
+
// src/config.ts
|
|
4697
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync } from "fs";
|
|
4698
|
+
import { join as join4 } from "path";
|
|
4425
4699
|
import { homedir as homedir3 } from "os";
|
|
4426
4700
|
function getHooksDataDir() {
|
|
4427
4701
|
const explicit = process.env.HASNA_HOOKS_DATA_DIR ?? process.env.HOOKS_DATA_DIR;
|
|
4428
4702
|
if (explicit)
|
|
4429
4703
|
return explicit;
|
|
4430
|
-
return
|
|
4704
|
+
return join4(homedir3(), ".hasna", "hooks");
|
|
4431
4705
|
}
|
|
4432
4706
|
function getCustomHooksDir() {
|
|
4433
|
-
return
|
|
4707
|
+
return join4(getHooksDataDir(), "hooks");
|
|
4434
4708
|
}
|
|
4435
4709
|
function getLockPath() {
|
|
4436
4710
|
const explicit = process.env.HASNA_HOOKS_LOCK_PATH ?? process.env.HOOKS_LOCK_PATH;
|
|
4437
4711
|
if (explicit)
|
|
4438
4712
|
return explicit;
|
|
4439
|
-
return
|
|
4713
|
+
return join4(getHooksDataDir(), "hooks.lock");
|
|
4440
4714
|
}
|
|
4441
4715
|
function getConfigPath() {
|
|
4442
4716
|
const explicit = process.env.HASNA_HOOKS_CONFIG_PATH ?? process.env.HOOKS_CONFIG_PATH;
|
|
4443
4717
|
if (explicit)
|
|
4444
4718
|
return explicit;
|
|
4445
|
-
return
|
|
4719
|
+
return join4(getHooksDataDir(), "config.json");
|
|
4446
4720
|
}
|
|
4447
4721
|
function readConfig() {
|
|
4448
4722
|
try {
|
|
4449
4723
|
const path = getConfigPath();
|
|
4450
4724
|
if (!existsSync3(path))
|
|
4451
4725
|
return {};
|
|
4452
|
-
return JSON.parse(
|
|
4726
|
+
return JSON.parse(readFileSync3(path, "utf-8"));
|
|
4453
4727
|
} catch {
|
|
4454
4728
|
return {};
|
|
4455
4729
|
}
|
|
4456
4730
|
}
|
|
4457
4731
|
function writeConfig(config) {
|
|
4458
4732
|
const path = getConfigPath();
|
|
4459
|
-
mkdirSync2(
|
|
4733
|
+
mkdirSync2(join4(path, ".."), { recursive: true });
|
|
4460
4734
|
writeFileSync(path, JSON.stringify(config, null, 2) + `
|
|
4461
4735
|
`, "utf-8");
|
|
4462
4736
|
return path;
|
|
@@ -4469,9 +4743,11 @@ function resolveApiUrl() {
|
|
|
4469
4743
|
return cfg.api_url ? cfg.api_url.replace(/\/+$/, "") : undefined;
|
|
4470
4744
|
}
|
|
4471
4745
|
function resolveApiKey() {
|
|
4472
|
-
return
|
|
4746
|
+
return resolveCredential("hooks", process.env)?.apiKey;
|
|
4473
4747
|
}
|
|
4474
|
-
var init_config = () => {
|
|
4748
|
+
var init_config = __esm(() => {
|
|
4749
|
+
init_transport();
|
|
4750
|
+
});
|
|
4475
4751
|
|
|
4476
4752
|
// src/lib/semver.ts
|
|
4477
4753
|
var NUMERIC_ID = "(0|[1-9]\\d{0,15})", ALNUM_ID = "\\d*[A-Za-z-][0-9A-Za-z-]*", SEMVER_PATTERN;
|
|
@@ -4482,23 +4758,23 @@ var init_semver = __esm(() => {
|
|
|
4482
4758
|
// src/lib/manifest.ts
|
|
4483
4759
|
var exports_manifest = {};
|
|
4484
4760
|
__export(exports_manifest, {
|
|
4485
|
-
|
|
4486
|
-
shortManifestName: () => shortManifestName,
|
|
4487
|
-
scriptRelFor: () => scriptRelFor,
|
|
4488
|
-
resolveScript: () => resolveScript,
|
|
4489
|
-
readCustomManifest: () => readCustomManifest,
|
|
4490
|
-
parseManifest: () => parseManifest,
|
|
4491
|
-
normalizeManifestName: () => normalizeManifestName,
|
|
4492
|
-
manifestSchema: () => manifestSchema,
|
|
4493
|
-
listCustomHooks: () => listCustomHooks,
|
|
4494
|
-
isInlineScript: () => isInlineScript,
|
|
4495
|
-
customHookDir: () => customHookDir,
|
|
4496
|
-
assertContained: () => assertContained,
|
|
4761
|
+
HOOK_NAME_RE: () => HOOK_NAME_RE,
|
|
4497
4762
|
ScriptContainmentError: () => ScriptContainmentError,
|
|
4498
|
-
|
|
4763
|
+
assertContained: () => assertContained,
|
|
4764
|
+
customHookDir: () => customHookDir,
|
|
4765
|
+
isInlineScript: () => isInlineScript,
|
|
4766
|
+
listCustomHooks: () => listCustomHooks,
|
|
4767
|
+
manifestSchema: () => manifestSchema,
|
|
4768
|
+
normalizeManifestName: () => normalizeManifestName,
|
|
4769
|
+
parseManifest: () => parseManifest,
|
|
4770
|
+
readCustomManifest: () => readCustomManifest,
|
|
4771
|
+
resolveScript: () => resolveScript,
|
|
4772
|
+
scriptRelFor: () => scriptRelFor,
|
|
4773
|
+
shortManifestName: () => shortManifestName,
|
|
4774
|
+
writeCustomHook: () => writeCustomHook
|
|
4499
4775
|
});
|
|
4500
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as
|
|
4501
|
-
import { basename, dirname, isAbsolute, join as
|
|
4776
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, readdirSync as readdirSync2, realpathSync, writeFileSync as writeFileSync2 } from "fs";
|
|
4777
|
+
import { basename, dirname, isAbsolute, join as join5, normalize, resolve, sep } from "path";
|
|
4502
4778
|
function parseManifest(json) {
|
|
4503
4779
|
const parsed = JSON.parse(json);
|
|
4504
4780
|
return manifestSchema.parse(parsed);
|
|
@@ -4530,7 +4806,7 @@ function realpathNearest(path) {
|
|
|
4530
4806
|
tail.unshift(basename(cur));
|
|
4531
4807
|
cur = parent;
|
|
4532
4808
|
}
|
|
4533
|
-
return
|
|
4809
|
+
return join5(realpathSync(cur), ...tail);
|
|
4534
4810
|
}
|
|
4535
4811
|
function assertContainedReal(root, target, raw) {
|
|
4536
4812
|
const rootReal = realpathSync(root);
|
|
@@ -4563,25 +4839,25 @@ function resolveScript(manifest, manifestDir) {
|
|
|
4563
4839
|
if (isInlineScript(manifest))
|
|
4564
4840
|
return { path: scriptRelFor(manifest), content: manifest.script };
|
|
4565
4841
|
const rel = scriptRelFor(manifest);
|
|
4566
|
-
const resolved = assertContained(
|
|
4842
|
+
const resolved = assertContained(join5(manifestDir, rel), manifestDir, manifest.script);
|
|
4567
4843
|
if (!existsSync4(resolved)) {
|
|
4568
4844
|
throw new Error(`manifest script file not found: ${resolved}`);
|
|
4569
4845
|
}
|
|
4570
|
-
return { path: rel, content:
|
|
4846
|
+
return { path: rel, content: readFileSync4(resolved, "utf-8") };
|
|
4571
4847
|
}
|
|
4572
4848
|
function customHookDir(name) {
|
|
4573
|
-
return
|
|
4849
|
+
return join5(getCustomHooksDir(), shortManifestName(name));
|
|
4574
4850
|
}
|
|
4575
4851
|
function readCustomManifest(name) {
|
|
4576
4852
|
const dir = customHookDir(name);
|
|
4577
|
-
const manifestPath =
|
|
4853
|
+
const manifestPath = join5(dir, "manifest.json");
|
|
4578
4854
|
if (!existsSync4(manifestPath))
|
|
4579
4855
|
return;
|
|
4580
4856
|
try {
|
|
4581
|
-
const manifest = parseManifest(
|
|
4857
|
+
const manifest = parseManifest(readFileSync4(manifestPath, "utf-8"));
|
|
4582
4858
|
const script = resolveScript(manifest, dir);
|
|
4583
4859
|
const scriptIsInline = isInlineScript(manifest);
|
|
4584
|
-
return { manifest, scriptPath:
|
|
4860
|
+
return { manifest, scriptPath: join5(dir, script.path), scriptContent: script.content, scriptIsInline };
|
|
4585
4861
|
} catch (err) {
|
|
4586
4862
|
if (err instanceof ScriptContainmentError)
|
|
4587
4863
|
throw err;
|
|
@@ -4605,7 +4881,7 @@ function writeCustomHook(name, manifest, scriptContent, scriptRelPath) {
|
|
|
4605
4881
|
if (isAbsolute(scriptRelPath)) {
|
|
4606
4882
|
throw new ScriptContainmentError(scriptRelPath, dir);
|
|
4607
4883
|
}
|
|
4608
|
-
const scriptPath = scriptRelPath.includes("/") ?
|
|
4884
|
+
const scriptPath = scriptRelPath.includes("/") ? join5(dir, scriptRelPath) : join5(dir, scriptRelPath);
|
|
4609
4885
|
assertContainedLexical(scriptPath, dir, scriptRelPath);
|
|
4610
4886
|
mkdirSync3(dir, { recursive: true });
|
|
4611
4887
|
assertContainedReal(dir, scriptPath, scriptRelPath);
|
|
@@ -4613,7 +4889,7 @@ function writeCustomHook(name, manifest, scriptContent, scriptRelPath) {
|
|
|
4613
4889
|
if (scriptDir !== dir)
|
|
4614
4890
|
mkdirSync3(scriptDir, { recursive: true });
|
|
4615
4891
|
writeFileSync2(scriptPath, scriptContent, "utf-8");
|
|
4616
|
-
writeFileSync2(
|
|
4892
|
+
writeFileSync2(join5(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + `
|
|
4617
4893
|
`, "utf-8");
|
|
4618
4894
|
return { dir, scriptPath };
|
|
4619
4895
|
}
|
|
@@ -4652,26 +4928,26 @@ var init_manifest = __esm(() => {
|
|
|
4652
4928
|
// src/lib/store.ts
|
|
4653
4929
|
var exports_store = {};
|
|
4654
4930
|
__export(exports_store, {
|
|
4655
|
-
|
|
4656
|
-
verifyScriptHash: () => verifyScriptHash,
|
|
4657
|
-
upsertHookRecord: () => upsertHookRecord,
|
|
4658
|
-
sha256Of: () => sha256Of,
|
|
4659
|
-
sha256File: () => sha256File,
|
|
4660
|
-
setPinnedHook: () => setPinnedHook,
|
|
4661
|
-
retrustHook: () => retrustHook,
|
|
4662
|
-
removePinnedHook: () => removePinnedHook,
|
|
4663
|
-
removeHookRecord: () => removeHookRecord,
|
|
4664
|
-
removeHookFromStore: () => removeHookFromStore,
|
|
4665
|
-
readLock: () => readLock,
|
|
4666
|
-
pinInstalledHook: () => pinInstalledHook,
|
|
4667
|
-
listHookRecords: () => listHookRecords,
|
|
4668
|
-
getPinnedHook: () => getPinnedHook,
|
|
4669
|
-
getHookRecord: () => getHookRecord,
|
|
4931
|
+
LockFileError: () => LockFileError,
|
|
4670
4932
|
checkScriptHash: () => checkScriptHash,
|
|
4671
|
-
|
|
4933
|
+
getHookRecord: () => getHookRecord,
|
|
4934
|
+
getPinnedHook: () => getPinnedHook,
|
|
4935
|
+
listHookRecords: () => listHookRecords,
|
|
4936
|
+
pinInstalledHook: () => pinInstalledHook,
|
|
4937
|
+
readLock: () => readLock,
|
|
4938
|
+
removeHookFromStore: () => removeHookFromStore,
|
|
4939
|
+
removeHookRecord: () => removeHookRecord,
|
|
4940
|
+
removePinnedHook: () => removePinnedHook,
|
|
4941
|
+
retrustHook: () => retrustHook,
|
|
4942
|
+
setPinnedHook: () => setPinnedHook,
|
|
4943
|
+
sha256File: () => sha256File,
|
|
4944
|
+
sha256Of: () => sha256Of,
|
|
4945
|
+
upsertHookRecord: () => upsertHookRecord,
|
|
4946
|
+
verifyScriptHash: () => verifyScriptHash,
|
|
4947
|
+
writeLock: () => writeLock
|
|
4672
4948
|
});
|
|
4673
4949
|
import { createHash } from "crypto";
|
|
4674
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as
|
|
4950
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3, renameSync } from "fs";
|
|
4675
4951
|
import { readFile } from "fs/promises";
|
|
4676
4952
|
import { randomBytes } from "crypto";
|
|
4677
4953
|
function sha256Of(input) {
|
|
@@ -4737,7 +5013,7 @@ function readLock() {
|
|
|
4737
5013
|
return { hooks: {} };
|
|
4738
5014
|
let raw;
|
|
4739
5015
|
try {
|
|
4740
|
-
raw =
|
|
5016
|
+
raw = readFileSync5(path, "utf-8");
|
|
4741
5017
|
} catch {
|
|
4742
5018
|
throw new LockFileError(`hooks.lock (${path}) could not be read. Repair: fix permissions or move the file aside and run 'hooks sync'.`);
|
|
4743
5019
|
}
|
|
@@ -4834,7 +5110,7 @@ async function verifyScriptHash(name, scriptPath) {
|
|
|
4834
5110
|
return checkScriptHash(name, await sha256File(scriptPath));
|
|
4835
5111
|
}
|
|
4836
5112
|
function retrustHook(name, scriptPath, version, source) {
|
|
4837
|
-
const actual = sha256Of(
|
|
5113
|
+
const actual = sha256Of(readFileSync5(scriptPath));
|
|
4838
5114
|
const db = getDb();
|
|
4839
5115
|
upsertHookRecord(db, {
|
|
4840
5116
|
name,
|
|
@@ -4861,24 +5137,24 @@ var init_store = __esm(() => {
|
|
|
4861
5137
|
// src/lib/profiles.ts
|
|
4862
5138
|
var exports_profiles = {};
|
|
4863
5139
|
__export(exports_profiles, {
|
|
4864
|
-
|
|
4865
|
-
touchProfile: () => touchProfile,
|
|
4866
|
-
listProfiles: () => listProfiles,
|
|
4867
|
-
importProfiles: () => importProfiles,
|
|
4868
|
-
getProfilesDir: () => getProfilesDir,
|
|
4869
|
-
getProfile: () => getProfile,
|
|
4870
|
-
exportProfiles: () => exportProfiles,
|
|
5140
|
+
createProfile: () => createProfile,
|
|
4871
5141
|
deleteProfile: () => deleteProfile,
|
|
4872
|
-
|
|
5142
|
+
exportProfiles: () => exportProfiles,
|
|
5143
|
+
getProfile: () => getProfile,
|
|
5144
|
+
getProfilesDir: () => getProfilesDir,
|
|
5145
|
+
importProfiles: () => importProfiles,
|
|
5146
|
+
listProfiles: () => listProfiles,
|
|
5147
|
+
touchProfile: () => touchProfile,
|
|
5148
|
+
updateProfile: () => updateProfile
|
|
4873
5149
|
});
|
|
4874
|
-
import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as
|
|
4875
|
-
import { join as
|
|
5150
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync5, readdirSync as readdirSync3, rmSync as rmSync2, cpSync as cpSync2 } from "fs";
|
|
5151
|
+
import { join as join8 } from "path";
|
|
4876
5152
|
import { homedir as homedir5 } from "os";
|
|
4877
5153
|
function resolveProfilesDir() {
|
|
4878
|
-
const newDir =
|
|
4879
|
-
const oldDir =
|
|
5154
|
+
const newDir = join8(homedir5(), ".hasna", "hooks", "profiles");
|
|
5155
|
+
const oldDir = join8(homedir5(), ".hooks", "profiles");
|
|
4880
5156
|
if (!existsSync8(newDir) && existsSync8(oldDir)) {
|
|
4881
|
-
mkdirSync6(
|
|
5157
|
+
mkdirSync6(join8(homedir5(), ".hasna", "hooks"), { recursive: true });
|
|
4882
5158
|
cpSync2(oldDir, newDir, { recursive: true });
|
|
4883
5159
|
}
|
|
4884
5160
|
return newDir;
|
|
@@ -4889,7 +5165,7 @@ function ensureProfilesDir() {
|
|
|
4889
5165
|
}
|
|
4890
5166
|
}
|
|
4891
5167
|
function profilePath(id) {
|
|
4892
|
-
return
|
|
5168
|
+
return join8(PROFILES_DIR, `${id}.json`);
|
|
4893
5169
|
}
|
|
4894
5170
|
function shortUuid() {
|
|
4895
5171
|
return crypto.randomUUID().slice(0, 8);
|
|
@@ -4920,7 +5196,7 @@ function getProfile(id) {
|
|
|
4920
5196
|
try {
|
|
4921
5197
|
if (!existsSync8(path))
|
|
4922
5198
|
return null;
|
|
4923
|
-
return JSON.parse(
|
|
5199
|
+
return JSON.parse(readFileSync7(path, "utf-8"));
|
|
4924
5200
|
} catch {
|
|
4925
5201
|
return null;
|
|
4926
5202
|
}
|
|
@@ -4933,7 +5209,7 @@ function listProfiles() {
|
|
|
4933
5209
|
const profiles = [];
|
|
4934
5210
|
for (const file of files) {
|
|
4935
5211
|
try {
|
|
4936
|
-
const content =
|
|
5212
|
+
const content = readFileSync7(join8(PROFILES_DIR, file), "utf-8");
|
|
4937
5213
|
profiles.push(JSON.parse(content));
|
|
4938
5214
|
} catch {}
|
|
4939
5215
|
}
|
|
@@ -5002,8 +5278,8 @@ var init_profiles = __esm(() => {
|
|
|
5002
5278
|
});
|
|
5003
5279
|
|
|
5004
5280
|
// src/lib/hook-env.ts
|
|
5005
|
-
import { dirname as
|
|
5006
|
-
import { existsSync as existsSync10, statSync } from "fs";
|
|
5281
|
+
import { dirname as dirname5, isAbsolute as isAbsolute2 } from "path";
|
|
5282
|
+
import { existsSync as existsSync10, statSync as statSync2 } from "fs";
|
|
5007
5283
|
function isDeniedEnvName(name) {
|
|
5008
5284
|
const upper = name.toUpperCase();
|
|
5009
5285
|
if (DENY_PREFIXES.some((prefix) => upper.startsWith(prefix)))
|
|
@@ -5030,7 +5306,7 @@ function isUnsafePathEntry(entry, home) {
|
|
|
5030
5306
|
if (entry === "/var/tmp" || entry.startsWith("/var/tmp/"))
|
|
5031
5307
|
return true;
|
|
5032
5308
|
try {
|
|
5033
|
-
if ((
|
|
5309
|
+
if ((statSync2(entry).mode & 2) !== 0)
|
|
5034
5310
|
return true;
|
|
5035
5311
|
} catch {}
|
|
5036
5312
|
return false;
|
|
@@ -5039,7 +5315,7 @@ function sanitizeHookPath(pathValue, home) {
|
|
|
5039
5315
|
const baseline = PATH_BASELINE.slice();
|
|
5040
5316
|
if (process.platform === "darwin" && existsSync10("/opt/homebrew/bin"))
|
|
5041
5317
|
baseline.push("/opt/homebrew/bin");
|
|
5042
|
-
const bunDir =
|
|
5318
|
+
const bunDir = dirname5(process.execPath);
|
|
5043
5319
|
const seen = new Set;
|
|
5044
5320
|
const out = [];
|
|
5045
5321
|
for (const dir of baseline) {
|
|
@@ -5158,12 +5434,12 @@ var init_hook_env = __esm(() => {
|
|
|
5158
5434
|
// src/lib/run.ts
|
|
5159
5435
|
var exports_run = {};
|
|
5160
5436
|
__export(exports_run, {
|
|
5161
|
-
|
|
5162
|
-
|
|
5437
|
+
HookTimeoutError: () => HookTimeoutError,
|
|
5438
|
+
executeVerifiedScript: () => executeVerifiedScript
|
|
5163
5439
|
});
|
|
5164
5440
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
5165
5441
|
import { rmSync as rmSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
5166
|
-
import { basename as basename2, dirname as
|
|
5442
|
+
import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute3, join as join11 } from "path";
|
|
5167
5443
|
function extensionOf(scriptPath) {
|
|
5168
5444
|
const base = basename2(scriptPath);
|
|
5169
5445
|
const dot = base.lastIndexOf(".");
|
|
@@ -5232,9 +5508,9 @@ async function readPipeWithDeadline(stream, deadlineMs) {
|
|
|
5232
5508
|
return chunks.join("");
|
|
5233
5509
|
}
|
|
5234
5510
|
async function executeVerifiedScript(options) {
|
|
5235
|
-
const scriptDir =
|
|
5511
|
+
const scriptDir = dirname6(options.scriptPath);
|
|
5236
5512
|
const interpreter = interpreterFor(options.name, options.scriptPath, options.content);
|
|
5237
|
-
const tempPath =
|
|
5513
|
+
const tempPath = join11(scriptDir, `.hooks-verified-${randomBytes2(12).toString("hex")}.${interpreter.tempExt}`);
|
|
5238
5514
|
let proc = null;
|
|
5239
5515
|
let timedOut = false;
|
|
5240
5516
|
const timeoutTimer = options.timeout ? setTimeout(() => {
|
|
@@ -5321,10 +5597,10 @@ var init_run = __esm(() => {
|
|
|
5321
5597
|
// src/lib/db-writer.ts
|
|
5322
5598
|
var exports_db_writer = {};
|
|
5323
5599
|
__export(exports_db_writer, {
|
|
5324
|
-
|
|
5325
|
-
resolveEventType: () => resolveEventType,
|
|
5600
|
+
normalizeEventType: () => normalizeEventType,
|
|
5326
5601
|
recordHookRun: () => recordHookRun,
|
|
5327
|
-
|
|
5602
|
+
resolveEventType: () => resolveEventType,
|
|
5603
|
+
writeHookEvent: () => writeHookEvent
|
|
5328
5604
|
});
|
|
5329
5605
|
function nanoid2() {
|
|
5330
5606
|
return crypto.randomUUID().replace(/-/g, "").slice(0, 21);
|
|
@@ -6223,6 +6499,16 @@ var HOOKS = [
|
|
|
6223
6499
|
matcher: "",
|
|
6224
6500
|
tags: ["errors", "failures", "logging", "debugging"]
|
|
6225
6501
|
},
|
|
6502
|
+
{
|
|
6503
|
+
name: "spiral-detector",
|
|
6504
|
+
displayName: "Spiral Detector",
|
|
6505
|
+
description: "Interrupts sessions after five consecutive identical command failures",
|
|
6506
|
+
version: "0.1.0",
|
|
6507
|
+
category: "Observability",
|
|
6508
|
+
event: "PostToolUse",
|
|
6509
|
+
matcher: "",
|
|
6510
|
+
tags: ["spiral", "failures", "retries", "safety", "interrupt"]
|
|
6511
|
+
},
|
|
6226
6512
|
{
|
|
6227
6513
|
name: "stylescheck",
|
|
6228
6514
|
displayName: "Styles Check",
|
|
@@ -6388,21 +6674,21 @@ function getHook(name) {
|
|
|
6388
6674
|
return HOOKS.find((h) => h.name === name);
|
|
6389
6675
|
}
|
|
6390
6676
|
// src/lib/installer.ts
|
|
6391
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as
|
|
6392
|
-
import { join as
|
|
6677
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync6, rmSync, writeFileSync as writeFileSync4 } from "fs";
|
|
6678
|
+
import { join as join7, dirname as dirname3 } from "path";
|
|
6393
6679
|
import { homedir as homedir4 } from "os";
|
|
6394
6680
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6395
6681
|
|
|
6396
6682
|
// src/lib/resolve.ts
|
|
6397
|
-
import { dirname as dirname2, join as
|
|
6683
|
+
import { dirname as dirname2, join as join6 } from "path";
|
|
6398
6684
|
import { existsSync as existsSync5 } from "fs";
|
|
6399
6685
|
import { fileURLToPath } from "url";
|
|
6400
6686
|
init_manifest();
|
|
6401
6687
|
function resolveBundledHooksDir(moduleDir = dirname2(fileURLToPath(import.meta.url))) {
|
|
6402
|
-
const sourceLayout =
|
|
6403
|
-
if (existsSync5(
|
|
6688
|
+
const sourceLayout = join6(moduleDir, "..", "..", "hooks");
|
|
6689
|
+
if (existsSync5(join6(sourceLayout, "hook-gitguard")))
|
|
6404
6690
|
return sourceLayout;
|
|
6405
|
-
return
|
|
6691
|
+
return join6(moduleDir, "..", "hooks");
|
|
6406
6692
|
}
|
|
6407
6693
|
function resolveHookDir(name) {
|
|
6408
6694
|
const custom2 = readCustomManifest(name);
|
|
@@ -6413,10 +6699,10 @@ function resolveHookDir(name) {
|
|
|
6413
6699
|
return;
|
|
6414
6700
|
const short = shortManifestName(name);
|
|
6415
6701
|
const bundledBase = resolveBundledHooksDir();
|
|
6416
|
-
const direct =
|
|
6702
|
+
const direct = join6(bundledBase, short);
|
|
6417
6703
|
if (existsSync5(direct))
|
|
6418
6704
|
return direct;
|
|
6419
|
-
const prefixed =
|
|
6705
|
+
const prefixed = join6(bundledBase, `hook-${short}`);
|
|
6420
6706
|
return existsSync5(prefixed) ? prefixed : undefined;
|
|
6421
6707
|
}
|
|
6422
6708
|
function resolveScriptPath(name) {
|
|
@@ -6426,7 +6712,7 @@ function resolveScriptPath(name) {
|
|
|
6426
6712
|
const dir = resolveHookDir(name);
|
|
6427
6713
|
if (!dir)
|
|
6428
6714
|
return;
|
|
6429
|
-
const script =
|
|
6715
|
+
const script = join6(dir, "src", "hook.ts");
|
|
6430
6716
|
return existsSync5(script) ? script : undefined;
|
|
6431
6717
|
}
|
|
6432
6718
|
function resolveHookMeta(name) {
|
|
@@ -6482,7 +6768,7 @@ function resolveHook(name) {
|
|
|
6482
6768
|
init_manifest();
|
|
6483
6769
|
init_store();
|
|
6484
6770
|
var __dirname2 = dirname3(fileURLToPath2(import.meta.url));
|
|
6485
|
-
var HOOKS_DIR = existsSync7(
|
|
6771
|
+
var HOOKS_DIR = existsSync7(join7(__dirname2, "..", "..", "hooks", "hook-gitguard")) ? join7(__dirname2, "..", "..", "hooks") : join7(__dirname2, "..", "hooks");
|
|
6486
6772
|
function normalizeHookName(name) {
|
|
6487
6773
|
return name.startsWith("hook-") ? name : `hook-${name}`;
|
|
6488
6774
|
}
|
|
@@ -6552,16 +6838,16 @@ function getSettingsPath(scope = "global", target = "claude", codewithConfigPath
|
|
|
6552
6838
|
return globalOverride;
|
|
6553
6839
|
const dir = getTargetSettingsDir(target);
|
|
6554
6840
|
if (scope === "project") {
|
|
6555
|
-
return target === "codewith" ?
|
|
6841
|
+
return target === "codewith" ? join7(process.cwd(), dir, "config.toml") : join7(process.cwd(), dir, "settings.json");
|
|
6556
6842
|
}
|
|
6557
|
-
return target === "codewith" ?
|
|
6843
|
+
return target === "codewith" ? join7(homedir4(), dir, "config.toml") : join7(homedir4(), dir, "settings.json");
|
|
6558
6844
|
}
|
|
6559
6845
|
function getHookPath(name) {
|
|
6560
6846
|
const shortName = shortHookName(name);
|
|
6561
6847
|
const resolved = resolveHookDir(shortName);
|
|
6562
6848
|
if (resolved)
|
|
6563
6849
|
return resolved;
|
|
6564
|
-
return
|
|
6850
|
+
return join7(HOOKS_DIR, normalizeHookName(shortName));
|
|
6565
6851
|
}
|
|
6566
6852
|
function hookExists(name) {
|
|
6567
6853
|
return existsSync7(getHookPath(name));
|
|
@@ -6570,7 +6856,7 @@ function readSettings(scope = "global", target = "claude") {
|
|
|
6570
6856
|
const path = getSettingsPath(scope, target);
|
|
6571
6857
|
try {
|
|
6572
6858
|
if (existsSync7(path)) {
|
|
6573
|
-
return JSON.parse(
|
|
6859
|
+
return JSON.parse(readFileSync6(path, "utf-8"));
|
|
6574
6860
|
}
|
|
6575
6861
|
} catch (error) {
|
|
6576
6862
|
console.warn(`[hooks] Failed to read settings at ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -6669,7 +6955,7 @@ function buildCodewithTomlFragment(name, profile) {
|
|
|
6669
6955
|
function readCodewithConfig(scope, configPath) {
|
|
6670
6956
|
const path = getSettingsPath(scope, "codewith", configPath);
|
|
6671
6957
|
try {
|
|
6672
|
-
return existsSync7(path) ?
|
|
6958
|
+
return existsSync7(path) ? readFileSync6(path, "utf-8") : "";
|
|
6673
6959
|
} catch {
|
|
6674
6960
|
return "";
|
|
6675
6961
|
}
|
|
@@ -6679,7 +6965,7 @@ function appendCodewithFragment(fragment, scope, configPath) {
|
|
|
6679
6965
|
const dir = dirname3(path);
|
|
6680
6966
|
if (!existsSync7(dir))
|
|
6681
6967
|
mkdirSync5(dir, { recursive: true });
|
|
6682
|
-
const existing = existsSync7(path) ?
|
|
6968
|
+
const existing = existsSync7(path) ? readFileSync6(path, "utf-8") : "";
|
|
6683
6969
|
const sep2 = existing.trim() ? `
|
|
6684
6970
|
|
|
6685
6971
|
` : "";
|
|
@@ -6724,7 +7010,7 @@ function installForTarget(name, scope, overwrite, target, profile, codewithMode
|
|
|
6724
7010
|
target,
|
|
6725
7011
|
fragment,
|
|
6726
7012
|
applied: false,
|
|
6727
|
-
note: "Codewith install is fragment-only by default;
|
|
7013
|
+
note: "Codewith install is fragment-only by default; configs should own applying this TOML."
|
|
6728
7014
|
};
|
|
6729
7015
|
}
|
|
6730
7016
|
if (!codewithConfigPath) {
|
|
@@ -6751,7 +7037,7 @@ function installForTarget(name, scope, overwrite, target, profile, codewithMode
|
|
|
6751
7037
|
fragment,
|
|
6752
7038
|
applied: true,
|
|
6753
7039
|
configPath: path,
|
|
6754
|
-
note: "Direct Codewith config write was explicitly requested; prefer
|
|
7040
|
+
note: "Direct Codewith config write was explicitly requested; prefer configs for managed machines."
|
|
6755
7041
|
};
|
|
6756
7042
|
} catch (error) {
|
|
6757
7043
|
return {
|
|
@@ -6983,7 +7269,7 @@ function codewithHasHookEntry(name, scope = "global") {
|
|
|
6983
7269
|
if (!existsSync7(path))
|
|
6984
7270
|
return false;
|
|
6985
7271
|
try {
|
|
6986
|
-
return codewithHasHookEntryInText(
|
|
7272
|
+
return codewithHasHookEntryInText(readFileSync6(path, "utf-8"), name);
|
|
6987
7273
|
} catch {
|
|
6988
7274
|
return false;
|
|
6989
7275
|
}
|
|
@@ -7015,7 +7301,7 @@ function uninstallHook(name, scope = "global", target = "claude") {
|
|
|
7015
7301
|
if (target === "codewith" || target === "all") {
|
|
7016
7302
|
const codewithPath = getSettingsPath(scope, "codewith");
|
|
7017
7303
|
if (existsSync7(codewithPath)) {
|
|
7018
|
-
const before =
|
|
7304
|
+
const before = readFileSync6(codewithPath, "utf-8");
|
|
7019
7305
|
const after = removeCodewithHookEntry(before, shortName);
|
|
7020
7306
|
if (after.removed) {
|
|
7021
7307
|
writeFileSync4(codewithPath, after.text, "utf-8");
|
|
@@ -7059,20 +7345,20 @@ init_profiles();
|
|
|
7059
7345
|
init_config();
|
|
7060
7346
|
init_store();
|
|
7061
7347
|
init_manifest();
|
|
7062
|
-
import { existsSync as existsSync11, readFileSync as
|
|
7348
|
+
import { existsSync as existsSync11, readFileSync as readFileSync11 } from "fs";
|
|
7063
7349
|
|
|
7064
7350
|
// src/lib/custom-install.ts
|
|
7065
7351
|
init_config();
|
|
7066
7352
|
init_manifest();
|
|
7067
|
-
import { existsSync as existsSync9, mkdirSync as mkdirSync7, mkdtempSync, readFileSync as
|
|
7068
|
-
import { join as
|
|
7353
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync7, mkdtempSync, readFileSync as readFileSync8, rmSync as rmSync3 } from "fs";
|
|
7354
|
+
import { join as join9 } from "path";
|
|
7069
7355
|
import { tmpdir } from "os";
|
|
7070
7356
|
function manifestAt(dir) {
|
|
7071
|
-
const path =
|
|
7357
|
+
const path = join9(dir, "manifest.json");
|
|
7072
7358
|
if (!existsSync9(path))
|
|
7073
7359
|
return;
|
|
7074
7360
|
try {
|
|
7075
|
-
return parseManifest(
|
|
7361
|
+
return parseManifest(readFileSync8(path, "utf-8"));
|
|
7076
7362
|
} catch {
|
|
7077
7363
|
return;
|
|
7078
7364
|
}
|
|
@@ -7091,8 +7377,8 @@ function writeFromManifest(manifest, srcDir, source, kind) {
|
|
|
7091
7377
|
return { name, version: manifest.version, kind, source, dir, scriptPath };
|
|
7092
7378
|
}
|
|
7093
7379
|
async function cloneToTemp(gitUrl) {
|
|
7094
|
-
const tmp = mkdtempSync(
|
|
7095
|
-
const proc = Bun.spawn(["git", "clone", "--depth", "1", "--quiet", gitUrl,
|
|
7380
|
+
const tmp = mkdtempSync(join9(tmpdir(), "hooks-install-"));
|
|
7381
|
+
const proc = Bun.spawn(["git", "clone", "--depth", "1", "--quiet", gitUrl, join9(tmp, "repo")], {
|
|
7096
7382
|
stdout: "pipe",
|
|
7097
7383
|
stderr: "pipe"
|
|
7098
7384
|
});
|
|
@@ -7105,7 +7391,7 @@ async function cloneToTemp(gitUrl) {
|
|
|
7105
7391
|
rmSync3(tmp, { recursive: true, force: true });
|
|
7106
7392
|
throw new Error(`git clone failed (${exitCode}): ${stderr.trim()}`);
|
|
7107
7393
|
}
|
|
7108
|
-
return
|
|
7394
|
+
return join9(tmp, "repo");
|
|
7109
7395
|
}
|
|
7110
7396
|
function isGitUrl(value) {
|
|
7111
7397
|
return value.startsWith("git@") || value.startsWith("ssh://") || value.startsWith("file://") || value.endsWith(".git");
|
|
@@ -7133,7 +7419,7 @@ async function installCustomSource(source) {
|
|
|
7133
7419
|
throw new Error(`No such local path: ${source}`);
|
|
7134
7420
|
}
|
|
7135
7421
|
if (source.endsWith("manifest.json")) {
|
|
7136
|
-
const manifest = parseManifest(
|
|
7422
|
+
const manifest = parseManifest(readFileSync8(source, "utf-8"));
|
|
7137
7423
|
return writeFromManifest(manifest, source.substring(0, source.lastIndexOf("/")), source, "local");
|
|
7138
7424
|
}
|
|
7139
7425
|
return copyManifestDir(source, source, "local");
|
|
@@ -7164,7 +7450,7 @@ async function installManifestUrl(url) {
|
|
|
7164
7450
|
}
|
|
7165
7451
|
// src/lib/sync.ts
|
|
7166
7452
|
import { createHash as createHash2 } from "crypto";
|
|
7167
|
-
import { readFileSync as
|
|
7453
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
7168
7454
|
init_config();
|
|
7169
7455
|
init_db();
|
|
7170
7456
|
init_store();
|
|
@@ -7173,7 +7459,7 @@ function sha256OfText(text) {
|
|
|
7173
7459
|
return createHash2("sha256").update(text).digest("hex");
|
|
7174
7460
|
}
|
|
7175
7461
|
function sha256OfPath(path) {
|
|
7176
|
-
return createHash2("sha256").update(
|
|
7462
|
+
return createHash2("sha256").update(readFileSync9(path)).digest("hex");
|
|
7177
7463
|
}
|
|
7178
7464
|
function collectBundledCatalog() {
|
|
7179
7465
|
const out = [];
|
|
@@ -7428,6 +7714,9 @@ async function fetchPinnedHook(name, version, apiUrl) {
|
|
|
7428
7714
|
init_manifest();
|
|
7429
7715
|
init_store();
|
|
7430
7716
|
init_config();
|
|
7717
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
7718
|
+
import { dirname as dirname4, join as join10 } from "path";
|
|
7719
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
7431
7720
|
|
|
7432
7721
|
// src/lib/secure-compare.ts
|
|
7433
7722
|
function secureEqual(a, b) {
|
|
@@ -7442,11 +7731,170 @@ function secureEqual(a, b) {
|
|
|
7442
7731
|
return diff === 0;
|
|
7443
7732
|
}
|
|
7444
7733
|
|
|
7734
|
+
// src/openapi.ts
|
|
7735
|
+
var openApiDocument = {
|
|
7736
|
+
openapi: "3.1.0",
|
|
7737
|
+
info: {
|
|
7738
|
+
title: "Hooks Registry API",
|
|
7739
|
+
version: "0.1.0",
|
|
7740
|
+
description: "Local registry HTTP API for @hasna/hooks: catalog, lock, and artifact reads are open; publishing (PUT /api/v1/hooks) requires the API key (authorization: Bearer or x-api-key)."
|
|
7741
|
+
},
|
|
7742
|
+
servers: [{ url: "/" }],
|
|
7743
|
+
paths: {
|
|
7744
|
+
"/health": {
|
|
7745
|
+
get: {
|
|
7746
|
+
summary: "Liveness probe",
|
|
7747
|
+
operationId: "getHealth",
|
|
7748
|
+
responses: {
|
|
7749
|
+
"200": {
|
|
7750
|
+
description: "Service is alive",
|
|
7751
|
+
content: {
|
|
7752
|
+
"application/json": {
|
|
7753
|
+
schema: { type: "object", additionalProperties: true }
|
|
7754
|
+
}
|
|
7755
|
+
}
|
|
7756
|
+
}
|
|
7757
|
+
}
|
|
7758
|
+
}
|
|
7759
|
+
},
|
|
7760
|
+
"/ready": {
|
|
7761
|
+
get: {
|
|
7762
|
+
summary: "Readiness probe \u2014 the hooks.lock store is readable",
|
|
7763
|
+
operationId: "getReady",
|
|
7764
|
+
responses: {
|
|
7765
|
+
"200": { description: "Lock store is readable" },
|
|
7766
|
+
"503": { description: "Lock store is not readable" }
|
|
7767
|
+
}
|
|
7768
|
+
}
|
|
7769
|
+
},
|
|
7770
|
+
"/version": {
|
|
7771
|
+
get: {
|
|
7772
|
+
summary: "Package version",
|
|
7773
|
+
operationId: "getVersion",
|
|
7774
|
+
responses: {
|
|
7775
|
+
"200": {
|
|
7776
|
+
description: "The installed @hasna/hooks version",
|
|
7777
|
+
content: {
|
|
7778
|
+
"application/json": {
|
|
7779
|
+
schema: { type: "object", properties: { version: { type: "string" } } }
|
|
7780
|
+
}
|
|
7781
|
+
}
|
|
7782
|
+
}
|
|
7783
|
+
}
|
|
7784
|
+
}
|
|
7785
|
+
},
|
|
7786
|
+
"/openapi.json": {
|
|
7787
|
+
get: {
|
|
7788
|
+
summary: "This OpenAPI document",
|
|
7789
|
+
operationId: "getOpenApi",
|
|
7790
|
+
responses: { "200": { description: "OpenAPI 3.1 document" } }
|
|
7791
|
+
}
|
|
7792
|
+
},
|
|
7793
|
+
"/api/v1/catalog": {
|
|
7794
|
+
get: {
|
|
7795
|
+
summary: "List the full hook catalog with versions and sha256",
|
|
7796
|
+
operationId: "getCatalog",
|
|
7797
|
+
responses: {
|
|
7798
|
+
"200": {
|
|
7799
|
+
description: "Catalog entries",
|
|
7800
|
+
content: {
|
|
7801
|
+
"application/json": {
|
|
7802
|
+
schema: {
|
|
7803
|
+
type: "object",
|
|
7804
|
+
properties: {
|
|
7805
|
+
hooks: {
|
|
7806
|
+
type: "array",
|
|
7807
|
+
items: {
|
|
7808
|
+
type: "object",
|
|
7809
|
+
properties: {
|
|
7810
|
+
name: { type: "string" },
|
|
7811
|
+
version: { type: "string" },
|
|
7812
|
+
sha256: { type: "string" },
|
|
7813
|
+
events: { type: "array", items: { type: "string" } }
|
|
7814
|
+
}
|
|
7815
|
+
}
|
|
7816
|
+
}
|
|
7817
|
+
}
|
|
7818
|
+
}
|
|
7819
|
+
}
|
|
7820
|
+
}
|
|
7821
|
+
}
|
|
7822
|
+
}
|
|
7823
|
+
}
|
|
7824
|
+
},
|
|
7825
|
+
"/api/v1/lock": {
|
|
7826
|
+
get: {
|
|
7827
|
+
summary: "Read the published hooks.lock with per-hook versions",
|
|
7828
|
+
operationId: "getLock",
|
|
7829
|
+
responses: { "200": { description: "The lock file" } }
|
|
7830
|
+
}
|
|
7831
|
+
},
|
|
7832
|
+
"/api/v1/hooks/{name}/{version}": {
|
|
7833
|
+
get: {
|
|
7834
|
+
summary: "Fetch one hook's manifest and script",
|
|
7835
|
+
operationId: "getHookArtifact",
|
|
7836
|
+
parameters: [
|
|
7837
|
+
{ name: "name", in: "path", required: true, schema: { type: "string" } },
|
|
7838
|
+
{ name: "version", in: "path", required: true, schema: { type: "string" } }
|
|
7839
|
+
],
|
|
7840
|
+
responses: {
|
|
7841
|
+
"200": {
|
|
7842
|
+
description: "Hook artifact; x-hook-sha256 header carries the script hash"
|
|
7843
|
+
},
|
|
7844
|
+
"400": { description: "Invalid URL encoding or semver version" },
|
|
7845
|
+
"404": { description: "Hook not found locally" }
|
|
7846
|
+
}
|
|
7847
|
+
}
|
|
7848
|
+
},
|
|
7849
|
+
"/api/v1/hooks": {
|
|
7850
|
+
put: {
|
|
7851
|
+
summary: "Publish/retrust a hook (requires the API key)",
|
|
7852
|
+
operationId: "putHook",
|
|
7853
|
+
requestBody: {
|
|
7854
|
+
required: true,
|
|
7855
|
+
content: {
|
|
7856
|
+
"application/json": {
|
|
7857
|
+
schema: {
|
|
7858
|
+
type: "object",
|
|
7859
|
+
properties: { name: { type: "string" }, version: { type: "string" } }
|
|
7860
|
+
}
|
|
7861
|
+
}
|
|
7862
|
+
}
|
|
7863
|
+
},
|
|
7864
|
+
responses: {
|
|
7865
|
+
"200": { description: "Hook retrusted and pin updated" },
|
|
7866
|
+
"401": { description: "Missing or invalid API key" },
|
|
7867
|
+
"404": { description: "Hook not found in local store" },
|
|
7868
|
+
"409": { description: "Version mismatch with the local store" }
|
|
7869
|
+
},
|
|
7870
|
+
security: [{ apiKey: [] }]
|
|
7871
|
+
}
|
|
7872
|
+
}
|
|
7873
|
+
},
|
|
7874
|
+
components: {
|
|
7875
|
+
securitySchemes: {
|
|
7876
|
+
apiKey: {
|
|
7877
|
+
type: "http",
|
|
7878
|
+
scheme: "bearer",
|
|
7879
|
+
description: "The registry API key (HASNA_HOOKS_API_KEY / HOOKS_API_KEY)."
|
|
7880
|
+
}
|
|
7881
|
+
}
|
|
7882
|
+
}
|
|
7883
|
+
};
|
|
7884
|
+
|
|
7445
7885
|
// src/serve.ts
|
|
7446
7886
|
init_semver();
|
|
7447
7887
|
var DEFAULT_SERVE_PORT = 39428;
|
|
7448
7888
|
var SERVE_HOST = "127.0.0.1";
|
|
7449
7889
|
var SERVE_SERVICE_NAME = "hooks-registry";
|
|
7890
|
+
function packageVersion() {
|
|
7891
|
+
try {
|
|
7892
|
+
const pkgPath = join10(dirname4(fileURLToPath3(import.meta.url)), "..", "package.json");
|
|
7893
|
+
return JSON.parse(readFileSync10(pkgPath, "utf-8")).version || "0.0.0";
|
|
7894
|
+
} catch {
|
|
7895
|
+
return "0.0.0";
|
|
7896
|
+
}
|
|
7897
|
+
}
|
|
7450
7898
|
async function buildCatalog() {
|
|
7451
7899
|
const byName = new Map;
|
|
7452
7900
|
for (const meta of HOOKS) {
|
|
@@ -7519,6 +7967,20 @@ function handleServeRequest(req, apiKey) {
|
|
|
7519
7967
|
if (url.pathname === "/health" && req.method === "GET") {
|
|
7520
7968
|
return Promise.resolve(json({ status: "ok", name: SERVE_SERVICE_NAME }));
|
|
7521
7969
|
}
|
|
7970
|
+
if (url.pathname === "/ready" && req.method === "GET") {
|
|
7971
|
+
try {
|
|
7972
|
+
readLock();
|
|
7973
|
+
return Promise.resolve(json({ status: "ready", name: SERVE_SERVICE_NAME }));
|
|
7974
|
+
} catch {
|
|
7975
|
+
return Promise.resolve(json({ status: "not-ready", name: SERVE_SERVICE_NAME }, 503));
|
|
7976
|
+
}
|
|
7977
|
+
}
|
|
7978
|
+
if (url.pathname === "/version" && req.method === "GET") {
|
|
7979
|
+
return Promise.resolve(json({ version: packageVersion() }));
|
|
7980
|
+
}
|
|
7981
|
+
if (url.pathname === "/openapi.json" && req.method === "GET") {
|
|
7982
|
+
return Promise.resolve(json(openApiDocument));
|
|
7983
|
+
}
|
|
7522
7984
|
if (url.pathname === "/api/v1/catalog" && req.method === "GET") {
|
|
7523
7985
|
return buildCatalog().then((catalog) => json({ hooks: catalog }));
|
|
7524
7986
|
}
|
|
@@ -7693,7 +8155,7 @@ async function runHook(name, input, options = {}) {
|
|
|
7693
8155
|
if (!script)
|
|
7694
8156
|
throw new Error(`Hook script not found: ${name}`);
|
|
7695
8157
|
const { sha256Of: sha256Of2, checkScriptHash: checkScriptHash2 } = await Promise.resolve().then(() => (init_store(), exports_store));
|
|
7696
|
-
const content =
|
|
8158
|
+
const content = readFileSync11(script);
|
|
7697
8159
|
const check = checkScriptHash2(name, sha256Of2(content));
|
|
7698
8160
|
if (!check.ok) {
|
|
7699
8161
|
throw new Error(`Hook '${name}' script changed since it was trusted (sha256 ${check.expected} != ${check.actual}). Run 'hooks trust ${name}' to trust the new content.`);
|
|
@@ -7782,102 +8244,102 @@ async function runHook(name, input, options = {}) {
|
|
|
7782
8244
|
return { output, stderr: stderrText, exitCode };
|
|
7783
8245
|
}
|
|
7784
8246
|
export {
|
|
7785
|
-
|
|
7786
|
-
writeCustomHook,
|
|
7787
|
-
writeConfig,
|
|
7788
|
-
verifyScriptHash,
|
|
7789
|
-
upsertHookRecord,
|
|
7790
|
-
updateProfile,
|
|
7791
|
-
uninstallHook,
|
|
7792
|
-
touchProfile,
|
|
7793
|
-
syncHooks,
|
|
7794
|
-
storageSync,
|
|
7795
|
-
storagePush,
|
|
7796
|
-
storagePull,
|
|
7797
|
-
startServeServer,
|
|
7798
|
-
sha256Of,
|
|
7799
|
-
sha256File,
|
|
7800
|
-
setPinnedHook,
|
|
7801
|
-
searchHooks,
|
|
7802
|
-
runStorageMigrations,
|
|
7803
|
-
runHook,
|
|
7804
|
-
retrustHook,
|
|
7805
|
-
resolveTables,
|
|
7806
|
-
resolveScriptPath,
|
|
7807
|
-
resolveScript,
|
|
7808
|
-
resolveHookMeta,
|
|
7809
|
-
resolveHookDir,
|
|
7810
|
-
resolveHook,
|
|
7811
|
-
resolveApiUrl,
|
|
7812
|
-
resolveApiKey,
|
|
7813
|
-
removeProjectHook,
|
|
7814
|
-
removePinnedHook,
|
|
7815
|
-
removeHookRecord,
|
|
7816
|
-
removeHookFromStore,
|
|
7817
|
-
removeHook,
|
|
7818
|
-
readLock,
|
|
7819
|
-
readCustomManifest,
|
|
7820
|
-
readConfig,
|
|
7821
|
-
provisionCloudflareResources,
|
|
7822
|
-
planSync,
|
|
7823
|
-
pinInstalledHook,
|
|
7824
|
-
parseStorageTables,
|
|
7825
|
-
parseManifest,
|
|
7826
|
-
listProjectHooks,
|
|
7827
|
-
listProfiles,
|
|
7828
|
-
listHookRecords,
|
|
7829
|
-
listCustomHooks,
|
|
7830
|
-
isEventSupported,
|
|
7831
|
-
isCustomSource,
|
|
7832
|
-
installHooksForProject,
|
|
7833
|
-
installHooks,
|
|
7834
|
-
installHookForProject,
|
|
7835
|
-
installHook,
|
|
7836
|
-
installCustomSource,
|
|
7837
|
-
importProfiles,
|
|
7838
|
-
hookExists,
|
|
7839
|
-
handleServeRequest,
|
|
7840
|
-
getSyncMetaAll,
|
|
7841
|
-
getStorageStatus,
|
|
7842
|
-
getStoragePg,
|
|
7843
|
-
getStorageDatabaseUrl,
|
|
7844
|
-
getStorageDatabaseEnvName,
|
|
7845
|
-
getStorageDatabaseEnv,
|
|
7846
|
-
getStorageBackend,
|
|
7847
|
-
getSettingsPath,
|
|
7848
|
-
getRegisteredHooksForTarget,
|
|
7849
|
-
getRegisteredHooks,
|
|
7850
|
-
getProfilesDir,
|
|
7851
|
-
getProfile,
|
|
7852
|
-
getPinnedHook,
|
|
7853
|
-
getLockPath,
|
|
7854
|
-
getInstalledHooks,
|
|
7855
|
-
getHooksDataDir,
|
|
7856
|
-
getHooksByCategory,
|
|
7857
|
-
getHookRecord,
|
|
7858
|
-
getHookPath,
|
|
7859
|
-
getHookEvents,
|
|
7860
|
-
getHook,
|
|
7861
|
-
getCustomHooksDir,
|
|
7862
|
-
getConfigPath,
|
|
7863
|
-
fetchPinnedHook,
|
|
7864
|
-
exportProfiles,
|
|
7865
|
-
deleteProfile,
|
|
7866
|
-
createProfile,
|
|
7867
|
-
checkScriptHash,
|
|
7868
|
-
buildCodewithTomlFragment,
|
|
7869
|
-
STORAGE_TABLES,
|
|
7870
|
-
STORAGE_DATABASE_ENV,
|
|
7871
|
-
STORAGE_BACKEND_ENV,
|
|
7872
|
-
STORAGE_BACKENDS,
|
|
7873
|
-
RETIRED_STORAGE_MODE_ENV,
|
|
7874
|
-
HOOK_EVENTS,
|
|
7875
|
-
HOOKS_STORAGE_TABLES,
|
|
7876
|
-
HOOKS_STORAGE_FALLBACK_ENV,
|
|
7877
|
-
HOOKS_STORAGE_ENV,
|
|
7878
|
-
HOOKS_STORAGE_BACKEND_FALLBACK_ENV,
|
|
7879
|
-
HOOKS_STORAGE_BACKEND_ENV,
|
|
7880
|
-
HOOKS,
|
|
8247
|
+
CATEGORIES,
|
|
7881
8248
|
DEFAULT_SERVE_PORT,
|
|
7882
|
-
|
|
8249
|
+
HOOKS,
|
|
8250
|
+
HOOKS_STORAGE_BACKEND_ENV,
|
|
8251
|
+
HOOKS_STORAGE_BACKEND_FALLBACK_ENV,
|
|
8252
|
+
HOOKS_STORAGE_ENV,
|
|
8253
|
+
HOOKS_STORAGE_FALLBACK_ENV,
|
|
8254
|
+
HOOKS_STORAGE_TABLES,
|
|
8255
|
+
HOOK_EVENTS,
|
|
8256
|
+
RETIRED_STORAGE_MODE_ENV,
|
|
8257
|
+
STORAGE_BACKENDS,
|
|
8258
|
+
STORAGE_BACKEND_ENV,
|
|
8259
|
+
STORAGE_DATABASE_ENV,
|
|
8260
|
+
STORAGE_TABLES,
|
|
8261
|
+
buildCodewithTomlFragment,
|
|
8262
|
+
checkScriptHash,
|
|
8263
|
+
createProfile,
|
|
8264
|
+
deleteProfile,
|
|
8265
|
+
exportProfiles,
|
|
8266
|
+
fetchPinnedHook,
|
|
8267
|
+
getConfigPath,
|
|
8268
|
+
getCustomHooksDir,
|
|
8269
|
+
getHook,
|
|
8270
|
+
getHookEvents,
|
|
8271
|
+
getHookPath,
|
|
8272
|
+
getHookRecord,
|
|
8273
|
+
getHooksByCategory,
|
|
8274
|
+
getHooksDataDir,
|
|
8275
|
+
getInstalledHooks,
|
|
8276
|
+
getLockPath,
|
|
8277
|
+
getPinnedHook,
|
|
8278
|
+
getProfile,
|
|
8279
|
+
getProfilesDir,
|
|
8280
|
+
getRegisteredHooks,
|
|
8281
|
+
getRegisteredHooksForTarget,
|
|
8282
|
+
getSettingsPath,
|
|
8283
|
+
getStorageBackend,
|
|
8284
|
+
getStorageDatabaseEnv,
|
|
8285
|
+
getStorageDatabaseEnvName,
|
|
8286
|
+
getStorageDatabaseUrl,
|
|
8287
|
+
getStoragePg,
|
|
8288
|
+
getStorageStatus,
|
|
8289
|
+
getSyncMetaAll,
|
|
8290
|
+
handleServeRequest,
|
|
8291
|
+
hookExists,
|
|
8292
|
+
importProfiles,
|
|
8293
|
+
installCustomSource,
|
|
8294
|
+
installHook,
|
|
8295
|
+
installHookForProject,
|
|
8296
|
+
installHooks,
|
|
8297
|
+
installHooksForProject,
|
|
8298
|
+
isCustomSource,
|
|
8299
|
+
isEventSupported,
|
|
8300
|
+
listCustomHooks,
|
|
8301
|
+
listHookRecords,
|
|
8302
|
+
listProfiles,
|
|
8303
|
+
listProjectHooks,
|
|
8304
|
+
parseManifest,
|
|
8305
|
+
parseStorageTables,
|
|
8306
|
+
pinInstalledHook,
|
|
8307
|
+
planSync,
|
|
8308
|
+
provisionCloudflareResources,
|
|
8309
|
+
readConfig,
|
|
8310
|
+
readCustomManifest,
|
|
8311
|
+
readLock,
|
|
8312
|
+
removeHook,
|
|
8313
|
+
removeHookFromStore,
|
|
8314
|
+
removeHookRecord,
|
|
8315
|
+
removePinnedHook,
|
|
8316
|
+
removeProjectHook,
|
|
8317
|
+
resolveApiKey,
|
|
8318
|
+
resolveApiUrl,
|
|
8319
|
+
resolveHook,
|
|
8320
|
+
resolveHookDir,
|
|
8321
|
+
resolveHookMeta,
|
|
8322
|
+
resolveScript,
|
|
8323
|
+
resolveScriptPath,
|
|
8324
|
+
resolveTables,
|
|
8325
|
+
retrustHook,
|
|
8326
|
+
runHook,
|
|
8327
|
+
runStorageMigrations,
|
|
8328
|
+
searchHooks,
|
|
8329
|
+
setPinnedHook,
|
|
8330
|
+
sha256File,
|
|
8331
|
+
sha256Of,
|
|
8332
|
+
startServeServer,
|
|
8333
|
+
storagePull,
|
|
8334
|
+
storagePush,
|
|
8335
|
+
storageSync,
|
|
8336
|
+
syncHooks,
|
|
8337
|
+
touchProfile,
|
|
8338
|
+
uninstallHook,
|
|
8339
|
+
updateProfile,
|
|
8340
|
+
upsertHookRecord,
|
|
8341
|
+
verifyScriptHash,
|
|
8342
|
+
writeConfig,
|
|
8343
|
+
writeCustomHook,
|
|
8344
|
+
writeLock
|
|
7883
8345
|
};
|