@technomoron/agent-run 0.99.29 → 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.
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();
@@ -298,6 +315,7 @@ function renderHelp(topic) {
298
315
  ' --all Check every repo under path',
299
316
  ''
300
317
  ],
318
+ status: ['Usage:', ' agent-run status', '', 'Show the native agent capability matrix.', ''],
301
319
  init: ['Usage:', ' agent-run init [path]', '', 'Deprecated alias for `agent-run generate [path]`.', ''],
302
320
  generate: ['Usage:', ' agent-run generate [path]', '', 'Create a minimal mapped profile marker and render generated files.', ''],
303
321
  setup: ['Usage:', ' agent-run setup [org/repo]', '', 'Install the default config tree and selected profile.', ''],
@@ -328,15 +346,20 @@ function renderHelp(topic) {
328
346
  `agent-run ${agentRunVersion()}`,
329
347
  '',
330
348
  'Usage:',
331
- ' agent-run <codex|claude|check|setup|generate|edit|update> [options]',
349
+ ' agent-run <codex|claude|gemini|grok|check|status|setup|generate|edit|update> [options]',
332
350
  '',
333
351
  'Commands:',
334
352
  ' codex [--none] [--create] [--local] [--show] [--generate] [--yolo|--sandboxed] [--network] [args...]',
335
353
  ' Run codex with generated private config',
336
354
  ' claude [--none] [--create] [--local] [--show] [--generate] [--yolo] [args...]',
337
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',
338
360
  ' setup [org/repo] Install the default tree and selected profile',
339
361
  ' check [--all] [path] Validate generated profile output',
362
+ ' status Show native agent capabilities',
340
363
  ' generate [path] Create a sparse profile marker and render output',
341
364
  ' edit [path] Create or open local.md.njk',
342
365
  ' update [--all] [path] Regenerate existing profile output',
@@ -354,10 +377,11 @@ function renderHelp(topic) {
354
377
  ' --generate Generate files without running the tool',
355
378
  ' --yolo Run unattended without approval prompts:',
356
379
  ' Codex with -a never -s danger-full-access,',
357
- ' Claude with --dangerously-skip-permissions',
380
+ ' Claude with --dangerously-skip-permissions,',
381
+ ' Gemini with --yolo, Grok with --always-approve',
358
382
  '',
359
- 'Codex wrapper options:',
360
- ' --sandboxed Run Codex with workspace-write sandbox (default)',
383
+ 'Sandbox wrapper options:',
384
+ ' --sandboxed Run Codex, Gemini, or Grok in its native workspace sandbox',
361
385
  ' --network Enable network for the workspace-write sandbox',
362
386
  ''
363
387
  ].join('\n');
@@ -379,7 +403,7 @@ function normalizeCommandName(value) {
379
403
  if (base === 'agent-run') {
380
404
  return null;
381
405
  }
382
- 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)
383
407
  ? base
384
408
  : null;
385
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);
@@ -82,14 +91,29 @@ function runTool(parsed) {
82
91
  }
83
92
  checkLocalAiFiles(command, projectRoot, agentDir, preview.context, wrapperArgs.local);
84
93
  const realBinary = (0, process_1.findRealBinary)(command);
85
- const permissionArgs = (0, tools_1.getPermissionArgs)(command);
86
94
  const rendered = (0, renderer_1.syncAgentProfile)(projectRoot, agentDir);
87
- if (command === 'codex') {
88
- (0, tools_1.runCodex)(realBinary, permissionArgs, rendered.context, args, wrapperArgs);
89
- }
90
- else {
91
- (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}`);
92
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'));
93
117
  }
94
118
  function validateRunModes(parsed, projectRoot) {
95
119
  const { command, wrapperArgs } = parsed;
@@ -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
+ }
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderProfileConfig = renderProfileConfig;
4
+ exports.parseJsonObject = parseJsonObject;
5
+ const fs = require("fs");
6
+ const path = require("path");
7
+ const templates_1 = require("../templates");
8
+ const utils_1 = require("../utils");
9
+ function renderProfileConfig(env, configRoot, context, overrideFileName, globalTemplatePath, fallback, label, trace) {
10
+ const templatePath = resolveProfileOverrideTemplate(configRoot, context, overrideFileName, globalTemplatePath);
11
+ const content = templatePath
12
+ ? (0, templates_1.renderTemplateFile)(env, configRoot, templatePath, context, trace)
13
+ : fallback();
14
+ (0, templates_1.assertNoUnexpandedTemplateVars)(label, content);
15
+ return content.replace(/\n*$/, '\n');
16
+ }
17
+ function parseJsonObject(content, label) {
18
+ try {
19
+ const parsed = JSON.parse(content);
20
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
21
+ throw new Error('top-level value must be an object');
22
+ }
23
+ return parsed;
24
+ }
25
+ catch (error) {
26
+ throw new Error(`invalid generated ${label} JSON: ${(0, utils_1.formatError)(error)}`);
27
+ }
28
+ }
29
+ function resolveProfileOverrideTemplate(configRoot, context, overrideFileName, globalTemplatePath) {
30
+ const overridePath = `${context.profile}/overrides/${overrideFileName}`;
31
+ if (fs.existsSync(path.join(configRoot, overridePath))) {
32
+ return overridePath;
33
+ }
34
+ return fs.existsSync(path.join(configRoot, globalTemplatePath)) ? globalTemplatePath : null;
35
+ }
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildCanonicalSkills = buildCanonicalSkills;
4
+ exports.renderSkillFiles = renderSkillFiles;
5
+ exports.validateRenderedSkill = validateRenderedSkill;
6
+ const fs = require("fs");
7
+ const path = require("path");
8
+ const templates_1 = require("../templates");
9
+ function buildCanonicalSkills(env, configRoot, manifest, context, trace) {
10
+ return manifest.skills.install.map((name) => {
11
+ const sourceTemplate = manifest.skills.overrides[name] ?? `global/skills/${name}/SKILL.md.njk`;
12
+ const sourcePath = (0, templates_1.resolveConfigPath)(configRoot, sourceTemplate, context);
13
+ if (!fs.existsSync(sourcePath)) {
14
+ throw new Error(`missing skill template for ${name}: ${sourcePath}`);
15
+ }
16
+ const renderedContent = (0, templates_1.renderTemplateFile)(env, configRoot, sourceTemplate, context, trace);
17
+ (0, templates_1.assertNoUnexpandedTemplateVars)(`skill ${name}`, renderedContent);
18
+ validateRenderedSkill(renderedContent, sourcePath);
19
+ return { name, sourcePath, renderedContent, description: extractSkillDescription(renderedContent) };
20
+ });
21
+ }
22
+ function renderSkillFiles(context, targetDir) {
23
+ return context.skills.map((skill) => ({
24
+ path: path.join(targetDir, skill.name, 'SKILL.md'),
25
+ content: skill.renderedContent
26
+ }));
27
+ }
28
+ function validateRenderedSkill(content, sourcePath) {
29
+ const frontMatter = /^---\n([\s\S]*?)\n---\n/.exec(content.replace(/\r\n/g, '\n'));
30
+ if (frontMatter === null) {
31
+ throw new Error(`generated skill missing YAML front matter: ${sourcePath}`);
32
+ }
33
+ const yaml = frontMatter[1] ?? '';
34
+ if (!/^name:\s*\S+/m.test(yaml)) {
35
+ throw new Error(`generated skill missing name: ${sourcePath}`);
36
+ }
37
+ if (!/^description:\s*(?:\S|>\s*$)/m.test(yaml)) {
38
+ throw new Error(`generated skill missing description: ${sourcePath}`);
39
+ }
40
+ }
41
+ function extractSkillDescription(content) {
42
+ const normalized = content.replace(/\r\n/g, '\n');
43
+ const simple = /^description:\s*['"]?(.+?)['"]?\s*$/m.exec(normalized);
44
+ if (simple?.[1]) {
45
+ return simple[1].trim();
46
+ }
47
+ const folded = /^description:\s*>\s*\n((?:[ \t]+.+\n?)+)/m.exec(normalized);
48
+ return folded?.[1]
49
+ ? folded[1].split('\n').map((line) => line.trim()).filter(Boolean).join(' ')
50
+ : '';
51
+ }
package/dist/constants.js CHANGED
@@ -11,7 +11,7 @@ exports.LIVE_DIR_NAME = 'live';
11
11
  exports.CONFIG_ROOT_OVERRIDE_ENV = 'AGENT_RUN_CONFIG_ROOT_OVERRIDE';
12
12
  exports.CONFIG_DIR_ENV = 'AGENT_CONFIG_DIR';
13
13
  exports.VERBOSE_ENV = 'AGENT_RUN_VERBOSE';
14
- exports.PACKAGE_VERSION = '0.99.29';
14
+ exports.PACKAGE_VERSION = '0.99.30';
15
15
  exports.UNEXPANDED_TEMPLATE_RE = /\{\{[^}]+\}\}|\{%[^%]+%\}/;
16
16
  exports.LOCAL_AI_FILE_NAMES = new Set([
17
17
  'AGENTS.md',
@@ -19,10 +19,11 @@ exports.LOCAL_AI_FILE_NAMES = new Set([
19
19
  'AGENTS.override.md',
20
20
  'CLAUDE.md',
21
21
  'CLAUDE.local.md',
22
+ 'GEMINI.md',
22
23
  '.mcp.json',
23
24
  'codex.md'
24
25
  ]);
25
- exports.LOCAL_AI_DIRECTORY_NAMES = new Set(['.agents', '.claude', '.codex']);
26
+ exports.LOCAL_AI_DIRECTORY_NAMES = new Set(['.agents', '.claude', '.codex', '.gemini', '.grok']);
26
27
  exports.GENERATED_GITIGNORE_ENTRIES = [
27
28
  '# Generated agent-run live profiles',
28
29
  '**/live/',
@@ -55,6 +56,8 @@ exports.REQUIRED_GLOBAL_TEMPLATES = [
55
56
  'global/snippets/verification.md.njk',
56
57
  'global/tool-templates/codex-config.toml.njk',
57
58
  'global/tool-templates/claude-settings.json.njk',
59
+ 'global/tool-templates/gemini-settings.json.njk',
60
+ 'global/tool-templates/grok-config.toml.njk',
58
61
  'global/skills/commit-workflow/SKILL.md.njk',
59
62
  'global/skills/github-release/SKILL.md.njk',
60
63
  'global/skills/release-package-check/SKILL.md.njk',
package/dist/defaults.js CHANGED
@@ -8,6 +8,8 @@ exports.defaultGlobalTemplates = defaultGlobalTemplates;
8
8
  exports.defaultToolInstructionsTemplate = defaultToolInstructionsTemplate;
9
9
  exports.defaultCodexConfigContent = defaultCodexConfigContent;
10
10
  exports.defaultClaudeSettingsContent = defaultClaudeSettingsContent;
11
+ exports.defaultGeminiSettingsContent = defaultGeminiSettingsContent;
12
+ exports.defaultGrokConfigContent = defaultGrokConfigContent;
11
13
  exports.legacyDefaultClaudeSettingsContent = legacyDefaultClaudeSettingsContent;
12
14
  function defaultManifest(profile) {
13
15
  return {
@@ -22,8 +24,11 @@ function defaultManifest(profile) {
22
24
  },
23
25
  tools: {
24
26
  codex: true,
25
- claude: true
27
+ claude: true,
28
+ gemini: true,
29
+ grok: true
26
30
  },
31
+ mcp: { servers: {} },
27
32
  checks: ['agent-run check .', 'pnpm run cleanbuild'],
28
33
  guardrails: {
29
34
  blockGitWrite: true,
@@ -95,6 +100,8 @@ function defaultGlobalTemplates() {
95
100
  ['global/snippets/verification.md.njk', defaultVerificationSnippet()],
96
101
  ['global/tool-templates/codex-config.toml.njk', defaultCodexConfigTemplate()],
97
102
  ['global/tool-templates/claude-settings.json.njk', defaultClaudeSettingsTemplate()],
103
+ ['global/tool-templates/gemini-settings.json.njk', defaultGeminiSettingsTemplate()],
104
+ ['global/tool-templates/grok-config.toml.njk', defaultGrokConfigTemplate()],
98
105
  ['global/skills/commit-workflow/SKILL.md.njk', defaultCommitWorkflowSkill()],
99
106
  ['global/skills/github-release/SKILL.md.njk', defaultGithubReleaseSkill()],
100
107
  ['global/skills/release-package-check/SKILL.md.njk', defaultReleasePackageCheckSkill()],
@@ -138,7 +145,7 @@ function defaultNoAiFilesSnippet() {
138
145
  return [
139
146
  '## Agent File Storage',
140
147
  '',
141
- 'Do not create AGENTS.md, CLAUDE.md, .agents, .claude, .codex, or other agent runtime files inside the project repository.',
148
+ 'Do not create AGENTS.md, CLAUDE.md, GEMINI.md, .agents, .claude, .codex, .gemini, .grok, or other agent runtime files inside the project repository.',
142
149
  'Generated agent-only files belong under {{ paths.liveDir }}.',
143
150
  'Source config files belong under {{ profileDir }}.',
144
151
  ''
@@ -211,7 +218,7 @@ function defaultReleasePackageCheckSkill() {
211
218
  '- The year in the top `CHANGES` version line appears in `package.json.copyright`.',
212
219
  '- The git `origin` remote matches `<org>/<repo>`.',
213
220
  '- The current branch has an upstream configured and is in sync with it after fetching.',
214
- '- The repository does not contain agent files or directories such as `AGENTS.md`, `CLAUDE.md`, `codex.md`, `.claude`, or `.codex`.',
221
+ '- The repository does not contain agent files or directories such as `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, `codex.md`, `.claude`, `.codex`, `.gemini`, or `.grok`.',
215
222
  '- The repository root has a lockfile: `pnpm-lock.yaml`, `package-lock.json`, `yarn.lock`, `bun.lockb`, or `bun.lock`.',
216
223
  '- Every GitHub Actions `uses:` reference is pinned to a full commit SHA.',
217
224
  '- The pinned `actions/setup-node` release is v6-compatible and workflows use `node-version: 24`.',
@@ -342,11 +349,11 @@ function defaultToolInstructionsTemplate(isClaude) {
342
349
  '',
343
350
  isClaude
344
351
  ? 'Claude receives {{ agentDir }}/CLAUDE.md through `--append-system-prompt-file` and loads generated skills as a local plugin.'
345
- : 'Codex loads AGENTS.md, config.toml, and skills natively from {{ paths.codexHomeDir }}.',
352
+ : 'Codex, Gemini, and Grok load this canonical AGENTS.md content from their private agent-run runtimes.',
346
353
  '',
347
354
  '## Mandatory Path Rule',
348
355
  '',
349
- 'Do not create AGENTS.md, CLAUDE.md, .agents, .claude, .codex, or AI-related files inside the project repository.',
356
+ 'Do not create AGENTS.md, CLAUDE.md, GEMINI.md, .agents, .claude, .codex, .gemini, .grok, or AI-related files inside the project repository.',
350
357
  'Generated agent-only files must be stored under the agent directory shown above.',
351
358
  'Source config files must be stored under the profile source directory shown above.',
352
359
  ''
@@ -394,6 +401,19 @@ function defaultClaudeSettingsTemplate() {
394
401
  '{% endif %}'
395
402
  ].join('\n');
396
403
  }
404
+ function defaultGeminiSettingsTemplate() {
405
+ return [
406
+ '{',
407
+ ' "general": {',
408
+ ' "enableAutoUpdate": true',
409
+ ' }',
410
+ '}',
411
+ ''
412
+ ].join('\n');
413
+ }
414
+ function defaultGrokConfigTemplate() {
415
+ return ['# Generated by agent-run. Do not edit directly.', ''].join('\n');
416
+ }
397
417
  function defaultCodexConfigContent(context) {
398
418
  return [
399
419
  '# Generated by agent-run. Do not edit directly.',
@@ -428,6 +448,12 @@ function defaultClaudeSettingsContent(context) {
428
448
  }
429
449
  return `${JSON.stringify(settings, null, 2)}\n`;
430
450
  }
451
+ function defaultGeminiSettingsContent(_context) {
452
+ return `${JSON.stringify({ general: { enableAutoUpdate: true } }, null, 2)}\n`;
453
+ }
454
+ function defaultGrokConfigContent() {
455
+ return '# Generated by agent-run. Do not edit directly.\n';
456
+ }
431
457
  function legacyDefaultClaudeSettingsContent(context, agentDir) {
432
458
  return `${JSON.stringify({
433
459
  env: {
package/dist/manifest.js CHANGED
@@ -100,6 +100,11 @@ function mergeManifest(base, override, allowProfile, inferredProfile) {
100
100
  agent: { ...base.agent, ...override.agent },
101
101
  skills: mergeSkills(base.skills, override.skills),
102
102
  tools: { ...base.tools, ...override.tools },
103
+ mcp: {
104
+ ...base.mcp,
105
+ ...override.mcp,
106
+ servers: { ...base.mcp?.servers, ...override.mcp?.servers }
107
+ },
103
108
  guardrails: { ...base.guardrails, ...override.guardrails },
104
109
  paths: { ...base.paths, ...override.paths }
105
110
  };
@@ -151,8 +156,11 @@ function normalizeManifest(manifest, profile) {
151
156
  },
152
157
  tools: {
153
158
  codex: manifest.tools?.codex ?? true,
154
- claude: manifest.tools?.claude ?? true
159
+ claude: manifest.tools?.claude ?? true,
160
+ gemini: manifest.tools?.gemini ?? true,
161
+ grok: manifest.tools?.grok ?? true
155
162
  },
163
+ mcpServers: normalizeMcpServers(manifest.mcp?.servers ?? {}),
156
164
  checks: manifest.checks ?? fallback.checks ?? [],
157
165
  guardrails: {
158
166
  blockGitWrite: manifest.guardrails?.blockGitWrite ?? true,
@@ -170,6 +178,34 @@ function normalizeManifest(manifest, profile) {
170
178
  }
171
179
  };
172
180
  }
181
+ function normalizeMcpServers(servers) {
182
+ const normalized = {};
183
+ for (const [name, server] of Object.entries(servers ?? {})) {
184
+ if (!/^[A-Za-z0-9._-]+$/.test(name)) {
185
+ throw new Error(`invalid MCP server name: ${name}`);
186
+ }
187
+ const transport = server.transport ?? (server.command ? 'stdio' : 'http');
188
+ if (transport === 'stdio') {
189
+ if (!server.command?.trim() || server.url !== undefined) {
190
+ throw new Error(`MCP server ${name} must set command and must not set url for stdio transport`);
191
+ }
192
+ }
193
+ else if (!server.url?.trim() || server.command !== undefined) {
194
+ throw new Error(`MCP server ${name} must set url and must not set command for ${transport} transport`);
195
+ }
196
+ normalized[name] = {
197
+ transport,
198
+ ...(server.command ? { command: server.command } : {}),
199
+ args: server.args ?? [],
200
+ ...(server.cwd ? { cwd: server.cwd } : {}),
201
+ env: server.env ?? {},
202
+ ...(server.url ? { url: server.url } : {}),
203
+ headers: server.headers ?? {},
204
+ enabled: server.enabled ?? true
205
+ };
206
+ }
207
+ return normalized;
208
+ }
173
209
  function normalizeInstalledSkills(names) {
174
210
  return [...new Set(names.map(normalizeSkillName))];
175
211
  }