@technomoron/agent-run 0.99.21 → 0.99.23
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/CHANGES +29 -0
- package/README.md +41 -26
- package/dist/agent-run.js +510 -218
- package/examples/basic-config/agent-config/gitignore +17 -0
- package/examples/basic-config/agent-config/global/skills/release-package-check/SKILL.md.njk +3 -0
- package/examples/basic-config/agent-config/global/tool-templates/claude-settings.json.njk +11 -13
- package/examples/basic-config/agent-config/global/tool-templates/codex-config.toml.njk +4 -5
- package/examples/basic-config/agent-config/starter/basic-project/overrides/claude-settings.json.njk +11 -13
- package/examples/basic-config/agent-config/starter/basic-project/overrides/codex-config.toml.njk +4 -5
- package/ops/systemd/ai-tools-update.service +8 -3
- package/package.json +7 -4
- package/scripts/install-systemd-jobs.sh +0 -2
- package/scripts/update-ai-tools.sh +18 -8
package/dist/agent-run.js
CHANGED
|
@@ -7,23 +7,35 @@ exports.resolveAgentDir = resolveAgentDir;
|
|
|
7
7
|
exports.findProjectRoot = findProjectRoot;
|
|
8
8
|
exports.resolveProfile = resolveProfile;
|
|
9
9
|
exports.defaultConfigRoot = defaultConfigRoot;
|
|
10
|
+
exports.defaultConfigRootSearchCandidates = defaultConfigRootSearchCandidates;
|
|
11
|
+
exports.parseEditorCommand = parseEditorCommand;
|
|
10
12
|
const childProcess = require("child_process");
|
|
11
13
|
const fs = require("fs");
|
|
12
14
|
const os = require("os");
|
|
13
15
|
const path = require("path");
|
|
16
|
+
const crossSpawn = require("cross-spawn");
|
|
14
17
|
const jsonc_parser_1 = require("jsonc-parser");
|
|
15
18
|
const nunjucks = require("nunjucks");
|
|
16
19
|
const IS_WINDOWS = process.platform === 'win32';
|
|
17
20
|
const ENV_FILE_NAME = '.agent-run.env';
|
|
18
21
|
const IGNORE_FILE_NAME = '.agent-run-ignore';
|
|
19
|
-
const LOCAL_AI_FILE_NAMES = new Set([
|
|
22
|
+
const LOCAL_AI_FILE_NAMES = new Set([
|
|
23
|
+
'AGENTS.md',
|
|
24
|
+
'AGENTS-MODS.md',
|
|
25
|
+
'AGENTS.override.md',
|
|
26
|
+
'CLAUDE.md',
|
|
27
|
+
'CLAUDE.local.md',
|
|
28
|
+
'.mcp.json',
|
|
29
|
+
'codex.md'
|
|
30
|
+
]);
|
|
31
|
+
const LOCAL_AI_DIRECTORY_NAMES = new Set(['.agents', '.claude', '.codex']);
|
|
20
32
|
const CONFIG_ROOT_OVERRIDE_ENV = 'AGENT_RUN_CONFIG_ROOT_OVERRIDE';
|
|
21
33
|
const CONFIG_DIR_ENV = 'AGENT_CONFIG_DIR';
|
|
22
34
|
const VERBOSE_ENV = 'AGENT_RUN_VERBOSE';
|
|
23
35
|
const MANIFEST_FILE_NAME = 'agent-run.jsonc';
|
|
24
36
|
const LOCAL_TEMPLATE_FILE_NAME = 'local.md.njk';
|
|
25
37
|
const LIVE_DIR_NAME = 'live';
|
|
26
|
-
const PACKAGE_VERSION = '0.99.
|
|
38
|
+
const PACKAGE_VERSION = '0.99.23';
|
|
27
39
|
const UNEXPANDED_TEMPLATE_RE = /\{\{[^}]+\}\}|\{%[^%]+%\}/;
|
|
28
40
|
const agentRunEnvCache = new Map();
|
|
29
41
|
const GENERATED_GITIGNORE_ENTRIES = [
|
|
@@ -31,7 +43,24 @@ const GENERATED_GITIGNORE_ENTRIES = [
|
|
|
31
43
|
'**/live/',
|
|
32
44
|
'',
|
|
33
45
|
'# Optional generated caches',
|
|
34
|
-
'**/.agent-run-cache/'
|
|
46
|
+
'**/.agent-run-cache/',
|
|
47
|
+
'',
|
|
48
|
+
'# Legacy Codex runtime state',
|
|
49
|
+
'**/auth.json',
|
|
50
|
+
'**/history.jsonl',
|
|
51
|
+
'**/sessions/',
|
|
52
|
+
'**/archived_sessions/',
|
|
53
|
+
'**/log/',
|
|
54
|
+
'**/logs/',
|
|
55
|
+
'**/shell_snapshots/',
|
|
56
|
+
'**/*.sqlite*',
|
|
57
|
+
'**/models_cache.json',
|
|
58
|
+
'**/cache/',
|
|
59
|
+
'**/.tmp/',
|
|
60
|
+
'**/installation_id',
|
|
61
|
+
'**/version.json',
|
|
62
|
+
'**/.personality_migration',
|
|
63
|
+
'**/skills/.system/'
|
|
35
64
|
];
|
|
36
65
|
const REQUIRED_GLOBAL_TEMPLATES = [
|
|
37
66
|
'global/agents/code.md.njk',
|
|
@@ -217,6 +246,7 @@ function parseRunCommand(command, inputArgs) {
|
|
|
217
246
|
}
|
|
218
247
|
if (arg === '--') {
|
|
219
248
|
passthrough = true;
|
|
249
|
+
args.push(arg);
|
|
220
250
|
continue;
|
|
221
251
|
}
|
|
222
252
|
if (arg === '--none') {
|
|
@@ -261,15 +291,15 @@ function parseRunCommand(command, inputArgs) {
|
|
|
261
291
|
}
|
|
262
292
|
if (arg === '--network') {
|
|
263
293
|
if (command !== 'codex') {
|
|
264
|
-
fail('--network is only supported for agent-run codex
|
|
294
|
+
fail('--network is only supported for agent-run codex');
|
|
265
295
|
}
|
|
266
296
|
wrapperArgs.codexNetwork = true;
|
|
267
297
|
continue;
|
|
268
298
|
}
|
|
269
299
|
args.push(arg);
|
|
270
300
|
}
|
|
271
|
-
if (wrapperArgs.codexNetwork && wrapperArgs.codexSandboxMode
|
|
272
|
-
fail('--network
|
|
301
|
+
if (wrapperArgs.codexNetwork && wrapperArgs.codexSandboxMode === 'danger') {
|
|
302
|
+
fail('--network cannot be combined with agent-run codex --danger');
|
|
273
303
|
}
|
|
274
304
|
return { args, command, wrapperArgs };
|
|
275
305
|
}
|
|
@@ -442,9 +472,9 @@ function renderHelp(topic) {
|
|
|
442
472
|
' --generate Generate files without running the tool',
|
|
443
473
|
'',
|
|
444
474
|
'Codex wrapper options:',
|
|
445
|
-
' --danger Run Codex with no sandbox: -a never -s danger-full-access
|
|
446
|
-
' --sandboxed Run Codex with workspace-write sandbox',
|
|
447
|
-
' --network Enable network for
|
|
475
|
+
' --danger Run Codex with no sandbox: -a never -s danger-full-access',
|
|
476
|
+
' --sandboxed Run Codex with workspace-write sandbox (default)',
|
|
477
|
+
' --network Enable network for the workspace-write sandbox',
|
|
448
478
|
''
|
|
449
479
|
].join('\n');
|
|
450
480
|
}
|
|
@@ -524,23 +554,29 @@ function runTool(parsed) {
|
|
|
524
554
|
fail(profileResult.reason);
|
|
525
555
|
}
|
|
526
556
|
const profile = profileResult.profile;
|
|
527
|
-
const configRoot = defaultConfigRoot(projectRoot);
|
|
528
|
-
const agentDir = resolveAgentDir(projectRoot);
|
|
529
557
|
if (wrapperArgs.show) {
|
|
558
|
+
const configRoot = defaultConfigRoot(projectRoot);
|
|
559
|
+
const agentDir = path.join(configRoot, profile);
|
|
560
|
+
verbose(`profile=${profile} configRoot=${configRoot} agentPath=${agentDir}`);
|
|
530
561
|
showToolProfile(command, projectRoot, agentDir);
|
|
531
562
|
return;
|
|
532
563
|
}
|
|
533
564
|
if (wrapperArgs.create) {
|
|
534
565
|
runInit({ command: 'init', targetPath: projectRoot });
|
|
535
566
|
}
|
|
567
|
+
const configRoot = defaultConfigRoot(projectRoot);
|
|
568
|
+
const agentDir = path.join(configRoot, profile);
|
|
569
|
+
verbose(`profile=${profile} configRoot=${configRoot} agentPath=${agentDir}`);
|
|
536
570
|
ensureRunnableProfile(configRoot, agentDir, profile);
|
|
571
|
+
const preview = renderProfile(projectRoot, agentDir, true, command);
|
|
572
|
+
ensureToolEnabled(preview.context, command);
|
|
537
573
|
if (wrapperArgs.generate) {
|
|
538
574
|
const rendered = syncAgentProfile(projectRoot, agentDir);
|
|
539
575
|
printUpdateSummary(rendered);
|
|
540
576
|
return;
|
|
541
577
|
}
|
|
542
578
|
const localAiFiles = findLocalAiFiles(projectRoot, agentDir);
|
|
543
|
-
if (localAiFiles.length > 0) {
|
|
579
|
+
if (preview.context.guardrails.forbidRepoAiFiles && localAiFiles.length > 0) {
|
|
544
580
|
if (!wrapperArgs.local) {
|
|
545
581
|
failForLocalAiFiles(command, projectRoot, localAiFiles);
|
|
546
582
|
}
|
|
@@ -548,16 +584,17 @@ function runTool(parsed) {
|
|
|
548
584
|
}
|
|
549
585
|
const realBinary = findRealBinary(command);
|
|
550
586
|
const permissionArgs = getPermissionArgs(command);
|
|
551
|
-
syncAgentProfile(projectRoot, agentDir);
|
|
587
|
+
const rendered = syncAgentProfile(projectRoot, agentDir);
|
|
552
588
|
if (command === 'codex') {
|
|
553
|
-
runCodex(realBinary, permissionArgs,
|
|
589
|
+
runCodex(realBinary, permissionArgs, rendered.context, args, wrapperArgs);
|
|
554
590
|
return;
|
|
555
591
|
}
|
|
556
|
-
runClaude(realBinary, permissionArgs,
|
|
592
|
+
runClaude(realBinary, permissionArgs, rendered.context, args, wrapperArgs);
|
|
557
593
|
}
|
|
558
594
|
function showToolProfile(command, projectRoot, agentDir) {
|
|
559
595
|
const trace = { sourceFiles: new Set() };
|
|
560
596
|
const rendered = renderProfile(projectRoot, agentDir, true, command, trace);
|
|
597
|
+
ensureToolEnabled(rendered.context, command);
|
|
561
598
|
const generatedFiles = uniqueSorted(rendered.files.map((file) => file.path));
|
|
562
599
|
const sourceFiles = uniqueSorted([...trace.sourceFiles]);
|
|
563
600
|
process.stdout.write([
|
|
@@ -574,6 +611,11 @@ function showToolProfile(command, projectRoot, agentDir) {
|
|
|
574
611
|
''
|
|
575
612
|
].join('\n'));
|
|
576
613
|
}
|
|
614
|
+
function ensureToolEnabled(context, command) {
|
|
615
|
+
if (!context.tools[command]) {
|
|
616
|
+
fail(`${command} is disabled for agent-run profile ${context.profile}`);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
577
619
|
function uniqueSorted(values) {
|
|
578
620
|
return [...new Set(values.map((value) => path.resolve(value)))].sort((a, b) => a.localeCompare(b));
|
|
579
621
|
}
|
|
@@ -588,8 +630,9 @@ function runInit(parsed) {
|
|
|
588
630
|
return;
|
|
589
631
|
}
|
|
590
632
|
const profile = resolveProfile(projectRoot);
|
|
591
|
-
const configRoot = defaultConfigRoot(projectRoot);
|
|
592
|
-
const agentDir =
|
|
633
|
+
const configRoot = defaultConfigRoot(projectRoot, { preferProjectRoot: true });
|
|
634
|
+
const agentDir = path.join(configRoot, profile);
|
|
635
|
+
verbose(`profile=${profile} configRoot=${configRoot} agentPath=${agentDir}`);
|
|
593
636
|
ensureConfigRootLayout(configRoot);
|
|
594
637
|
ensureConfigRootGitignore(configRoot);
|
|
595
638
|
ensureDefaultGlobalTemplates(configRoot);
|
|
@@ -895,31 +938,20 @@ function convertLegacyTemplateVars(content) {
|
|
|
895
938
|
.replace(/^\n{2,}/, '\n');
|
|
896
939
|
}
|
|
897
940
|
function migrateCodexRuntimeFiles(agentDir) {
|
|
898
|
-
const runtimeNames =
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
'cache',
|
|
903
|
-
'history.jsonl',
|
|
904
|
-
'installation_id',
|
|
905
|
-
'log',
|
|
906
|
-
'logs_2.sqlite',
|
|
907
|
-
'models_cache.json',
|
|
908
|
-
'sessions',
|
|
909
|
-
'shell_snapshots',
|
|
910
|
-
'skills',
|
|
911
|
-
'state_5.sqlite',
|
|
912
|
-
'tmp',
|
|
913
|
-
'version.json'
|
|
914
|
-
];
|
|
915
|
-
const codexHome = path.join(agentDir, 'memories', 'codex-home');
|
|
941
|
+
const runtimeNames = fs
|
|
942
|
+
.readdirSync(agentDir)
|
|
943
|
+
.filter((name) => /^(?:\.personality_migration|\.tmp|auth\.json|cache|history\.jsonl|installation_id|log|logs|models_cache\.json|sessions|archived_sessions|shell_snapshots|skills|tmp|version\.json)$/.test(name) || /^(?:logs|state)_.*\.sqlite(?:-.+)?$/.test(name));
|
|
944
|
+
const codexHome = path.join(agentDir, LIVE_DIR_NAME, 'memories', 'codex-home');
|
|
916
945
|
let moved = 0;
|
|
917
946
|
for (const name of runtimeNames) {
|
|
918
947
|
const source = path.join(agentDir, name);
|
|
919
|
-
|
|
920
|
-
if (!fs.existsSync(source) || fs.existsSync(target)) {
|
|
948
|
+
if (!fs.existsSync(source) && !isSymlink(source)) {
|
|
921
949
|
continue;
|
|
922
950
|
}
|
|
951
|
+
let target = path.join(codexHome, name);
|
|
952
|
+
if (fs.existsSync(target) || isSymlink(target)) {
|
|
953
|
+
target = nextBackupPath(target);
|
|
954
|
+
}
|
|
923
955
|
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
924
956
|
fs.renameSync(source, target);
|
|
925
957
|
moved += 1;
|
|
@@ -1081,7 +1113,11 @@ function normalizeManifest(manifest, profile) {
|
|
|
1081
1113
|
};
|
|
1082
1114
|
}
|
|
1083
1115
|
function normalizeSkillName(name) {
|
|
1084
|
-
|
|
1116
|
+
const normalized = name === 'code-review' ? 'code-review-organizer' : name;
|
|
1117
|
+
if (!/^[A-Za-z0-9._-]+$/.test(normalized)) {
|
|
1118
|
+
throw new Error(`invalid skill name: ${name}`);
|
|
1119
|
+
}
|
|
1120
|
+
return normalized;
|
|
1085
1121
|
}
|
|
1086
1122
|
function normalizeInstalledSkills(names) {
|
|
1087
1123
|
return [...new Set(names.map(normalizeSkillName))];
|
|
@@ -1108,12 +1144,13 @@ function defaultAgentIncludesForProfile(profile) {
|
|
|
1108
1144
|
return [`{{ profile }}/${LOCAL_TEMPLATE_FILE_NAME}`, `{{ profile }}/AGENTS-MODS.md`];
|
|
1109
1145
|
}
|
|
1110
1146
|
function createNunjucksEnv(configRoot) {
|
|
1111
|
-
|
|
1147
|
+
const env = new nunjucks.Environment(new nunjucks.FileSystemLoader(configRoot, { noCache: true }), {
|
|
1112
1148
|
autoescape: false,
|
|
1113
1149
|
trimBlocks: true,
|
|
1114
1150
|
lstripBlocks: true,
|
|
1115
1151
|
throwOnUndefined: true
|
|
1116
1152
|
});
|
|
1153
|
+
return env;
|
|
1117
1154
|
}
|
|
1118
1155
|
function buildRenderContext(projectRoot, profileDir, configRoot, manifest, env) {
|
|
1119
1156
|
const date = localDateString();
|
|
@@ -1127,6 +1164,7 @@ function buildRenderContext(projectRoot, profileDir, configRoot, manifest, env)
|
|
|
1127
1164
|
configRoot,
|
|
1128
1165
|
date,
|
|
1129
1166
|
checks: manifest.checks,
|
|
1167
|
+
tools: manifest.tools,
|
|
1130
1168
|
guardrails: manifest.guardrails
|
|
1131
1169
|
};
|
|
1132
1170
|
const reviewDir = resolveRuntimePath(configRoot, manifest.paths.reviewDir, env, baseContext);
|
|
@@ -1295,20 +1333,23 @@ function renderProfile(projectRoot, agentDir, checkOnly = false, targetTool = nu
|
|
|
1295
1333
|
});
|
|
1296
1334
|
context.skills = renderSkills(env, configRoot, manifest, context, trace);
|
|
1297
1335
|
const files = [];
|
|
1298
|
-
if (targetTool === null || targetTool === 'codex') {
|
|
1336
|
+
if (context.tools.codex && (targetTool === null || targetTool === 'codex')) {
|
|
1299
1337
|
const agentsContent = renderToolInstructions('AGENTS.md', env, configRoot, context, false, trace);
|
|
1300
|
-
files.push({ path: path.join(context.paths.
|
|
1301
|
-
files.push({ path: path.join(context.paths.
|
|
1338
|
+
files.push({ path: path.join(context.paths.codexHomeDir, 'AGENTS.md'), content: agentsContent });
|
|
1339
|
+
files.push({ path: path.join(context.paths.codexHomeDir, 'config.toml'), content: renderCodexConfig(env, configRoot, context, trace) });
|
|
1302
1340
|
files.push(...renderNativeSkillFiles(context, 'codex'));
|
|
1303
1341
|
}
|
|
1304
|
-
if (targetTool === null || targetTool === 'claude') {
|
|
1342
|
+
if (context.tools.claude && (targetTool === null || targetTool === 'claude')) {
|
|
1305
1343
|
const claudeContent = renderToolInstructions('CLAUDE.md', env, configRoot, context, true, trace);
|
|
1306
1344
|
files.push({ path: path.join(context.paths.liveDir, 'CLAUDE.md'), content: claudeContent });
|
|
1307
|
-
files.push({ path: path.join(context.paths.liveDir, '.claude', 'CLAUDE.md'), content: claudeContent });
|
|
1308
1345
|
files.push({
|
|
1309
1346
|
path: path.join(context.paths.liveDir, '.claude', 'agent-run-settings.json'),
|
|
1310
1347
|
content: renderClaudeSettings(env, configRoot, context, trace)
|
|
1311
1348
|
});
|
|
1349
|
+
files.push({
|
|
1350
|
+
path: path.join(context.paths.liveDir, '.claude', '.claude-plugin', 'plugin.json'),
|
|
1351
|
+
content: claudePluginManifestContent(context)
|
|
1352
|
+
});
|
|
1312
1353
|
files.push(...renderNativeSkillFiles(context, 'claude'));
|
|
1313
1354
|
}
|
|
1314
1355
|
files.push(...renderGuardShims(context));
|
|
@@ -1324,12 +1365,61 @@ function renderProfile(projectRoot, agentDir, checkOnly = false, targetTool = nu
|
|
|
1324
1365
|
function syncAgentProfile(projectRoot, agentDir, options) {
|
|
1325
1366
|
const rendered = renderProfile(projectRoot, agentDir, false, null, undefined, options);
|
|
1326
1367
|
syncRuntimeDirs(rendered.context);
|
|
1368
|
+
removeStaleGeneratedEntries(rendered);
|
|
1327
1369
|
for (const file of rendered.files) {
|
|
1328
1370
|
writeGeneratedFile(file.path, file.content, file.executable ?? false);
|
|
1329
1371
|
}
|
|
1372
|
+
removeLegacyGeneratedCodexFiles(rendered.context);
|
|
1330
1373
|
removeLegacyGeneratedClaudeSettings(rendered.context);
|
|
1331
1374
|
return rendered;
|
|
1332
1375
|
}
|
|
1376
|
+
function removeStaleGeneratedEntries(rendered) {
|
|
1377
|
+
const expectedFiles = new Set(rendered.files.map((file) => path.resolve(file.path)));
|
|
1378
|
+
for (const filePath of [
|
|
1379
|
+
path.join(rendered.context.paths.codexHomeDir, 'AGENTS.md'),
|
|
1380
|
+
path.join(rendered.context.paths.codexHomeDir, 'config.toml'),
|
|
1381
|
+
path.join(rendered.context.paths.liveDir, 'CLAUDE.md'),
|
|
1382
|
+
path.join(rendered.context.paths.liveDir, '.claude', 'CLAUDE.md'),
|
|
1383
|
+
path.join(rendered.context.paths.liveDir, '.claude', 'agent-run-settings.json'),
|
|
1384
|
+
path.join(rendered.context.paths.liveDir, '.claude', '.claude-plugin', 'plugin.json')
|
|
1385
|
+
]) {
|
|
1386
|
+
if (!expectedFiles.has(path.resolve(filePath))) {
|
|
1387
|
+
fs.rmSync(filePath, { force: true });
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
for (const name of ['git', 'git.cmd', 'npm', 'npm.cmd', 'pnpm', 'pnpm.cmd', 'gh', 'gh.cmd']) {
|
|
1391
|
+
const filePath = path.join(rendered.context.paths.binDir, name);
|
|
1392
|
+
if (!expectedFiles.has(path.resolve(filePath))) {
|
|
1393
|
+
fs.rmSync(filePath, { force: true });
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
const skillNames = new Set(rendered.skills.map((skill) => skill.name));
|
|
1397
|
+
removeStaleGeneratedSkills(rendered.context.paths.codexSkillsDir, rendered.context.tools.codex ? skillNames : new Set(), new Set(['.system']));
|
|
1398
|
+
removeStaleGeneratedSkills(rendered.context.paths.claudeSkillsDir, rendered.context.tools.claude ? skillNames : new Set());
|
|
1399
|
+
}
|
|
1400
|
+
function removeStaleGeneratedSkills(dir, expectedNames, preservedNames = new Set()) {
|
|
1401
|
+
if (!fs.existsSync(dir)) {
|
|
1402
|
+
return;
|
|
1403
|
+
}
|
|
1404
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
1405
|
+
if (expectedNames.has(entry.name) || preservedNames.has(entry.name)) {
|
|
1406
|
+
continue;
|
|
1407
|
+
}
|
|
1408
|
+
fs.rmSync(path.join(dir, entry.name), { recursive: true, force: true });
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
function removeLegacyGeneratedCodexFiles(context) {
|
|
1412
|
+
for (const legacyPath of [
|
|
1413
|
+
path.join(context.paths.liveDir, 'AGENTS.md'),
|
|
1414
|
+
path.join(context.paths.liveDir, 'config.toml')
|
|
1415
|
+
]) {
|
|
1416
|
+
if (!fs.existsSync(legacyPath)) {
|
|
1417
|
+
continue;
|
|
1418
|
+
}
|
|
1419
|
+
fs.rmSync(legacyPath);
|
|
1420
|
+
verbose(`removed legacy generated Codex file ${legacyPath}`);
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1333
1423
|
function removeLegacyGeneratedClaudeSettings(context) {
|
|
1334
1424
|
for (const legacyPath of [
|
|
1335
1425
|
path.join(context.paths.liveDir, '.claude', 'settings.json'),
|
|
@@ -1459,8 +1549,24 @@ function renderClaudeSettings(env, configRoot, context, trace) {
|
|
|
1459
1549
|
? renderTemplateFile(env, configRoot, templatePath, context, trace)
|
|
1460
1550
|
: defaultClaudeSettingsContent(context);
|
|
1461
1551
|
assertNoUnexpandedTemplateVars('claude settings.json', content);
|
|
1552
|
+
try {
|
|
1553
|
+
JSON.parse(content);
|
|
1554
|
+
}
|
|
1555
|
+
catch (error) {
|
|
1556
|
+
throw new Error(`invalid generated Claude settings JSON: ${formatError(error)}`);
|
|
1557
|
+
}
|
|
1462
1558
|
return content.replace(/\n*$/, '\n');
|
|
1463
1559
|
}
|
|
1560
|
+
function claudePluginManifestContent(context) {
|
|
1561
|
+
return `${JSON.stringify({
|
|
1562
|
+
name: 'agent-run-profile',
|
|
1563
|
+
description: `Generated skills for agent-run profile ${context.profile}`,
|
|
1564
|
+
version: '1.0.0',
|
|
1565
|
+
author: {
|
|
1566
|
+
name: 'Technomoron'
|
|
1567
|
+
}
|
|
1568
|
+
}, null, 2)}\n`;
|
|
1569
|
+
}
|
|
1464
1570
|
function resolveProfileOverrideTemplate(configRoot, context, overrideFileName, globalTemplatePath) {
|
|
1465
1571
|
const overridePath = `${context.profile}/overrides/${overrideFileName}`;
|
|
1466
1572
|
if (fs.existsSync(path.join(configRoot, overridePath))) {
|
|
@@ -1472,18 +1578,31 @@ function resolveProfileOverrideTemplate(configRoot, context, overrideFileName, g
|
|
|
1472
1578
|
return null;
|
|
1473
1579
|
}
|
|
1474
1580
|
function renderGuardShims(context) {
|
|
1581
|
+
const names = [];
|
|
1582
|
+
if (context.guardrails.blockGitWrite) {
|
|
1583
|
+
names.push('git');
|
|
1584
|
+
}
|
|
1585
|
+
if (context.guardrails.blockPublish) {
|
|
1586
|
+
names.push('npm', 'pnpm');
|
|
1587
|
+
}
|
|
1588
|
+
if (context.guardrails.blockGithubRelease) {
|
|
1589
|
+
names.push('gh');
|
|
1590
|
+
}
|
|
1475
1591
|
if (IS_WINDOWS) {
|
|
1476
|
-
return
|
|
1592
|
+
return names.map((name) => ({
|
|
1477
1593
|
path: path.join(context.paths.binDir, `${name}.cmd`),
|
|
1478
1594
|
content: windowsShim(name)
|
|
1479
1595
|
}));
|
|
1480
1596
|
}
|
|
1481
|
-
return
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1597
|
+
return names.map((name) => ({
|
|
1598
|
+
path: path.join(context.paths.binDir, name),
|
|
1599
|
+
content: name === 'git'
|
|
1600
|
+
? posixGitShim()
|
|
1601
|
+
: name === 'gh'
|
|
1602
|
+
? posixGhShim()
|
|
1603
|
+
: posixPublishShim(name),
|
|
1604
|
+
executable: true
|
|
1605
|
+
}));
|
|
1487
1606
|
}
|
|
1488
1607
|
function syncRuntimeDirs(context) {
|
|
1489
1608
|
for (const dir of [
|
|
@@ -1566,16 +1685,11 @@ function nextBackupPath(filePath) {
|
|
|
1566
1685
|
return candidate;
|
|
1567
1686
|
}
|
|
1568
1687
|
function removeLegacyCodexSkillDirs(context) {
|
|
1569
|
-
const
|
|
1570
|
-
if (
|
|
1688
|
+
const legacyCodexDir = path.join(context.paths.liveDir, '.agents');
|
|
1689
|
+
if (isSamePathOrDescendant(context.paths.codexSkillsDir, legacyCodexDir)) {
|
|
1571
1690
|
return;
|
|
1572
1691
|
}
|
|
1573
|
-
|
|
1574
|
-
const legacySkillDir = path.join(legacyCodexSkillsDir, skill.name);
|
|
1575
|
-
if (fs.existsSync(legacySkillDir)) {
|
|
1576
|
-
fs.rmSync(legacySkillDir, { recursive: true, force: true });
|
|
1577
|
-
}
|
|
1578
|
-
}
|
|
1692
|
+
fs.rmSync(legacyCodexDir, { recursive: true, force: true });
|
|
1579
1693
|
}
|
|
1580
1694
|
function removeLegacyCodeReviewSkillDirs(context) {
|
|
1581
1695
|
if (!context.skills.some((skill) => skill.name === 'code-review-organizer')) {
|
|
@@ -1681,12 +1795,30 @@ function renderLegacyAgentsMods(sourceFile, stack = [], trace) {
|
|
|
1681
1795
|
let sawLeadingInclude = false;
|
|
1682
1796
|
let insertedOverrideNote = false;
|
|
1683
1797
|
let contentStarted = false;
|
|
1798
|
+
let fence = null;
|
|
1684
1799
|
for (const line of lines) {
|
|
1685
1800
|
const trimmed = line.trim();
|
|
1801
|
+
const isIndentedCode = /^(?: {4}|\t)/.test(line);
|
|
1802
|
+
const fenceMatch = isIndentedCode ? null : /^(`{3,}|~{3,})/.exec(trimmed);
|
|
1803
|
+
if (fence !== null) {
|
|
1804
|
+
output.push(line);
|
|
1805
|
+
contentStarted = true;
|
|
1806
|
+
const marker = fenceMatch?.[1];
|
|
1807
|
+
if (marker !== undefined &&
|
|
1808
|
+
marker[0] === fence.character &&
|
|
1809
|
+
marker.length >= fence.length &&
|
|
1810
|
+
trimmed.slice(marker.length).trim() === '') {
|
|
1811
|
+
fence = null;
|
|
1812
|
+
}
|
|
1813
|
+
continue;
|
|
1814
|
+
}
|
|
1686
1815
|
if (!contentStarted && trimmed === '') {
|
|
1687
1816
|
continue;
|
|
1688
1817
|
}
|
|
1689
|
-
if (
|
|
1818
|
+
if (fenceMatch?.[1]) {
|
|
1819
|
+
fence = { character: fenceMatch[1][0] ?? '`', length: fenceMatch[1].length };
|
|
1820
|
+
}
|
|
1821
|
+
if (fence === null && !isIndentedCode && trimmed.startsWith('@')) {
|
|
1690
1822
|
const includePath = trimmed.slice(1).trim();
|
|
1691
1823
|
if (!includePath) {
|
|
1692
1824
|
continue;
|
|
@@ -1739,28 +1871,39 @@ function checkProject(projectRoot) {
|
|
|
1739
1871
|
const findings = [];
|
|
1740
1872
|
verbose(`checking project ${projectRoot}`);
|
|
1741
1873
|
const profileResult = resolveProfileResult(projectRoot);
|
|
1742
|
-
const excludedDir = profileResult.profile === null ? null : resolveAgentDir(projectRoot);
|
|
1743
|
-
const localAiFiles = findLocalAiFiles(projectRoot, excludedDir);
|
|
1744
|
-
for (const file of localAiFiles) {
|
|
1745
|
-
findings.push({ message: `local AI file in project: ${file}`, severity: 'ERROR' });
|
|
1746
|
-
}
|
|
1747
1874
|
if (profileResult.profile === null) {
|
|
1875
|
+
for (const file of findLocalAiFiles(projectRoot, null)) {
|
|
1876
|
+
findings.push({ message: `local AI file in project: ${file}`, severity: 'ERROR' });
|
|
1877
|
+
}
|
|
1748
1878
|
findings.push({ message: profileResult.reason, severity: 'ERROR' });
|
|
1749
1879
|
return findings;
|
|
1750
1880
|
}
|
|
1751
1881
|
const agentDir = resolveAgentDir(projectRoot);
|
|
1882
|
+
let forbidRepoAiFiles = true;
|
|
1883
|
+
try {
|
|
1884
|
+
const manifest = normalizeManifest(loadManifest(defaultConfigRoot(projectRoot), agentDir, profileResult.profile), profileResult.profile);
|
|
1885
|
+
forbidRepoAiFiles = manifest.guardrails.forbidRepoAiFiles;
|
|
1886
|
+
}
|
|
1887
|
+
catch {
|
|
1888
|
+
// The profile check below reports malformed or missing source details.
|
|
1889
|
+
}
|
|
1890
|
+
if (forbidRepoAiFiles) {
|
|
1891
|
+
for (const file of findLocalAiFiles(projectRoot, agentDir)) {
|
|
1892
|
+
findings.push({ message: `local AI file in project: ${file}`, severity: 'ERROR' });
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1752
1895
|
findings.push(...checkAgentDirectory(projectRoot, agentDir));
|
|
1753
1896
|
return findings;
|
|
1754
1897
|
}
|
|
1755
1898
|
function checkAgentDirectory(projectRoot, agentDir) {
|
|
1756
1899
|
const findings = [];
|
|
1757
1900
|
const configRoot = defaultConfigRoot(projectRoot);
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1901
|
+
const manifestPath = path.join(agentDir, MANIFEST_FILE_NAME);
|
|
1902
|
+
const legacyPath = path.join(agentDir, 'AGENTS-MODS.md');
|
|
1903
|
+
const localPath = path.join(agentDir, LOCAL_TEMPLATE_FILE_NAME);
|
|
1904
|
+
if (!fs.existsSync(manifestPath) && !fs.existsSync(legacyPath) && !fs.existsSync(localPath)) {
|
|
1905
|
+
findings.push({ message: `missing manifest or legacy/local source file in ${agentDir}`, severity: 'ERROR' });
|
|
1906
|
+
return findings;
|
|
1764
1907
|
}
|
|
1765
1908
|
for (const relativeTemplate of REQUIRED_GLOBAL_TEMPLATES) {
|
|
1766
1909
|
if (!fs.existsSync(path.join(configRoot, relativeTemplate))) {
|
|
@@ -1779,20 +1922,26 @@ function checkSourceTree(rootPath) {
|
|
|
1779
1922
|
findings: checkProject(projectRoot),
|
|
1780
1923
|
label: projectRoot
|
|
1781
1924
|
}));
|
|
1782
|
-
return { entries, hasErrors: entries.some((entry) => hasErrors(entry.findings)), root: rootPath };
|
|
1925
|
+
return { entries, hasErrors: entries.length === 0 || entries.some((entry) => hasErrors(entry.findings)), root: rootPath };
|
|
1783
1926
|
}
|
|
1784
1927
|
function findSourceRepos(rootPath) {
|
|
1785
1928
|
const resolvedRoot = path.resolve(rootPath);
|
|
1786
1929
|
const repos = new Set();
|
|
1787
|
-
if (
|
|
1788
|
-
|
|
1930
|
+
if (isIgnoredDir(resolvedRoot)) {
|
|
1931
|
+
return [];
|
|
1932
|
+
}
|
|
1933
|
+
if (looksLikeRepoRoot(resolvedRoot)) {
|
|
1934
|
+
return [resolvedRoot];
|
|
1789
1935
|
}
|
|
1790
|
-
walkSourceTree(resolvedRoot, 0, (fullPath, entry
|
|
1791
|
-
if (entry.isDirectory()
|
|
1792
|
-
|
|
1936
|
+
walkSourceTree(resolvedRoot, 0, (fullPath, entry) => {
|
|
1937
|
+
if (!entry.isDirectory()) {
|
|
1938
|
+
return undefined;
|
|
1939
|
+
}
|
|
1940
|
+
if (shouldPruneSourceTreeEntry(entry) || isIgnoredDir(fullPath)) {
|
|
1793
1941
|
return 'skip';
|
|
1794
1942
|
}
|
|
1795
|
-
if (
|
|
1943
|
+
if (fs.existsSync(path.join(fullPath, '.git'))) {
|
|
1944
|
+
repos.add(fullPath);
|
|
1796
1945
|
return 'skip';
|
|
1797
1946
|
}
|
|
1798
1947
|
return undefined;
|
|
@@ -1802,6 +1951,12 @@ function findSourceRepos(rootPath) {
|
|
|
1802
1951
|
function looksLikeRepoRoot(dir) {
|
|
1803
1952
|
return fs.existsSync(path.join(dir, '.git')) || fs.existsSync(path.join(dir, 'package.json'));
|
|
1804
1953
|
}
|
|
1954
|
+
function shouldPruneSourceTreeEntry(entry) {
|
|
1955
|
+
return (entry.name === 'node_modules' ||
|
|
1956
|
+
entry.name === '.pnpm-store' ||
|
|
1957
|
+
entry.name === 'dist' ||
|
|
1958
|
+
entry.name === 'build');
|
|
1959
|
+
}
|
|
1805
1960
|
function printProjectReport(projectRoot, findings) {
|
|
1806
1961
|
process.stdout.write(`Check: ${projectRoot}\n`);
|
|
1807
1962
|
if (findings.length === 0) {
|
|
@@ -1816,7 +1971,7 @@ function printProjectReport(projectRoot, findings) {
|
|
|
1816
1971
|
function printBatchReport(rootPath, entries) {
|
|
1817
1972
|
process.stdout.write(`Check all: ${rootPath}\n`);
|
|
1818
1973
|
if (entries.length === 0) {
|
|
1819
|
-
process.stdout.write('
|
|
1974
|
+
process.stdout.write('ERROR no source repos found\n');
|
|
1820
1975
|
return;
|
|
1821
1976
|
}
|
|
1822
1977
|
let totalErrors = 0;
|
|
@@ -1847,86 +2002,80 @@ function countWarnings(findings) {
|
|
|
1847
2002
|
function formatError(error) {
|
|
1848
2003
|
return error instanceof Error ? error.message : String(error);
|
|
1849
2004
|
}
|
|
1850
|
-
function runCodex(realBinary, permissionArgs,
|
|
1851
|
-
const liveDir =
|
|
1852
|
-
const
|
|
2005
|
+
function runCodex(realBinary, permissionArgs, context, args, wrapperArgs) {
|
|
2006
|
+
const liveDir = context.paths.liveDir;
|
|
2007
|
+
const codexHomeDir = context.paths.codexHomeDir;
|
|
2008
|
+
const projectRoot = context.projectRoot;
|
|
2009
|
+
const agentsPath = path.join(codexHomeDir, 'AGENTS.md');
|
|
1853
2010
|
if (!fs.existsSync(agentsPath)) {
|
|
1854
|
-
failMissingConfig('codex',
|
|
2011
|
+
failMissingConfig('codex', context.profileDir);
|
|
1855
2012
|
}
|
|
1856
2013
|
const guardBin = path.join(liveDir, 'bin');
|
|
1857
|
-
const
|
|
2014
|
+
const realPath = process.env.AGENT_RUN_REAL_PATH ?? process.env.PATH ?? '';
|
|
1858
2015
|
fs.mkdirSync(codexHomeDir, { recursive: true });
|
|
1859
2016
|
const env = {
|
|
1860
2017
|
...process.env,
|
|
1861
2018
|
CODEX_HOME: codexHomeDir,
|
|
1862
2019
|
AGENT_DIR: liveDir,
|
|
1863
|
-
AGENT_PROFILE_DIR:
|
|
2020
|
+
AGENT_PROFILE_DIR: context.profileDir,
|
|
1864
2021
|
AGENT_RUN_PROJECT_ROOT: projectRoot,
|
|
1865
|
-
|
|
2022
|
+
AGENT_RUN_REAL_PATH: realPath,
|
|
2023
|
+
PATH: `${guardBin}${path.delimiter}${realPath}`
|
|
1866
2024
|
};
|
|
1867
2025
|
const codexRuntimeArgs = buildCodexRuntimeArgs(wrapperArgs);
|
|
1868
2026
|
execCommand(realBinary, [
|
|
1869
2027
|
...permissionArgs,
|
|
1870
2028
|
...codexRuntimeArgs,
|
|
1871
|
-
'--config',
|
|
1872
|
-
`system_prompt_file=${agentsPath}`,
|
|
1873
|
-
'--config',
|
|
1874
|
-
'project_doc_max_bytes=65536',
|
|
1875
|
-
...globalMemoryArgs(configRoot),
|
|
1876
2029
|
'-C',
|
|
1877
2030
|
projectRoot,
|
|
1878
|
-
...(wrapperArgs.codexSandboxMode
|
|
2031
|
+
...(wrapperArgs.codexSandboxMode !== 'danger' && wrapperArgs.codexNetwork
|
|
1879
2032
|
? ['--config', 'sandbox_workspace_write.network_access=true']
|
|
1880
2033
|
: []),
|
|
1881
2034
|
...args
|
|
1882
|
-
],
|
|
2035
|
+
], env, codexHomeDir, (code) => postflightProjectCheck(projectRoot, context.profileDir, context.guardrails.forbidRepoAiFiles, wrapperArgs.local, code));
|
|
1883
2036
|
}
|
|
1884
2037
|
function buildCodexRuntimeArgs(wrapperArgs) {
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
return ['-s', 'workspace-write'];
|
|
2038
|
+
if (wrapperArgs.codexSandboxMode === 'danger') {
|
|
2039
|
+
return ['-a', 'never', '-s', 'danger-full-access'];
|
|
1888
2040
|
}
|
|
1889
|
-
return ['-a', '
|
|
2041
|
+
return ['-a', 'on-request', '-s', 'workspace-write'];
|
|
1890
2042
|
}
|
|
1891
|
-
function runClaude(realBinary, permissionArgs,
|
|
1892
|
-
const liveDir =
|
|
2043
|
+
function runClaude(realBinary, permissionArgs, context, args, wrapperArgs) {
|
|
2044
|
+
const liveDir = context.paths.liveDir;
|
|
2045
|
+
const projectRoot = context.projectRoot;
|
|
1893
2046
|
const claudePath = path.join(liveDir, 'CLAUDE.md');
|
|
1894
2047
|
if (!fs.existsSync(claudePath)) {
|
|
1895
|
-
failMissingConfig('claude',
|
|
2048
|
+
failMissingConfig('claude', context.profileDir);
|
|
1896
2049
|
}
|
|
1897
2050
|
const guardBin = path.join(liveDir, 'bin');
|
|
1898
2051
|
const claudeConfigDir = path.join(liveDir, '.claude');
|
|
2052
|
+
const realPath = process.env.AGENT_RUN_REAL_PATH ?? process.env.PATH ?? '';
|
|
1899
2053
|
const env = {
|
|
1900
2054
|
...process.env,
|
|
1901
|
-
CLAUDE_CONFIG_DIR: claudeConfigDir,
|
|
1902
2055
|
AGENT_DIR: liveDir,
|
|
1903
|
-
AGENT_PROFILE_DIR:
|
|
2056
|
+
AGENT_PROFILE_DIR: context.profileDir,
|
|
1904
2057
|
AGENT_RUN_PROJECT_ROOT: projectRoot,
|
|
1905
|
-
|
|
2058
|
+
AGENT_RUN_REAL_PATH: realPath,
|
|
2059
|
+
PATH: `${guardBin}${path.delimiter}${realPath}`
|
|
1906
2060
|
};
|
|
1907
2061
|
execCommand(realBinary, [
|
|
1908
2062
|
...permissionArgs,
|
|
2063
|
+
'--append-system-prompt-file',
|
|
2064
|
+
claudePath,
|
|
1909
2065
|
'--settings',
|
|
1910
2066
|
path.join(claudeConfigDir, 'agent-run-settings.json'),
|
|
1911
|
-
'--
|
|
1912
|
-
|
|
1913
|
-
'--add-dir',
|
|
1914
|
-
liveDir,
|
|
1915
|
-
...globalMemoryArgs(configRoot),
|
|
2067
|
+
'--plugin-dir',
|
|
2068
|
+
claudeConfigDir,
|
|
1916
2069
|
...args
|
|
1917
|
-
],
|
|
1918
|
-
}
|
|
1919
|
-
function profileLiveDir(agentDir) {
|
|
1920
|
-
return path.join(agentDir, LIVE_DIR_NAME);
|
|
1921
|
-
}
|
|
1922
|
-
function globalMemoryArgs(configRoot) {
|
|
1923
|
-
const memoryDir = globalMemoryDir(configRoot);
|
|
1924
|
-
return fs.existsSync(memoryDir) ? ['--add-dir', memoryDir] : [];
|
|
2070
|
+
], env, projectRoot, (code) => postflightProjectCheck(projectRoot, context.profileDir, context.guardrails.forbidRepoAiFiles, wrapperArgs.local, code));
|
|
1925
2071
|
}
|
|
1926
2072
|
function globalMemoryDir(configRoot) {
|
|
1927
2073
|
return path.join(configRoot, 'notes', 'memory');
|
|
1928
2074
|
}
|
|
1929
|
-
function postflightProjectCheck(projectRoot, agentDir, allowLocal, code) {
|
|
2075
|
+
function postflightProjectCheck(projectRoot, agentDir, forbidRepoAiFiles, allowLocal, code) {
|
|
2076
|
+
if (!forbidRepoAiFiles) {
|
|
2077
|
+
return code;
|
|
2078
|
+
}
|
|
1930
2079
|
const localAiFiles = findLocalAiFiles(projectRoot, agentDir);
|
|
1931
2080
|
if (localAiFiles.length > 0) {
|
|
1932
2081
|
warnForLocalAiFiles(null, projectRoot, localAiFiles);
|
|
@@ -2014,17 +2163,16 @@ function isSamePathOrDescendant(candidatePath, parentPath) {
|
|
|
2014
2163
|
function shouldPruneProjectEntry(entry) {
|
|
2015
2164
|
return (entry.isDirectory() &&
|
|
2016
2165
|
(entry.name === '.git' ||
|
|
2017
|
-
entry.name === '.claude' ||
|
|
2018
2166
|
entry.name === 'node_modules' ||
|
|
2019
2167
|
entry.name === '.pnpm-store' ||
|
|
2020
2168
|
entry.name === 'dist' ||
|
|
2021
2169
|
entry.name === 'build'));
|
|
2022
2170
|
}
|
|
2023
2171
|
function isLocalAiDirectory(entry) {
|
|
2024
|
-
return
|
|
2172
|
+
return LOCAL_AI_DIRECTORY_NAMES.has(entry.name);
|
|
2025
2173
|
}
|
|
2026
2174
|
function isLocalAiFile(entry) {
|
|
2027
|
-
return
|
|
2175
|
+
return LOCAL_AI_FILE_NAMES.has(entry.name);
|
|
2028
2176
|
}
|
|
2029
2177
|
function isIgnoredDir(dir) {
|
|
2030
2178
|
if (fs.existsSync(path.join(dir, IGNORE_FILE_NAME))) {
|
|
@@ -2057,13 +2205,13 @@ function findProjectRoot(cwd) {
|
|
|
2057
2205
|
let gitRoot = '';
|
|
2058
2206
|
for (;;) {
|
|
2059
2207
|
const packagePath = path.join(dir, 'package.json');
|
|
2208
|
+
if (isWorkspaceRoot(dir)) {
|
|
2209
|
+
workspaceRoot = dir;
|
|
2210
|
+
}
|
|
2060
2211
|
if (fs.existsSync(packagePath)) {
|
|
2061
2212
|
if (!nearestPackageRoot) {
|
|
2062
2213
|
nearestPackageRoot = dir;
|
|
2063
2214
|
}
|
|
2064
|
-
if (isWorkspaceRoot(dir)) {
|
|
2065
|
-
workspaceRoot = dir;
|
|
2066
|
-
}
|
|
2067
2215
|
}
|
|
2068
2216
|
if (fs.existsSync(path.join(dir, '.git'))) {
|
|
2069
2217
|
gitRoot = dir;
|
|
@@ -2137,26 +2285,16 @@ function resolveGitHubProfile(projectRoot) {
|
|
|
2137
2285
|
return remote === null ? null : parseGitHubRemoteProfile(remote);
|
|
2138
2286
|
}
|
|
2139
2287
|
function readGitOrigin(projectRoot) {
|
|
2140
|
-
const
|
|
2141
|
-
|
|
2288
|
+
const result = childProcess.spawnSync('git', ['-C', projectRoot, 'config', '--get', 'remote.origin.url'], {
|
|
2289
|
+
encoding: 'utf8',
|
|
2290
|
+
shell: false,
|
|
2291
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
2292
|
+
});
|
|
2293
|
+
if (result.status !== 0) {
|
|
2142
2294
|
return null;
|
|
2143
2295
|
}
|
|
2144
|
-
const
|
|
2145
|
-
|
|
2146
|
-
for (const line of lines) {
|
|
2147
|
-
const trimmed = line.trim();
|
|
2148
|
-
if (trimmed.startsWith('[')) {
|
|
2149
|
-
inOrigin = trimmed === '[remote "origin"]';
|
|
2150
|
-
continue;
|
|
2151
|
-
}
|
|
2152
|
-
if (inOrigin && trimmed.startsWith('url')) {
|
|
2153
|
-
const equalsIndex = trimmed.indexOf('=');
|
|
2154
|
-
if (equalsIndex >= 0) {
|
|
2155
|
-
return trimmed.slice(equalsIndex + 1).trim();
|
|
2156
|
-
}
|
|
2157
|
-
}
|
|
2158
|
-
}
|
|
2159
|
-
return null;
|
|
2296
|
+
const remote = result.stdout.trim();
|
|
2297
|
+
return remote || null;
|
|
2160
2298
|
}
|
|
2161
2299
|
function parseGitHubRemoteProfile(remote) {
|
|
2162
2300
|
const patterns = [
|
|
@@ -2181,7 +2319,8 @@ function parseProfile(profile, source) {
|
|
|
2181
2319
|
return { profile: null, reason: `${source} must be relative to the config root, not an absolute path` };
|
|
2182
2320
|
}
|
|
2183
2321
|
const segments = normalized.split('/').filter((segment) => segment.length > 0);
|
|
2184
|
-
if (segments.length === 0 ||
|
|
2322
|
+
if (segments.length === 0 ||
|
|
2323
|
+
segments.some((segment) => segment === '.' || segment === '..' || !/^[A-Za-z0-9._-]+$/.test(segment))) {
|
|
2185
2324
|
return { profile: null, reason: `${source} must be a clean relative path like org/my-project` };
|
|
2186
2325
|
}
|
|
2187
2326
|
return { profile: segments.join('/'), reason: '' };
|
|
@@ -2232,7 +2371,7 @@ function parseBooleanEnv(value) {
|
|
|
2232
2371
|
const normalized = value.trim().toLowerCase();
|
|
2233
2372
|
return normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on';
|
|
2234
2373
|
}
|
|
2235
|
-
function defaultConfigRoot(projectRoot) {
|
|
2374
|
+
function defaultConfigRoot(projectRoot, options = {}) {
|
|
2236
2375
|
const overrideRoot = process.env[CONFIG_ROOT_OVERRIDE_ENV];
|
|
2237
2376
|
if (overrideRoot) {
|
|
2238
2377
|
return path.resolve(overrideRoot);
|
|
@@ -2255,9 +2394,15 @@ function defaultConfigRoot(projectRoot) {
|
|
|
2255
2394
|
}
|
|
2256
2395
|
}
|
|
2257
2396
|
if (projectRoot) {
|
|
2258
|
-
|
|
2397
|
+
const projectConfigRoot = defaultCodeConfigRoot(projectRoot);
|
|
2398
|
+
if (options.preferProjectRoot || fs.existsSync(projectConfigRoot)) {
|
|
2399
|
+
return projectConfigRoot;
|
|
2400
|
+
}
|
|
2401
|
+
const discoveredConfigRoot = findExistingConfigRoot(defaultConfigRootSearchCandidates());
|
|
2402
|
+
return discoveredConfigRoot ?? projectConfigRoot;
|
|
2259
2403
|
}
|
|
2260
|
-
|
|
2404
|
+
const discoveredConfigRoot = findExistingConfigRoot(defaultConfigRootSearchCandidates());
|
|
2405
|
+
return discoveredConfigRoot ?? path.join(os.homedir(), '.agent-config');
|
|
2261
2406
|
}
|
|
2262
2407
|
function defaultCodeConfigRoot(projectRoot) {
|
|
2263
2408
|
const resolvedProjectRoot = path.resolve(projectRoot);
|
|
@@ -2268,6 +2413,39 @@ function defaultCodeConfigRoot(projectRoot) {
|
|
|
2268
2413
|
}
|
|
2269
2414
|
return path.join(codeRoot, 'agent-config');
|
|
2270
2415
|
}
|
|
2416
|
+
function findExistingConfigRoot(candidates) {
|
|
2417
|
+
for (const candidate of candidates) {
|
|
2418
|
+
if (fs.existsSync(candidate)) {
|
|
2419
|
+
return candidate;
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
return null;
|
|
2423
|
+
}
|
|
2424
|
+
function defaultConfigRootSearchCandidates(_projectRoot, options = {}) {
|
|
2425
|
+
const platform = options.platform ?? process.platform;
|
|
2426
|
+
const pathApi = platform === 'win32' ? path.win32 : path.posix;
|
|
2427
|
+
const homeDir = options.homeDir ?? os.homedir();
|
|
2428
|
+
const configNames = ['agent-config', 'agent-configs'];
|
|
2429
|
+
const candidates = [];
|
|
2430
|
+
if (platform === 'win32') {
|
|
2431
|
+
for (const codeRoot of [pathApi.join(homeDir, 'Documents', 'code'), pathApi.join(homeDir, 'Desktop', 'code'), 'C:\\code']) {
|
|
2432
|
+
for (const configName of configNames) {
|
|
2433
|
+
candidates.push(pathApi.join(codeRoot, configName));
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
return candidates;
|
|
2437
|
+
}
|
|
2438
|
+
for (const configName of configNames) {
|
|
2439
|
+
candidates.push(pathApi.join(homeDir, 'code', configName));
|
|
2440
|
+
}
|
|
2441
|
+
for (const configName of configNames) {
|
|
2442
|
+
candidates.push(pathApi.join(homeDir, configName));
|
|
2443
|
+
}
|
|
2444
|
+
for (const configName of configNames) {
|
|
2445
|
+
candidates.push(pathApi.join(homeDir, `.${configName}`));
|
|
2446
|
+
}
|
|
2447
|
+
return candidates;
|
|
2448
|
+
}
|
|
2271
2449
|
function findRealBinary(tool) {
|
|
2272
2450
|
const pathValue = process.env.PATH || '';
|
|
2273
2451
|
const pathDirs = pathValue.split(path.delimiter).filter((entry) => entry.length > 0);
|
|
@@ -2360,12 +2538,12 @@ function isExecutable(filePath) {
|
|
|
2360
2538
|
}
|
|
2361
2539
|
}
|
|
2362
2540
|
function execTool(command, args) {
|
|
2363
|
-
verbose(`exec tool: ${formatCommand(command, args)}
|
|
2364
|
-
execCommand(command, args
|
|
2541
|
+
verbose(`exec tool: ${formatCommand(command, args)}`);
|
|
2542
|
+
execCommand(command, args);
|
|
2365
2543
|
}
|
|
2366
|
-
function execCommand(command, args,
|
|
2367
|
-
verbose(`spawn: ${formatCommand(command, args)}
|
|
2368
|
-
const child =
|
|
2544
|
+
function execCommand(command, args, env, cwd, onExit) {
|
|
2545
|
+
verbose(`spawn: ${formatCommand(command, args)} cwd=${cwd ?? process.cwd()}`);
|
|
2546
|
+
const child = crossSpawn(command, args, { cwd, env, stdio: 'inherit' });
|
|
2369
2547
|
child.on('exit', (code, signal) => {
|
|
2370
2548
|
if (signal) {
|
|
2371
2549
|
process.kill(process.pid, signal);
|
|
@@ -2381,33 +2559,106 @@ function execCommand(command, args, shell, env, cwd, onExit) {
|
|
|
2381
2559
|
function openEditor(filePath) {
|
|
2382
2560
|
const visual = process.env.VISUAL?.trim();
|
|
2383
2561
|
if (visual) {
|
|
2384
|
-
|
|
2562
|
+
runConfiguredEditor(visual, filePath);
|
|
2385
2563
|
return;
|
|
2386
2564
|
}
|
|
2387
2565
|
const editor = process.env.EDITOR?.trim();
|
|
2388
2566
|
if (editor) {
|
|
2389
|
-
|
|
2567
|
+
runConfiguredEditor(editor, filePath);
|
|
2390
2568
|
return;
|
|
2391
2569
|
}
|
|
2392
2570
|
const vscodeCommand = findVsCodeEditorCommand();
|
|
2393
2571
|
if (vscodeCommand !== null) {
|
|
2394
|
-
execCommand(vscodeCommand, ['--reuse-window', filePath]
|
|
2572
|
+
execCommand(vscodeCommand, ['--reuse-window', filePath]);
|
|
2395
2573
|
return;
|
|
2396
2574
|
}
|
|
2397
2575
|
const fallbackEditor = findFallbackEditor();
|
|
2398
2576
|
if (fallbackEditor !== null) {
|
|
2399
|
-
execCommand(fallbackEditor, [filePath]
|
|
2577
|
+
execCommand(fallbackEditor, [filePath]);
|
|
2400
2578
|
return;
|
|
2401
2579
|
}
|
|
2402
2580
|
if (IS_WINDOWS) {
|
|
2403
|
-
execCommand('
|
|
2581
|
+
execCommand('explorer.exe', [filePath]);
|
|
2404
2582
|
return;
|
|
2405
2583
|
}
|
|
2406
2584
|
if (process.platform === 'darwin') {
|
|
2407
|
-
execCommand('open', [filePath]
|
|
2585
|
+
execCommand('open', [filePath]);
|
|
2408
2586
|
return;
|
|
2409
2587
|
}
|
|
2410
|
-
execCommand('xdg-open', [filePath]
|
|
2588
|
+
execCommand('xdg-open', [filePath]);
|
|
2589
|
+
}
|
|
2590
|
+
function runConfiguredEditor(commandLine, filePath) {
|
|
2591
|
+
let editorArgs;
|
|
2592
|
+
try {
|
|
2593
|
+
editorArgs = parseEditorCommand(commandLine);
|
|
2594
|
+
}
|
|
2595
|
+
catch (error) {
|
|
2596
|
+
fail(`invalid editor command: ${formatError(error)}`);
|
|
2597
|
+
}
|
|
2598
|
+
const [command, ...args] = editorArgs;
|
|
2599
|
+
if (!command) {
|
|
2600
|
+
fail('invalid editor command: command is empty');
|
|
2601
|
+
}
|
|
2602
|
+
execCommand(command, [...args, filePath]);
|
|
2603
|
+
}
|
|
2604
|
+
function parseEditorCommand(commandLine) {
|
|
2605
|
+
const args = [];
|
|
2606
|
+
let current = '';
|
|
2607
|
+
let quote = null;
|
|
2608
|
+
let tokenStarted = false;
|
|
2609
|
+
for (let index = 0; index < commandLine.length; index += 1) {
|
|
2610
|
+
const character = commandLine[index] ?? '';
|
|
2611
|
+
if (quote !== null) {
|
|
2612
|
+
if (character === quote) {
|
|
2613
|
+
quote = null;
|
|
2614
|
+
continue;
|
|
2615
|
+
}
|
|
2616
|
+
if (character === '\\' && quote === '"') {
|
|
2617
|
+
const next = commandLine[index + 1];
|
|
2618
|
+
if (next === '"' || next === '\\') {
|
|
2619
|
+
current += next;
|
|
2620
|
+
index += 1;
|
|
2621
|
+
continue;
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
current += character;
|
|
2625
|
+
continue;
|
|
2626
|
+
}
|
|
2627
|
+
if (character === "'" || character === '"') {
|
|
2628
|
+
quote = character;
|
|
2629
|
+
tokenStarted = true;
|
|
2630
|
+
continue;
|
|
2631
|
+
}
|
|
2632
|
+
if (/\s/.test(character)) {
|
|
2633
|
+
if (tokenStarted) {
|
|
2634
|
+
args.push(current);
|
|
2635
|
+
current = '';
|
|
2636
|
+
tokenStarted = false;
|
|
2637
|
+
}
|
|
2638
|
+
continue;
|
|
2639
|
+
}
|
|
2640
|
+
if (character === '\\') {
|
|
2641
|
+
const next = commandLine[index + 1];
|
|
2642
|
+
if (next !== undefined && (/\s/.test(next) || next === "'" || next === '"' || next === '\\')) {
|
|
2643
|
+
current += next;
|
|
2644
|
+
index += 1;
|
|
2645
|
+
tokenStarted = true;
|
|
2646
|
+
continue;
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
current += character;
|
|
2650
|
+
tokenStarted = true;
|
|
2651
|
+
}
|
|
2652
|
+
if (quote !== null) {
|
|
2653
|
+
throw new Error(`unterminated ${quote} quote`);
|
|
2654
|
+
}
|
|
2655
|
+
if (tokenStarted) {
|
|
2656
|
+
args.push(current);
|
|
2657
|
+
}
|
|
2658
|
+
if (args.length === 0) {
|
|
2659
|
+
throw new Error('command is empty');
|
|
2660
|
+
}
|
|
2661
|
+
return args;
|
|
2411
2662
|
}
|
|
2412
2663
|
function findVsCodeEditorCommand() {
|
|
2413
2664
|
if (!isRunningInVsCodeTerminal()) {
|
|
@@ -2452,13 +2703,6 @@ function findExecutable(command) {
|
|
|
2452
2703
|
}
|
|
2453
2704
|
return null;
|
|
2454
2705
|
}
|
|
2455
|
-
function shouldUseShell(command) {
|
|
2456
|
-
if (!IS_WINDOWS) {
|
|
2457
|
-
return false;
|
|
2458
|
-
}
|
|
2459
|
-
const extension = path.extname(command).toLowerCase();
|
|
2460
|
-
return extension === '.cmd' || extension === '.bat';
|
|
2461
|
-
}
|
|
2462
2706
|
function localDateString() {
|
|
2463
2707
|
const now = new Date();
|
|
2464
2708
|
const year = String(now.getFullYear()).padStart(4, '0');
|
|
@@ -2608,7 +2852,8 @@ function defaultReleasePackageCheckSkill() {
|
|
|
2608
2852
|
'- The current branch has an upstream configured and is in sync with it after fetching.',
|
|
2609
2853
|
'- The repository does not contain agent files or directories such as `AGENTS.md`, `CLAUDE.md`, `codex.md`, `.claude`, or `.codex`.',
|
|
2610
2854
|
'- The repository root has a lockfile: `pnpm-lock.yaml`, `package-lock.json`, `yarn.lock`, `bun.lockb`, or `bun.lock`.',
|
|
2611
|
-
'- GitHub
|
|
2855
|
+
'- Every GitHub Actions `uses:` reference is pinned to a full commit SHA.',
|
|
2856
|
+
'- The pinned `actions/setup-node` release is v6-compatible and workflows use `node-version: 24`.',
|
|
2612
2857
|
'- GitHub workflows do not reference `node20` or `node22`.',
|
|
2613
2858
|
'- The git working tree is clean for files under the checked package directory.',
|
|
2614
2859
|
'- If files beyond `CHANGES` changed in the checked package directory, that package `CHANGES` file is also changed.',
|
|
@@ -2724,8 +2969,8 @@ function defaultToolInstructionsTemplate(isClaude) {
|
|
|
2724
2969
|
'## Tool Note',
|
|
2725
2970
|
'',
|
|
2726
2971
|
isClaude
|
|
2727
|
-
? 'Claude
|
|
2728
|
-
: 'Codex
|
|
2972
|
+
? 'Claude receives {{ agentDir }}/CLAUDE.md through `--append-system-prompt-file` and loads generated skills as a local plugin.'
|
|
2973
|
+
: 'Codex loads AGENTS.md, config.toml, and skills natively from {{ paths.codexHomeDir }}.',
|
|
2729
2974
|
'',
|
|
2730
2975
|
'## Mandatory Path Rule',
|
|
2731
2976
|
'',
|
|
@@ -2740,14 +2985,14 @@ function defaultCodexConfigTemplate() {
|
|
|
2740
2985
|
'# Generated by agent-run. Do not edit directly.',
|
|
2741
2986
|
'',
|
|
2742
2987
|
'project_doc_max_bytes = 65536',
|
|
2988
|
+
'approval_policy = "on-request"',
|
|
2743
2989
|
'sandbox_mode = "workspace-write"',
|
|
2744
2990
|
'',
|
|
2745
2991
|
'[sandbox_workspace_write]',
|
|
2746
2992
|
'writable_roots = [',
|
|
2747
|
-
'
|
|
2748
|
-
'
|
|
2749
|
-
'
|
|
2750
|
-
' "{{ paths.globalMemoryDir }}"',
|
|
2993
|
+
' {{ projectRoot | dump }},',
|
|
2994
|
+
' {{ paths.reviewDir | dump }},',
|
|
2995
|
+
' {{ paths.memoriesDir | dump }}',
|
|
2751
2996
|
']',
|
|
2752
2997
|
'network_access = false',
|
|
2753
2998
|
''
|
|
@@ -2756,19 +3001,17 @@ function defaultCodexConfigTemplate() {
|
|
|
2756
3001
|
function defaultClaudeSettingsTemplate() {
|
|
2757
3002
|
const envBlock = [
|
|
2758
3003
|
' "env": {',
|
|
2759
|
-
' "AGENT_DIR":
|
|
2760
|
-
' "AGENT_PROFILE_DIR":
|
|
2761
|
-
' "AGENT_RUN_PROJECT_ROOT":
|
|
2762
|
-
' "AGENT_GLOBAL_MEMORY_DIR":
|
|
3004
|
+
' "AGENT_DIR": {{ agentDir | dump }},',
|
|
3005
|
+
' "AGENT_PROFILE_DIR": {{ profileDir | dump }},',
|
|
3006
|
+
' "AGENT_RUN_PROJECT_ROOT": {{ projectRoot | dump }},',
|
|
3007
|
+
' "AGENT_GLOBAL_MEMORY_DIR": {{ paths.globalMemoryDir | dump }}',
|
|
2763
3008
|
' }'
|
|
2764
3009
|
].join('\n');
|
|
2765
3010
|
return [
|
|
2766
3011
|
'{% if permissionsAllow.length %}{',
|
|
2767
3012
|
envBlock + ',',
|
|
2768
3013
|
' "permissions": {',
|
|
2769
|
-
' "allow":
|
|
2770
|
-
' "{{ permissionsAllow | join(\'",\\n "\') }}"',
|
|
2771
|
-
' ]',
|
|
3014
|
+
' "allow": {{ permissionsAllow | dump }}',
|
|
2772
3015
|
' }',
|
|
2773
3016
|
'}',
|
|
2774
3017
|
'{% else %}{',
|
|
@@ -2782,14 +3025,14 @@ function defaultCodexConfigContent(context) {
|
|
|
2782
3025
|
'# Generated by agent-run. Do not edit directly.',
|
|
2783
3026
|
'',
|
|
2784
3027
|
'project_doc_max_bytes = 65536',
|
|
3028
|
+
'approval_policy = "on-request"',
|
|
2785
3029
|
'sandbox_mode = "workspace-write"',
|
|
2786
3030
|
'',
|
|
2787
3031
|
'[sandbox_workspace_write]',
|
|
2788
3032
|
'writable_roots = [',
|
|
2789
3033
|
` ${jsonString(context.projectRoot)},`,
|
|
2790
|
-
` ${jsonString(context.
|
|
2791
|
-
` ${jsonString(context.
|
|
2792
|
-
` ${jsonString(context.paths.globalMemoryDir)}`,
|
|
3034
|
+
` ${jsonString(context.paths.reviewDir)},`,
|
|
3035
|
+
` ${jsonString(context.paths.memoriesDir)}`,
|
|
2793
3036
|
']',
|
|
2794
3037
|
'network_access = false',
|
|
2795
3038
|
''
|
|
@@ -2825,18 +3068,17 @@ function posixGitShim() {
|
|
|
2825
3068
|
return [
|
|
2826
3069
|
'#!/usr/bin/env bash',
|
|
2827
3070
|
'set -euo pipefail',
|
|
2828
|
-
'
|
|
2829
|
-
'
|
|
2830
|
-
'
|
|
2831
|
-
'
|
|
2832
|
-
'
|
|
2833
|
-
'
|
|
2834
|
-
'
|
|
2835
|
-
'
|
|
2836
|
-
'
|
|
2837
|
-
'
|
|
2838
|
-
'
|
|
2839
|
-
'exec git "$@"',
|
|
3071
|
+
'for arg in "$@"; do',
|
|
3072
|
+
' case "$arg" in',
|
|
3073
|
+
' commit|tag|push)',
|
|
3074
|
+
' if [ "${AGENT_RUN_ALLOW_GIT_WRITE:-}" != "1" ]; then',
|
|
3075
|
+
' echo "agent-run: blocked git $arg. Review and run it manually, or set AGENT_RUN_ALLOW_GIT_WRITE=1 for this invocation." >&2',
|
|
3076
|
+
' exit 42',
|
|
3077
|
+
' fi',
|
|
3078
|
+
' ;;',
|
|
3079
|
+
' esac',
|
|
3080
|
+
'done',
|
|
3081
|
+
...posixRunRealCommand('git'),
|
|
2840
3082
|
''
|
|
2841
3083
|
].join('\n');
|
|
2842
3084
|
}
|
|
@@ -2844,14 +3086,13 @@ function posixPublishShim(tool) {
|
|
|
2844
3086
|
return [
|
|
2845
3087
|
'#!/usr/bin/env bash',
|
|
2846
3088
|
'set -euo pipefail',
|
|
2847
|
-
'
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
'
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
`exec ${tool} "$@"`,
|
|
3089
|
+
'for arg in "$@"; do',
|
|
3090
|
+
' if [ "$arg" = "publish" ] && [ "${AGENT_RUN_ALLOW_PUBLISH:-}" != "1" ]; then',
|
|
3091
|
+
` echo "agent-run: blocked ${tool} publish. Review and run it manually, or set AGENT_RUN_ALLOW_PUBLISH=1 for this invocation." >&2`,
|
|
3092
|
+
' exit 42',
|
|
3093
|
+
' fi',
|
|
3094
|
+
'done',
|
|
3095
|
+
...posixRunRealCommand(tool),
|
|
2855
3096
|
''
|
|
2856
3097
|
].join('\n');
|
|
2857
3098
|
}
|
|
@@ -2859,44 +3100,95 @@ function posixGhShim() {
|
|
|
2859
3100
|
return [
|
|
2860
3101
|
'#!/usr/bin/env bash',
|
|
2861
3102
|
'set -euo pipefail',
|
|
2862
|
-
'
|
|
2863
|
-
'
|
|
2864
|
-
'
|
|
2865
|
-
'
|
|
2866
|
-
'
|
|
2867
|
-
'
|
|
2868
|
-
'
|
|
2869
|
-
'
|
|
3103
|
+
'saw_release=0',
|
|
3104
|
+
'for arg in "$@"; do',
|
|
3105
|
+
' if [ "$arg" = "release" ]; then',
|
|
3106
|
+
' saw_release=1',
|
|
3107
|
+
' elif [ "$saw_release" = "1" ] && [ "$arg" = "create" ] && [ "${AGENT_RUN_ALLOW_GITHUB_RELEASE:-}" != "1" ]; then',
|
|
3108
|
+
' echo "agent-run: blocked gh release create. Review and run it manually, or set AGENT_RUN_ALLOW_GITHUB_RELEASE=1 for this invocation." >&2',
|
|
3109
|
+
' exit 42',
|
|
3110
|
+
' fi',
|
|
3111
|
+
'done',
|
|
3112
|
+
...posixRunRealCommand('gh'),
|
|
2870
3113
|
''
|
|
2871
3114
|
].join('\n');
|
|
2872
3115
|
}
|
|
3116
|
+
function posixRunRealCommand(tool) {
|
|
3117
|
+
return [
|
|
3118
|
+
'shim_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"',
|
|
3119
|
+
'real_path="${AGENT_RUN_REAL_PATH:-}"',
|
|
3120
|
+
'if [ -z "$real_path" ]; then',
|
|
3121
|
+
' case "${PATH:-}" in',
|
|
3122
|
+
' "$shim_dir") real_path="" ;;',
|
|
3123
|
+
' "$shim_dir":*) real_path="${PATH#*:}" ;;',
|
|
3124
|
+
' *) real_path="${PATH:-}" ;;',
|
|
3125
|
+
' esac',
|
|
3126
|
+
'fi',
|
|
3127
|
+
`real_command="$(PATH="$real_path" command -v ${tool} || true)"`,
|
|
3128
|
+
'if [ -z "$real_command" ]; then',
|
|
3129
|
+
` echo "agent-run: cannot find the real ${tool} command outside the guard directory." >&2`,
|
|
3130
|
+
' exit 127',
|
|
3131
|
+
'fi',
|
|
3132
|
+
`if [ "$real_command" = "$shim_dir/${tool}" ]; then`,
|
|
3133
|
+
` echo "agent-run: refused recursive ${tool} guard resolution." >&2`,
|
|
3134
|
+
' exit 127',
|
|
3135
|
+
'fi',
|
|
3136
|
+
'PATH="$real_path" exec "$real_command" "$@"'
|
|
3137
|
+
];
|
|
3138
|
+
}
|
|
2873
3139
|
function windowsShim(name) {
|
|
3140
|
+
const executable = name === 'npm' || name === 'pnpm' ? `${name}.cmd` : `${name}.exe`;
|
|
2874
3141
|
const guard = name === 'git'
|
|
2875
3142
|
? [
|
|
2876
|
-
'
|
|
2877
|
-
'if
|
|
2878
|
-
'if /I "
|
|
2879
|
-
'goto
|
|
3143
|
+
':scan',
|
|
3144
|
+
'if "%~1"=="" goto run',
|
|
3145
|
+
'if /I "%~1"=="commit" goto block_git',
|
|
3146
|
+
'if /I "%~1"=="tag" goto block_git',
|
|
3147
|
+
'if /I "%~1"=="push" goto block_git',
|
|
3148
|
+
'shift',
|
|
3149
|
+
'goto scan',
|
|
2880
3150
|
':block_git',
|
|
2881
3151
|
'if "%AGENT_RUN_ALLOW_GIT_WRITE%"=="1" goto run',
|
|
2882
|
-
'echo agent-run: blocked git
|
|
3152
|
+
'echo agent-run: blocked git write. Review and run it manually, or set AGENT_RUN_ALLOW_GIT_WRITE=1 for this invocation. 1>&2',
|
|
2883
3153
|
'exit /b 42'
|
|
2884
3154
|
]
|
|
2885
3155
|
: name === 'gh'
|
|
2886
3156
|
? [
|
|
2887
|
-
'
|
|
2888
|
-
'
|
|
3157
|
+
'set "SAW_RELEASE=0"',
|
|
3158
|
+
':scan',
|
|
3159
|
+
'if "%~1"=="" goto run',
|
|
3160
|
+
'if /I "%~1"=="release" set "SAW_RELEASE=1"',
|
|
3161
|
+
'if "%SAW_RELEASE%"=="1" if /I "%~1"=="create" goto block_release',
|
|
3162
|
+
'shift',
|
|
3163
|
+
'goto scan',
|
|
3164
|
+
':block_release',
|
|
2889
3165
|
'if "%AGENT_RUN_ALLOW_GITHUB_RELEASE%"=="1" goto run',
|
|
2890
3166
|
'echo agent-run: blocked gh release create. Review and run it manually, or set AGENT_RUN_ALLOW_GITHUB_RELEASE=1 for this invocation. 1>&2',
|
|
2891
3167
|
'exit /b 42'
|
|
2892
3168
|
]
|
|
2893
3169
|
: [
|
|
2894
|
-
'
|
|
3170
|
+
':scan',
|
|
3171
|
+
'if "%~1"=="" goto run',
|
|
3172
|
+
'if /I "%~1"=="publish" goto block_publish',
|
|
3173
|
+
'shift',
|
|
3174
|
+
'goto scan',
|
|
3175
|
+
':block_publish',
|
|
2895
3176
|
'if "%AGENT_RUN_ALLOW_PUBLISH%"=="1" goto run',
|
|
2896
3177
|
`echo agent-run: blocked ${name} publish. Review and run it manually, or set AGENT_RUN_ALLOW_PUBLISH=1 for this invocation. 1>&2`,
|
|
2897
3178
|
'exit /b 42'
|
|
2898
3179
|
];
|
|
2899
|
-
return [
|
|
3180
|
+
return [
|
|
3181
|
+
'@echo off',
|
|
3182
|
+
...guard,
|
|
3183
|
+
':run',
|
|
3184
|
+
'if defined AGENT_RUN_REAL_PATH (',
|
|
3185
|
+
' set "PATH=%AGENT_RUN_REAL_PATH%"',
|
|
3186
|
+
') else (',
|
|
3187
|
+
' for /f "tokens=1,* delims=;" %%A in ("%PATH%") do set "PATH=%%B"',
|
|
3188
|
+
')',
|
|
3189
|
+
`${executable} %*`,
|
|
3190
|
+
''
|
|
3191
|
+
].join('\n');
|
|
2900
3192
|
}
|
|
2901
3193
|
function isVerbose() {
|
|
2902
3194
|
return parseBooleanEnv(process.env[VERBOSE_ENV]);
|