@guilz-dev/belay 0.9.3 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -1
- package/dist/adapters/codex/hooks.d.ts +1 -0
- package/dist/adapters/codex/hooks.js +3 -0
- package/dist/adapters/cursor/dispatcher-generation.d.ts +3 -0
- package/dist/adapters/cursor/dispatcher-generation.js +5 -0
- package/dist/adapters/cursor/hook-dispatch-entry.d.ts +7 -0
- package/dist/adapters/cursor/hook-dispatch-entry.js +126 -0
- package/dist/adapters/cursor/hook-router.d.ts +23 -0
- package/dist/adapters/cursor/hook-router.js +266 -0
- package/dist/adapters/cursor/hooks.d.ts +4 -0
- package/dist/adapters/cursor/hooks.js +99 -8
- package/dist/adapters/cursor/routing-layout.d.ts +5 -0
- package/dist/adapters/cursor/routing-layout.js +29 -0
- package/dist/adapters/cursor/runtime-entry.d.ts +17 -0
- package/dist/adapters/cursor/runtime-entry.js +39 -39
- package/dist/adapters/layouts/scope.d.ts +5 -0
- package/dist/adapters/layouts/scope.js +71 -0
- package/dist/adapters/shared/gate-runtime.js +1 -0
- package/dist/bundle/claude-runtime.mjs +1932 -1740
- package/dist/bundle/codex-runtime.mjs +1934 -1742
- package/dist/bundle/cursor-dispatcher.mjs +443 -0
- package/dist/bundle/cursor-runtime.mjs +2017 -1821
- package/dist/commands/doctor.js +191 -15
- package/dist/commands/health-snapshot.js +9 -4
- package/dist/commands/recovery-checkpoints.d.ts +3 -3
- package/dist/config-io.js +11 -2
- package/dist/core/audit-legacy-archive.js +43 -25
- package/dist/core/classify-tool.js +16 -8
- package/dist/core/effect-ir/argv-delegate.d.ts +9 -0
- package/dist/core/effect-ir/argv-delegate.js +42 -0
- package/dist/core/effect-ir/shell-lower/argv-delegate-gate.d.ts +3 -0
- package/dist/core/effect-ir/shell-lower/argv-delegate-gate.js +40 -0
- package/dist/core/effect-ir/shell-lower/augment.d.ts +4 -0
- package/dist/core/effect-ir/shell-lower/augment.js +97 -0
- package/dist/core/effect-ir/shell-lower/context.d.ts +10 -0
- package/dist/core/effect-ir/shell-lower/context.js +1 -0
- package/dist/core/effect-ir/shell-lower/decode-process.d.ts +9 -0
- package/dist/core/effect-ir/shell-lower/decode-process.js +201 -0
- package/dist/core/effect-ir/shell-lower/decoders/belay.d.ts +2 -0
- package/dist/core/effect-ir/shell-lower/decoders/belay.js +29 -0
- package/dist/core/effect-ir/shell-lower/decoders/builtins.d.ts +7 -0
- package/dist/core/effect-ir/shell-lower/decoders/builtins.js +194 -0
- package/dist/core/effect-ir/shell-lower/decoders/decode-package-exec.d.ts +4 -0
- package/dist/core/effect-ir/shell-lower/decoders/decode-package-exec.js +68 -0
- package/dist/core/effect-ir/shell-lower/decoders/docker.d.ts +3 -0
- package/dist/core/effect-ir/shell-lower/decoders/docker.js +43 -0
- package/dist/core/effect-ir/shell-lower/decoders/filesystem.d.ts +9 -0
- package/dist/core/effect-ir/shell-lower/decoders/filesystem.js +217 -0
- package/dist/core/effect-ir/shell-lower/decoders/prisma.d.ts +2 -0
- package/dist/core/effect-ir/shell-lower/decoders/prisma.js +54 -0
- package/dist/core/effect-ir/shell-lower/decoders/ruby.d.ts +11 -0
- package/dist/core/effect-ir/shell-lower/decoders/ruby.js +147 -0
- package/dist/core/effect-ir/shell-lower/decoders/toolchain.d.ts +7 -0
- package/dist/core/effect-ir/shell-lower/decoders/toolchain.js +155 -0
- package/dist/core/effect-ir/shell-lower/requirement.d.ts +12 -0
- package/dist/core/effect-ir/shell-lower/requirement.js +65 -0
- package/dist/core/effect-ir/shell-lower/segment.d.ts +11 -0
- package/dist/core/effect-ir/shell-lower/segment.js +82 -0
- package/dist/core/effect-ir/shell-lower/tokens.d.ts +26 -0
- package/dist/core/effect-ir/shell-lower/tokens.js +183 -0
- package/dist/core/effect-ir/shell-lower.d.ts +2 -7
- package/dist/core/effect-ir/shell-lower.js +60 -1502
- package/dist/core/glob.js +32 -16
- package/dist/core/integrity.d.ts +2 -2
- package/dist/core/integrity.js +52 -12
- package/dist/core/replay-scrub.d.ts +2 -0
- package/dist/core/replay-scrub.js +8 -0
- package/dist/core/shell-substitution.js +2 -2
- package/dist/core/shell-unparseable.js +3 -1
- package/dist/defaults.d.ts +7 -3
- package/dist/defaults.js +29 -22
- package/dist/installer/bootstrap.d.ts +1 -0
- package/dist/installer/bootstrap.js +2 -2
- package/dist/installer/runtime-artifacts.js +43 -13
- package/dist/installer.js +44 -9
- package/dist/node-resolution.d.ts +1 -0
- package/dist/node-resolution.js +61 -0
- package/dist/templates.d.ts +7 -4
- package/dist/templates.js +42 -4
- package/dist/types.d.ts +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/core/glob.js
CHANGED
|
@@ -1,26 +1,42 @@
|
|
|
1
|
+
import { escapeRegex } from './approval.js';
|
|
2
|
+
function globPatternToRegexSource(pattern) {
|
|
3
|
+
let result = '';
|
|
4
|
+
let index = 0;
|
|
5
|
+
while (index < pattern.length) {
|
|
6
|
+
const char = pattern[index];
|
|
7
|
+
if (char === '*' && pattern[index + 1] === '*') {
|
|
8
|
+
index += 2;
|
|
9
|
+
if (pattern[index] === '/') {
|
|
10
|
+
result += '(?:.*/)?';
|
|
11
|
+
index += 1;
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
result += '.*';
|
|
15
|
+
}
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (char === '*') {
|
|
19
|
+
result += '[^/]*';
|
|
20
|
+
index += 1;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
result += escapeRegex(char);
|
|
24
|
+
index += 1;
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
function globPatternMatches(pattern, normalized, baseName) {
|
|
29
|
+
const regex = new RegExp(`^${globPatternToRegexSource(pattern)}$`);
|
|
30
|
+
return regex.test(normalized) || regex.test(baseName);
|
|
31
|
+
}
|
|
1
32
|
export function matchesSensitivePath(filePath, patterns) {
|
|
2
33
|
const normalized = filePath.replaceAll('\\', '/');
|
|
3
34
|
const segments = normalized.split('/');
|
|
4
35
|
const baseName = segments.at(-1) ?? normalized;
|
|
5
36
|
for (const pattern of patterns) {
|
|
6
37
|
const normalizedPattern = pattern.replaceAll('\\', '/');
|
|
7
|
-
if (normalizedPattern.includes('**')) {
|
|
8
|
-
const parts = normalizedPattern.split('**').map((part) => part.replace(/^\/+|\/+$/g, ''));
|
|
9
|
-
const prefix = parts[0]?.replace(/\/+$/, '') ?? '';
|
|
10
|
-
const suffix = parts[1]?.replace(/^\/+/, '') ?? '';
|
|
11
|
-
if (prefix && !normalized.startsWith(prefix)) {
|
|
12
|
-
continue;
|
|
13
|
-
}
|
|
14
|
-
if (suffix && !normalized.includes(suffix)) {
|
|
15
|
-
continue;
|
|
16
|
-
}
|
|
17
|
-
if (prefix || suffix) {
|
|
18
|
-
return true;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
38
|
if (normalizedPattern.includes('*')) {
|
|
22
|
-
|
|
23
|
-
if (regex.test(normalized) || regex.test(baseName)) {
|
|
39
|
+
if (globPatternMatches(normalizedPattern, normalized, baseName)) {
|
|
24
40
|
return true;
|
|
25
41
|
}
|
|
26
42
|
continue;
|
package/dist/core/integrity.d.ts
CHANGED
|
@@ -8,9 +8,9 @@ export interface IntegrityManifest {
|
|
|
8
8
|
}
|
|
9
9
|
export declare function sha256File(filePath: string): Promise<string>;
|
|
10
10
|
export declare function integrityManifestPath(layout: AdapterLayout, repoRoot: string): string;
|
|
11
|
-
export declare function runtimeIntegrityFiles(
|
|
11
|
+
export declare function runtimeIntegrityFiles(layout: AdapterLayout, paths: ScopedPaths): string[];
|
|
12
12
|
export declare function writeIntegrityManifest(repoRoot: string, layout: AdapterLayout, filePaths: string[]): Promise<void>;
|
|
13
|
-
export declare function verifyIntegrityManifest(repoRoot: string, layout: AdapterLayout): Promise<{
|
|
13
|
+
export declare function verifyIntegrityManifest(repoRoot: string, layout: AdapterLayout, requiredFilePaths?: string[]): Promise<{
|
|
14
14
|
ok: boolean;
|
|
15
15
|
mismatches: string[];
|
|
16
16
|
}>;
|
package/dist/core/integrity.js
CHANGED
|
@@ -3,8 +3,24 @@ import { existsSync } from 'node:fs';
|
|
|
3
3
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { getAdapterLayout } from '../adapters/layouts/index.js';
|
|
6
|
-
import { resolveScopedPaths } from '../adapters/layouts/scope.js';
|
|
6
|
+
import { isPathInside, resolveScopedPaths } from '../adapters/layouts/scope.js';
|
|
7
7
|
import { resolveAdapterName } from '../config-io.js';
|
|
8
|
+
const GLOBAL_INTEGRITY_PREFIX = '@global/';
|
|
9
|
+
function portableRelativePath(from, to) {
|
|
10
|
+
return path.relative(from, to).split(path.sep).join('/');
|
|
11
|
+
}
|
|
12
|
+
function integrityManifestFileKey(repoRoot, layout, filePath) {
|
|
13
|
+
const absolutePath = path.resolve(filePath);
|
|
14
|
+
if (isPathInside(absolutePath, repoRoot)) {
|
|
15
|
+
return portableRelativePath(repoRoot, absolutePath);
|
|
16
|
+
}
|
|
17
|
+
const globalPaths = resolveScopedPaths(layout, 'global', repoRoot);
|
|
18
|
+
const globalAgentDir = path.dirname(globalPaths.hooksSettingsPath);
|
|
19
|
+
if (isPathInside(absolutePath, globalAgentDir)) {
|
|
20
|
+
return `${GLOBAL_INTEGRITY_PREFIX}${portableRelativePath(globalAgentDir, absolutePath)}`;
|
|
21
|
+
}
|
|
22
|
+
throw new Error(`Integrity file is outside the repository and global adapter root: ${filePath}`);
|
|
23
|
+
}
|
|
8
24
|
export async function sha256File(filePath) {
|
|
9
25
|
const content = await readFile(filePath);
|
|
10
26
|
return createHash('sha256').update(content).digest('hex');
|
|
@@ -12,11 +28,8 @@ export async function sha256File(filePath) {
|
|
|
12
28
|
export function integrityManifestPath(layout, repoRoot) {
|
|
13
29
|
return path.join(layout.repoLocalStateDir(repoRoot), 'integrity-manifest.json');
|
|
14
30
|
}
|
|
15
|
-
export function runtimeIntegrityFiles(
|
|
31
|
+
export function runtimeIntegrityFiles(layout, paths) {
|
|
16
32
|
const files = [paths.configPath];
|
|
17
|
-
if (paths.scope !== 'project') {
|
|
18
|
-
return files;
|
|
19
|
-
}
|
|
20
33
|
const hooksDir = paths.hooksDir;
|
|
21
34
|
const runtimeDir = paths.runtimeDir;
|
|
22
35
|
return [
|
|
@@ -28,7 +41,9 @@ export function runtimeIntegrityFiles(_layout, paths) {
|
|
|
28
41
|
path.join(hooksDir, 'belay-audit.mjs'),
|
|
29
42
|
path.join(hooksDir, 'belay-runner'),
|
|
30
43
|
path.join(hooksDir, 'belay-runner.cmd'),
|
|
44
|
+
...(layout.name === 'cursor' ? [path.join(hooksDir, 'belay-runner.ps1')] : []),
|
|
31
45
|
path.join(runtimeDir, 'core.mjs'),
|
|
46
|
+
...(layout.name === 'cursor' ? [path.join(runtimeDir, 'dispatcher.mjs')] : []),
|
|
32
47
|
];
|
|
33
48
|
}
|
|
34
49
|
export async function writeIntegrityManifest(repoRoot, layout, filePaths) {
|
|
@@ -37,8 +52,8 @@ export async function writeIntegrityManifest(repoRoot, layout, filePaths) {
|
|
|
37
52
|
if (!existsSync(filePath)) {
|
|
38
53
|
continue;
|
|
39
54
|
}
|
|
40
|
-
const
|
|
41
|
-
files[
|
|
55
|
+
const manifestKey = integrityManifestFileKey(repoRoot, layout, filePath);
|
|
56
|
+
files[manifestKey] = await sha256File(filePath);
|
|
42
57
|
}
|
|
43
58
|
const manifest = {
|
|
44
59
|
version: 1,
|
|
@@ -49,22 +64,47 @@ export async function writeIntegrityManifest(repoRoot, layout, filePaths) {
|
|
|
49
64
|
await mkdir(path.dirname(manifestPath), { recursive: true });
|
|
50
65
|
await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
|
|
51
66
|
}
|
|
52
|
-
export async function verifyIntegrityManifest(repoRoot, layout) {
|
|
67
|
+
export async function verifyIntegrityManifest(repoRoot, layout, requiredFilePaths = []) {
|
|
53
68
|
const manifestPath = integrityManifestPath(layout, repoRoot);
|
|
54
69
|
if (!existsSync(manifestPath)) {
|
|
55
70
|
return { ok: false, mismatches: ['missing integrity-manifest.json'] };
|
|
56
71
|
}
|
|
57
72
|
const manifest = JSON.parse(await readFile(manifestPath, 'utf8'));
|
|
58
73
|
const mismatches = [];
|
|
59
|
-
|
|
60
|
-
|
|
74
|
+
const requiredPathsByKey = new Map(requiredFilePaths.map((requiredPath) => [
|
|
75
|
+
integrityManifestFileKey(repoRoot, layout, requiredPath),
|
|
76
|
+
path.resolve(requiredPath),
|
|
77
|
+
]));
|
|
78
|
+
for (const requiredPath of requiredFilePaths) {
|
|
79
|
+
const requiredKey = integrityManifestFileKey(repoRoot, layout, requiredPath);
|
|
80
|
+
if (!Object.hasOwn(manifest.files ?? {}, requiredKey)) {
|
|
81
|
+
mismatches.push(`missing integrity pin ${requiredKey}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
for (const [manifestKey, expectedHash] of Object.entries(manifest.files ?? {})) {
|
|
85
|
+
let absolutePath;
|
|
86
|
+
if (manifestKey.startsWith(GLOBAL_INTEGRITY_PREFIX)) {
|
|
87
|
+
const requiredPath = requiredPathsByKey.get(manifestKey);
|
|
88
|
+
if (!requiredPath) {
|
|
89
|
+
mismatches.push(`unrecognized global integrity pin ${manifestKey}`);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
absolutePath = requiredPath;
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
absolutePath = path.resolve(repoRoot, manifestKey);
|
|
96
|
+
if (!isPathInside(absolutePath, repoRoot)) {
|
|
97
|
+
mismatches.push(`integrity pin escapes repository ${manifestKey}`);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
61
101
|
if (!existsSync(absolutePath)) {
|
|
62
|
-
mismatches.push(`missing ${
|
|
102
|
+
mismatches.push(`missing ${manifestKey}`);
|
|
63
103
|
continue;
|
|
64
104
|
}
|
|
65
105
|
const actualHash = await sha256File(absolutePath);
|
|
66
106
|
if (actualHash !== expectedHash) {
|
|
67
|
-
mismatches.push(`hash mismatch ${
|
|
107
|
+
mismatches.push(`hash mismatch ${manifestKey}`);
|
|
68
108
|
}
|
|
69
109
|
}
|
|
70
110
|
return { ok: mismatches.length === 0, mismatches };
|
|
@@ -3,6 +3,8 @@ import type { ScrubOptions } from './types.js';
|
|
|
3
3
|
export declare function redactToolInvocationId(value: unknown, rawToolUseId?: string): unknown;
|
|
4
4
|
/** Subagent fingerprint input — must match classify-subagent `fingerprintSource`. */
|
|
5
5
|
export declare function subagentFingerprintSource(payload: Record<string, unknown>, scrubOptions: ScrubOptions): unknown;
|
|
6
|
+
/** Stable tool-input source for fingerprints — redacts volatile ids only, not secret values. */
|
|
7
|
+
export declare function fingerprintToolInputSource(payload: Record<string, unknown>): Record<string, unknown>;
|
|
6
8
|
/**
|
|
7
9
|
* Scrubbed payload used for replay envelope hashing — aligned with classifier fingerprints.
|
|
8
10
|
* Tool: scrubbed `tool_input`. Subagent: description/prompt subset. Other: full payload.
|
|
@@ -40,6 +40,14 @@ export function subagentFingerprintSource(payload, scrubOptions) {
|
|
|
40
40
|
}
|
|
41
41
|
return scrubValue(payload, scrubOptions);
|
|
42
42
|
}
|
|
43
|
+
/** Stable tool-input source for fingerprints — redacts volatile ids only, not secret values. */
|
|
44
|
+
export function fingerprintToolInputSource(payload) {
|
|
45
|
+
const toolInput = payload.tool_input;
|
|
46
|
+
if (!toolInput || typeof toolInput !== 'object') {
|
|
47
|
+
return {};
|
|
48
|
+
}
|
|
49
|
+
return redactToolInvocationId(toolInput, typeof payload.tool_use_id === 'string' ? payload.tool_use_id : undefined);
|
|
50
|
+
}
|
|
43
51
|
/**
|
|
44
52
|
* Scrubbed payload used for replay envelope hashing — aligned with classifier fingerprints.
|
|
45
53
|
* Tool: scrubbed `tool_input`. Subagent: description/prompt subset. Other: full payload.
|
|
@@ -16,7 +16,7 @@ export function findCommandSubstitutions(command) {
|
|
|
16
16
|
index += 1;
|
|
17
17
|
continue;
|
|
18
18
|
}
|
|
19
|
-
if (char === '\\' &&
|
|
19
|
+
if (char === '\\' && inDouble) {
|
|
20
20
|
escaping = true;
|
|
21
21
|
index += 1;
|
|
22
22
|
continue;
|
|
@@ -156,7 +156,7 @@ function extractBalancedParenContent(command, start) {
|
|
|
156
156
|
index += 1;
|
|
157
157
|
continue;
|
|
158
158
|
}
|
|
159
|
-
if (char === '\\' &&
|
|
159
|
+
if (char === '\\' && inDouble) {
|
|
160
160
|
escaping = true;
|
|
161
161
|
index += 1;
|
|
162
162
|
continue;
|
package/dist/defaults.d.ts
CHANGED
|
@@ -5,13 +5,17 @@ export type ManagedHookDefinition = {
|
|
|
5
5
|
command: string;
|
|
6
6
|
placement: 'prepend' | 'append';
|
|
7
7
|
matcher?: string;
|
|
8
|
+
failClosed?: boolean;
|
|
8
9
|
};
|
|
9
|
-
export
|
|
10
|
+
export type CursorManagedHookDefinition = ManagedHookDefinition & {
|
|
11
|
+
failClosed: true;
|
|
12
|
+
};
|
|
13
|
+
export declare function getManagedHookEntries(platform?: NodeJS.Platform, hooksDir?: string, repoRoot?: string, runnerPathMode?: 'absolute' | 'legacy-relative' | 'legacy-absolute' | 'legacy-quoted-absolute' | 'legacy-bare-powershell-absolute'): Array<{
|
|
10
14
|
event: string;
|
|
11
|
-
definition:
|
|
15
|
+
definition: CursorManagedHookDefinition;
|
|
12
16
|
}>;
|
|
13
17
|
/** @deprecated Use getManagedHookEntries instead. */
|
|
14
|
-
export declare function getManagedHookEvents(platform?: NodeJS.Platform): Record<string,
|
|
18
|
+
export declare function getManagedHookEvents(platform?: NodeJS.Platform): Record<string, CursorManagedHookDefinition>;
|
|
15
19
|
export declare const EMPTY_APPROVALS: {
|
|
16
20
|
readonly version: 1;
|
|
17
21
|
readonly approvals: readonly [];
|
package/dist/defaults.js
CHANGED
|
@@ -1,29 +1,41 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { cursorLayout } from './adapters/layouts/cursor.js';
|
|
3
|
-
import { buildRunnerInvocation } from './adapters/layouts/scope.js';
|
|
3
|
+
import { buildAbsoluteRunnerInvocation, buildLegacyAbsoluteRunnerInvocation, buildLegacyBarePowerShellRunnerInvocation, buildLegacyQuotedAbsoluteRunnerInvocation, buildRunnerInvocation, } from './adapters/layouts/scope.js';
|
|
4
4
|
import { DEFAULT_CONFIG_V3 } from './core/config.js';
|
|
5
5
|
export { PACKAGE_NAME } from './branding.js';
|
|
6
6
|
export const DEFAULT_CONFIG = DEFAULT_CONFIG_V3;
|
|
7
|
-
function runnerCommand(platform, hooksDir,
|
|
8
|
-
|
|
7
|
+
function runnerCommand(platform, hooksDir, _repoRoot, hookScript, runnerPathMode, ...args) {
|
|
8
|
+
if (runnerPathMode === 'absolute') {
|
|
9
|
+
return buildAbsoluteRunnerInvocation(platform, hooksDir, hookScript, ...args);
|
|
10
|
+
}
|
|
11
|
+
if (runnerPathMode === 'legacy-absolute') {
|
|
12
|
+
return buildLegacyAbsoluteRunnerInvocation(platform, hooksDir, hookScript, ...args);
|
|
13
|
+
}
|
|
14
|
+
if (runnerPathMode === 'legacy-quoted-absolute') {
|
|
15
|
+
return buildLegacyQuotedAbsoluteRunnerInvocation(platform, hooksDir, hookScript, ...args);
|
|
16
|
+
}
|
|
17
|
+
if (runnerPathMode === 'legacy-bare-powershell-absolute') {
|
|
18
|
+
return buildLegacyBarePowerShellRunnerInvocation(platform, hooksDir, hookScript, ...args);
|
|
19
|
+
}
|
|
20
|
+
return buildRunnerInvocation(platform, hooksDir, _repoRoot, hookScript, ...args);
|
|
9
21
|
}
|
|
10
|
-
export function getManagedHookEntries(platform = process.platform, hooksDir, repoRoot) {
|
|
22
|
+
export function getManagedHookEntries(platform = process.platform, hooksDir, repoRoot, runnerPathMode = 'absolute') {
|
|
11
23
|
const resolvedRepo = path.resolve(repoRoot ?? process.cwd());
|
|
12
24
|
const resolvedHooksDir = hooksDir ?? cursorLayout.hooksDir(resolvedRepo);
|
|
13
|
-
const toolGate = runnerCommand(platform, resolvedHooksDir, resolvedRepo, 'belay-tool-gate', 'preToolUse');
|
|
14
|
-
const subagentGate = runnerCommand(platform, resolvedHooksDir, resolvedRepo, 'belay-tool-gate', 'subagentStart');
|
|
15
|
-
|
|
25
|
+
const toolGate = runnerCommand(platform, resolvedHooksDir, resolvedRepo, 'belay-tool-gate', runnerPathMode, 'preToolUse');
|
|
26
|
+
const subagentGate = runnerCommand(platform, resolvedHooksDir, resolvedRepo, 'belay-tool-gate', runnerPathMode, 'subagentStart');
|
|
27
|
+
const entries = [
|
|
16
28
|
{
|
|
17
29
|
event: 'beforeSubmitPrompt',
|
|
18
30
|
definition: {
|
|
19
|
-
command: runnerCommand(platform, resolvedHooksDir, resolvedRepo, 'belay-before-submit'),
|
|
31
|
+
command: runnerCommand(platform, resolvedHooksDir, resolvedRepo, 'belay-before-submit', runnerPathMode),
|
|
20
32
|
placement: 'prepend',
|
|
21
33
|
},
|
|
22
34
|
},
|
|
23
35
|
{
|
|
24
36
|
event: 'beforeShellExecution',
|
|
25
37
|
definition: {
|
|
26
|
-
command: runnerCommand(platform, resolvedHooksDir, resolvedRepo, 'belay-shell-gate'),
|
|
38
|
+
command: runnerCommand(platform, resolvedHooksDir, resolvedRepo, 'belay-shell-gate', runnerPathMode),
|
|
27
39
|
placement: 'prepend',
|
|
28
40
|
},
|
|
29
41
|
},
|
|
@@ -59,15 +71,6 @@ export function getManagedHookEntries(platform = process.platform, hooksDir, rep
|
|
|
59
71
|
matcher: 'Delete',
|
|
60
72
|
},
|
|
61
73
|
},
|
|
62
|
-
{
|
|
63
|
-
event: 'preToolUse',
|
|
64
|
-
definition: {
|
|
65
|
-
command: toolGate,
|
|
66
|
-
placement: 'prepend',
|
|
67
|
-
// Agent Shell tool (preToolUse) — distinct from terminal beforeShellExecution.
|
|
68
|
-
matcher: 'Shell',
|
|
69
|
-
},
|
|
70
|
-
},
|
|
71
74
|
{
|
|
72
75
|
event: 'subagentStart',
|
|
73
76
|
definition: {
|
|
@@ -119,32 +122,36 @@ export function getManagedHookEntries(platform = process.platform, hooksDir, rep
|
|
|
119
122
|
{
|
|
120
123
|
event: 'postToolUse',
|
|
121
124
|
definition: {
|
|
122
|
-
command: runnerCommand(platform, resolvedHooksDir, resolvedRepo, 'belay-audit', 'postToolUse'),
|
|
125
|
+
command: runnerCommand(platform, resolvedHooksDir, resolvedRepo, 'belay-audit', runnerPathMode, 'postToolUse'),
|
|
123
126
|
placement: 'append',
|
|
124
127
|
},
|
|
125
128
|
},
|
|
126
129
|
{
|
|
127
130
|
event: 'postToolUseFailure',
|
|
128
131
|
definition: {
|
|
129
|
-
command: runnerCommand(platform, resolvedHooksDir, resolvedRepo, 'belay-audit', 'postToolUseFailure'),
|
|
132
|
+
command: runnerCommand(platform, resolvedHooksDir, resolvedRepo, 'belay-audit', runnerPathMode, 'postToolUseFailure'),
|
|
130
133
|
placement: 'append',
|
|
131
134
|
},
|
|
132
135
|
},
|
|
133
136
|
{
|
|
134
137
|
event: 'stop',
|
|
135
138
|
definition: {
|
|
136
|
-
command: runnerCommand(platform, resolvedHooksDir, resolvedRepo, 'belay-audit', 'stop'),
|
|
139
|
+
command: runnerCommand(platform, resolvedHooksDir, resolvedRepo, 'belay-audit', runnerPathMode, 'stop'),
|
|
137
140
|
placement: 'append',
|
|
138
141
|
},
|
|
139
142
|
},
|
|
140
143
|
{
|
|
141
144
|
event: 'sessionEnd',
|
|
142
145
|
definition: {
|
|
143
|
-
command: runnerCommand(platform, resolvedHooksDir, resolvedRepo, 'belay-audit', 'sessionEnd'),
|
|
146
|
+
command: runnerCommand(platform, resolvedHooksDir, resolvedRepo, 'belay-audit', runnerPathMode, 'sessionEnd'),
|
|
144
147
|
placement: 'append',
|
|
145
148
|
},
|
|
146
149
|
},
|
|
147
150
|
];
|
|
151
|
+
return entries.map(({ event, definition }) => ({
|
|
152
|
+
event,
|
|
153
|
+
definition: { ...definition, failClosed: true },
|
|
154
|
+
}));
|
|
148
155
|
}
|
|
149
156
|
/** @deprecated Use getManagedHookEntries instead. */
|
|
150
157
|
export function getManagedHookEvents(platform = process.platform) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ScopedPaths } from '../adapters/layouts/scope.js';
|
|
2
2
|
import type { AdapterName } from '../adapters/layouts/types.js';
|
|
3
3
|
import type { BelayConfigV3 } from '../core/config.js';
|
|
4
|
+
export declare const CURSOR_COMMAND_ARTIFACTS: readonly ["belay-approve.md", "belay-why.md", "belay-explain.md", "belay-status.md", "belay-report.md", "belay-recover.md"];
|
|
4
5
|
export declare function bootstrapStateFiles(repoRoot: string, config: BelayConfigV3, paths: ScopedPaths): Promise<void>;
|
|
5
6
|
export declare function writeSkillArtifacts(adapterName: AdapterName, paths: ScopedPaths): Promise<void>;
|
|
@@ -4,7 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import { approvedApprovalsPath, pendingApprovalsPath } from '../config-io.js';
|
|
5
5
|
import { EMPTY_APPROVALS } from '../defaults.js';
|
|
6
6
|
const BUNDLED_SKILL_TEMPLATE_URL = new URL('../../skills/belay/SKILL.md', import.meta.url);
|
|
7
|
-
const
|
|
7
|
+
export const CURSOR_COMMAND_ARTIFACTS = [
|
|
8
8
|
'belay-approve.md',
|
|
9
9
|
'belay-why.md',
|
|
10
10
|
'belay-explain.md',
|
|
@@ -53,7 +53,7 @@ export async function writeSkillArtifacts(adapterName, paths) {
|
|
|
53
53
|
await writeFile(path.join(paths.skillsDir, 'SKILL.md'), bundledSkill, 'utf8');
|
|
54
54
|
if (adapterName === 'cursor' && paths.commandsDir) {
|
|
55
55
|
await mkdir(paths.commandsDir, { recursive: true });
|
|
56
|
-
for (const fileName of
|
|
56
|
+
for (const fileName of CURSOR_COMMAND_ARTIFACTS) {
|
|
57
57
|
const bundledCommand = await readBundledTemplate(new URL(`../../skills/belay/${fileName}`, import.meta.url));
|
|
58
58
|
await writeFile(path.join(paths.commandsDir, fileName), bundledCommand, 'utf8');
|
|
59
59
|
}
|
|
@@ -1,23 +1,53 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { chmod, mkdir, realpath, rename, rm, writeFile } from 'node:fs/promises';
|
|
2
3
|
import path from 'node:path';
|
|
3
|
-
import { buildRunnerScript, buildWindowsRunnerScript } from '../node-resolution.js';
|
|
4
|
-
import { renderAuditHook, renderBeforeSubmitHook, renderRuntimeCore, renderShellGateHook, renderToolGateHook, } from '../templates.js';
|
|
4
|
+
import { buildRunnerScript, buildWindowsPowerShellRunnerScript, buildWindowsRunnerScript, } from '../node-resolution.js';
|
|
5
|
+
import { renderAuditHook, renderBeforeSubmitHook, renderCursorDispatcher, renderRuntimeCore, renderShellGateHook, renderToolGateHook, } from '../templates.js';
|
|
5
6
|
async function writeFileMaybeExecutable(filePath, content, executable = false) {
|
|
6
7
|
await mkdir(path.dirname(filePath), { recursive: true });
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
await
|
|
8
|
+
const temporaryPath = path.join(path.dirname(filePath), `.${path.basename(filePath)}.${process.pid}.${randomUUID()}.tmp`);
|
|
9
|
+
try {
|
|
10
|
+
await writeFile(temporaryPath, content, { encoding: 'utf8', flag: 'wx' });
|
|
11
|
+
if (executable) {
|
|
12
|
+
await chmod(temporaryPath, 0o755);
|
|
13
|
+
}
|
|
14
|
+
await rename(temporaryPath, filePath);
|
|
15
|
+
}
|
|
16
|
+
finally {
|
|
17
|
+
await rm(temporaryPath, { force: true });
|
|
10
18
|
}
|
|
11
19
|
}
|
|
12
20
|
export async function writeRuntimeArtifacts(adapterName, paths) {
|
|
13
21
|
const { hooksDir, runtimeDir } = paths;
|
|
22
|
+
const cursorOrigin = adapterName === 'cursor'
|
|
23
|
+
? paths.scope === 'global'
|
|
24
|
+
? { scope: 'global' }
|
|
25
|
+
: { scope: 'project', repoRoot: await realpath(paths.repoRoot) }
|
|
26
|
+
: undefined;
|
|
27
|
+
const artifacts = {
|
|
28
|
+
auditHook: renderAuditHook(adapterName, cursorOrigin),
|
|
29
|
+
beforeSubmitHook: renderBeforeSubmitHook(adapterName, cursorOrigin),
|
|
30
|
+
core: await renderRuntimeCore(adapterName),
|
|
31
|
+
dispatcher: adapterName === 'cursor' ? await renderCursorDispatcher() : undefined,
|
|
32
|
+
runner: buildRunnerScript(process.execPath),
|
|
33
|
+
runnerCmd: buildWindowsRunnerScript(process.execPath),
|
|
34
|
+
runnerPowerShell: adapterName === 'cursor' ? buildWindowsPowerShellRunnerScript(process.execPath) : undefined,
|
|
35
|
+
shellGateHook: renderShellGateHook(adapterName, cursorOrigin),
|
|
36
|
+
toolGateHook: renderToolGateHook(adapterName, cursorOrigin),
|
|
37
|
+
};
|
|
14
38
|
await mkdir(hooksDir, { recursive: true });
|
|
15
39
|
await mkdir(runtimeDir, { recursive: true });
|
|
16
|
-
await writeFileMaybeExecutable(path.join(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
await writeFileMaybeExecutable(path.join(
|
|
21
|
-
await writeFileMaybeExecutable(path.join(hooksDir, 'belay-runner'),
|
|
22
|
-
|
|
40
|
+
await writeFileMaybeExecutable(path.join(runtimeDir, 'core.mjs'), artifacts.core);
|
|
41
|
+
if (artifacts.dispatcher !== undefined) {
|
|
42
|
+
await writeFileMaybeExecutable(path.join(runtimeDir, 'dispatcher.mjs'), artifacts.dispatcher);
|
|
43
|
+
}
|
|
44
|
+
await writeFileMaybeExecutable(path.join(hooksDir, 'belay-runner'), artifacts.runner, true);
|
|
45
|
+
await writeFileMaybeExecutable(path.join(hooksDir, 'belay-runner.cmd'), artifacts.runnerCmd);
|
|
46
|
+
if (artifacts.runnerPowerShell !== undefined) {
|
|
47
|
+
await writeFileMaybeExecutable(path.join(hooksDir, 'belay-runner.ps1'), artifacts.runnerPowerShell);
|
|
48
|
+
}
|
|
49
|
+
await writeFileMaybeExecutable(path.join(hooksDir, 'belay-before-submit.mjs'), artifacts.beforeSubmitHook);
|
|
50
|
+
await writeFileMaybeExecutable(path.join(hooksDir, 'belay-shell-gate.mjs'), artifacts.shellGateHook);
|
|
51
|
+
await writeFileMaybeExecutable(path.join(hooksDir, 'belay-tool-gate.mjs'), artifacts.toolGateHook);
|
|
52
|
+
await writeFileMaybeExecutable(path.join(hooksDir, 'belay-audit.mjs'), artifacts.auditHook);
|
|
23
53
|
}
|
package/dist/installer.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { mergeCursorHooksFile, stripCursorHooksFile } from './adapters/cursor/hooks.js';
|
|
4
|
+
import { hasManagedCursorHookEntries, mergeCursorHooksFile, stripCursorHooksFile, } from './adapters/cursor/hooks.js';
|
|
5
5
|
import { cursorLayout } from './adapters/layouts/cursor.js';
|
|
6
6
|
import { resolveScopedPaths } from './adapters/layouts/scope.js';
|
|
7
7
|
import { getAdapter } from './adapters/registry.js';
|
|
@@ -13,7 +13,7 @@ import { isFreshConfigInput, mergeConfig, normalizeConfig, } from './core/config
|
|
|
13
13
|
import { runtimeIntegrityFiles, writeIntegrityManifest } from './core/integrity.js';
|
|
14
14
|
import { hasValidCloudConsent, isCloudJudgeConfig, migrateImplicitLocalJudgeIfNeeded, resolveInitJudgeConfig, } from './core/judge-config.js';
|
|
15
15
|
import { resolveJudgeTransport } from './core/judge-runtime-detection.js';
|
|
16
|
-
import { bootstrapStateFiles, writeSkillArtifacts } from './installer/bootstrap.js';
|
|
16
|
+
import { bootstrapStateFiles, CURSOR_COMMAND_ARTIFACTS, writeSkillArtifacts, } from './installer/bootstrap.js';
|
|
17
17
|
import { writeRuntimeArtifacts } from './installer/runtime-artifacts.js';
|
|
18
18
|
import { applyInstallScope, resolveOperationScope } from './installer/scope-config.js';
|
|
19
19
|
import { applyConfigPreset } from './presets.js';
|
|
@@ -64,7 +64,6 @@ export async function initCursorProject(options = {}) {
|
|
|
64
64
|
const mergedHooks = mergeCursorHooksFile(hooksFile, process.platform, paths.hooksDir, repoRoot);
|
|
65
65
|
await ensureDir(paths.hooksDir);
|
|
66
66
|
const config = await mergeAndWriteConfig(repoRoot, 'cursor');
|
|
67
|
-
await applyInstallScope(repoRoot, 'cursor', scope, config);
|
|
68
67
|
await writeRuntimeArtifacts('cursor', paths);
|
|
69
68
|
await bootstrapStateFiles(repoRoot, config, paths);
|
|
70
69
|
if (withSkill) {
|
|
@@ -72,8 +71,12 @@ export async function initCursorProject(options = {}) {
|
|
|
72
71
|
}
|
|
73
72
|
await mkdir(path.dirname(paths.hooksSettingsPath), { recursive: true });
|
|
74
73
|
await writeFile(paths.hooksSettingsPath, `${JSON.stringify(mergedHooks, null, 2)}\n`, 'utf8');
|
|
74
|
+
const installedConfig = await applyInstallScope(repoRoot, 'cursor', scope, config);
|
|
75
|
+
if (scope === 'global') {
|
|
76
|
+
await cleanupStaleProjectCursorInstall(repoRoot);
|
|
77
|
+
}
|
|
75
78
|
await writeIntegrityManifest(repoRoot, cursorLayout, runtimeIntegrityFiles(cursorLayout, paths));
|
|
76
|
-
await archiveLegacyAuditLogIfNeeded(repoRoot,
|
|
79
|
+
await archiveLegacyAuditLogIfNeeded(repoRoot, installedConfig);
|
|
77
80
|
return { repoRoot, withSkill };
|
|
78
81
|
}
|
|
79
82
|
export async function upgradeCursorProject(options = {}) {
|
|
@@ -81,17 +84,31 @@ export async function upgradeCursorProject(options = {}) {
|
|
|
81
84
|
const scope = await resolveOperationScope(repoRoot, 'cursor', options);
|
|
82
85
|
const paths = resolveScopedPaths(cursorLayout, scope, repoRoot);
|
|
83
86
|
const config = await mergeAndWriteConfig(repoRoot, 'cursor');
|
|
84
|
-
await applyInstallScope(repoRoot, 'cursor', scope, config);
|
|
85
87
|
await writeRuntimeArtifacts('cursor', paths);
|
|
86
88
|
const hooksFile = await loadHooksFile(paths.hooksSettingsPath);
|
|
87
89
|
const merged = mergeCursorHooksFile(hooksFile, process.platform, paths.hooksDir, repoRoot);
|
|
88
90
|
await mkdir(path.dirname(paths.hooksSettingsPath), { recursive: true });
|
|
89
91
|
await writeFile(paths.hooksSettingsPath, `${JSON.stringify(merged, null, 2)}\n`, 'utf8');
|
|
92
|
+
if (scope === 'project') {
|
|
93
|
+
const globalPaths = resolveScopedPaths(cursorLayout, 'global', repoRoot);
|
|
94
|
+
if (existsSync(globalPaths.hooksSettingsPath)) {
|
|
95
|
+
const globalHooks = await loadHooksFile(globalPaths.hooksSettingsPath);
|
|
96
|
+
if (hasManagedCursorHookEntries(globalHooks, process.platform, globalPaths.hooksDir, repoRoot)) {
|
|
97
|
+
await writeRuntimeArtifacts('cursor', globalPaths);
|
|
98
|
+
const mergedGlobal = mergeCursorHooksFile(globalHooks, process.platform, globalPaths.hooksDir, repoRoot);
|
|
99
|
+
await writeFile(globalPaths.hooksSettingsPath, `${JSON.stringify(mergedGlobal, null, 2)}\n`, 'utf8');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
90
103
|
if (options.withSkill) {
|
|
91
104
|
await writeSkillArtifacts('cursor', paths);
|
|
92
105
|
}
|
|
106
|
+
const installedConfig = await applyInstallScope(repoRoot, 'cursor', scope, config);
|
|
107
|
+
if (scope === 'global') {
|
|
108
|
+
await cleanupStaleProjectCursorInstall(repoRoot);
|
|
109
|
+
}
|
|
93
110
|
await writeIntegrityManifest(repoRoot, cursorLayout, runtimeIntegrityFiles(cursorLayout, paths));
|
|
94
|
-
await archiveLegacyAuditLogIfNeeded(repoRoot,
|
|
111
|
+
await archiveLegacyAuditLogIfNeeded(repoRoot, installedConfig);
|
|
95
112
|
return { repoRoot };
|
|
96
113
|
}
|
|
97
114
|
const BELAY_HOOK_ARTIFACTS = [
|
|
@@ -101,16 +118,34 @@ const BELAY_HOOK_ARTIFACTS = [
|
|
|
101
118
|
'belay-audit.mjs',
|
|
102
119
|
'belay-runner',
|
|
103
120
|
'belay-runner.cmd',
|
|
121
|
+
'belay-runner.ps1',
|
|
104
122
|
];
|
|
105
123
|
async function removeBelayHookArtifacts(paths) {
|
|
106
124
|
for (const fileName of BELAY_HOOK_ARTIFACTS) {
|
|
107
125
|
await rm(path.join(paths.hooksDir, fileName), { force: true });
|
|
108
126
|
}
|
|
109
|
-
|
|
110
|
-
|
|
127
|
+
for (const fileName of ['core.mjs', 'dispatcher.mjs']) {
|
|
128
|
+
await rm(path.join(paths.runtimeDir, fileName), { force: true });
|
|
129
|
+
}
|
|
130
|
+
await rm(path.join(paths.skillsDir, 'SKILL.md'), { force: true });
|
|
111
131
|
if (paths.commandsDir) {
|
|
112
|
-
|
|
132
|
+
for (const fileName of CURSOR_COMMAND_ARTIFACTS) {
|
|
133
|
+
await rm(path.join(paths.commandsDir, fileName), { force: true });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
async function cleanupStaleProjectCursorInstall(repoRoot) {
|
|
138
|
+
const projectPaths = resolveScopedPaths(cursorLayout, 'project', repoRoot);
|
|
139
|
+
if (!existsSync(projectPaths.hooksSettingsPath)) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const projectHooks = await loadHooksFile(projectPaths.hooksSettingsPath);
|
|
143
|
+
if (!hasManagedCursorHookEntries(projectHooks, process.platform, projectPaths.hooksDir, repoRoot)) {
|
|
144
|
+
return;
|
|
113
145
|
}
|
|
146
|
+
const stripped = stripCursorHooksFile(projectHooks, process.platform, projectPaths.hooksDir, repoRoot);
|
|
147
|
+
await writeFile(projectPaths.hooksSettingsPath, `${JSON.stringify(stripped, null, 2)}\n`, 'utf8');
|
|
148
|
+
await removeBelayHookArtifacts(projectPaths);
|
|
114
149
|
}
|
|
115
150
|
export async function uninstallCursorProject(options = {}) {
|
|
116
151
|
const repoRoot = path.resolve(options.targetDir ?? process.cwd());
|
|
@@ -6,3 +6,4 @@ export interface NodeResolutionResult {
|
|
|
6
6
|
export declare function resolveNodeBinary(): NodeResolutionResult;
|
|
7
7
|
export declare function buildRunnerScript(defaultNodePath: string): string;
|
|
8
8
|
export declare function buildWindowsRunnerScript(defaultNodePath: string): string;
|
|
9
|
+
export declare function buildWindowsPowerShellRunnerScript(defaultNodePath: string): string;
|
package/dist/node-resolution.js
CHANGED
|
@@ -235,3 +235,64 @@ echo {}
|
|
|
235
235
|
exit /b 0
|
|
236
236
|
`;
|
|
237
237
|
}
|
|
238
|
+
export function buildWindowsPowerShellRunnerScript(defaultNodePath) {
|
|
239
|
+
const encodedDefault = Buffer.from(defaultNodePath, 'utf8').toString('base64');
|
|
240
|
+
return `$ErrorActionPreference = 'Stop'
|
|
241
|
+
|
|
242
|
+
$DefaultNode = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${encodedDefault}'))
|
|
243
|
+
$HookName = if ($args.Count -gt 0) { [string] $args[0] } else { '' }
|
|
244
|
+
$HookArguments = if ($args.Count -gt 1) { @($args[1..($args.Count - 1)]) } else { @() }
|
|
245
|
+
|
|
246
|
+
function Resolve-Node {
|
|
247
|
+
if ($DefaultNode -and (Test-Path -LiteralPath $DefaultNode -PathType Leaf)) {
|
|
248
|
+
return $DefaultNode
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
$PathNode = Get-Command node.exe -CommandType Application -ErrorAction SilentlyContinue
|
|
252
|
+
if ($null -ne $PathNode) {
|
|
253
|
+
return $PathNode.Source
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if ($env:USERPROFILE) {
|
|
257
|
+
$NvmRoot = Join-Path $env:USERPROFILE '.nvm'
|
|
258
|
+
if (Test-Path -LiteralPath $NvmRoot -PathType Container) {
|
|
259
|
+
foreach ($VersionDirectory in Get-ChildItem -LiteralPath $NvmRoot -Directory | Sort-Object Name -Descending) {
|
|
260
|
+
$Candidate = Join-Path $VersionDirectory.FullName 'node.exe'
|
|
261
|
+
if (Test-Path -LiteralPath $Candidate -PathType Leaf) {
|
|
262
|
+
return $Candidate
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return $null
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function Fail-Audit {
|
|
272
|
+
[Console]::Error.WriteLine('belay: unable to resolve Node for audit hook')
|
|
273
|
+
[Console]::Out.WriteLine('{}')
|
|
274
|
+
exit 0
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (-not $HookName) {
|
|
278
|
+
Fail-Audit
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
$NodeBin = Resolve-Node
|
|
282
|
+
if (-not $NodeBin) {
|
|
283
|
+
if ($HookName -eq 'belay-before-submit') {
|
|
284
|
+
[Console]::Out.WriteLine('{"continue":false,"user_message":"belay could not resolve Node. Install or expose Node, run belay doctor, then retry."}')
|
|
285
|
+
exit 0
|
|
286
|
+
}
|
|
287
|
+
if ($HookName -in @('belay-shell-gate', 'belay-tool-gate')) {
|
|
288
|
+
[Console]::Out.WriteLine('{"permission":"deny","user_message":"belay could not resolve Node. Install or expose Node, run belay doctor, then retry."}')
|
|
289
|
+
exit 0
|
|
290
|
+
}
|
|
291
|
+
Fail-Audit
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
$ScriptPath = Join-Path $PSScriptRoot "$HookName.mjs"
|
|
295
|
+
& $NodeBin $ScriptPath @HookArguments
|
|
296
|
+
exit $LASTEXITCODE
|
|
297
|
+
`;
|
|
298
|
+
}
|