@hasna/hooks 0.8.0 → 0.9.1
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 +33 -5
- package/bin/hooks-mcp.js +4862 -0
- package/bin/index.js +592 -170
- package/bin/serve.js +33 -10
- package/dist/db/index.d.ts +8 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +2870 -2709
- package/dist/lib/db-writer.d.ts +30 -1
- package/dist/lib/installer.d.ts +40 -1
- package/dist/lib/local-opt-in.d.ts +26 -0
- package/dist/lib/registration.d.ts +75 -0
- package/dist/lib/registry.d.ts +14 -0
- package/dist/lib/sync.d.ts +17 -1
- package/dist/sdk/index.d.ts +103 -0
- package/dist/sdk/index.js +1050 -0
- package/dist/storage.js +15 -1
- package/hooks/hook-agent-rules-version-check/README.md +1 -1
- package/hooks/hook-trash-guard/README.md +147 -0
- package/hooks/hook-trash-guard/package.json +12 -0
- package/hooks/hook-trash-guard/src/hook.ts +1142 -0
- package/package.json +10 -3
- package/scripts/validate-package.ts +31 -4
- package/hooks/codewith-native-common.test.ts +0 -1935
- package/hooks/hook-affected-tests/tsconfig.json +0 -25
- package/hooks/hook-agent-rules-version-check/src/hook.test.ts +0 -104
- package/hooks/hook-agent-rules-version-check/tsconfig.json +0 -25
- package/hooks/hook-announce-start/tsconfig.json +0 -25
- package/hooks/hook-announce-stop/tsconfig.json +0 -25
- package/hooks/hook-autoformat/tsconfig.json +0 -25
- package/hooks/hook-branchprotect/tsconfig.json +0 -25
- package/hooks/hook-checkbugs/tsconfig.json +0 -15
- package/hooks/hook-checkdocs/tsconfig.json +0 -15
- package/hooks/hook-checkfiles/tsconfig.json +0 -15
- package/hooks/hook-checklint/tsconfig.json +0 -15
- package/hooks/hook-checkpoint/tsconfig.json +0 -25
- package/hooks/hook-checksecurity/tsconfig.json +0 -15
- package/hooks/hook-checktasks/tsconfig.json +0 -20
- package/hooks/hook-checktests/tsconfig.json +0 -15
- package/hooks/hook-conflict-detect/tsconfig.json +0 -25
- package/hooks/hook-contextrefresh/tsconfig.json +0 -25
- package/hooks/hook-desktopnotify/tsconfig.json +0 -25
- package/hooks/hook-dm-inject/tsconfig.json +0 -25
- package/hooks/hook-envsetup/tsconfig.json +0 -25
- package/hooks/hook-failure-to-task/tsconfig.json +0 -25
- package/hooks/hook-filelock/tsconfig.json +0 -25
- package/hooks/hook-fleet-blockers-gate/src/hook.test.ts +0 -302
- package/hooks/hook-fleet-blockers-gate/tsconfig.json +0 -25
- package/hooks/hook-fleet-catchup/src/hook.test.ts +0 -156
- package/hooks/hook-fleet-catchup/tsconfig.json +0 -25
- package/hooks/hook-gitguard/tsconfig.json +0 -25
- package/hooks/hook-knowledge-context/src/hook.test.ts +0 -379
- package/hooks/hook-packageage/tsconfig.json +0 -25
- package/hooks/hook-permissionguard/tsconfig.json +0 -25
- package/hooks/hook-phonenotify/tsconfig.json +0 -25
- package/hooks/hook-precompact/tsconfig.json +0 -25
- package/hooks/hook-protectfiles/tsconfig.json +0 -25
- package/hooks/hook-scanoutput/src/hook.test.ts +0 -217
- package/hooks/hook-spiral-detector/src/hook.test.ts +0 -72
- package/hooks/hook-stylescheck/tsconfig.json +0 -25
- package/hooks/hook-typecheck-gate/tsconfig.json +0 -25
- package/hooks/hook-workspace-repos-guard/src/hook.test.ts +0 -466
- package/hooks/hook-workspace-repos-guard/tsconfig.json +0 -21
- package/hooks/mention-context/src/hook.test.ts +0 -68
package/dist/lib/db-writer.d.ts
CHANGED
|
@@ -1,8 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared hook DB writer — single write path for all observability hooks.
|
|
3
3
|
* Never throws: errors are written to stderr only.
|
|
4
|
+
*
|
|
5
|
+
* GATED (hasna/apps#1720 fail-closed ruling, W6 2026-09-11). This used to be
|
|
6
|
+
* the one ungated path to ~/.hasna/hooks/hooks.db: every `hooks run`, every
|
|
7
|
+
* MCP run tool and four bundled hook runtimes called it, and it created the
|
|
8
|
+
* store on first write with no credential decision at all — a silent local
|
|
9
|
+
* default. It now writes ONLY when the environment selects the on-box store
|
|
10
|
+
* through the explicit opt-in (HASNA_HOOKS_LOCAL=1 / HOOKS_LOCAL=1, answered
|
|
11
|
+
* from the env dictionary alone — no Keychain or disk read on the per-event
|
|
12
|
+
* hot path). On the hosted route (a process-wide refusal installed by the CLI
|
|
13
|
+
* gate / MCP startup, or any authority variable set in the env) and in an
|
|
14
|
+
* unconfigured environment it prints ONE stderr line naming the opt-in and
|
|
15
|
+
* writes nothing: the hosted registry has no event route to port to.
|
|
16
|
+
*/
|
|
17
|
+
import type { HookEventRow } from "../db/schema.js";
|
|
18
|
+
import type { HooksLocalOptInEnv } from "./resolver-types.js";
|
|
19
|
+
/** Where a hook event may land: the on-box store, or nowhere (with the reason). */
|
|
20
|
+
export type HookEventSink = {
|
|
21
|
+
kind: "local";
|
|
22
|
+
} | {
|
|
23
|
+
kind: "refused";
|
|
24
|
+
reason: string;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Decide the event sink from the env dictionary and the process-wide store
|
|
28
|
+
* refusal ONLY — never the resolver, so agents' per-event `hooks run` and the
|
|
29
|
+
* bundled hook children never pay a `security` spawn here (the CLI gate
|
|
30
|
+
* already decided the route for the parent process).
|
|
4
31
|
*/
|
|
5
|
-
|
|
32
|
+
export declare function resolveHookEventSink(env?: HooksLocalOptInEnv): HookEventSink;
|
|
33
|
+
/** Reset the once-per-process sink refusal notice. Test seam only. */
|
|
34
|
+
export declare function __resetHookEventSinkNotice(): void;
|
|
6
35
|
export type HookEventInput = Omit<HookEventRow, "id" | "timestamp"> & {
|
|
7
36
|
timestamp?: string;
|
|
8
37
|
};
|
package/dist/lib/installer.d.ts
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* Hooks run directly from the globally installed @hasna/hooks package.
|
|
10
10
|
* No files are copied. The settings entry points to `hooks run <name>`.
|
|
11
11
|
*/
|
|
12
|
-
import { type HookEvent } from "./registry.js";
|
|
12
|
+
import { type HookEvent, type HookMeta } from "./registry.js";
|
|
13
|
+
import { type StaleRegistration } from "./registration.js";
|
|
13
14
|
export type Scope = "global" | "project";
|
|
14
15
|
export type Target = "claude" | "gemini" | "codewith" | "all";
|
|
15
16
|
type SingleTarget = Exclude<Target, "all">;
|
|
@@ -47,6 +48,18 @@ export declare function hookExists(name: string): boolean;
|
|
|
47
48
|
/** Whether a hook's event can be registered for the given target */
|
|
48
49
|
export declare function isEventSupported(internalEvent: HookEvent, target: SingleTarget): boolean;
|
|
49
50
|
export declare function buildCodewithTomlFragment(name: string, profile?: string): string;
|
|
51
|
+
/**
|
|
52
|
+
* The one conflict class that BLOCKS an install: two PreToolUse hooks on
|
|
53
|
+
* overlapping matchers that both rewrite the tool input
|
|
54
|
+
* (`hookSpecificOutput.updatedInput`). The harness keeps ONE rewrite per tool
|
|
55
|
+
* call — last writer wins — so whichever guard loses is silently disarmed
|
|
56
|
+
* with no error anywhere. `trash-guard` is the first such hook.
|
|
57
|
+
*
|
|
58
|
+
* Every other conflict stays advisory: it still installs, with the warning.
|
|
59
|
+
*/
|
|
60
|
+
export declare function detectRewriteConflict(name: string, scope: Scope, target: SingleTarget,
|
|
61
|
+
/** Injected for tests: the rewrite claim is a registry concern, the overlap is not. */
|
|
62
|
+
lookup?: (hookName: string) => HookMeta | undefined): string | undefined;
|
|
50
63
|
export declare function installHook(name: string, options?: InstallOptions): InstallResult;
|
|
51
64
|
export declare function installHooks(names: string[], options?: InstallOptions): InstallResult[];
|
|
52
65
|
export declare function getRegisteredHooksForTarget(scope?: Scope, target?: SingleTarget): string[];
|
|
@@ -54,6 +67,32 @@ export declare function getRegisteredHooks(scope?: Scope): string[];
|
|
|
54
67
|
/** @deprecated Use getRegisteredHooks instead */
|
|
55
68
|
export declare const getInstalledHooks: typeof getRegisteredHooks;
|
|
56
69
|
export declare function removeHook(name: string, scope?: Scope, target?: Target): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Every settings registration for the scope whose hook name does not resolve.
|
|
72
|
+
*
|
|
73
|
+
* A stale registration makes the agent spawn `hooks run <name>` on every event
|
|
74
|
+
* it is wired to, and each spawn fails — the failure is invisible to `hooks
|
|
75
|
+
* install`/`hooks remove` because nothing re-checks names already written.
|
|
76
|
+
*/
|
|
77
|
+
export declare function scanStaleRegistrations(scope?: Scope): StaleRegistration[];
|
|
78
|
+
export interface PruneStaleResult {
|
|
79
|
+
/** Settings file edited. */
|
|
80
|
+
file: string;
|
|
81
|
+
/** The registrations removed from it. */
|
|
82
|
+
removed: StaleRegistration[];
|
|
83
|
+
/** Sibling backup of the pre-edit bytes (`<settings>.bak`). */
|
|
84
|
+
backupPath: string;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Remove every stale registration the scope carries, and nothing else.
|
|
88
|
+
*
|
|
89
|
+
* Edits only the entries whose own command is stale: sibling hooks in the same
|
|
90
|
+
* entry, unrelated event keys, matchers and every non-hook settings key are
|
|
91
|
+
* preserved, the file stays valid JSON, and a `.bak` sibling is written before
|
|
92
|
+
* the edit. Idempotent — a second call finds nothing stale and writes nothing.
|
|
93
|
+
* A registration whose hook resolves is never touched.
|
|
94
|
+
*/
|
|
95
|
+
export declare function pruneStaleRegistrations(scope?: Scope): PruneStaleResult[];
|
|
57
96
|
export interface UninstallResult {
|
|
58
97
|
name: string;
|
|
59
98
|
removed: boolean;
|
|
@@ -22,6 +22,32 @@ export declare function hooksAuthorityEnvKeys(): string[];
|
|
|
22
22
|
export declare function hasHooksEnvAuthorityIntent(env?: HooksLocalOptInEnv): boolean;
|
|
23
23
|
/** True when this environment should be served by the on-box bundled store. */
|
|
24
24
|
export declare function selectsHooksLocalStore(env?: HooksLocalOptInEnv): boolean;
|
|
25
|
+
/** The credential tiers every fail-closed line names, in chain order. Never a value. */
|
|
26
|
+
export declare const HOOKS_CREDENTIAL_TIERS = "the Keychain item hasna.credentials.hooks.api-key, ~/.hasna/hooks/config/credentials, or HASNA_HOOKS_API_KEY";
|
|
27
|
+
/** The opt-in every refusal names, canonical spelling first. */
|
|
28
|
+
export declare const HOOKS_LOCAL_OPT_IN_HINT = "HASNA_HOOKS_LOCAL=1 (alias HOOKS_LOCAL=1)";
|
|
29
|
+
/**
|
|
30
|
+
* The ONE line an unconfigured client surface prints before exiting non-zero
|
|
31
|
+
* (hasna/apps#1720 ruling, W6 target state): the stable code, the tiers
|
|
32
|
+
* consulted, the opt-in, and the refusal — no credential value, no local
|
|
33
|
+
* default. The CLI gate, the MCP server and the SDK all print this spelling.
|
|
34
|
+
*/
|
|
35
|
+
export declare function hooksFailClosedLine(detail?: string): string;
|
|
36
|
+
/**
|
|
37
|
+
* The refusal every local-only verb / tool returns on the HOSTED route: the
|
|
38
|
+
* on-box SQLite store is a different dataset from the registry authority the
|
|
39
|
+
* run is routed to, so it is never opened — not even to answer an empty
|
|
40
|
+
* result. Installed process-wide through `refuseLocalStore()` in
|
|
41
|
+
* `src/db/index.ts`. Mirrors todos' `REMOTE_COMMAND_UNSUPPORTED`.
|
|
42
|
+
*/
|
|
43
|
+
export declare function hooksHostedRouteLocalStoreRefusal(authority: string): string;
|
|
44
|
+
/**
|
|
45
|
+
* The line the hook-event writer prints (once per process) when it has no
|
|
46
|
+
* sink: the hosted registry has no event route, so without the explicit
|
|
47
|
+
* local opt-in there is nowhere to record the event — and it is NOT written
|
|
48
|
+
* to a silently created hooks.db.
|
|
49
|
+
*/
|
|
50
|
+
export declare function hooksEventSinkRefusal(): string;
|
|
25
51
|
/**
|
|
26
52
|
* The environment as the resolver should see it: every authority/credential
|
|
27
53
|
* variable that is DECLARED BUT BLANK removed.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings registration checks shared by the CLI doctor and the MCP
|
|
3
|
+
* hooks_doctor.
|
|
4
|
+
*
|
|
5
|
+
* Event and matcher are separate settings fields — settings.hooks[event] is
|
|
6
|
+
* an array of { matcher?, hooks: [...] } entries — so a composite event such
|
|
7
|
+
* as 'PreToolUse:Bash' must be split before lookup, never used as a key.
|
|
8
|
+
*/
|
|
9
|
+
/** A settings registration whose hook name cannot resolve. */
|
|
10
|
+
export interface StaleRegistration {
|
|
11
|
+
/** Settings file the registration lives in. */
|
|
12
|
+
file: string;
|
|
13
|
+
/** Settings event key holding the entry (PreToolUse, …). */
|
|
14
|
+
event: string;
|
|
15
|
+
/** The registered hook name that does not resolve. */
|
|
16
|
+
hook: string;
|
|
17
|
+
/** The raw command string from the settings entry. */
|
|
18
|
+
command: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Find settings registrations whose hook does not resolve.
|
|
22
|
+
*
|
|
23
|
+
* A registration is a `hooks run <name>` (or legacy `hook-<name>`) command;
|
|
24
|
+
* it is stale when `resolveHook(name)` finds no bundled, custom or stored
|
|
25
|
+
* hook — exactly the lookup `hooks run` performs, so a stale registration
|
|
26
|
+
* fails on every tool call it is wired to. Direct-path wiring outside the
|
|
27
|
+
* installer's own command forms is reported by `countSettingsWiring`, never
|
|
28
|
+
* treated as a hook registration.
|
|
29
|
+
*/
|
|
30
|
+
export declare function findStaleRegistrations(settings: Record<string, unknown>, file: string, resolves?: (name: string) => boolean): StaleRegistration[];
|
|
31
|
+
/**
|
|
32
|
+
* Whether two tool matchers can select the same tool call. Deliberately loose
|
|
33
|
+
* (identical, or one a substring of the other) — a false positive costs one
|
|
34
|
+
* advisory warning, a false negative costs a silently disarmed guard.
|
|
35
|
+
*/
|
|
36
|
+
export declare function matchersOverlap(a: string, b: string): boolean;
|
|
37
|
+
/** The metadata slice `findRewriteOverlaps` needs from a hook. */
|
|
38
|
+
export interface RewriteHookInfo {
|
|
39
|
+
matcher?: string;
|
|
40
|
+
event?: string;
|
|
41
|
+
events?: string[];
|
|
42
|
+
rewritesInput?: boolean;
|
|
43
|
+
}
|
|
44
|
+
/** Two installed hooks that both rewrite the tool input on the same tool. */
|
|
45
|
+
export interface RewriteOverlap {
|
|
46
|
+
hooks: [string, string];
|
|
47
|
+
event: string;
|
|
48
|
+
matchers: [string, string];
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Installed hooks that both rewrite the tool input
|
|
52
|
+
* (`hookSpecificOutput.updatedInput`) on an overlapping PreToolUse matcher.
|
|
53
|
+
*
|
|
54
|
+
* This is a real defect, not a style nit: the harness applies ONE rewrite per
|
|
55
|
+
* tool call, so one of the two guards is silently disarmed. The installer
|
|
56
|
+
* refuses the pairing up front; doctor reports it for settings files it did
|
|
57
|
+
* not write (hand-edited, or written before the rule existed).
|
|
58
|
+
*/
|
|
59
|
+
export declare function findRewriteOverlaps(names: string[], lookup: (name: string) => RewriteHookInfo | undefined): RewriteOverlap[];
|
|
60
|
+
/**
|
|
61
|
+
* Whether a hook is registered in a settings file.
|
|
62
|
+
*
|
|
63
|
+
* An entry matches when it carries a `hooks run <name>` command and, when the
|
|
64
|
+
* hook declares a matcher, the entry's matcher is absent (fires on all tools)
|
|
65
|
+
* or consistent with the hook's matcher (equal, or one regex matches the
|
|
66
|
+
* other).
|
|
67
|
+
*/
|
|
68
|
+
/**
|
|
69
|
+
* Count every raw hook entry wired in a settings file — including
|
|
70
|
+
* direct-path entries that never went through the CLI (`command` values that
|
|
71
|
+
* are not `hooks run <name>`). Doctor reports this count alongside the
|
|
72
|
+
* registered count so its "healthy" verdict names its bounds (P2-16b).
|
|
73
|
+
*/
|
|
74
|
+
export declare function countSettingsWiring(settings: Record<string, unknown>): number;
|
|
75
|
+
export declare function hookRegisteredInSettings(settings: Record<string, unknown>, name: string, eventSpec: string, hookMatcher: string): boolean;
|
package/dist/lib/registry.d.ts
CHANGED
|
@@ -13,6 +13,20 @@ export interface HookMeta {
|
|
|
13
13
|
events?: HookEvent[];
|
|
14
14
|
matcher: string;
|
|
15
15
|
tags: string[];
|
|
16
|
+
/**
|
|
17
|
+
* This hook rewrites the tool input (`hookSpecificOutput.updatedInput`).
|
|
18
|
+
* The agent harness keeps ONE rewrite per tool call — last writer wins — so
|
|
19
|
+
* two input-rewriting hooks on overlapping PreToolUse matchers silently
|
|
20
|
+
* disarm each other. Install refuses that pairing, and `hooks doctor`
|
|
21
|
+
* reports it.
|
|
22
|
+
*/
|
|
23
|
+
rewritesInput?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Timeout (seconds) written into the agent's settings entry for this hook.
|
|
26
|
+
* The harness default is 600s, and a TIMED-OUT HOOK DOES NOT BLOCK: a guard
|
|
27
|
+
* that needs a verdict on stdout must declare a timeout it can meet.
|
|
28
|
+
*/
|
|
29
|
+
timeoutSeconds?: number;
|
|
16
30
|
}
|
|
17
31
|
export declare const CATEGORIES: readonly ["Git Safety", "Code Quality", "Security", "Notifications", "Context Management", "Workflow Automation", "Environment", "Permissions", "Observability", "Agent Teams"];
|
|
18
32
|
export type Category = (typeof CATEGORIES)[number];
|
package/dist/lib/sync.d.ts
CHANGED
|
@@ -92,7 +92,8 @@ export interface PinnedHookInstall {
|
|
|
92
92
|
sha256: string;
|
|
93
93
|
source: string;
|
|
94
94
|
source_ref: string;
|
|
95
|
-
artifact
|
|
95
|
+
/** The registry artifact for remote pins; null for bundled-registry pins. */
|
|
96
|
+
artifact: ArtifactResponse | null;
|
|
96
97
|
scriptPath: string;
|
|
97
98
|
}
|
|
98
99
|
/**
|
|
@@ -107,4 +108,19 @@ export interface PinnedHookInstall {
|
|
|
107
108
|
* are first-class, never rejected as "not the latest".
|
|
108
109
|
*/
|
|
109
110
|
export declare function fetchPinnedHook(name: string, version: string, apiUrl: string, apiKey: string): Promise<PinnedHookInstall>;
|
|
111
|
+
/**
|
|
112
|
+
* Pin an exact hook version from the BUNDLED registry (the on-box catalog).
|
|
113
|
+
* Powers `hooks install <name>@<version>` / `hooks update <name>@<version>`
|
|
114
|
+
* under the explicit local opt-in (HASNA_HOOKS_LOCAL=1) — the only route on
|
|
115
|
+
* which `resolveHooksTransport` returns `mode: "local"`; with nothing
|
|
116
|
+
* configured the CLI gate has already failed closed, and on the hosted route
|
|
117
|
+
* the remote registry answers the pin. The exact version named must be the
|
|
118
|
+
* bundled one, and the script stays at its bundled path — the pin (lock + DB
|
|
119
|
+
* record) is the trust anchor, exactly as local `hooks sync` pins the catalog.
|
|
120
|
+
* Carried from hasna/apps#1888 (its good half).
|
|
121
|
+
*
|
|
122
|
+
* A name or version the bundled registry does not carry is a DATA error with
|
|
123
|
+
* the available version named — the same shape as a remote pin miss.
|
|
124
|
+
*/
|
|
125
|
+
export declare function installPinnedFromBundled(name: string, version: string): Promise<PinnedHookInstall>;
|
|
110
126
|
export {};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@hasna/hooks/sdk` — the hosted hooks registry client (package-surfaces
|
|
3
|
+
* rule: one package, four surfaces; this is the importable one).
|
|
4
|
+
*
|
|
5
|
+
* SELF-CONTAINED. The built bundle (`dist/sdk/index.js`, `bun build --target
|
|
6
|
+
* bun`) imports node builtins only: the `@hasna/contracts/client` credential
|
|
7
|
+
* seam is inlined at build time, and nothing here reaches `bun:sqlite` or the
|
|
8
|
+
* on-box store — the SDK has no local mode. `src/sdk/index.test.ts` pins both.
|
|
9
|
+
*
|
|
10
|
+
* FAIL CLOSED (hasna/apps#1720). `createHooksClient()` resolves the registry
|
|
11
|
+
* authority and credential through the ONE @hasna/contracts chain (env pair →
|
|
12
|
+
* Keychain item `hasna.credentials.hooks.api-key` → `~/.hasna/hooks/config/
|
|
13
|
+
* credentials`), as a strict pair, fresh per call. With nothing resolved it
|
|
14
|
+
* THROWS the `REMOTE_API_*` diagnostic naming the tiers and the opt-in; it
|
|
15
|
+
* never returns a client that answers from a local file. The explicit local
|
|
16
|
+
* opt-in (`HASNA_HOOKS_LOCAL=1`) is refused here by name: the SDK is a hosted
|
|
17
|
+
* client and the on-box store is the CLI's, opt-in only.
|
|
18
|
+
*/
|
|
19
|
+
import { hooksRegistryOrigin, type HooksRemoteAuthority, type HooksTransportOptions } from "../lib/transport.js";
|
|
20
|
+
import type { HooksCredentialOptions, HooksLocalOptInEnv } from "../lib/resolver-types.js";
|
|
21
|
+
export type { HooksRemoteAuthority, HooksCredentialOptions, HooksLocalOptInEnv };
|
|
22
|
+
export { hooksRegistryOrigin };
|
|
23
|
+
/** One row of `GET /api/v1/catalog` — the registry's latest version of a hook. */
|
|
24
|
+
export interface HooksRegistryCatalogEntry {
|
|
25
|
+
name: string;
|
|
26
|
+
version: string;
|
|
27
|
+
sha256: string;
|
|
28
|
+
events: string[];
|
|
29
|
+
description: string;
|
|
30
|
+
source: string;
|
|
31
|
+
versions: string[];
|
|
32
|
+
}
|
|
33
|
+
/** One pin of `GET /api/v1/lock` — the exact version the registry wants clients on. */
|
|
34
|
+
export interface HooksRegistryLockEntry {
|
|
35
|
+
version: string;
|
|
36
|
+
sha256: string;
|
|
37
|
+
source?: string;
|
|
38
|
+
versions?: string[];
|
|
39
|
+
}
|
|
40
|
+
export interface HooksRegistryLock {
|
|
41
|
+
hooks: Record<string, HooksRegistryLockEntry>;
|
|
42
|
+
}
|
|
43
|
+
/** `GET /api/v1/hooks/:name/:version` — a versioned artifact (manifest + script). */
|
|
44
|
+
export interface HooksRegistryArtifact {
|
|
45
|
+
manifest: {
|
|
46
|
+
name: string;
|
|
47
|
+
version: string;
|
|
48
|
+
description?: string;
|
|
49
|
+
events: string[];
|
|
50
|
+
script: string;
|
|
51
|
+
script_kind?: "inline" | "file";
|
|
52
|
+
args?: string[];
|
|
53
|
+
timeout_ms?: number;
|
|
54
|
+
};
|
|
55
|
+
script: string;
|
|
56
|
+
/** The registry's `x-hook-sha256` header for the served row, when present. */
|
|
57
|
+
sha256: string | null;
|
|
58
|
+
}
|
|
59
|
+
export interface HooksClientOptions {
|
|
60
|
+
/** Injectable environment (tests). Defaults to `process.env`. */
|
|
61
|
+
env?: HooksLocalOptInEnv;
|
|
62
|
+
/** Tier-1 credential inputs and the injectable `security` runner. */
|
|
63
|
+
credentials?: HooksCredentialOptions;
|
|
64
|
+
/** Injectable fetch (tests). Defaults to the global `fetch`. */
|
|
65
|
+
fetch?: typeof fetch;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Resolve the hosted authority the SDK will talk to. Throws — never returns a
|
|
69
|
+
* local fallback — when nothing resolves, and refuses the local opt-in by
|
|
70
|
+
* name: there is no SDK surface for the on-box store.
|
|
71
|
+
*/
|
|
72
|
+
export declare function resolveHooksSdkAuthority(env?: HooksLocalOptInEnv, options?: Omit<HooksTransportOptions, "notice">): HooksRemoteAuthority;
|
|
73
|
+
/**
|
|
74
|
+
* Hosted registry client. Holds the resolved credential privately and sends
|
|
75
|
+
* it only to the authority it was resolved together with (strict pair).
|
|
76
|
+
*/
|
|
77
|
+
export declare class HooksClient {
|
|
78
|
+
#private;
|
|
79
|
+
/** Registry origin WITHOUT `/v1` — the registry routes hang off `<origin>/api/v1`. */
|
|
80
|
+
readonly origin: string;
|
|
81
|
+
/** The resolver's `<origin>/v1` authority, for diagnostics. */
|
|
82
|
+
readonly v1BaseUrl: string;
|
|
83
|
+
/** WHERE the credential came from (env NAME, Keychain reference, file PATH). Never a value. */
|
|
84
|
+
readonly apiKeySource: string | null;
|
|
85
|
+
/** WHERE the authority came from. Never a value. */
|
|
86
|
+
readonly apiUrlSource: string | null;
|
|
87
|
+
constructor(authority: HooksRemoteAuthority, options?: {
|
|
88
|
+
fetch?: typeof fetch;
|
|
89
|
+
});
|
|
90
|
+
/** Build the request URL for a registry route (exposed so callers can log WHERE without a key). */
|
|
91
|
+
urlFor(path: string): string;
|
|
92
|
+
health(): Promise<{
|
|
93
|
+
status: string;
|
|
94
|
+
name: string;
|
|
95
|
+
}>;
|
|
96
|
+
catalog(): Promise<HooksRegistryCatalogEntry[]>;
|
|
97
|
+
lock(): Promise<HooksRegistryLock>;
|
|
98
|
+
artifact(name: string, version: string): Promise<HooksRegistryArtifact>;
|
|
99
|
+
private request;
|
|
100
|
+
private getJson;
|
|
101
|
+
}
|
|
102
|
+
/** Resolve the hosted authority (fail closed) and return a client bound to it. */
|
|
103
|
+
export declare function createHooksClient(options?: HooksClientOptions): HooksClient;
|