@technomoron/agent-run 0.99.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.
@@ -0,0 +1,1169 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.renderAgentsMods = renderAgentsMods;
5
+ exports.syncGeneratedAgentsFile = syncGeneratedAgentsFile;
6
+ exports.main = main;
7
+ exports.parseInvocation = parseInvocation;
8
+ exports.resolveAgentDir = resolveAgentDir;
9
+ exports.findProjectRoot = findProjectRoot;
10
+ exports.resolveProfile = resolveProfile;
11
+ exports.defaultConfigRoot = defaultConfigRoot;
12
+ const childProcess = require("child_process");
13
+ const fs = require("fs");
14
+ const os = require("os");
15
+ const path = require("path");
16
+ const IS_WINDOWS = process.platform === 'win32';
17
+ const ENV_FILE_NAME = '.agent-run.env';
18
+ const IGNORE_FILE_NAME = '.agent-run-ignore';
19
+ const LOCAL_AI_FILE_NAMES = new Set(['AGENTS.md', 'AGENTS-MODS.md', 'CLAUDE.md', 'codex.md']);
20
+ const CONFIG_ROOT_OVERRIDE_ENV = 'AGENT_RUN_CONFIG_ROOT_OVERRIDE';
21
+ const VERBOSE_ENV = 'AGENT_RUN_VERBOSE';
22
+ const agentRunEnvCache = new Map();
23
+ function renderAgentsMods(sourceFile, stack = []) {
24
+ const resolvedSource = path.resolve(sourceFile);
25
+ verbose(`render ${resolvedSource}`);
26
+ if (stack.includes(resolvedSource)) {
27
+ throw new Error(`Include cycle detected: ${[...stack, resolvedSource].join(' -> ')}`);
28
+ }
29
+ const lines = fs.readFileSync(resolvedSource, 'utf8').replace(/\r\n/g, '\n').split('\n');
30
+ const output = [];
31
+ const nextStack = [...stack, resolvedSource];
32
+ let sawLeadingInclude = false;
33
+ let insertedOverrideNote = false;
34
+ let contentStarted = false;
35
+ for (const line of lines) {
36
+ const trimmed = line.trim();
37
+ if (!contentStarted && trimmed === '') {
38
+ continue;
39
+ }
40
+ if (trimmed.startsWith('@')) {
41
+ const includePath = trimmed.slice(1).trim();
42
+ if (!includePath) {
43
+ continue;
44
+ }
45
+ const resolvedInclude = resolveIncludePath(resolvedSource, includePath);
46
+ verbose(`include ${includePath} -> ${resolvedInclude}`);
47
+ output.push(renderAgentsMods(resolvedInclude, nextStack));
48
+ if (!contentStarted) {
49
+ sawLeadingInclude = true;
50
+ }
51
+ continue;
52
+ }
53
+ if (sawLeadingInclude && !insertedOverrideNote) {
54
+ verbose(`insert override note in ${resolvedSource}`);
55
+ output.push('');
56
+ output.push('If anything below this point conflicts with anything included above,');
57
+ output.push('the later instructions below take precedence.');
58
+ output.push('');
59
+ insertedOverrideNote = true;
60
+ }
61
+ output.push(line);
62
+ contentStarted = true;
63
+ }
64
+ return output.join('\n').replace(/\n{3,}/g, '\n\n').trimEnd() + '\n';
65
+ }
66
+ function syncGeneratedAgentsFile(agentDir) {
67
+ const modsPath = path.join(agentDir, 'AGENTS-MODS.md');
68
+ const agentsPath = path.join(agentDir, 'AGENTS.md');
69
+ verbose(`sync generated files from ${modsPath}`);
70
+ const rendered = expandAgentsTemplateVariables(renderAgentsMods(modsPath), buildAgentsTemplateContext(agentDir));
71
+ fs.writeFileSync(agentsPath, rendered, 'utf8');
72
+ verbose(`write ${agentsPath}`);
73
+ const claudePath = path.join(agentDir, 'CLAUDE.md');
74
+ fs.writeFileSync(claudePath, '@AGENTS.md\n', 'utf8');
75
+ verbose(`write ${claudePath}`);
76
+ }
77
+ function buildAgentsTemplateContext(agentDir) {
78
+ const resolvedAgentDir = path.resolve(agentDir);
79
+ const resolvedConfigRoot = path.resolve(defaultConfigRoot());
80
+ const profile = path.relative(resolvedConfigRoot, resolvedAgentDir).replace(/\\/g, '/');
81
+ return {
82
+ agentDir: resolvedAgentDir,
83
+ agentsModsPath: path.join(resolvedAgentDir, 'AGENTS-MODS.md'),
84
+ agentsPath: path.join(resolvedAgentDir, 'AGENTS.md'),
85
+ claudePath: path.join(resolvedAgentDir, 'CLAUDE.md'),
86
+ configRoot: resolvedConfigRoot,
87
+ profile
88
+ };
89
+ }
90
+ function expandAgentsTemplateVariables(content, context) {
91
+ const replacements = new Map([
92
+ ['AGENT_DIR', context.agentDir],
93
+ ['AGENTS_MODS_PATH', context.agentsModsPath],
94
+ ['AGENTS_PATH', context.agentsPath],
95
+ ['CLAUDE_PATH', context.claudePath],
96
+ ['CONFIG_ROOT', context.configRoot],
97
+ ['PROFILE', context.profile]
98
+ ]);
99
+ return content.replace(/\{\{([A-Z_]+)\}\}/g, (match, key) => replacements.get(key) ?? match);
100
+ }
101
+ function resolveIncludePath(sourceFile, includePath) {
102
+ if (path.isAbsolute(includePath)) {
103
+ verbose(`resolve include absolute ${includePath}`);
104
+ return includePath;
105
+ }
106
+ const sourceRelativePath = path.resolve(path.dirname(sourceFile), includePath);
107
+ if (fs.existsSync(sourceRelativePath)) {
108
+ verbose(`resolve include relative ${includePath} -> ${sourceRelativePath}`);
109
+ return sourceRelativePath;
110
+ }
111
+ const includeRoot = findIncludeRoot(sourceFile);
112
+ const fallbackPath = path.resolve(includeRoot, includePath.replace(/^(\.\.\/)+/, ''));
113
+ verbose(`resolve include root fallback ${includePath} -> ${fallbackPath}`);
114
+ return fallbackPath;
115
+ }
116
+ function findIncludeRoot(sourceFile) {
117
+ let dir = path.dirname(sourceFile);
118
+ for (;;) {
119
+ const templatesDir = path.join(dir, 'templates');
120
+ if (fs.existsSync(templatesDir)) {
121
+ return dir;
122
+ }
123
+ const parent = path.dirname(dir);
124
+ if (parent === dir) {
125
+ return path.dirname(sourceFile);
126
+ }
127
+ dir = parent;
128
+ }
129
+ }
130
+ function main(invokedTool, argv) {
131
+ const parsed = parseInvocation(invokedTool, argv);
132
+ if (parsed.command === 'check') {
133
+ runCheck(parsed);
134
+ return;
135
+ }
136
+ if (parsed.command === 'init') {
137
+ runInit(parsed);
138
+ return;
139
+ }
140
+ if (parsed.command === 'edit') {
141
+ runEdit(parsed);
142
+ return;
143
+ }
144
+ if (parsed.command === 'update') {
145
+ runUpdate(parsed);
146
+ return;
147
+ }
148
+ runTool(parsed);
149
+ }
150
+ function parseInvocation(invokedTool, argv) {
151
+ let command = normalizeCommandName(invokedTool);
152
+ const extracted = extractGlobalOptions(argv);
153
+ const inputArgs = extracted.args;
154
+ if (extracted.verbose) {
155
+ process.env[VERBOSE_ENV] = '1';
156
+ }
157
+ if (extracted.configRootOverride !== null) {
158
+ process.env[CONFIG_ROOT_OVERRIDE_ENV] = extracted.configRootOverride;
159
+ }
160
+ if (command === null) {
161
+ if (inputArgs.length === 0) {
162
+ fail('usage: agent-run <codex|claude|check|init|edit|update> [options]');
163
+ }
164
+ const firstArg = inputArgs.shift();
165
+ command = normalizeCommandName(firstArg ?? '');
166
+ if (command === null) {
167
+ fail(`unknown command: ${firstArg ?? ''}`);
168
+ }
169
+ }
170
+ if (command === 'check') {
171
+ return parseCheckCommand(inputArgs);
172
+ }
173
+ if (command === 'init') {
174
+ return parseInitCommand(inputArgs);
175
+ }
176
+ if (command === 'edit') {
177
+ return parseEditCommand(inputArgs);
178
+ }
179
+ if (command === 'update') {
180
+ return parseUpdateCommand(inputArgs);
181
+ }
182
+ return parseRunCommand(command, inputArgs);
183
+ }
184
+ function extractGlobalOptions(argv) {
185
+ const args = [];
186
+ let configRootOverride = null;
187
+ let verbose = false;
188
+ let passthrough = false;
189
+ for (let index = 0; index < argv.length; index += 1) {
190
+ const arg = argv[index];
191
+ if (passthrough || arg === undefined) {
192
+ if (arg !== undefined) {
193
+ args.push(arg);
194
+ }
195
+ continue;
196
+ }
197
+ if (arg === '--') {
198
+ passthrough = true;
199
+ args.push(arg);
200
+ continue;
201
+ }
202
+ if (arg === '-v' || arg === '--verbose') {
203
+ verbose = true;
204
+ continue;
205
+ }
206
+ if (arg === '--config-root') {
207
+ const nextArg = argv[index + 1];
208
+ if (nextArg === undefined) {
209
+ fail('missing value for --config-root');
210
+ }
211
+ configRootOverride = path.resolve(nextArg);
212
+ index += 1;
213
+ continue;
214
+ }
215
+ if (arg.startsWith('--config-root=')) {
216
+ const rootValue = arg.slice('--config-root='.length);
217
+ if (!rootValue) {
218
+ fail('missing value for --config-root');
219
+ }
220
+ configRootOverride = path.resolve(rootValue);
221
+ continue;
222
+ }
223
+ args.push(arg);
224
+ }
225
+ return { args, configRootOverride, verbose };
226
+ }
227
+ function parseRunCommand(command, inputArgs) {
228
+ const wrapperArgs = {
229
+ none: false,
230
+ create: false
231
+ };
232
+ const args = [];
233
+ let passthrough = false;
234
+ for (const arg of inputArgs) {
235
+ if (passthrough) {
236
+ args.push(arg);
237
+ continue;
238
+ }
239
+ if (arg === '--') {
240
+ passthrough = true;
241
+ continue;
242
+ }
243
+ if (arg === '--none') {
244
+ wrapperArgs.none = true;
245
+ continue;
246
+ }
247
+ if (arg === '--create') {
248
+ wrapperArgs.create = true;
249
+ continue;
250
+ }
251
+ args.push(arg);
252
+ }
253
+ return { args, command, wrapperArgs };
254
+ }
255
+ function parseCheckCommand(inputArgs) {
256
+ let all = false;
257
+ let targetPath = process.cwd();
258
+ for (const arg of inputArgs) {
259
+ if (arg === '--all') {
260
+ all = true;
261
+ continue;
262
+ }
263
+ if (arg.startsWith('--')) {
264
+ fail(`unknown check option: ${arg}`);
265
+ }
266
+ targetPath = arg;
267
+ }
268
+ return {
269
+ all,
270
+ command: 'check',
271
+ targetPath: path.resolve(targetPath)
272
+ };
273
+ }
274
+ function parseInitCommand(inputArgs) {
275
+ let targetPath = process.cwd();
276
+ for (const arg of inputArgs) {
277
+ if (arg.startsWith('--')) {
278
+ fail(`unknown init option: ${arg}`);
279
+ }
280
+ targetPath = arg;
281
+ }
282
+ return {
283
+ command: 'init',
284
+ targetPath: path.resolve(targetPath)
285
+ };
286
+ }
287
+ function parseEditCommand(inputArgs) {
288
+ let targetPath = process.cwd();
289
+ for (const arg of inputArgs) {
290
+ if (arg.startsWith('--')) {
291
+ fail(`unknown edit option: ${arg}`);
292
+ }
293
+ targetPath = arg;
294
+ }
295
+ return {
296
+ command: 'edit',
297
+ targetPath: path.resolve(targetPath)
298
+ };
299
+ }
300
+ function parseUpdateCommand(inputArgs) {
301
+ let targetPath = process.cwd();
302
+ for (const arg of inputArgs) {
303
+ if (arg.startsWith('--')) {
304
+ fail(`unknown update option: ${arg}`);
305
+ }
306
+ targetPath = arg;
307
+ }
308
+ return {
309
+ command: 'update',
310
+ targetPath: path.resolve(targetPath)
311
+ };
312
+ }
313
+ function normalizeCommandName(value) {
314
+ if (!value) {
315
+ return null;
316
+ }
317
+ const base = path.basename(value).toLowerCase();
318
+ if (base === 'agent-run' || base === 'agent-run.js' || base === 'agent-run.cmd') {
319
+ return null;
320
+ }
321
+ if (base === 'codex' || base === 'codex.cmd' || base === 'codex.exe' || base === 'codex.bat') {
322
+ return 'codex';
323
+ }
324
+ if (base === 'claude' || base === 'claude.cmd' || base === 'claude.exe' || base === 'claude.bat') {
325
+ return 'claude';
326
+ }
327
+ if (base === 'check') {
328
+ return 'check';
329
+ }
330
+ if (base === 'init') {
331
+ return 'init';
332
+ }
333
+ if (base === 'edit') {
334
+ return 'edit';
335
+ }
336
+ if (base === 'update') {
337
+ return 'update';
338
+ }
339
+ return null;
340
+ }
341
+ function runTool(parsed) {
342
+ const { args, command, wrapperArgs } = parsed;
343
+ const realBinary = findRealBinary(command);
344
+ const permissionArgs = getPermissionArgs(command);
345
+ const projectRoot = findProjectRoot(process.cwd());
346
+ verbose(`run ${command}: cwd=${process.cwd()} projectRoot=${projectRoot}`);
347
+ if (wrapperArgs.none || isIgnoredDir(projectRoot)) {
348
+ verbose(`wrapper bypassed for ${command}${wrapperArgs.none ? ' via --none' : ' because project is ignored'}`);
349
+ execTool(realBinary, [...permissionArgs, ...args]);
350
+ return;
351
+ }
352
+ const profileResult = resolveProfileResult(projectRoot);
353
+ const excludedDir = profileResult.profile === null ? null : resolveAgentDir(projectRoot);
354
+ const localAiFiles = findLocalAiFiles(projectRoot, excludedDir);
355
+ if (localAiFiles.length > 0) {
356
+ failForLocalAiFiles(command, projectRoot, localAiFiles);
357
+ }
358
+ if (profileResult.profile === null) {
359
+ fail(profileResult.reason);
360
+ }
361
+ const agentDir = resolveAgentDir(projectRoot);
362
+ verbose(`resolved agent path: ${agentDir}`);
363
+ if (wrapperArgs.create) {
364
+ createBlankAgentFiles(agentDir);
365
+ process.stderr.write(`agent-run: created blank agent files in ${agentDir}\n`);
366
+ }
367
+ if (fs.existsSync(path.join(agentDir, 'AGENTS-MODS.md'))) {
368
+ syncGeneratedAgentsFile(agentDir);
369
+ }
370
+ if (command === 'codex') {
371
+ runCodex(realBinary, permissionArgs, agentDir, args);
372
+ return;
373
+ }
374
+ runClaude(realBinary, permissionArgs, agentDir, args);
375
+ }
376
+ function runInit(parsed) {
377
+ const projectRoot = findProjectRoot(parsed.targetPath);
378
+ verbose(`init target=${parsed.targetPath} projectRoot=${projectRoot}`);
379
+ if (isIgnoredDir(projectRoot)) {
380
+ process.stdout.write(`SKIP ignored ${projectRoot}\n`);
381
+ return;
382
+ }
383
+ const agentDir = resolveAgentDir(projectRoot);
384
+ createBlankAgentFiles(agentDir);
385
+ if (fs.existsSync(path.join(agentDir, 'AGENTS-MODS.md'))) {
386
+ syncGeneratedAgentsFile(agentDir);
387
+ }
388
+ process.stdout.write(`OK initialized ${agentDir}\n`);
389
+ }
390
+ function runEdit(parsed) {
391
+ const projectRoot = findProjectRoot(parsed.targetPath);
392
+ verbose(`edit target=${parsed.targetPath} projectRoot=${projectRoot}`);
393
+ if (isIgnoredDir(projectRoot)) {
394
+ process.stdout.write(`SKIP ignored ${projectRoot}\n`);
395
+ process.exit(0);
396
+ }
397
+ const agentDir = resolveAgentDir(projectRoot);
398
+ createBlankAgentFiles(agentDir);
399
+ if (fs.existsSync(path.join(agentDir, 'AGENTS-MODS.md'))) {
400
+ syncGeneratedAgentsFile(agentDir);
401
+ }
402
+ const modsPath = path.join(agentDir, 'AGENTS-MODS.md');
403
+ process.stdout.write(`Edit: ${modsPath}\n`);
404
+ openEditor(modsPath);
405
+ }
406
+ function runUpdate(parsed) {
407
+ const projectRoot = findProjectRoot(parsed.targetPath);
408
+ verbose(`update target=${parsed.targetPath} projectRoot=${projectRoot}`);
409
+ if (isIgnoredDir(projectRoot)) {
410
+ process.stdout.write(`SKIP ignored ${projectRoot}\n`);
411
+ return;
412
+ }
413
+ const agentDir = resolveAgentDir(projectRoot);
414
+ const modsPath = path.join(agentDir, 'AGENTS-MODS.md');
415
+ if (!fs.existsSync(modsPath)) {
416
+ fail(`missing AGENTS-MODS.md: ${modsPath}`);
417
+ }
418
+ syncGeneratedAgentsFile(agentDir);
419
+ process.stdout.write(`OK updated ${path.join(agentDir, 'AGENTS.md')}\n`);
420
+ }
421
+ function runCheck(parsed) {
422
+ if (parsed.all) {
423
+ verbose(`check --all root=${parsed.targetPath}`);
424
+ const report = checkSourceTree(parsed.targetPath);
425
+ printBatchReport(report.root, report.entries);
426
+ process.exit(report.hasErrors ? 1 : 0);
427
+ }
428
+ const projectRoot = findProjectRoot(parsed.targetPath);
429
+ verbose(`check target=${parsed.targetPath} projectRoot=${projectRoot}`);
430
+ if (isIgnoredDir(projectRoot)) {
431
+ process.stdout.write(`Check: ${projectRoot}\n`);
432
+ process.stdout.write('SKIP ignored by .agent-run-ignore\n');
433
+ process.exit(0);
434
+ }
435
+ const findings = checkProject(projectRoot);
436
+ printProjectReport(projectRoot, findings);
437
+ process.exit(hasErrors(findings) ? 1 : 0);
438
+ }
439
+ function checkProject(projectRoot) {
440
+ const findings = [];
441
+ verbose(`checking project ${projectRoot}`);
442
+ const profileResult = resolveProfileResult(projectRoot);
443
+ const excludedDir = profileResult.profile === null ? null : resolveAgentDir(projectRoot);
444
+ const localAiFiles = findLocalAiFiles(projectRoot, excludedDir);
445
+ for (const file of localAiFiles) {
446
+ findings.push({
447
+ message: `local AI file in project: ${file}`,
448
+ severity: 'ERROR'
449
+ });
450
+ }
451
+ if (profileResult.profile === null) {
452
+ findings.push({
453
+ message: profileResult.reason,
454
+ severity: 'ERROR'
455
+ });
456
+ return findings;
457
+ }
458
+ const agentDir = resolveAgentDir(projectRoot);
459
+ findings.push(...checkAgentDirectory(agentDir));
460
+ return findings;
461
+ }
462
+ function checkSourceTree(rootPath) {
463
+ const sourceRepos = findSourceRepos(rootPath);
464
+ const entries = sourceRepos.map((projectRoot) => ({
465
+ findings: checkProject(projectRoot),
466
+ label: projectRoot
467
+ }));
468
+ return {
469
+ entries,
470
+ hasErrors: entries.some((entry) => hasErrors(entry.findings)),
471
+ root: rootPath
472
+ };
473
+ }
474
+ function findSourceRepos(rootPath) {
475
+ const resolvedRoot = path.resolve(rootPath);
476
+ const repos = new Set();
477
+ if (!isIgnoredDir(resolvedRoot) && looksLikeRepoRoot(resolvedRoot)) {
478
+ repos.add(resolvedRoot);
479
+ }
480
+ walkSourceTree(resolvedRoot, 0, (fullPath, entry, depth) => {
481
+ if (entry.isDirectory() && entry.name === '.git') {
482
+ repos.add(path.dirname(fullPath));
483
+ return 'skip';
484
+ }
485
+ if (entry.isDirectory() && depth >= 3) {
486
+ return 'skip';
487
+ }
488
+ return undefined;
489
+ });
490
+ return [...repos].sort();
491
+ }
492
+ function looksLikeRepoRoot(dir) {
493
+ if (fs.existsSync(path.join(dir, '.git'))) {
494
+ return true;
495
+ }
496
+ return fs.existsSync(path.join(dir, 'package.json'));
497
+ }
498
+ function checkAgentDirectory(agentDir) {
499
+ const findings = [];
500
+ const modsPath = path.join(agentDir, 'AGENTS-MODS.md');
501
+ const agentsPath = path.join(agentDir, 'AGENTS.md');
502
+ const claudePath = path.join(agentDir, 'CLAUDE.md');
503
+ if (!fs.existsSync(modsPath)) {
504
+ findings.push({
505
+ message: `missing AGENTS-MODS.md: ${modsPath}`,
506
+ severity: 'ERROR'
507
+ });
508
+ }
509
+ if (!fs.existsSync(agentsPath)) {
510
+ findings.push({
511
+ message: `missing AGENTS.md: ${agentsPath}`,
512
+ severity: 'ERROR'
513
+ });
514
+ }
515
+ if (!fs.existsSync(claudePath)) {
516
+ findings.push({
517
+ message: `missing CLAUDE.md: ${claudePath}`,
518
+ severity: 'ERROR'
519
+ });
520
+ }
521
+ if (fs.existsSync(claudePath)) {
522
+ const claudeText = fs.readFileSync(claudePath, 'utf8').replace(/\r/g, '');
523
+ if (claudeText !== '@AGENTS.md\n' && claudeText !== '@AGENTS.md') {
524
+ findings.push({
525
+ message: `CLAUDE.md must contain only @AGENTS.md: ${claudePath}`,
526
+ severity: 'ERROR'
527
+ });
528
+ }
529
+ }
530
+ if (fs.existsSync(modsPath) && fs.existsSync(agentsPath)) {
531
+ try {
532
+ const rendered = renderAgentsMods(modsPath);
533
+ const generated = fs.readFileSync(agentsPath, 'utf8').replace(/\r\n/g, '\n');
534
+ if (generated !== rendered) {
535
+ findings.push({
536
+ message: `AGENTS.md is out of date with AGENTS-MODS.md: ${agentsPath}`,
537
+ severity: 'ERROR'
538
+ });
539
+ }
540
+ }
541
+ catch (error) {
542
+ findings.push({
543
+ message: `failed to render AGENTS-MODS.md in ${agentDir}: ${formatError(error)}`,
544
+ severity: 'ERROR'
545
+ });
546
+ }
547
+ }
548
+ return findings;
549
+ }
550
+ function printProjectReport(projectRoot, findings) {
551
+ process.stdout.write(`Check: ${projectRoot}\n`);
552
+ if (findings.length === 0) {
553
+ process.stdout.write('OK no issues found\n');
554
+ return;
555
+ }
556
+ for (const finding of findings) {
557
+ process.stdout.write(`${finding.severity} ${finding.message}\n`);
558
+ }
559
+ process.stdout.write(`Summary: ${countErrors(findings)} error(s), ${countWarnings(findings)} warning(s)\n`);
560
+ }
561
+ function printBatchReport(rootPath, entries) {
562
+ process.stdout.write(`Check all: ${rootPath}\n`);
563
+ if (entries.length === 0) {
564
+ process.stdout.write('WARN no source repos found\n');
565
+ return;
566
+ }
567
+ let totalErrors = 0;
568
+ let totalWarnings = 0;
569
+ for (const entry of entries) {
570
+ if (entry.findings.length === 0) {
571
+ process.stdout.write(`OK ${entry.label}\n`);
572
+ continue;
573
+ }
574
+ process.stdout.write(`FAIL ${entry.label}\n`);
575
+ for (const finding of entry.findings) {
576
+ process.stdout.write(` ${finding.severity} ${finding.message}\n`);
577
+ }
578
+ totalErrors += countErrors(entry.findings);
579
+ totalWarnings += countWarnings(entry.findings);
580
+ }
581
+ process.stdout.write(`Summary: ${totalErrors} error(s), ${totalWarnings} warning(s)\n`);
582
+ }
583
+ function hasErrors(findings) {
584
+ return findings.some((finding) => finding.severity === 'ERROR');
585
+ }
586
+ function countErrors(findings) {
587
+ return findings.filter((finding) => finding.severity === 'ERROR').length;
588
+ }
589
+ function countWarnings(findings) {
590
+ return findings.filter((finding) => finding.severity === 'WARN').length;
591
+ }
592
+ function formatError(error) {
593
+ return error instanceof Error ? error.message : String(error);
594
+ }
595
+ function createBlankAgentFiles(agentDir) {
596
+ fs.mkdirSync(agentDir, { recursive: true });
597
+ verbose(`ensure agent path exists: ${agentDir}`);
598
+ const modsPath = path.join(agentDir, 'AGENTS-MODS.md');
599
+ const agentsPath = path.join(agentDir, 'AGENTS.md');
600
+ const claudePath = path.join(agentDir, 'CLAUDE.md');
601
+ if (!fs.existsSync(modsPath)) {
602
+ fs.writeFileSync(modsPath, '\n', 'utf8');
603
+ verbose(`created ${modsPath}`);
604
+ }
605
+ else {
606
+ verbose(`exists ${modsPath}`);
607
+ }
608
+ if (!fs.existsSync(agentsPath)) {
609
+ fs.writeFileSync(agentsPath, '\n', 'utf8');
610
+ verbose(`created ${agentsPath}`);
611
+ }
612
+ else {
613
+ verbose(`exists ${agentsPath}`);
614
+ }
615
+ if (!fs.existsSync(claudePath)) {
616
+ fs.writeFileSync(claudePath, '@AGENTS.md\n', 'utf8');
617
+ verbose(`created ${claudePath}`);
618
+ }
619
+ else {
620
+ verbose(`exists ${claudePath}`);
621
+ }
622
+ }
623
+ function runCodex(realBinary, permissionArgs, agentDir, args) {
624
+ const agentsPath = path.join(agentDir, 'AGENTS.md');
625
+ if (fs.existsSync(agentsPath)) {
626
+ execTool(realBinary, [...permissionArgs, '--config', `system_prompt_file=${agentsPath}`, ...args]);
627
+ return;
628
+ }
629
+ failMissingConfig('codex', agentDir);
630
+ }
631
+ function runClaude(realBinary, permissionArgs, agentDir, args) {
632
+ const claudePath = path.join(agentDir, 'CLAUDE.md');
633
+ const agentsPath = path.join(agentDir, 'AGENTS.md');
634
+ if (fs.existsSync(claudePath) && fs.existsSync(agentsPath)) {
635
+ execTool(realBinary, [...permissionArgs, '--add-dir', agentDir, ...args]);
636
+ return;
637
+ }
638
+ failMissingConfig('claude', agentDir);
639
+ }
640
+ function failMissingConfig(tool, agentDir) {
641
+ process.stderr.write(`agent-run: no ${tool} config found for this project: ${agentDir}\n`);
642
+ process.stderr.write(`agent-run: run \`agent-run ${tool} --none\` to bypass agent setup, or \`agent-run ${tool} --create\` to create blank agent files.\n`);
643
+ process.exit(1);
644
+ }
645
+ function getPermissionArgs(tool) {
646
+ if (process.env.AGENT_WRAPPER_FORCE_PERMISSIVE !== '1') {
647
+ return [];
648
+ }
649
+ if (tool === 'codex') {
650
+ return ['-a', 'never', '-s', 'danger-full-access'];
651
+ }
652
+ if (tool === 'claude' && typeof process.getuid === 'function' && process.getuid() !== 0) {
653
+ return ['--permission-mode', 'bypassPermissions'];
654
+ }
655
+ return [];
656
+ }
657
+ function resolveAgentDir(projectRoot) {
658
+ const profile = resolveProfile(projectRoot);
659
+ const configRoot = defaultConfigRoot(projectRoot);
660
+ const agentDir = path.join(configRoot, profile);
661
+ verbose(`profile=${profile} configRoot=${configRoot} agentPath=${agentDir}`);
662
+ return agentDir;
663
+ }
664
+ function findLocalAiFiles(projectRoot, excludedDir = null) {
665
+ const matches = [];
666
+ const rootIgnored = isIgnoredDir(projectRoot);
667
+ if (rootIgnored) {
668
+ return matches;
669
+ }
670
+ const resolvedExcludedDir = excludedDir ? path.resolve(excludedDir) : null;
671
+ walk(projectRoot, (fullPath, entry) => {
672
+ if (resolvedExcludedDir !== null && isSamePathOrDescendant(fullPath, resolvedExcludedDir)) {
673
+ return entry.isDirectory() ? 'skip' : undefined;
674
+ }
675
+ if (isLocalAiDirectory(entry)) {
676
+ matches.push(fullPath);
677
+ return 'skip';
678
+ }
679
+ if (isLocalAiFile(entry)) {
680
+ matches.push(fullPath);
681
+ }
682
+ return undefined;
683
+ });
684
+ return matches.sort();
685
+ }
686
+ function walkSourceTree(dir, depth, visitor) {
687
+ if (isIgnoredDir(dir)) {
688
+ return;
689
+ }
690
+ const entries = fs
691
+ .readdirSync(dir, { withFileTypes: true })
692
+ .sort((a, b) => a.name.localeCompare(b.name));
693
+ for (const entry of entries) {
694
+ const fullPath = path.join(dir, entry.name);
695
+ const result = visitor(fullPath, entry, depth + 1);
696
+ if (entry.isDirectory() && result !== 'skip') {
697
+ walkSourceTree(fullPath, depth + 1, visitor);
698
+ }
699
+ }
700
+ }
701
+ function walk(dir, visitor) {
702
+ if (isIgnoredDir(dir)) {
703
+ return;
704
+ }
705
+ const entries = fs
706
+ .readdirSync(dir, { withFileTypes: true })
707
+ .sort((a, b) => a.name.localeCompare(b.name));
708
+ for (const entry of entries) {
709
+ const fullPath = path.join(dir, entry.name);
710
+ if (shouldPruneProjectEntry(entry)) {
711
+ continue;
712
+ }
713
+ const result = visitor(fullPath, entry);
714
+ if (entry.isDirectory() && result !== 'skip') {
715
+ walk(fullPath, visitor);
716
+ }
717
+ }
718
+ }
719
+ function isSamePathOrDescendant(candidatePath, parentPath) {
720
+ const relative = path.relative(parentPath, candidatePath);
721
+ return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
722
+ }
723
+ function shouldPruneProjectEntry(entry) {
724
+ return (entry.isDirectory() &&
725
+ (entry.name === '.git' ||
726
+ entry.name === 'node_modules' ||
727
+ entry.name === '.pnpm-store' ||
728
+ entry.name === 'dist' ||
729
+ entry.name === 'build'));
730
+ }
731
+ function isLocalAiDirectory(entry) {
732
+ return entry.isDirectory() && (entry.name === '.claude' || entry.name === '.codex');
733
+ }
734
+ function isLocalAiFile(entry) {
735
+ return entry.isFile() && LOCAL_AI_FILE_NAMES.has(entry.name);
736
+ }
737
+ function isIgnoredDir(dir) {
738
+ if (fs.existsSync(path.join(dir, IGNORE_FILE_NAME))) {
739
+ return true;
740
+ }
741
+ const env = readAgentRunEnv(dir);
742
+ return parseBooleanEnv(env.AGENT_RUN_IGNORE);
743
+ }
744
+ function failForLocalAiFiles(tool, projectRoot, localAiFiles) {
745
+ process.stderr.write(`agent-run: found local AI files in project root ${projectRoot}\n`);
746
+ for (const file of localAiFiles) {
747
+ process.stderr.write(`agent-run: ${file}\n`);
748
+ }
749
+ process.stderr.write(`agent-run: move these files manually out of the project, or run \`${tool}\` directly if you want to use the local files.\n`);
750
+ process.stderr.write(`agent-run: run \`agent-run ${tool} --none\` to bypass the wrapper for this invocation.\n`);
751
+ process.exit(1);
752
+ }
753
+ function findProjectRoot(cwd) {
754
+ let dir = path.resolve(cwd);
755
+ let nearestPackageRoot = '';
756
+ let workspaceRoot = '';
757
+ let gitRoot = '';
758
+ for (;;) {
759
+ const packagePath = path.join(dir, 'package.json');
760
+ if (fs.existsSync(packagePath)) {
761
+ if (!nearestPackageRoot) {
762
+ nearestPackageRoot = dir;
763
+ }
764
+ if (isWorkspaceRoot(dir)) {
765
+ workspaceRoot = dir;
766
+ }
767
+ }
768
+ if (fs.existsSync(path.join(dir, '.git'))) {
769
+ gitRoot = dir;
770
+ break;
771
+ }
772
+ const parent = path.dirname(dir);
773
+ if (parent === dir) {
774
+ break;
775
+ }
776
+ dir = parent;
777
+ }
778
+ const resolvedRoot = workspaceRoot || nearestPackageRoot || gitRoot || path.resolve(cwd);
779
+ verbose(`findProjectRoot cwd=${path.resolve(cwd)} workspaceRoot=${workspaceRoot || '-'} nearestPackageRoot=${nearestPackageRoot || '-'} gitRoot=${gitRoot || '-'} resolved=${resolvedRoot}`);
780
+ return resolvedRoot;
781
+ }
782
+ function isWorkspaceRoot(dir) {
783
+ if (fs.existsSync(path.join(dir, 'pnpm-workspace.yaml'))) {
784
+ return true;
785
+ }
786
+ const packagePath = path.join(dir, 'package.json');
787
+ if (!fs.existsSync(packagePath)) {
788
+ return false;
789
+ }
790
+ try {
791
+ const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
792
+ return Array.isArray(pkg.workspaces) || (pkg.workspaces !== null && typeof pkg.workspaces === 'object');
793
+ }
794
+ catch {
795
+ return false;
796
+ }
797
+ }
798
+ function resolveProfile(projectRoot) {
799
+ const result = resolveProfileResult(projectRoot);
800
+ if (result.profile === null) {
801
+ fail(result.reason);
802
+ }
803
+ return result.profile;
804
+ }
805
+ function resolveProfileResult(projectRoot) {
806
+ const env = readAgentRunEnv(projectRoot);
807
+ const envProfile = env.AGENT_RUN_PROFILE?.trim();
808
+ if (envProfile) {
809
+ verbose(`using ${ENV_FILE_NAME} AGENT_RUN_PROFILE=${envProfile}`);
810
+ return parseProfile(envProfile, `${ENV_FILE_NAME} AGENT_RUN_PROFILE`);
811
+ }
812
+ const packagePath = path.join(projectRoot, 'package.json');
813
+ if (fs.existsSync(packagePath)) {
814
+ try {
815
+ const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
816
+ if (typeof pkg.name === 'string' && pkg.name.length > 0) {
817
+ verbose(`using package.json name=${pkg.name}`);
818
+ return parseProfile(pkg.name.startsWith('@') ? pkg.name.slice(1) : pkg.name, `${packagePath} name`);
819
+ }
820
+ }
821
+ catch {
822
+ return {
823
+ profile: null,
824
+ reason: `cannot parse package.json: ${packagePath}`
825
+ };
826
+ }
827
+ }
828
+ return {
829
+ profile: null,
830
+ reason: `cannot resolve agent profile for ${projectRoot}; add ${ENV_FILE_NAME} with AGENT_RUN_PROFILE=<org/project> or set package.json.name`
831
+ };
832
+ }
833
+ function parseProfile(profile, source) {
834
+ const normalized = profile.trim().replace(/\\/g, '/');
835
+ if (!normalized) {
836
+ return {
837
+ profile: null,
838
+ reason: `${source} must be a non-empty path relative to the config root`
839
+ };
840
+ }
841
+ if (normalized.startsWith('/') || normalized.startsWith('\\\\') || /^[A-Za-z]:\//.test(normalized)) {
842
+ return {
843
+ profile: null,
844
+ reason: `${source} must be relative to the config root, not an absolute path`
845
+ };
846
+ }
847
+ const segments = normalized.split('/').filter((segment) => segment.length > 0);
848
+ if (segments.length === 0 || segments.some((segment) => segment === '.' || segment === '..')) {
849
+ return {
850
+ profile: null,
851
+ reason: `${source} must be a clean relative path like org/my-project`
852
+ };
853
+ }
854
+ return {
855
+ profile: segments.join('/'),
856
+ reason: ''
857
+ };
858
+ }
859
+ function readAgentRunEnv(dir) {
860
+ const resolvedDir = path.resolve(dir);
861
+ const cached = agentRunEnvCache.get(resolvedDir);
862
+ if (cached) {
863
+ return cached;
864
+ }
865
+ const filePath = path.join(resolvedDir, ENV_FILE_NAME);
866
+ if (!fs.existsSync(filePath)) {
867
+ verbose(`no ${ENV_FILE_NAME} in ${resolvedDir}`);
868
+ const emptyEnv = {};
869
+ agentRunEnvCache.set(resolvedDir, emptyEnv);
870
+ return emptyEnv;
871
+ }
872
+ verbose(`read ${filePath}`);
873
+ const env = {};
874
+ const lines = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n').split('\n');
875
+ for (const rawLine of lines) {
876
+ const line = rawLine.trim();
877
+ if (!line || line.startsWith('#')) {
878
+ continue;
879
+ }
880
+ const equalsIndex = line.indexOf('=');
881
+ if (equalsIndex <= 0) {
882
+ continue;
883
+ }
884
+ const key = line.slice(0, equalsIndex).trim();
885
+ let value = line.slice(equalsIndex + 1).trim();
886
+ if (value.length >= 2 &&
887
+ ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")))) {
888
+ value = value.slice(1, -1);
889
+ }
890
+ env[key] = value;
891
+ }
892
+ agentRunEnvCache.set(resolvedDir, env);
893
+ return env;
894
+ }
895
+ function parseBooleanEnv(value) {
896
+ if (value === undefined) {
897
+ return false;
898
+ }
899
+ const normalized = value.trim().toLowerCase();
900
+ return normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on';
901
+ }
902
+ function defaultConfigRoot(projectRoot) {
903
+ const overrideRoot = process.env[CONFIG_ROOT_OVERRIDE_ENV];
904
+ if (overrideRoot) {
905
+ const resolved = path.resolve(overrideRoot);
906
+ verbose(`using --config-root override: ${resolved}`);
907
+ return resolved;
908
+ }
909
+ if (process.env.AGENT_CONFIG_ROOT) {
910
+ const resolved = path.resolve(process.env.AGENT_CONFIG_ROOT);
911
+ verbose(`using AGENT_CONFIG_ROOT env: ${resolved}`);
912
+ return resolved;
913
+ }
914
+ if (projectRoot) {
915
+ const env = readAgentRunEnv(projectRoot);
916
+ const localConfigRoot = env.AGENT_CONFIG_ROOT?.trim();
917
+ if (localConfigRoot) {
918
+ const resolved = path.resolve(projectRoot, localConfigRoot);
919
+ verbose(`using ${ENV_FILE_NAME} AGENT_CONFIG_ROOT: ${resolved}`);
920
+ return resolved;
921
+ }
922
+ }
923
+ if (IS_WINDOWS) {
924
+ const resolved = path.join(os.homedir(), 'Documents', 'source', 'agent-configs');
925
+ verbose(`using default config root: ${resolved}`);
926
+ return resolved;
927
+ }
928
+ const resolved = path.join(os.homedir(), 'source', 'agent-configs');
929
+ verbose(`using default config root: ${resolved}`);
930
+ return resolved;
931
+ }
932
+ function findRealBinary(tool) {
933
+ const pathValue = process.env.PATH || '';
934
+ const pathDirs = pathValue.split(path.delimiter).filter((entry) => entry.length > 0);
935
+ const currentScriptArg = process.argv[1];
936
+ const currentScript = currentScriptArg ? fs.realpathSync(currentScriptArg) : '';
937
+ const wrapperCandidates = new Set([
938
+ path.join(__dirname, tool),
939
+ path.join(__dirname, `${tool}.js`),
940
+ path.join(__dirname, `${tool}.cmd`),
941
+ path.join(__dirname, `${tool}.bat`),
942
+ path.join(__dirname, `${tool}.exe`),
943
+ path.join(__dirname, 'agent-run'),
944
+ path.join(__dirname, 'agent-run.js'),
945
+ path.join(__dirname, 'agent-run.cmd'),
946
+ path.join(__dirname, 'agent-run.bat'),
947
+ path.join(__dirname, 'agent-run.exe')
948
+ ]
949
+ .filter((candidate) => fs.existsSync(candidate))
950
+ .map((candidate) => {
951
+ try {
952
+ return fs.realpathSync(candidate);
953
+ }
954
+ catch {
955
+ return candidate;
956
+ }
957
+ }));
958
+ const extensions = getExecutableExtensions(tool);
959
+ for (const dir of pathDirs) {
960
+ for (const extension of extensions) {
961
+ const candidate = path.join(dir, `${tool}${extension}`);
962
+ if (!fs.existsSync(candidate)) {
963
+ continue;
964
+ }
965
+ if (!isExecutable(candidate)) {
966
+ continue;
967
+ }
968
+ let resolvedCandidate = candidate;
969
+ try {
970
+ resolvedCandidate = fs.realpathSync(candidate);
971
+ }
972
+ catch {
973
+ resolvedCandidate = candidate;
974
+ }
975
+ if (resolvedCandidate === currentScript) {
976
+ continue;
977
+ }
978
+ if (wrapperCandidates.has(resolvedCandidate)) {
979
+ continue;
980
+ }
981
+ return candidate;
982
+ }
983
+ }
984
+ fail(`no ${tool} binary found in PATH`);
985
+ }
986
+ function getExecutableExtensions(tool) {
987
+ if (!IS_WINDOWS) {
988
+ return [''];
989
+ }
990
+ if (path.extname(tool)) {
991
+ return [''];
992
+ }
993
+ const pathExt = (process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM')
994
+ .split(';')
995
+ .filter((entry) => entry.length > 0)
996
+ .map((entry) => entry.toLowerCase());
997
+ return [''].concat(pathExt);
998
+ }
999
+ function isExecutable(filePath) {
1000
+ try {
1001
+ if (IS_WINDOWS) {
1002
+ return fs.statSync(filePath).isFile();
1003
+ }
1004
+ fs.accessSync(filePath, fs.constants.X_OK);
1005
+ return true;
1006
+ }
1007
+ catch {
1008
+ return false;
1009
+ }
1010
+ }
1011
+ function execTool(command, args) {
1012
+ verbose(`exec tool: ${formatCommand(command, args)} shell=${String(shouldUseShell(command))}`);
1013
+ execCommand(command, args, shouldUseShell(command));
1014
+ }
1015
+ function execCommand(command, args, shell, env) {
1016
+ verbose(`spawn: ${formatCommand(command, args)} shell=${String(shell)}`);
1017
+ const child = childProcess.spawn(command, args, {
1018
+ env,
1019
+ shell,
1020
+ stdio: 'inherit'
1021
+ });
1022
+ child.on('exit', (code, signal) => {
1023
+ if (signal) {
1024
+ process.kill(process.pid, signal);
1025
+ return;
1026
+ }
1027
+ process.exit(code === null ? 1 : code);
1028
+ });
1029
+ child.on('error', (error) => {
1030
+ fail(error.message);
1031
+ });
1032
+ }
1033
+ function openEditor(filePath) {
1034
+ const visual = process.env.VISUAL?.trim();
1035
+ if (visual) {
1036
+ verbose(`open editor via VISUAL=${visual} file=${filePath}`);
1037
+ execCommand(visual, [filePath], true);
1038
+ return;
1039
+ }
1040
+ const editor = process.env.EDITOR?.trim();
1041
+ if (editor) {
1042
+ verbose(`open editor via EDITOR=${editor} file=${filePath}`);
1043
+ execCommand(editor, [filePath], true);
1044
+ return;
1045
+ }
1046
+ const vscodeCommand = findVsCodeEditorCommand();
1047
+ if (vscodeCommand !== null) {
1048
+ verbose(`open editor via VS Code command=${vscodeCommand} file=${filePath}`);
1049
+ execCommand(vscodeCommand, ['--reuse-window', filePath], shouldUseShell(vscodeCommand));
1050
+ return;
1051
+ }
1052
+ const fallbackEditor = findFallbackEditor();
1053
+ if (fallbackEditor !== null) {
1054
+ verbose(`open editor via fallback command=${fallbackEditor} file=${filePath}`);
1055
+ execCommand(fallbackEditor, [filePath], shouldUseShell(fallbackEditor));
1056
+ return;
1057
+ }
1058
+ if (IS_WINDOWS) {
1059
+ verbose(`open editor via cmd.exe start file=${filePath}`);
1060
+ execCommand('cmd.exe', ['/c', 'start', '', filePath], false);
1061
+ return;
1062
+ }
1063
+ if (process.platform === 'darwin') {
1064
+ verbose(`open editor via open file=${filePath}`);
1065
+ execCommand('open', [filePath], false);
1066
+ return;
1067
+ }
1068
+ verbose(`open editor via xdg-open file=${filePath}`);
1069
+ execCommand('xdg-open', [filePath], false);
1070
+ }
1071
+ function findVsCodeEditorCommand() {
1072
+ if (!isRunningInVsCodeTerminal()) {
1073
+ return null;
1074
+ }
1075
+ for (const candidate of ['code', 'codium']) {
1076
+ const resolved = findExecutable(candidate);
1077
+ if (resolved !== null) {
1078
+ return resolved;
1079
+ }
1080
+ }
1081
+ return null;
1082
+ }
1083
+ function isRunningInVsCodeTerminal() {
1084
+ const termProgram = process.env.TERM_PROGRAM?.trim().toLowerCase();
1085
+ if (termProgram === 'vscode') {
1086
+ return true;
1087
+ }
1088
+ return (Boolean(process.env.VSCODE_GIT_IPC_HANDLE) ||
1089
+ Boolean(process.env.VSCODE_IPC_HOOK) ||
1090
+ Boolean(process.env.VSCODE_IPC_HOOK_CLI));
1091
+ }
1092
+ function findFallbackEditor() {
1093
+ const candidates = IS_WINDOWS
1094
+ ? ['notepad.exe']
1095
+ : ['joe', 'sensible-editor', 'editor', 'nano', 'nvim', 'vim', 'vi'];
1096
+ for (const candidate of candidates) {
1097
+ const resolved = findExecutable(candidate);
1098
+ if (resolved !== null) {
1099
+ return resolved;
1100
+ }
1101
+ }
1102
+ return null;
1103
+ }
1104
+ function findExecutable(command) {
1105
+ const pathValue = process.env.PATH || '';
1106
+ const pathDirs = pathValue.split(path.delimiter).filter((entry) => entry.length > 0);
1107
+ const extensions = getExecutableExtensionsForCommand(command);
1108
+ for (const dir of pathDirs) {
1109
+ for (const extension of extensions) {
1110
+ const candidate = path.join(dir, `${command}${extension}`);
1111
+ if (!fs.existsSync(candidate)) {
1112
+ continue;
1113
+ }
1114
+ if (!isExecutable(candidate)) {
1115
+ continue;
1116
+ }
1117
+ return candidate;
1118
+ }
1119
+ }
1120
+ return null;
1121
+ }
1122
+ function getExecutableExtensionsForCommand(command) {
1123
+ if (!IS_WINDOWS) {
1124
+ return [''];
1125
+ }
1126
+ if (path.extname(command)) {
1127
+ return [''];
1128
+ }
1129
+ const pathExt = (process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM')
1130
+ .split(';')
1131
+ .filter((entry) => entry.length > 0)
1132
+ .map((entry) => entry.toLowerCase());
1133
+ return [''].concat(pathExt);
1134
+ }
1135
+ function shouldUseShell(command) {
1136
+ if (!IS_WINDOWS) {
1137
+ return false;
1138
+ }
1139
+ const extension = path.extname(command).toLowerCase();
1140
+ return extension === '.cmd' || extension === '.bat';
1141
+ }
1142
+ function isVerbose() {
1143
+ return parseBooleanEnv(process.env[VERBOSE_ENV]);
1144
+ }
1145
+ function verbose(message) {
1146
+ if (!isVerbose()) {
1147
+ return;
1148
+ }
1149
+ process.stderr.write(`agent-run: ${message}\n`);
1150
+ }
1151
+ function formatCommand(command, args) {
1152
+ return [command, ...args].map(quoteArg).join(' ');
1153
+ }
1154
+ function quoteArg(value) {
1155
+ if (value === '') {
1156
+ return '""';
1157
+ }
1158
+ if (/^[A-Za-z0-9_./:=+-]+$/.test(value)) {
1159
+ return value;
1160
+ }
1161
+ return JSON.stringify(value);
1162
+ }
1163
+ function fail(message) {
1164
+ process.stderr.write(`agent-run: ${message}\n`);
1165
+ process.exit(1);
1166
+ }
1167
+ if (require.main === module) {
1168
+ main(path.basename(process.argv[1] ?? 'agent-run'), process.argv.slice(2));
1169
+ }