@guilz-dev/belay 0.9.2 → 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 +36 -2
- package/dist/adapters/codex/hooks.d.ts +1 -0
- package/dist/adapters/codex/hooks.js +3 -0
- package/dist/adapters/codex/runtime-entry.d.ts +3 -0
- package/dist/adapters/codex/runtime-entry.js +27 -4
- package/dist/adapters/cursor/cwd-resolution.d.ts +10 -0
- package/dist/adapters/cursor/cwd-resolution.js +58 -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 +5 -0
- package/dist/adapters/cursor/hooks.js +115 -3
- 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 +18 -0
- package/dist/adapters/cursor/runtime-entry.js +135 -34
- package/dist/adapters/layouts/scope.d.ts +5 -0
- package/dist/adapters/layouts/scope.js +71 -0
- package/dist/adapters/shared/gate-runtime.js +27 -3
- package/dist/adapters/shared/repo-root.js +20 -1
- package/dist/bundle/claude-runtime.mjs +2660 -1951
- package/dist/bundle/codex-runtime.mjs +2680 -1953
- package/dist/bundle/cursor-dispatcher.mjs +443 -0
- package/dist/bundle/cursor-runtime.mjs +5638 -4697
- package/dist/cli.js +33 -3
- package/dist/commands/doctor.js +215 -16
- package/dist/commands/health-snapshot.d.ts +3 -0
- package/dist/commands/health-snapshot.js +61 -0
- package/dist/commands/recovery-checkpoints.d.ts +3 -3
- package/dist/commands/report.js +14 -0
- package/dist/commands/status.js +15 -0
- package/dist/commands/where.d.ts +4 -0
- package/dist/commands/where.js +52 -0
- package/dist/config-io.js +11 -2
- package/dist/core/approval-repo-lookup.d.ts +16 -0
- package/dist/core/approval-repo-lookup.js +48 -0
- package/dist/core/audit-io.d.ts +1 -1
- package/dist/core/audit-io.js +1 -1
- package/dist/core/audit-legacy-archive.js +43 -25
- package/dist/core/audit-query.d.ts +1 -0
- package/dist/core/audit-query.js +7 -0
- package/dist/core/audit-serialize.d.ts +3 -0
- package/dist/core/audit-serialize.js +39 -3
- package/dist/core/audit-summary.d.ts +9 -0
- package/dist/core/audit-summary.js +58 -1
- package/dist/core/audit-types.d.ts +4 -0
- 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 +122 -1511
- 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 +3 -0
- package/dist/core/replay-scrub.js +30 -3
- package/dist/core/shell-substitution.js +2 -2
- package/dist/core/shell-tokenizer.d.ts +28 -0
- package/dist/core/shell-tokenizer.js +111 -29
- package/dist/core/shell-unparseable.js +3 -1
- package/dist/core/verdict/docker-compose-run.d.ts +18 -0
- package/dist/core/verdict/docker-compose-run.js +136 -0
- package/dist/core/verdict/launcher-resolve.js +28 -28
- package/dist/core/verdict/parser.d.ts +3 -0
- package/dist/core/verdict/parser.js +23 -40
- package/dist/core/verdict/recursive-invocation.d.ts +20 -0
- package/dist/core/verdict/recursive-invocation.js +224 -0
- package/dist/defaults.d.ts +7 -3
- package/dist/defaults.js +35 -21
- 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/scope-config.d.ts +2 -2
- package/dist/installer.d.ts +10 -1
- package/dist/installer.js +83 -7
- 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 +35 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -2
- package/skills/belay/SKILL.md +5 -0
- package/skills/belay/belay-report.md +4 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { expandMakeExpression, normalizeMakeRecipeLine, parseMakefileVariables,
|
|
3
|
+
import { expandMakeExpression, normalizeMakeRecipeLine, parseMakefileVariables, } from './makefile-expand.js';
|
|
4
4
|
const MAX_RESOLVE_DEPTH = 8;
|
|
5
5
|
const PNPM_BUILTIN_COMMANDS = new Set([
|
|
6
6
|
'add',
|
|
@@ -229,7 +229,6 @@ function resolveMakeRecipe(cwd, repoRoot, target, cliVars = {}) {
|
|
|
229
229
|
}
|
|
230
230
|
const makefileContent = readFileSync(makefilePath, 'utf8');
|
|
231
231
|
const makefileVars = parseMakefileVariables(makefileContent);
|
|
232
|
-
const phonyTargets = parsePhonyTargets(makefileContent);
|
|
233
232
|
const targets = parseMakefileRecipeContent(makefileContent);
|
|
234
233
|
if (!targets.has(target)) {
|
|
235
234
|
return { recipes: [], opaque: true, reason: 'make_target_undefined' };
|
|
@@ -237,42 +236,34 @@ function resolveMakeRecipe(cwd, repoRoot, target, cliVars = {}) {
|
|
|
237
236
|
const recipeLines = [];
|
|
238
237
|
const visiting = new Set();
|
|
239
238
|
const visited = new Set();
|
|
240
|
-
let
|
|
239
|
+
let hasDynamicPrerequisite = false;
|
|
240
|
+
let hasUndefinedPrerequisite = false;
|
|
241
|
+
let hasDependencyCycle = false;
|
|
241
242
|
const collect = (name) => {
|
|
242
243
|
if (visited.has(name)) {
|
|
243
|
-
return
|
|
244
|
+
return;
|
|
244
245
|
}
|
|
245
246
|
if (visiting.has(name)) {
|
|
246
|
-
|
|
247
|
+
hasDependencyCycle = true;
|
|
248
|
+
return;
|
|
247
249
|
}
|
|
248
250
|
const entry = targets.get(name);
|
|
249
251
|
if (!entry) {
|
|
250
|
-
|
|
252
|
+
if (!existsSync(path.resolve(path.dirname(makefilePath), name))) {
|
|
253
|
+
hasUndefinedPrerequisite = true;
|
|
254
|
+
}
|
|
255
|
+
return;
|
|
251
256
|
}
|
|
252
257
|
visiting.add(name);
|
|
253
|
-
|
|
258
|
+
hasDynamicPrerequisite ||= entry.opaquePrerequisites;
|
|
254
259
|
for (const prerequisite of entry.prerequisites) {
|
|
255
|
-
|
|
256
|
-
return false;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
// When the requested target has its own recipes, treat those as the
|
|
260
|
-
// authorization authority. Skip .PHONY / `_`-prefixed prerequisite
|
|
261
|
-
// recipes (e.g. `_start_test_deps` docker-compose up) so setup-only
|
|
262
|
-
// side effects do not obscure the target's direct command.
|
|
263
|
-
const skipPhonyPrerequisiteRecipes = name !== target &&
|
|
264
|
-
(phonyTargets.has(name) || name.startsWith('_')) &&
|
|
265
|
-
(targets.get(target)?.recipes.length ?? 0) > 0;
|
|
266
|
-
if (!skipPhonyPrerequisiteRecipes) {
|
|
267
|
-
recipeLines.push(...entry.recipes);
|
|
260
|
+
collect(prerequisite);
|
|
268
261
|
}
|
|
262
|
+
recipeLines.push(...entry.recipes);
|
|
269
263
|
visiting.delete(name);
|
|
270
264
|
visited.add(name);
|
|
271
|
-
return true;
|
|
272
265
|
};
|
|
273
|
-
|
|
274
|
-
return { recipes: recipeLines, opaque: true, reason: 'make_dependency_cycle' };
|
|
275
|
-
}
|
|
266
|
+
collect(target);
|
|
276
267
|
const expandedRecipes = [];
|
|
277
268
|
for (const line of recipeLines) {
|
|
278
269
|
const normalized = normalizeMakeRecipeLine(line);
|
|
@@ -287,18 +278,24 @@ function resolveMakeRecipe(cwd, repoRoot, target, cliVars = {}) {
|
|
|
287
278
|
return { recipes: expandedRecipes, opaque: true, reason: 'make_recipe_dynamic' };
|
|
288
279
|
}
|
|
289
280
|
}
|
|
290
|
-
if (
|
|
281
|
+
if (hasDependencyCycle) {
|
|
282
|
+
return { recipes: expandedRecipes, opaque: true, reason: 'make_dependency_cycle' };
|
|
283
|
+
}
|
|
284
|
+
if (hasDynamicPrerequisite) {
|
|
291
285
|
return { recipes: expandedRecipes, opaque: true, reason: 'make_prerequisite_dynamic' };
|
|
292
286
|
}
|
|
287
|
+
if (hasUndefinedPrerequisite) {
|
|
288
|
+
return { recipes: expandedRecipes, opaque: true, reason: 'make_prerequisite_undefined' };
|
|
289
|
+
}
|
|
293
290
|
return { recipes: expandedRecipes, opaque: false, reason: 'make_recipe_resolved' };
|
|
294
291
|
}
|
|
295
292
|
export function resolveLauncherRecipe(params) {
|
|
296
|
-
if (params.depth >= MAX_RESOLVE_DEPTH) {
|
|
297
|
-
return { recipes: [], opaque: true, reason: 'launcher_depth_exceeded' };
|
|
298
|
-
}
|
|
299
293
|
const tokens = params.tokens;
|
|
300
294
|
const scriptName = npmScriptName(tokens);
|
|
301
295
|
if (scriptName) {
|
|
296
|
+
if (params.depth >= MAX_RESOLVE_DEPTH) {
|
|
297
|
+
return { recipes: [], opaque: true, reason: 'launcher_depth_exceeded' };
|
|
298
|
+
}
|
|
302
299
|
const resolution = resolveNpmRecipe(params.cwd, params.repoRoot, scriptName, forwardedArgs(tokens));
|
|
303
300
|
if (tokens[0] === 'pnpm' &&
|
|
304
301
|
tokens[1] &&
|
|
@@ -313,6 +310,9 @@ export function resolveLauncherRecipe(params) {
|
|
|
313
310
|
return resolution;
|
|
314
311
|
}
|
|
315
312
|
if (tokens[0] === 'make') {
|
|
313
|
+
if (params.depth >= MAX_RESOLVE_DEPTH) {
|
|
314
|
+
return { recipes: [], opaque: true, reason: 'launcher_depth_exceeded' };
|
|
315
|
+
}
|
|
316
316
|
if (tokens.includes('-n') || tokens.includes('--dry-run')) {
|
|
317
317
|
return null;
|
|
318
318
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type ShellToken } from '../shell-tokenizer.js';
|
|
2
|
+
import { type RecursiveInvocation } from './recursive-invocation.js';
|
|
1
3
|
import type { VerdictOpacity } from './types.js';
|
|
2
4
|
export interface ParsedSegment {
|
|
3
5
|
tokens: string[];
|
|
@@ -16,6 +18,7 @@ export declare function peelTransparentWrappers(tokens: string[]): {
|
|
|
16
18
|
export declare function isVariableIndirectHead(head: string): boolean;
|
|
17
19
|
export declare function extractEvalBody(tokens: string[]): string | null;
|
|
18
20
|
export declare function extractRecursiveScript(tokens: string[]): string | null;
|
|
21
|
+
export declare function decodeRecursiveInvocationTokens(tokens: readonly ShellToken[]): RecursiveInvocation;
|
|
19
22
|
export declare function extractDockerComposeRunScript(tokens: string[]): string | null;
|
|
20
23
|
/**
|
|
21
24
|
* True when a recursive script is evaluated from a command argument rather
|
|
@@ -2,11 +2,12 @@ import path from 'node:path';
|
|
|
2
2
|
import { findCommandSubstitutions, findStructuralCommandSubstitutions, } from '../shell-substitution.js';
|
|
3
3
|
import { commandKey, tokenizeShell } from '../shell-tokenizer.js';
|
|
4
4
|
import { detectUnparseableShell } from '../shell-unparseable.js';
|
|
5
|
+
import { decodeDockerComposeRunValues } from './docker-compose-run.js';
|
|
6
|
+
import { decodeRecursiveInvocation, shellTokensFromValues, } from './recursive-invocation.js';
|
|
5
7
|
const ENV_PREFIX_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*=(?:'[^']*'|"[^"]*"|\S+)$/;
|
|
6
8
|
const MAX_WRAPPER_PEEL_DEPTH = 32;
|
|
7
9
|
const SHELL_INTERPRETERS = new Set(['bash', 'sh', 'zsh', 'dash', 'fish']);
|
|
8
10
|
const CODE_INTERPRETERS = new Set(['python', 'python3', 'node', 'ruby', 'perl', 'osascript']);
|
|
9
|
-
const SCRIPT_FLAGS = new Set(['-c', '-lc', '-e', '--eval']);
|
|
10
11
|
const INTERPRETER_SCRIPT_EXTENSIONS = new Set([
|
|
11
12
|
'.js',
|
|
12
13
|
'.mjs',
|
|
@@ -30,10 +31,6 @@ export function peelTransparentWrappers(tokens) {
|
|
|
30
31
|
let encounteredXargs = false;
|
|
31
32
|
let peelDepth = 0;
|
|
32
33
|
while (current.length > 0) {
|
|
33
|
-
if (peelDepth >= MAX_WRAPPER_PEEL_DEPTH) {
|
|
34
|
-
return { tokens: current, xargsStdinOpaque: false, encounteredXargs, opaque: true };
|
|
35
|
-
}
|
|
36
|
-
peelDepth += 1;
|
|
37
34
|
while (current.length > 0 && ENV_PREFIX_PATTERN.test(current[0] ?? '')) {
|
|
38
35
|
current.shift();
|
|
39
36
|
}
|
|
@@ -43,6 +40,10 @@ export function peelTransparentWrappers(tokens) {
|
|
|
43
40
|
const head = normalizeHead(current[0] ?? '');
|
|
44
41
|
if (head === 'xargs') {
|
|
45
42
|
encounteredXargs = true;
|
|
43
|
+
if (peelDepth >= MAX_WRAPPER_PEEL_DEPTH) {
|
|
44
|
+
return { tokens: current, xargsStdinOpaque: false, encounteredXargs, opaque: true };
|
|
45
|
+
}
|
|
46
|
+
peelDepth += 1;
|
|
46
47
|
const wrapper = peelXargsWrapper(current);
|
|
47
48
|
if (wrapper.kind === 'opaque') {
|
|
48
49
|
xargsStdinOpaque = current.length === 1;
|
|
@@ -60,6 +61,10 @@ export function peelTransparentWrappers(tokens) {
|
|
|
60
61
|
if (!wrapper) {
|
|
61
62
|
break;
|
|
62
63
|
}
|
|
64
|
+
if (peelDepth >= MAX_WRAPPER_PEEL_DEPTH) {
|
|
65
|
+
return { tokens: current, xargsStdinOpaque: false, encounteredXargs, opaque: true };
|
|
66
|
+
}
|
|
67
|
+
peelDepth += 1;
|
|
63
68
|
if (wrapper.kind === 'opaque') {
|
|
64
69
|
return { tokens: current, xargsStdinOpaque: false, encounteredXargs, opaque: true };
|
|
65
70
|
}
|
|
@@ -304,39 +309,19 @@ export function extractRecursiveScript(tokens) {
|
|
|
304
309
|
const body = filtered.slice(1).join(' ').trim();
|
|
305
310
|
return body || null;
|
|
306
311
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
312
|
+
const invocation = decodeRecursiveInvocation(shellTokensFromValues(filtered, { detectExpansion: false }));
|
|
313
|
+
return invocation.kind === 'static' ? invocation.script || null : null;
|
|
314
|
+
}
|
|
315
|
+
export function decodeRecursiveInvocationTokens(tokens) {
|
|
316
|
+
const values = tokens.map((token) => token.value);
|
|
317
|
+
const { tokens: filtered, opaque } = peelTransparentWrappers(values);
|
|
318
|
+
if (opaque)
|
|
319
|
+
return { kind: 'none' };
|
|
320
|
+
return decodeRecursiveInvocation(tokens.slice(values.length - filtered.length));
|
|
315
321
|
}
|
|
316
322
|
export function extractDockerComposeRunScript(tokens) {
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
if (!usesCompose) {
|
|
320
|
-
return null;
|
|
321
|
-
}
|
|
322
|
-
if (!tokens.includes('run')) {
|
|
323
|
-
return null;
|
|
324
|
-
}
|
|
325
|
-
const runIndex = tokens.indexOf('run');
|
|
326
|
-
const tail = tokens.slice(runIndex + 1);
|
|
327
|
-
for (let index = 0; index < tail.length; index += 1) {
|
|
328
|
-
const shellHead = normalizeHead(tail[index] ?? '');
|
|
329
|
-
if (!SHELL_INTERPRETERS.has(shellHead)) {
|
|
330
|
-
continue;
|
|
331
|
-
}
|
|
332
|
-
const flag = tail[index + 1] ?? '';
|
|
333
|
-
if (flag !== '-lc' && flag !== '-c') {
|
|
334
|
-
continue;
|
|
335
|
-
}
|
|
336
|
-
const body = tail[index + 2] ?? '';
|
|
337
|
-
return body || null;
|
|
338
|
-
}
|
|
339
|
-
return null;
|
|
323
|
+
const invocation = decodeDockerComposeRunValues(tokens);
|
|
324
|
+
return invocation.kind === 'recursive' ? invocation.script || null : null;
|
|
340
325
|
}
|
|
341
326
|
/**
|
|
342
327
|
* True when a recursive script is evaluated from a command argument rather
|
|
@@ -352,8 +337,7 @@ export function isDynamicRecursiveEvaluation(tokens) {
|
|
|
352
337
|
if (head === 'eval') {
|
|
353
338
|
return true;
|
|
354
339
|
}
|
|
355
|
-
return ((
|
|
356
|
-
filtered.some((token) => SCRIPT_FLAGS.has(token)));
|
|
340
|
+
return decodeRecursiveInvocation(shellTokensFromValues(filtered)).kind !== 'none';
|
|
357
341
|
}
|
|
358
342
|
export function isCommandInspection(tokens) {
|
|
359
343
|
return (normalizeHead(tokens[0] ?? '') === 'command' && peelCommandWrapper(tokens).kind === 'preserve');
|
|
@@ -370,8 +354,7 @@ export function isBareInterpreter(tokens) {
|
|
|
370
354
|
if (!SHELL_INTERPRETERS.has(head) && !CODE_INTERPRETERS.has(head)) {
|
|
371
355
|
return false;
|
|
372
356
|
}
|
|
373
|
-
|
|
374
|
-
if (hasScriptFlag) {
|
|
357
|
+
if (decodeRecursiveInvocation(shellTokensFromValues(peeled)).kind !== 'none') {
|
|
375
358
|
return false;
|
|
376
359
|
}
|
|
377
360
|
const args = peeled.slice(1);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ShellToken } from '../shell-tokenizer.js';
|
|
2
|
+
export type RecursiveInvocation = {
|
|
3
|
+
kind: 'static';
|
|
4
|
+
interpreter: string;
|
|
5
|
+
script: string;
|
|
6
|
+
} | {
|
|
7
|
+
kind: 'dynamic';
|
|
8
|
+
interpreter: string;
|
|
9
|
+
signal: 'shell.script_expanded';
|
|
10
|
+
} | {
|
|
11
|
+
kind: 'none';
|
|
12
|
+
} | {
|
|
13
|
+
kind: 'indeterminate';
|
|
14
|
+
interpreter: string;
|
|
15
|
+
signal: 'shell.interpreter_argv_incomplete' | 'shell.interpreter_option_unknown';
|
|
16
|
+
};
|
|
17
|
+
export declare function decodeRecursiveInvocation(tokens: readonly ShellToken[]): RecursiveInvocation;
|
|
18
|
+
export declare function shellTokensFromValues(values: readonly string[], options?: {
|
|
19
|
+
detectExpansion?: boolean;
|
|
20
|
+
}): ShellToken[];
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
const SHELL_INTERPRETERS = new Set(['bash', 'sh', 'zsh', 'dash', 'fish']);
|
|
3
|
+
const PYTHON_INTERPRETERS = new Set(['python', 'python3']);
|
|
4
|
+
const SHELL_SHORT_OPTIONS = new Set(['c', 'l', 'e', 'x', 'u']);
|
|
5
|
+
const SHELL_NON_SCRIPT_SHORT_OPTIONS = new Set(['n']);
|
|
6
|
+
const SHELL_TERMINAL_OPTIONS = new Map([
|
|
7
|
+
['bash', new Set(['--help', '--version'])],
|
|
8
|
+
['zsh', new Set(['--version'])],
|
|
9
|
+
['fish', new Set(['-h', '--help', '-v', '--version'])],
|
|
10
|
+
]);
|
|
11
|
+
const SHELL_VALUE_OPTIONS = new Set(['-O', '+O', '--init-file', '--rcfile']);
|
|
12
|
+
const NODE_TERMINAL_OPTIONS = new Set(['-h', '--help', '--help-all', '-v', '--version']);
|
|
13
|
+
const NODE_FILE_OPTIONS = new Set(['-c', '--check']);
|
|
14
|
+
const PYTHON_PROFILE = {
|
|
15
|
+
scriptOptions: new Set(['-c']),
|
|
16
|
+
terminalOptions: new Set(['-h', '--help', '-V', '-VV', '--version']),
|
|
17
|
+
terminalValueOptions: new Set(['-m']),
|
|
18
|
+
flagOptions: new Set([
|
|
19
|
+
'-b',
|
|
20
|
+
'-bb',
|
|
21
|
+
'-B',
|
|
22
|
+
'-d',
|
|
23
|
+
'-E',
|
|
24
|
+
'-I',
|
|
25
|
+
'-O',
|
|
26
|
+
'-OO',
|
|
27
|
+
'-P',
|
|
28
|
+
'-q',
|
|
29
|
+
'-s',
|
|
30
|
+
'-S',
|
|
31
|
+
'-u',
|
|
32
|
+
'-v',
|
|
33
|
+
'-x',
|
|
34
|
+
]),
|
|
35
|
+
valueOptions: new Set(['-W', '-X']),
|
|
36
|
+
attachedValuePrefixes: ['-W', '-X'],
|
|
37
|
+
};
|
|
38
|
+
const RUBY_PROFILE = {
|
|
39
|
+
scriptOptions: new Set(['-e']),
|
|
40
|
+
terminalOptions: new Set(['-h', '--help', '-v', '--version', '--copyright']),
|
|
41
|
+
terminalValueOptions: new Set([]),
|
|
42
|
+
flagOptions: new Set(['-d', '--debug', '-w']),
|
|
43
|
+
valueOptions: new Set(['-I']),
|
|
44
|
+
attachedValuePrefixes: ['-I'],
|
|
45
|
+
};
|
|
46
|
+
const PERL_PROFILE = {
|
|
47
|
+
scriptOptions: new Set(['-e']),
|
|
48
|
+
terminalOptions: new Set(['-h', '--help', '-v', '--version']),
|
|
49
|
+
terminalValueOptions: new Set([]),
|
|
50
|
+
flagOptions: new Set([]),
|
|
51
|
+
valueOptions: new Set(['-I']),
|
|
52
|
+
attachedValuePrefixes: ['-I'],
|
|
53
|
+
};
|
|
54
|
+
const OSASCRIPT_PROFILE = {
|
|
55
|
+
scriptOptions: new Set(['-e']),
|
|
56
|
+
terminalOptions: new Set(['-h', '--help']),
|
|
57
|
+
terminalValueOptions: new Set([]),
|
|
58
|
+
flagOptions: new Set([]),
|
|
59
|
+
valueOptions: new Set(['-l']),
|
|
60
|
+
attachedValuePrefixes: [],
|
|
61
|
+
};
|
|
62
|
+
function normalizeInterpreter(value) {
|
|
63
|
+
return path.basename(value);
|
|
64
|
+
}
|
|
65
|
+
function scriptResult(interpreter, token) {
|
|
66
|
+
if (!token) {
|
|
67
|
+
return { kind: 'indeterminate', interpreter, signal: 'shell.interpreter_argv_incomplete' };
|
|
68
|
+
}
|
|
69
|
+
if (token.parts.some((part) => part.hasExpansion)) {
|
|
70
|
+
return { kind: 'dynamic', interpreter, signal: 'shell.script_expanded' };
|
|
71
|
+
}
|
|
72
|
+
return { kind: 'static', interpreter, script: token.value };
|
|
73
|
+
}
|
|
74
|
+
function decodeShell(words, interpreter) {
|
|
75
|
+
for (let index = 1; index < words.length; index += 1) {
|
|
76
|
+
const option = words[index]?.value ?? '';
|
|
77
|
+
if (option === '--')
|
|
78
|
+
return { kind: 'none' };
|
|
79
|
+
if (SHELL_TERMINAL_OPTIONS.get(interpreter)?.has(option))
|
|
80
|
+
return { kind: 'none' };
|
|
81
|
+
if (interpreter === 'bash' && SHELL_VALUE_OPTIONS.has(option)) {
|
|
82
|
+
const operand = words[index + 1]?.value;
|
|
83
|
+
if (!operand || operand.startsWith('-')) {
|
|
84
|
+
return { kind: 'indeterminate', interpreter, signal: 'shell.interpreter_option_unknown' };
|
|
85
|
+
}
|
|
86
|
+
index += 1;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (!option.startsWith('-') || option === '-')
|
|
90
|
+
return { kind: 'none' };
|
|
91
|
+
const flags = [...option.slice(1)];
|
|
92
|
+
if (flags.length === 0) {
|
|
93
|
+
return { kind: 'indeterminate', interpreter, signal: 'shell.interpreter_option_unknown' };
|
|
94
|
+
}
|
|
95
|
+
if (flags.every((flag) => SHELL_SHORT_OPTIONS.has(flag))) {
|
|
96
|
+
if (!flags.includes('c'))
|
|
97
|
+
continue;
|
|
98
|
+
return scriptResult(interpreter, words[index + 1]);
|
|
99
|
+
}
|
|
100
|
+
if (flags.every((flag) => SHELL_SHORT_OPTIONS.has(flag) || SHELL_NON_SCRIPT_SHORT_OPTIONS.has(flag)) &&
|
|
101
|
+
flags.some((flag) => SHELL_NON_SCRIPT_SHORT_OPTIONS.has(flag))) {
|
|
102
|
+
return { kind: 'none' };
|
|
103
|
+
}
|
|
104
|
+
return { kind: 'indeterminate', interpreter, signal: 'shell.interpreter_option_unknown' };
|
|
105
|
+
}
|
|
106
|
+
return { kind: 'none' };
|
|
107
|
+
}
|
|
108
|
+
function decodeSeparated(words, interpreter, profile) {
|
|
109
|
+
for (let index = 1; index < words.length; index += 1) {
|
|
110
|
+
const option = words[index]?.value ?? '';
|
|
111
|
+
if (option === '--' || !option.startsWith('-') || option === '-')
|
|
112
|
+
return { kind: 'none' };
|
|
113
|
+
if (profile.scriptOptions.has(option)) {
|
|
114
|
+
return scriptResult(interpreter, words[index + 1]);
|
|
115
|
+
}
|
|
116
|
+
if (profile.terminalOptions.has(option))
|
|
117
|
+
return { kind: 'none' };
|
|
118
|
+
if (profile.terminalValueOptions.has(option)) {
|
|
119
|
+
const operand = words[index + 1]?.value;
|
|
120
|
+
if (!operand || operand.startsWith('-')) {
|
|
121
|
+
return { kind: 'indeterminate', interpreter, signal: 'shell.interpreter_option_unknown' };
|
|
122
|
+
}
|
|
123
|
+
return { kind: 'none' };
|
|
124
|
+
}
|
|
125
|
+
if (profile.flagOptions.has(option)) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (profile.valueOptions.has(option)) {
|
|
129
|
+
const operand = words[index + 1]?.value;
|
|
130
|
+
if (!operand || operand.startsWith('-')) {
|
|
131
|
+
return { kind: 'indeterminate', interpreter, signal: 'shell.interpreter_option_unknown' };
|
|
132
|
+
}
|
|
133
|
+
index += 1;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (profile.attachedValuePrefixes.some((prefix) => option.startsWith(prefix) && option.length > prefix.length)) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
return { kind: 'indeterminate', interpreter, signal: 'shell.interpreter_option_unknown' };
|
|
140
|
+
}
|
|
141
|
+
return { kind: 'none' };
|
|
142
|
+
}
|
|
143
|
+
function decodeNode(words, interpreter) {
|
|
144
|
+
const option = words[1]?.value ?? '';
|
|
145
|
+
if (option === '--' || !option.startsWith('-') || option === '-')
|
|
146
|
+
return { kind: 'none' };
|
|
147
|
+
if (option === '-e' || option === '--eval') {
|
|
148
|
+
return scriptResult(interpreter, words[2]);
|
|
149
|
+
}
|
|
150
|
+
if (option.startsWith('--eval=')) {
|
|
151
|
+
const script = option.slice('--eval='.length);
|
|
152
|
+
if (words[1]?.parts.some((part) => part.hasExpansion)) {
|
|
153
|
+
return { kind: 'dynamic', interpreter, signal: 'shell.script_expanded' };
|
|
154
|
+
}
|
|
155
|
+
return { kind: 'static', interpreter, script };
|
|
156
|
+
}
|
|
157
|
+
if (NODE_TERMINAL_OPTIONS.has(option))
|
|
158
|
+
return { kind: 'none' };
|
|
159
|
+
if (NODE_FILE_OPTIONS.has(option))
|
|
160
|
+
return { kind: 'none' };
|
|
161
|
+
return { kind: 'indeterminate', interpreter, signal: 'shell.interpreter_option_unknown' };
|
|
162
|
+
}
|
|
163
|
+
function decodeEval(words) {
|
|
164
|
+
const arguments_ = words.slice(1);
|
|
165
|
+
if (arguments_.length === 0)
|
|
166
|
+
return { kind: 'none' };
|
|
167
|
+
if (arguments_.some((word) => word.parts.some((part) => part.hasExpansion))) {
|
|
168
|
+
return { kind: 'dynamic', interpreter: 'eval', signal: 'shell.script_expanded' };
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
kind: 'static',
|
|
172
|
+
interpreter: 'eval',
|
|
173
|
+
script: arguments_.map((word) => word.value).join(' '),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
export function decodeRecursiveInvocation(tokens) {
|
|
177
|
+
if (tokens.some((token) => token.kind === 'operator'))
|
|
178
|
+
return { kind: 'none' };
|
|
179
|
+
const words = tokens.filter((token) => token.kind === 'word');
|
|
180
|
+
const interpreter = normalizeInterpreter(words[0]?.value ?? '');
|
|
181
|
+
if (!interpreter)
|
|
182
|
+
return { kind: 'none' };
|
|
183
|
+
if (interpreter === 'eval')
|
|
184
|
+
return decodeEval(words);
|
|
185
|
+
if (SHELL_INTERPRETERS.has(interpreter))
|
|
186
|
+
return decodeShell(words, interpreter);
|
|
187
|
+
if (PYTHON_INTERPRETERS.has(interpreter)) {
|
|
188
|
+
return decodeSeparated(words, interpreter, PYTHON_PROFILE);
|
|
189
|
+
}
|
|
190
|
+
if (interpreter === 'node')
|
|
191
|
+
return decodeNode(words, interpreter);
|
|
192
|
+
if (interpreter === 'ruby')
|
|
193
|
+
return decodeSeparated(words, interpreter, RUBY_PROFILE);
|
|
194
|
+
if (interpreter === 'perl')
|
|
195
|
+
return decodeSeparated(words, interpreter, PERL_PROFILE);
|
|
196
|
+
if (interpreter === 'osascript')
|
|
197
|
+
return decodeSeparated(words, interpreter, OSASCRIPT_PROFILE);
|
|
198
|
+
return { kind: 'none' };
|
|
199
|
+
}
|
|
200
|
+
export function shellTokensFromValues(values, options = {}) {
|
|
201
|
+
let offset = 0;
|
|
202
|
+
return values.map((value) => {
|
|
203
|
+
const start = offset;
|
|
204
|
+
const end = start + value.length;
|
|
205
|
+
offset = end + 1;
|
|
206
|
+
return {
|
|
207
|
+
kind: 'word',
|
|
208
|
+
value,
|
|
209
|
+
raw: value,
|
|
210
|
+
start,
|
|
211
|
+
end,
|
|
212
|
+
parts: [
|
|
213
|
+
{
|
|
214
|
+
value,
|
|
215
|
+
raw: value,
|
|
216
|
+
start,
|
|
217
|
+
end,
|
|
218
|
+
quote: 'unquoted',
|
|
219
|
+
hasExpansion: options.detectExpansion !== false && (value.includes('$') || value.includes('`')),
|
|
220
|
+
},
|
|
221
|
+
],
|
|
222
|
+
};
|
|
223
|
+
});
|
|
224
|
+
}
|
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,25 +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'),
|
|
126
|
+
placement: 'append',
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
event: 'postToolUseFailure',
|
|
131
|
+
definition: {
|
|
132
|
+
command: runnerCommand(platform, resolvedHooksDir, resolvedRepo, 'belay-audit', runnerPathMode, 'postToolUseFailure'),
|
|
123
133
|
placement: 'append',
|
|
124
134
|
},
|
|
125
135
|
},
|
|
126
136
|
{
|
|
127
137
|
event: 'stop',
|
|
128
138
|
definition: {
|
|
129
|
-
command: runnerCommand(platform, resolvedHooksDir, resolvedRepo, 'belay-audit', 'stop'),
|
|
139
|
+
command: runnerCommand(platform, resolvedHooksDir, resolvedRepo, 'belay-audit', runnerPathMode, 'stop'),
|
|
130
140
|
placement: 'append',
|
|
131
141
|
},
|
|
132
142
|
},
|
|
133
143
|
{
|
|
134
144
|
event: 'sessionEnd',
|
|
135
145
|
definition: {
|
|
136
|
-
command: runnerCommand(platform, resolvedHooksDir, resolvedRepo, 'belay-audit', 'sessionEnd'),
|
|
146
|
+
command: runnerCommand(platform, resolvedHooksDir, resolvedRepo, 'belay-audit', runnerPathMode, 'sessionEnd'),
|
|
137
147
|
placement: 'append',
|
|
138
148
|
},
|
|
139
149
|
},
|
|
140
150
|
];
|
|
151
|
+
return entries.map(({ event, definition }) => ({
|
|
152
|
+
event,
|
|
153
|
+
definition: { ...definition, failClosed: true },
|
|
154
|
+
}));
|
|
141
155
|
}
|
|
142
156
|
/** @deprecated Use getManagedHookEntries instead. */
|
|
143
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
|
}
|