@technomoron/agent-run 0.99.28 → 0.99.30

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.
Files changed (43) hide show
  1. package/CHANGES +26 -0
  2. package/README.md +202 -26
  3. package/dist/agents/claude.js +93 -0
  4. package/dist/agents/codex.js +77 -0
  5. package/dist/agents/gemini.js +112 -0
  6. package/dist/agents/grok.js +73 -0
  7. package/dist/agents/registry.js +26 -0
  8. package/dist/agents/shared.js +17 -0
  9. package/dist/agents/types.js +4 -0
  10. package/dist/checks.js +4 -0
  11. package/dist/cli.js +45 -12
  12. package/dist/commands.js +72 -18
  13. package/dist/config/render-instructions.js +54 -0
  14. package/dist/config/render-mcp.js +88 -0
  15. package/dist/config/render-settings.js +35 -0
  16. package/dist/config/render-skills.js +51 -0
  17. package/dist/config-tree.js +249 -0
  18. package/dist/constants.js +5 -2
  19. package/dist/defaults.js +65 -8
  20. package/dist/manifest.js +39 -2
  21. package/dist/renderer.js +81 -227
  22. package/dist/runtime/environment.js +17 -0
  23. package/dist/runtime/postflight.js +40 -0
  24. package/dist/runtime/shared-state.js +44 -0
  25. package/dist/runtime/spawn-agent.js +7 -0
  26. package/dist/templates.js +12 -0
  27. package/examples/basic-config/README.md +2 -1
  28. package/examples/basic-config/agent-config/agent-run.defaults.jsonc +8 -2
  29. package/examples/basic-config/agent-config/global/tool-templates/AGENTS.md.njk +2 -0
  30. package/examples/basic-config/agent-config/global/tool-templates/CLAUDE.md.njk +1 -0
  31. package/examples/basic-config/agent-config/global/tool-templates/claude-settings.json.njk +2 -0
  32. package/examples/basic-config/agent-config/global/tool-templates/codex-config.toml.njk +2 -1
  33. package/examples/basic-config/agent-config/global/tool-templates/gemini-settings.json.njk +5 -0
  34. package/examples/basic-config/agent-config/global/tool-templates/grok-config.toml.njk +1 -0
  35. package/examples/basic-config/agent-config/starter/basic-project/agent-run.jsonc +20 -0
  36. package/examples/basic-config/agent-config/starter/basic-project/notes/memory/README.md +8 -0
  37. package/examples/basic-config/agent-config/starter/basic-project/overrides/claude-settings.json.njk +2 -0
  38. package/examples/basic-config/agent-config/starter/basic-project/overrides/codex-config.toml.njk +2 -1
  39. package/ops/systemd/ai-tools-update.service +1 -1
  40. package/package.json +1 -1
  41. package/scripts/install-systemd-jobs.sh +15 -5
  42. package/scripts/update-ai-tools.sh +27 -16
  43. package/dist/tools.js +0 -119
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.grokAdapter = void 0;
4
+ const os = require("os");
5
+ const path = require("path");
6
+ const render_mcp_1 = require("../config/render-mcp");
7
+ const render_settings_1 = require("../config/render-settings");
8
+ const render_skills_1 = require("../config/render-skills");
9
+ const defaults_1 = require("../defaults");
10
+ const environment_1 = require("../runtime/environment");
11
+ const shared_state_1 = require("../runtime/shared-state");
12
+ const shared_1 = require("./shared");
13
+ function layout(context) {
14
+ return {
15
+ dir: context.paths.grokRuntimeDir,
16
+ instructionFile: path.join(context.paths.grokRuntimeDir, 'AGENTS.md'),
17
+ configFiles: [path.join(context.paths.grokRuntimeDir, 'config.toml')],
18
+ skillsDir: context.paths.grokSkillsDir,
19
+ requiredDirs: [context.paths.grokRuntimeDir, context.paths.grokSkillsDir]
20
+ };
21
+ }
22
+ function generate(generation) {
23
+ const agentLayout = layout(generation.context);
24
+ const baseConfig = (0, render_settings_1.renderProfileConfig)(generation.env, generation.configRoot, generation.context, 'grok-config.toml.njk', 'global/tool-templates/grok-config.toml.njk', () => (0, defaults_1.defaultGrokConfigContent)(), 'Grok config.toml', generation.trace);
25
+ const mcp = (0, render_mcp_1.renderGrokMcp)(generation.context);
26
+ const files = [
27
+ { path: agentLayout.instructionFile, content: generation.agentsMd },
28
+ { path: agentLayout.configFiles[0] ?? '', content: `${baseConfig.trimEnd()}${mcp ? `\n\n${mcp}` : '\n'}` },
29
+ ...(0, render_skills_1.renderSkillFiles)(generation.context, agentLayout.skillsDir)
30
+ ];
31
+ return { ...agentLayout, id: 'grok', projectDir: generation.context.projectRoot, files, context: generation.context };
32
+ }
33
+ exports.grokAdapter = {
34
+ id: 'grok',
35
+ displayName: 'Grok',
36
+ capabilities: {
37
+ instructions: true,
38
+ skills: true,
39
+ mcp: true,
40
+ hooks: true,
41
+ subagents: true,
42
+ headless: true
43
+ },
44
+ wrapperOptions: { sandboxed: true, network: false },
45
+ layout,
46
+ generate,
47
+ prepare(runtime) {
48
+ for (const name of ['auth.json', 'mcp_credentials.json']) {
49
+ (0, shared_state_1.linkSharedEntry)(path.join(os.homedir(), '.grok', name), path.join(runtime.dir, name), `Grok ${name}`);
50
+ }
51
+ },
52
+ spawn(runtime, options) {
53
+ (0, shared_1.assertRuntimeFile)('grok', runtime.instructionFile, runtime.context.profileDir);
54
+ const modeArgs = options.wrapperArgs.sandboxMode === 'danger'
55
+ ? ['--always-approve']
56
+ : options.wrapperArgs.sandboxMode === 'sandboxed'
57
+ ? ['--sandbox', 'workspace']
58
+ : [];
59
+ return {
60
+ command: options.binary,
61
+ args: [...modeArgs, ...options.passthroughArgs],
62
+ cwd: runtime.projectDir,
63
+ env: { ...(0, environment_1.buildAgentEnvironment)(runtime.context), GROK_HOME: runtime.dir },
64
+ onExit: (0, shared_1.withPostflight)(runtime, options.wrapperArgs)
65
+ };
66
+ },
67
+ bypassArgs(wrapperArgs) {
68
+ if (wrapperArgs.sandboxMode === 'danger') {
69
+ return ['--always-approve'];
70
+ }
71
+ return wrapperArgs.sandboxMode === 'sandboxed' ? ['--sandbox', 'workspace'] : [];
72
+ }
73
+ };
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.agentRegistry = void 0;
4
+ exports.getAgentAdapter = getAgentAdapter;
5
+ exports.listAgentAdapters = listAgentAdapters;
6
+ exports.enabledAgentAdapters = enabledAgentAdapters;
7
+ const claude_1 = require("./claude");
8
+ const codex_1 = require("./codex");
9
+ const gemini_1 = require("./gemini");
10
+ const grok_1 = require("./grok");
11
+ const types_1 = require("./types");
12
+ exports.agentRegistry = {
13
+ codex: codex_1.codexAdapter,
14
+ claude: claude_1.claudeAdapter,
15
+ gemini: gemini_1.geminiAdapter,
16
+ grok: grok_1.grokAdapter
17
+ };
18
+ function getAgentAdapter(id) {
19
+ return exports.agentRegistry[id];
20
+ }
21
+ function listAgentAdapters() {
22
+ return types_1.AGENT_IDS.map((id) => exports.agentRegistry[id]);
23
+ }
24
+ function enabledAgentAdapters(context) {
25
+ return listAgentAdapters().filter((adapter) => context.tools[adapter.id]);
26
+ }
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.assertRuntimeFile = assertRuntimeFile;
4
+ exports.withPostflight = withPostflight;
5
+ const fs = require("fs");
6
+ const postflight_1 = require("../runtime/postflight");
7
+ function assertRuntimeFile(tool, filePath, profileDir) {
8
+ if (fs.existsSync(filePath)) {
9
+ return;
10
+ }
11
+ process.stderr.write(`agent-run: no ${tool} config found for this project: ${profileDir}\n`);
12
+ process.stderr.write(`agent-run: run \`agent-run ${tool} --none\` to bypass agent setup, or \`agent-run ${tool} --create\` to create profile files.\n`);
13
+ process.exit(1);
14
+ }
15
+ function withPostflight(runtime, wrapperArgs) {
16
+ return (code) => (0, postflight_1.postflightProjectCheck)(runtime.context, wrapperArgs.local, code);
17
+ }
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AGENT_IDS = void 0;
4
+ exports.AGENT_IDS = ['codex', 'claude', 'gemini', 'grok'];
package/dist/checks.js CHANGED
@@ -8,6 +8,7 @@ exports.hasErrors = hasErrors;
8
8
  const fs = require("fs");
9
9
  const path = require("path");
10
10
  const constants_1 = require("./constants");
11
+ const config_tree_1 = require("./config-tree");
11
12
  const manifest_1 = require("./manifest");
12
13
  const project_1 = require("./project");
13
14
  const renderer_1 = require("./renderer");
@@ -47,6 +48,9 @@ function checkAgentDirectory(projectRoot, agentDir) {
47
48
  if (!(0, manifest_1.isProfileConfigured)(agentDir)) {
48
49
  return [{ message: `missing manifest or legacy/local source file in ${agentDir}`, severity: 'ERROR' }];
49
50
  }
51
+ for (const legacyPath of (0, config_tree_1.describeLegacyProfileLayout)(agentDir)) {
52
+ findings.push({ message: `old profile layout needs migration: ${legacyPath}`, severity: 'ERROR' });
53
+ }
50
54
  for (const relativeTemplate of constants_1.REQUIRED_GLOBAL_TEMPLATES) {
51
55
  if (!fs.existsSync(path.join(configRoot, relativeTemplate))) {
52
56
  findings.push({
package/dist/cli.js CHANGED
@@ -4,6 +4,8 @@ exports.parseInvocation = parseInvocation;
4
4
  const fs = require("fs");
5
5
  const path = require("path");
6
6
  const constants_1 = require("./constants");
7
+ const registry_1 = require("./agents/registry");
8
+ const types_1 = require("./agents/types");
7
9
  const project_1 = require("./project");
8
10
  const utils_1 = require("./utils");
9
11
  function parseInvocation(invokedTool, argv) {
@@ -25,7 +27,7 @@ function applyGlobalOptions(options) {
25
27
  }
26
28
  function extractCommand(args) {
27
29
  if (args.length === 0) {
28
- (0, utils_1.fail)('usage: agent-run <codex|claude|check|setup|generate|edit|update> [options] (run with --help for details)');
30
+ (0, utils_1.fail)('usage: agent-run <codex|claude|gemini|grok|check|status|setup|generate|edit|update> [options] (run with --help for details)');
29
31
  }
30
32
  if (isHelpFlag(args[0])) {
31
33
  printHelp('general');
@@ -48,6 +50,8 @@ function parseCommand(command, args) {
48
50
  switch (command) {
49
51
  case 'check':
50
52
  return parseCheckCommand(args);
53
+ case 'status':
54
+ return parseStatusCommand(args);
51
55
  case 'init':
52
56
  return parseInitCommand(args);
53
57
  case 'generate':
@@ -162,7 +166,11 @@ function applySandboxRunOption(command, arg, options) {
162
166
  if (!['--yolo', '--sandboxed', '--network'].includes(arg)) {
163
167
  return false;
164
168
  }
165
- if (arg !== '--yolo' && command !== 'codex') {
169
+ const adapter = (0, registry_1.getAgentAdapter)(command);
170
+ if (arg === '--sandboxed' && !adapter.wrapperOptions.sandboxed) {
171
+ (0, utils_1.fail)(`${arg} is not supported for agent-run ${command}`);
172
+ }
173
+ if (arg === '--network' && !adapter.wrapperOptions.network) {
166
174
  (0, utils_1.fail)(`${arg} is only supported for agent-run codex`);
167
175
  }
168
176
  if (arg === '--network') {
@@ -176,6 +184,15 @@ function applySandboxRunOption(command, arg, options) {
176
184
  options.sandboxMode = mode;
177
185
  return true;
178
186
  }
187
+ function parseStatusCommand(inputArgs) {
188
+ for (const arg of inputArgs) {
189
+ if (isHelpFlag(arg)) {
190
+ printHelp('status');
191
+ }
192
+ (0, utils_1.fail)(`unknown status option: ${arg}`);
193
+ }
194
+ return { command: 'status' };
195
+ }
179
196
  function parseCheckCommand(inputArgs) {
180
197
  let all = false;
181
198
  let targetPath = process.cwd();
@@ -254,16 +271,22 @@ function parseUpdateCommand(inputArgs) {
254
271
  }
255
272
  function parseMigrateConfigCommand(inputArgs) {
256
273
  let configRoot = (0, project_1.defaultConfigRoot)();
274
+ let yes = false;
257
275
  for (const arg of inputArgs) {
258
276
  if (isHelpFlag(arg)) {
259
277
  printHelp('migrate-config');
260
278
  }
261
- if (arg.startsWith('--')) {
279
+ if (arg === '--yes') {
280
+ yes = true;
281
+ }
282
+ else if (arg.startsWith('--')) {
262
283
  (0, utils_1.fail)(`unknown migrate-config option: ${arg}`);
263
284
  }
264
- configRoot = arg;
285
+ else {
286
+ configRoot = arg;
287
+ }
265
288
  }
266
- return { command: 'migrate-config', configRoot: path.resolve(configRoot) };
289
+ return { command: 'migrate-config', configRoot: path.resolve(configRoot), yes };
267
290
  }
268
291
  function isHelpFlag(value) {
269
292
  return value === '-h' || value === '--help';
@@ -292,6 +315,7 @@ function renderHelp(topic) {
292
315
  ' --all Check every repo under path',
293
316
  ''
294
317
  ],
318
+ status: ['Usage:', ' agent-run status', '', 'Show the native agent capability matrix.', ''],
295
319
  init: ['Usage:', ' agent-run init [path]', '', 'Deprecated alias for `agent-run generate [path]`.', ''],
296
320
  generate: ['Usage:', ' agent-run generate [path]', '', 'Create a minimal mapped profile marker and render generated files.', ''],
297
321
  setup: ['Usage:', ' agent-run setup [org/repo]', '', 'Install the default config tree and selected profile.', ''],
@@ -306,9 +330,12 @@ function renderHelp(topic) {
306
330
  ],
307
331
  'migrate-config': [
308
332
  'Usage:',
309
- ' agent-run migrate-config [config-root]',
333
+ ' agent-run migrate-config [--yes] [config-root]',
310
334
  '',
311
335
  'Convert an existing agent config tree to the current manifest and global-template layout.',
336
+ '',
337
+ 'Options:',
338
+ ' --yes Confirm tracked Git moves without prompting',
312
339
  ''
313
340
  ]
314
341
  };
@@ -319,19 +346,24 @@ function renderHelp(topic) {
319
346
  `agent-run ${agentRunVersion()}`,
320
347
  '',
321
348
  'Usage:',
322
- ' agent-run <codex|claude|check|setup|generate|edit|update> [options]',
349
+ ' agent-run <codex|claude|gemini|grok|check|status|setup|generate|edit|update> [options]',
323
350
  '',
324
351
  'Commands:',
325
352
  ' codex [--none] [--create] [--local] [--show] [--generate] [--yolo|--sandboxed] [--network] [args...]',
326
353
  ' Run codex with generated private config',
327
354
  ' claude [--none] [--create] [--local] [--show] [--generate] [--yolo] [args...]',
328
355
  ' Run claude with generated private config',
356
+ ' gemini [--none] [--create] [--local] [--show] [--generate] [--yolo|--sandboxed] [args...]',
357
+ ' Run gemini with generated private config',
358
+ ' grok [--none] [--create] [--local] [--show] [--generate] [--yolo|--sandboxed] [args...]',
359
+ ' Run grok with generated private config',
329
360
  ' setup [org/repo] Install the default tree and selected profile',
330
361
  ' check [--all] [path] Validate generated profile output',
362
+ ' status Show native agent capabilities',
331
363
  ' generate [path] Create a sparse profile marker and render output',
332
364
  ' edit [path] Create or open local.md.njk',
333
365
  ' update [--all] [path] Regenerate existing profile output',
334
- ' migrate-config [config-root] Convert existing config tree layout',
366
+ ' migrate-config [--yes] [config-root] Convert existing config tree layout',
335
367
  '',
336
368
  'Global options:',
337
369
  ' -h, --help Show this help text',
@@ -345,10 +377,11 @@ function renderHelp(topic) {
345
377
  ' --generate Generate files without running the tool',
346
378
  ' --yolo Run unattended without approval prompts:',
347
379
  ' Codex with -a never -s danger-full-access,',
348
- ' Claude with --dangerously-skip-permissions',
380
+ ' Claude with --dangerously-skip-permissions,',
381
+ ' Gemini with --yolo, Grok with --always-approve',
349
382
  '',
350
- 'Codex wrapper options:',
351
- ' --sandboxed Run Codex with workspace-write sandbox (default)',
383
+ 'Sandbox wrapper options:',
384
+ ' --sandboxed Run Codex, Gemini, or Grok in its native workspace sandbox',
352
385
  ' --network Enable network for the workspace-write sandbox',
353
386
  ''
354
387
  ].join('\n');
@@ -370,7 +403,7 @@ function normalizeCommandName(value) {
370
403
  if (base === 'agent-run') {
371
404
  return null;
372
405
  }
373
- return ['codex', 'claude', 'check', 'setup', 'generate', 'init', 'edit', 'update', 'migrate-config'].includes(base)
406
+ return [...types_1.AGENT_IDS, 'check', 'status', 'setup', 'generate', 'init', 'edit', 'update', 'migrate-config'].includes(base)
374
407
  ? base
375
408
  : null;
376
409
  }
package/dist/commands.js CHANGED
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.main = main;
4
4
  const fs = require("fs");
5
5
  const path = require("path");
6
+ const registry_1 = require("./agents/registry");
6
7
  const checks_1 = require("./checks");
7
8
  const cli_1 = require("./cli");
8
9
  const config_tree_1 = require("./config-tree");
@@ -11,7 +12,7 @@ const manifest_1 = require("./manifest");
11
12
  const process_1 = require("./process");
12
13
  const project_1 = require("./project");
13
14
  const renderer_1 = require("./renderer");
14
- const tools_1 = require("./tools");
15
+ const spawn_agent_1 = require("./runtime/spawn-agent");
15
16
  const utils_1 = require("./utils");
16
17
  function main(invokedTool, argv) {
17
18
  dispatch((0, cli_1.parseInvocation)(invokedTool, argv));
@@ -21,6 +22,9 @@ function dispatch(command) {
21
22
  case 'check':
22
23
  runCheck(command);
23
24
  return;
25
+ case 'status':
26
+ runStatus(command);
27
+ return;
24
28
  case 'init':
25
29
  runGenerate(command);
26
30
  return;
@@ -45,13 +49,18 @@ function dispatch(command) {
45
49
  }
46
50
  function runTool(parsed) {
47
51
  const { args, command, wrapperArgs } = parsed;
52
+ const adapter = (0, registry_1.getAgentAdapter)(command);
48
53
  const projectRoot = (0, project_1.findProjectRoot)(process.cwd());
49
54
  (0, utils_1.verbose)(`run ${command}: cwd=${process.cwd()} projectRoot=${projectRoot}`);
50
55
  validateRunModes(parsed, projectRoot);
51
56
  if (wrapperArgs.none || (0, project_1.isIgnoredDir)(projectRoot)) {
52
57
  (0, utils_1.verbose)(`wrapper bypassed for ${command}${wrapperArgs.none ? ' via --none' : ' because project is ignored'}`);
53
- const runtimeArgs = wrapperArgs.sandboxMode === 'danger' ? (0, tools_1.getDangerArgs)(command) : (0, tools_1.getPermissionArgs)(command);
54
- (0, process_1.execTool)((0, process_1.findRealBinary)(command), [...runtimeArgs, ...args]);
58
+ (0, spawn_agent_1.spawnAgent)({
59
+ command: (0, process_1.findRealBinary)(command),
60
+ args: [...adapter.bypassArgs(wrapperArgs), ...args],
61
+ cwd: process.cwd(),
62
+ env: { ...process.env }
63
+ });
55
64
  return;
56
65
  }
57
66
  const profileResult = (0, project_1.resolveProfileResult)(projectRoot);
@@ -59,9 +68,9 @@ function runTool(parsed) {
59
68
  (0, utils_1.fail)(profileResult.reason);
60
69
  }
61
70
  const profile = profileResult.profile;
71
+ const configRoot = (0, project_1.defaultConfigRoot)(projectRoot);
72
+ const agentDir = path.join(configRoot, profile);
62
73
  if (wrapperArgs.show) {
63
- const configRoot = (0, project_1.defaultConfigRoot)(projectRoot);
64
- const agentDir = path.join(configRoot, profile);
65
74
  (0, utils_1.verbose)(`profile=${profile} configRoot=${configRoot} agentPath=${agentDir}`);
66
75
  requireProfile(command, agentDir);
67
76
  showToolProfile(command, projectRoot, agentDir);
@@ -70,11 +79,10 @@ function runTool(parsed) {
70
79
  if (wrapperArgs.create) {
71
80
  runGenerate({ command: 'generate', targetPath: projectRoot });
72
81
  }
73
- const configRoot = (0, project_1.defaultConfigRoot)(projectRoot);
74
- const agentDir = path.join(configRoot, profile);
75
82
  (0, utils_1.verbose)(`profile=${profile} configRoot=${configRoot} agentPath=${agentDir}`);
76
83
  prepareConfigRoot(configRoot);
77
84
  requireProfile(command, agentDir);
85
+ migrateProfileOnStart(configRoot, agentDir, false);
78
86
  const preview = (0, renderer_1.renderProfile)(projectRoot, agentDir, true, command);
79
87
  ensureToolEnabled(preview.context, command);
80
88
  if (wrapperArgs.generate) {
@@ -83,14 +91,29 @@ function runTool(parsed) {
83
91
  }
84
92
  checkLocalAiFiles(command, projectRoot, agentDir, preview.context, wrapperArgs.local);
85
93
  const realBinary = (0, process_1.findRealBinary)(command);
86
- const permissionArgs = (0, tools_1.getPermissionArgs)(command);
87
94
  const rendered = (0, renderer_1.syncAgentProfile)(projectRoot, agentDir);
88
- if (command === 'codex') {
89
- (0, tools_1.runCodex)(realBinary, permissionArgs, rendered.context, args, wrapperArgs);
90
- }
91
- else {
92
- (0, tools_1.runClaude)(realBinary, permissionArgs, rendered.context, args, wrapperArgs);
95
+ const runtime = rendered.runtimes[command];
96
+ if (!runtime) {
97
+ (0, utils_1.fail)(`no generated ${command} runtime found for profile ${rendered.profile}`);
93
98
  }
99
+ (0, spawn_agent_1.spawnAgent)(adapter.spawn(runtime, { binary: realBinary, passthroughArgs: args, wrapperArgs }));
100
+ }
101
+ function runStatus(_parsed) {
102
+ const adapters = (0, registry_1.listAgentAdapters)();
103
+ const columns = adapters.map((adapter) => adapter.displayName);
104
+ const capabilityRows = [
105
+ ['Instructions', 'instructions'],
106
+ ['Skills', 'skills'],
107
+ ['MCP', 'mcp'],
108
+ ['Hooks', 'hooks'],
109
+ ['Subagents', 'subagents'],
110
+ ['Headless', 'headless']
111
+ ];
112
+ const firstWidth = Math.max(...capabilityRows.map(([label]) => label.length));
113
+ const widths = columns.map((label) => Math.max(label.length, 3));
114
+ const header = `${''.padEnd(firstWidth)} ${columns.map((label, index) => label.padStart(widths[index] ?? 3)).join(' ')}`;
115
+ const rows = capabilityRows.map(([label, capability]) => `${label.padEnd(firstWidth)} ${adapters.map((adapter, index) => (adapter.capabilities[capability] ? 'yes' : '-').padStart(widths[index] ?? 3)).join(' ')}`);
116
+ process.stdout.write(['Native agent capabilities', '', header, ...rows, ''].join('\n'));
94
117
  }
95
118
  function validateRunModes(parsed, projectRoot) {
96
119
  const { command, wrapperArgs } = parsed;
@@ -158,6 +181,7 @@ function runGenerate(parsed) {
158
181
  return;
159
182
  }
160
183
  const agentDir = initializeProfileSource(projectRoot, true);
184
+ migrateProfileOnStart((0, project_1.defaultConfigRoot)(projectRoot), agentDir, false);
161
185
  printUpdateSummary((0, renderer_1.syncAgentProfile)(projectRoot, agentDir));
162
186
  }
163
187
  function runSetup(parsed) {
@@ -170,7 +194,9 @@ function runSetup(parsed) {
170
194
  (0, utils_1.fail)(`starter config skeleton not found: ${sourcePath}`);
171
195
  }
172
196
  (0, config_tree_1.copySkeletonTree)(sourcePath, configRoot, new Set(['starter']));
197
+ (0, config_tree_1.ensurePortableSystemdFiles)(configRoot);
173
198
  const agentDir = path.join(configRoot, profile);
199
+ migrateProfileOnStart(configRoot, agentDir, false);
174
200
  (0, config_tree_1.copySkeletonTree)(path.join(sourcePath, 'starter', 'basic-project'), agentDir);
175
201
  if (profile !== detectedProfile) {
176
202
  writeProjectProfileMapping(projectRoot, profile);
@@ -193,9 +219,9 @@ function confirmProfile(detectedProfile) {
193
219
  }
194
220
  return requireValidProfile(promptLine('Profile (org/repo): '));
195
221
  }
196
- function promptLine(prompt) {
222
+ function promptLine(prompt, nonInteractiveMessage = 'setup requires a profile argument when input is not interactive') {
197
223
  if (!process.stdin.isTTY) {
198
- (0, utils_1.fail)('setup requires a profile argument when input is not interactive');
224
+ (0, utils_1.fail)(nonInteractiveMessage);
199
225
  }
200
226
  process.stdout.write(prompt);
201
227
  const bytes = [];
@@ -234,6 +260,7 @@ function runEdit(parsed) {
234
260
  return;
235
261
  }
236
262
  const agentDir = initializeProfileSource(projectRoot, false);
263
+ migrateProfileOnStart((0, project_1.defaultConfigRoot)(projectRoot), agentDir, false);
237
264
  const editPath = (0, manifest_1.createDefaultLocalFile)(agentDir);
238
265
  (0, renderer_1.syncAgentProfile)(projectRoot, agentDir);
239
266
  process.stdout.write(`Edit: ${editPath}\n`);
@@ -254,6 +281,7 @@ function runUpdate(parsed) {
254
281
  const agentDir = (0, project_1.resolveAgentDir)(projectRoot);
255
282
  prepareConfigRoot(configRoot);
256
283
  requireProfile(null, agentDir);
284
+ migrateProfileOnStart(configRoot, agentDir, false);
257
285
  printUpdateSummary((0, renderer_1.syncAgentProfile)(projectRoot, agentDir));
258
286
  }
259
287
  function runUpdateAll(targetPath) {
@@ -264,6 +292,7 @@ function runUpdateAll(targetPath) {
264
292
  prepareConfigRoot(configRoot);
265
293
  const profileDirs = (0, config_tree_1.findProfileDirs)(configRoot);
266
294
  for (const agentDir of profileDirs) {
295
+ migrateProfileOnStart(configRoot, agentDir, false);
267
296
  const profile = path.relative(configRoot, agentDir).replace(/\\/g, '/');
268
297
  const projectRoot = (0, project_1.projectRootForProfile)(profile, configRoot);
269
298
  (0, renderer_1.syncAgentProfile)(projectRoot, agentDir, { configRoot, profile });
@@ -281,15 +310,16 @@ function runMigrateConfig(parsed) {
281
310
  (0, config_tree_1.migrateOldTemplates)(configRoot);
282
311
  (0, config_tree_1.ensureDefaultGlobalTemplates)(configRoot);
283
312
  (0, manifest_1.ensureRootDefaultsFile)(configRoot);
284
- const profileDirs = (0, config_tree_1.findLegacyProfileDirs)(configRoot);
313
+ const profileDirs = (0, utils_1.uniqueSorted)([...(0, config_tree_1.findLegacyProfileDirs)(configRoot), ...(0, config_tree_1.findProfileDirs)(configRoot)]);
285
314
  let createdManifestCount = 0;
286
315
  let createdLocalCount = 0;
287
316
  let movedRuntimeCount = 0;
288
317
  let movedReviewCount = 0;
289
318
  let movedMemoryCount = 0;
290
319
  for (const agentDir of profileDirs) {
320
+ const layout = migrateProfileOnStart(configRoot, agentDir, parsed.yes);
291
321
  const localPath = path.join(agentDir, constants_1.LOCAL_TEMPLATE_FILE_NAME);
292
- if (!fs.existsSync(localPath)) {
322
+ if (!fs.existsSync(localPath) && fs.existsSync(path.join(agentDir, 'AGENTS-MODS.md'))) {
293
323
  fs.writeFileSync(localPath, (0, config_tree_1.convertLegacyTemplateVars)(fs.readFileSync(path.join(agentDir, 'AGENTS-MODS.md'), 'utf8')), 'utf8');
294
324
  createdLocalCount += 1;
295
325
  }
@@ -299,7 +329,7 @@ function runMigrateConfig(parsed) {
299
329
  }
300
330
  movedRuntimeCount += (0, config_tree_1.migrateCodexRuntimeFiles)(agentDir);
301
331
  movedReviewCount += (0, config_tree_1.migrateLooseFiles)(agentDir, /^REVIEW(?:-.+)?\.md$/, 'reviews');
302
- movedMemoryCount += (0, config_tree_1.migrateLooseFiles)(agentDir, /^memory.*\.md$/i, 'memories');
332
+ movedMemoryCount += layout.projectMemoryMoves;
303
333
  }
304
334
  process.stdout.write(`OK migrated ${configRoot}\n`);
305
335
  process.stdout.write(`Profiles converted: ${profileDirs.length}\n`);
@@ -309,6 +339,29 @@ function runMigrateConfig(parsed) {
309
339
  process.stdout.write(`Moved review files: ${movedReviewCount}\n`);
310
340
  process.stdout.write(`Moved memory files: ${movedMemoryCount}\n`);
311
341
  }
342
+ function migrateProfileOnStart(configRoot, profileDir, assumeYes) {
343
+ const result = (0, config_tree_1.migrateProfileLayout)(configRoot, profileDir, ({ gitRoot, moves }) => {
344
+ process.stdout.write('agent-run: this profile uses an older project-memory layout.\n');
345
+ process.stdout.write(`Git working tree: ${gitRoot}\n`);
346
+ for (const move of moves) {
347
+ process.stdout.write(` ${path.relative(gitRoot, move.source)} -> ${path.relative(gitRoot, move.target)}\n`);
348
+ }
349
+ if (assumeYes) {
350
+ return true;
351
+ }
352
+ const migrateCommand = (0, utils_1.formatCommand)('agent-run', ['migrate-config', '--yes', configRoot]);
353
+ const message = `tracked project-memory files need migration; rerun interactively or run \`${migrateCommand}\``;
354
+ const answer = promptLine('Stage these moves with git mv? [Y/n] ', message).trim().toLowerCase();
355
+ return answer === '' || answer === 'y' || answer === 'yes';
356
+ });
357
+ if (result.gitMoves > 0) {
358
+ process.stdout.write(`Staged Git moves: ${result.gitMoves}\n`);
359
+ }
360
+ if (result.runtimeMoves > 0) {
361
+ process.stdout.write(`Moved local Codex runtime entries: ${result.runtimeMoves}\n`);
362
+ }
363
+ return result;
364
+ }
312
365
  function runCheck(parsed) {
313
366
  if (parsed.all) {
314
367
  const report = (0, checks_1.checkSourceTree)(parsed.targetPath);
@@ -328,6 +381,7 @@ function prepareConfigRoot(configRoot) {
328
381
  (0, config_tree_1.ensureConfigRootLayout)(configRoot);
329
382
  (0, config_tree_1.ensureConfigRootGitignore)(configRoot);
330
383
  (0, config_tree_1.ensureDefaultGlobalTemplates)(configRoot);
384
+ (0, config_tree_1.ensurePortableSystemdFiles)(configRoot);
331
385
  }
332
386
  function activeProject(targetPath, action) {
333
387
  const projectRoot = (0, project_1.findProjectRoot)(targetPath);
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildCanonicalInstructions = buildCanonicalInstructions;
4
+ exports.writeAgentsMd = writeAgentsMd;
5
+ exports.writeClaudeMd = writeClaudeMd;
6
+ const fs = require("fs");
7
+ const path = require("path");
8
+ const constants_1 = require("../constants");
9
+ const defaults_1 = require("../defaults");
10
+ const templates_1 = require("../templates");
11
+ function buildCanonicalInstructions(env, configRoot, manifest, context, trace) {
12
+ const sections = [(0, templates_1.renderTemplateFile)(env, configRoot, manifest.agent.base, context, trace)];
13
+ for (const include of manifest.agent.includes) {
14
+ const includePath = (0, templates_1.resolveConfigPath)(configRoot, include, context);
15
+ if (!fs.existsSync(includePath)) {
16
+ if (include.includes('AGENTS-MODS.md') || include.includes(constants_1.LOCAL_TEMPLATE_FILE_NAME)) {
17
+ continue;
18
+ }
19
+ throw new Error(`missing template: ${includePath}`);
20
+ }
21
+ sections.push((0, templates_1.renderTemplateFile)(env, configRoot, include, context, trace));
22
+ }
23
+ const renderedSections = sections.map((section, index) => {
24
+ const label = index === 0 ? manifest.agent.base : manifest.agent.includes[index - 1] ?? `section ${index}`;
25
+ (0, templates_1.assertNoUnexpandedTemplateVars)(label, section);
26
+ return section.trimEnd();
27
+ });
28
+ renderedSections.push(projectMemoryInstructions(context));
29
+ return { sections: renderedSections };
30
+ }
31
+ function writeAgentsMd(env, configRoot, context, trace) {
32
+ return renderInstructionFile('AGENTS.md', env, configRoot, context, false, trace);
33
+ }
34
+ function writeClaudeMd(env, configRoot, context, trace) {
35
+ return renderInstructionFile('CLAUDE.md', env, configRoot, context, true, trace);
36
+ }
37
+ function renderInstructionFile(templateName, env, configRoot, context, isClaude, trace) {
38
+ const templatePath = `global/tool-templates/${templateName}.njk`;
39
+ const content = fs.existsSync(path.join(configRoot, templatePath))
40
+ ? (0, templates_1.renderTemplateFile)(env, configRoot, templatePath, context, trace)
41
+ : env.renderString((0, defaults_1.defaultToolInstructionsTemplate)(isClaude), context);
42
+ (0, templates_1.assertNoUnexpandedTemplateVars)(templateName, content);
43
+ return content.replace(/\n*$/, '\n');
44
+ }
45
+ function projectMemoryInstructions(context) {
46
+ return [
47
+ '## Project Memory',
48
+ '',
49
+ `Project memory is stored in ${context.paths.projectMemoryDir}.`,
50
+ 'Read README.md there before starting work when prior project context may matter, then read only the linked files relevant to the task.',
51
+ 'Do not store secrets, raw chat transcripts, or temporary task state there.',
52
+ 'Update project memory only when the user explicitly asks you to remember or update something for this project.'
53
+ ].join('\n');
54
+ }
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderCodexMcp = renderCodexMcp;
4
+ exports.renderGrokMcp = renderGrokMcp;
5
+ exports.renderClaudeMcp = renderClaudeMcp;
6
+ exports.renderGeminiMcp = renderGeminiMcp;
7
+ function renderCodexMcp(context) {
8
+ return renderTomlMcp(context.mcpServers, 'codex');
9
+ }
10
+ function renderGrokMcp(context) {
11
+ return renderTomlMcp(context.mcpServers, 'grok');
12
+ }
13
+ function renderClaudeMcp(context) {
14
+ return { mcpServers: renderJsonMcp(context.mcpServers, 'claude') };
15
+ }
16
+ function renderGeminiMcp(context) {
17
+ return renderJsonMcp(context.mcpServers, 'gemini');
18
+ }
19
+ function renderJsonMcp(servers, target) {
20
+ const result = {};
21
+ for (const [name, server] of Object.entries(servers)) {
22
+ if (!server.enabled) {
23
+ continue;
24
+ }
25
+ if (server.transport === 'stdio') {
26
+ result[name] = compactObject({
27
+ ...(target === 'claude' ? { type: 'stdio' } : {}),
28
+ command: server.command,
29
+ args: server.args.length > 0 ? server.args : undefined,
30
+ cwd: server.cwd,
31
+ env: Object.keys(server.env).length > 0 ? server.env : undefined
32
+ });
33
+ continue;
34
+ }
35
+ result[name] = compactObject({
36
+ ...(target === 'claude' ? { type: server.transport } : {}),
37
+ ...(target === 'gemini' && server.transport === 'http'
38
+ ? { httpUrl: server.url }
39
+ : { url: server.url }),
40
+ headers: Object.keys(server.headers).length > 0 ? server.headers : undefined
41
+ });
42
+ }
43
+ return result;
44
+ }
45
+ function renderTomlMcp(servers, target) {
46
+ const sections = [];
47
+ for (const [name, server] of Object.entries(servers)) {
48
+ const lines = [`[mcp_servers.${tomlKey(name)}]`];
49
+ if (server.transport === 'stdio') {
50
+ lines.push(`command = ${tomlString(server.command ?? '')}`);
51
+ if (server.args.length > 0) {
52
+ lines.push(`args = ${tomlArray(server.args)}`);
53
+ }
54
+ if (server.cwd) {
55
+ lines.push(`cwd = ${tomlString(server.cwd)}`);
56
+ }
57
+ if (Object.keys(server.env).length > 0) {
58
+ lines.push(`env = ${tomlInlineTable(server.env)}`);
59
+ }
60
+ }
61
+ else {
62
+ lines.push(`url = ${tomlString(server.url ?? '')}`);
63
+ if (Object.keys(server.headers).length > 0) {
64
+ lines.push(`${target === 'codex' ? 'http_headers' : 'headers'} = ${tomlInlineTable(server.headers)}`);
65
+ }
66
+ }
67
+ if (!server.enabled) {
68
+ lines.push('enabled = false');
69
+ }
70
+ sections.push(lines.join('\n'));
71
+ }
72
+ return sections.length > 0 ? `${sections.join('\n\n')}\n` : '';
73
+ }
74
+ function compactObject(value) {
75
+ return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
76
+ }
77
+ function tomlKey(value) {
78
+ return /^[A-Za-z0-9_-]+$/.test(value) ? value : tomlString(value);
79
+ }
80
+ function tomlString(value) {
81
+ return JSON.stringify(value);
82
+ }
83
+ function tomlArray(values) {
84
+ return `[${values.map(tomlString).join(', ')}]`;
85
+ }
86
+ function tomlInlineTable(values) {
87
+ return `{ ${Object.entries(values).map(([key, value]) => `${tomlKey(key)} = ${tomlString(value)}`).join(', ')} }`;
88
+ }