@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,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
+ }
@@ -3,14 +3,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ensureConfigRootLayout = ensureConfigRootLayout;
4
4
  exports.ensureConfigRootGitignore = ensureConfigRootGitignore;
5
5
  exports.ensureDefaultGlobalTemplates = ensureDefaultGlobalTemplates;
6
+ exports.ensurePortableSystemdFiles = ensurePortableSystemdFiles;
6
7
  exports.migrateOldTemplates = migrateOldTemplates;
7
8
  exports.findLegacyProfileDirs = findLegacyProfileDirs;
8
9
  exports.findProfileDirs = findProfileDirs;
9
10
  exports.convertLegacyTemplateVars = convertLegacyTemplateVars;
10
11
  exports.migrateCodexRuntimeFiles = migrateCodexRuntimeFiles;
11
12
  exports.migrateLooseFiles = migrateLooseFiles;
13
+ exports.describeLegacyProfileLayout = describeLegacyProfileLayout;
14
+ exports.migrateProfileLayout = migrateProfileLayout;
12
15
  exports.starterConfigRootPath = starterConfigRootPath;
13
16
  exports.copySkeletonTree = copySkeletonTree;
17
+ const childProcess = require("child_process");
14
18
  const fs = require("fs");
15
19
  const path = require("path");
16
20
  const constants_1 = require("./constants");
@@ -68,6 +72,25 @@ function ensureDefaultGlobalTemplates(configRoot) {
68
72
  }
69
73
  }
70
74
  }
75
+ function ensurePortableSystemdFiles(configRoot) {
76
+ const packageRoot = path.resolve(__dirname, '..');
77
+ const files = new Map([
78
+ ['install-systemd-jobs.sh', { source: 'scripts/install-systemd-jobs.sh', mode: 0o755 }],
79
+ ['update-ai-tools.sh', { source: 'scripts/update-ai-tools.sh', mode: 0o755 }],
80
+ ['ai-tools-update.service', { source: 'ops/systemd/ai-tools-update.service', mode: 0o644 }],
81
+ ['ai-tools-update.timer', { source: 'ops/systemd/ai-tools-update.timer', mode: 0o644 }]
82
+ ]);
83
+ for (const [targetName, asset] of files) {
84
+ const sourcePath = path.join(packageRoot, asset.source);
85
+ const targetPath = path.join(configRoot, targetName);
86
+ if (!fs.existsSync(sourcePath)) {
87
+ continue;
88
+ }
89
+ fs.copyFileSync(sourcePath, targetPath);
90
+ fs.chmodSync(targetPath, asset.mode);
91
+ (0, utils_1.verbose)(`updated ${targetPath}`);
92
+ }
93
+ }
71
94
  function migrateOldTemplates(configRoot) {
72
95
  const oldCodeTemplate = path.join(configRoot, 'templates', 'AGENTS-CODE.md');
73
96
  const newCodeTemplate = path.join(configRoot, 'global', 'agents', 'code.md.njk');
@@ -150,6 +173,232 @@ function migrateLooseFiles(agentDir, pattern, targetDirName) {
150
173
  }
151
174
  return moved;
152
175
  }
176
+ function describeLegacyProfileLayout(profileDir) {
177
+ const paths = [];
178
+ const legacyMemoryDir = path.join(profileDir, 'memory');
179
+ if (fs.existsSync(legacyMemoryDir)) {
180
+ paths.push(legacyMemoryDir);
181
+ }
182
+ for (const filePath of legacyProjectMemoryFiles(profileDir)) {
183
+ paths.push(filePath);
184
+ }
185
+ const legacyCodexHome = path.join(profileDir, 'memories', 'codex-home');
186
+ if (fs.existsSync(legacyCodexHome) || (0, utils_1.isSymlink)(legacyCodexHome)) {
187
+ paths.push(legacyCodexHome);
188
+ }
189
+ return [...new Set(paths)].sort();
190
+ }
191
+ function migrateProfileLayout(configRoot, profileDir, confirmGitMoves) {
192
+ const projectMoves = planProjectMemoryMoves(profileDir);
193
+ validateMoveTargets(projectMoves);
194
+ const git = findGitWorktree(configRoot);
195
+ const trackedMoves = git === null ? [] : projectMoves.filter((move) => isTrackedByGit(git, move.source));
196
+ if (git !== null && trackedMoves.length > 0 && !confirmGitMoves({ gitRoot: git.root, moves: trackedMoves })) {
197
+ throw new Error('profile layout migration declined; no files were moved');
198
+ }
199
+ let gitMoves = 0;
200
+ for (const move of projectMoves) {
201
+ fs.mkdirSync(path.dirname(move.target), { recursive: true });
202
+ if (git !== null && trackedMoves.includes(move)) {
203
+ runGitMove(git, move);
204
+ gitMoves += 1;
205
+ }
206
+ else {
207
+ fs.renameSync(move.source, move.target);
208
+ }
209
+ removeEmptyParents(path.dirname(move.source), profileDir);
210
+ (0, utils_1.verbose)(`moved project memory ${move.source} -> ${move.target}`);
211
+ }
212
+ removeEmptyParents(path.join(profileDir, 'memory'), profileDir);
213
+ removeEmptyParents(path.join(profileDir, 'memories'), profileDir);
214
+ const runtimeMoves = migrateLegacyCodexHome(profileDir);
215
+ return { gitMoves, projectMemoryMoves: projectMoves.length, runtimeMoves };
216
+ }
217
+ function planProjectMemoryMoves(profileDir) {
218
+ const targetDir = path.join(profileDir, 'notes', 'memory');
219
+ const moves = [];
220
+ const legacyMemoryDir = path.join(profileDir, 'memory');
221
+ for (const source of listTreeFiles(legacyMemoryDir)) {
222
+ moves.push({ source, target: path.join(targetDir, path.relative(legacyMemoryDir, source)) });
223
+ }
224
+ for (const source of legacyProjectMemoryFiles(profileDir)) {
225
+ moves.push({ source, target: path.join(targetDir, path.basename(source)) });
226
+ }
227
+ return moves.sort((left, right) => left.source.localeCompare(right.source));
228
+ }
229
+ function legacyProjectMemoryFiles(profileDir) {
230
+ const files = [];
231
+ for (const entry of safeReadDir(profileDir)) {
232
+ if (entry.isFile() && /^memory.*\.md$/i.test(entry.name)) {
233
+ files.push(path.join(profileDir, entry.name));
234
+ }
235
+ }
236
+ const legacyMemoriesDir = path.join(profileDir, 'memories');
237
+ for (const entry of safeReadDir(legacyMemoriesDir)) {
238
+ if (entry.isFile() && /\.md$/i.test(entry.name)) {
239
+ files.push(path.join(legacyMemoriesDir, entry.name));
240
+ }
241
+ }
242
+ return files.sort();
243
+ }
244
+ function safeReadDir(dir) {
245
+ try {
246
+ return fs.readdirSync(dir, { withFileTypes: true });
247
+ }
248
+ catch (error) {
249
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
250
+ return [];
251
+ }
252
+ throw error;
253
+ }
254
+ }
255
+ function listTreeFiles(root) {
256
+ const files = [];
257
+ for (const entry of safeReadDir(root)) {
258
+ const filePath = path.join(root, entry.name);
259
+ if (entry.isDirectory()) {
260
+ files.push(...listTreeFiles(filePath));
261
+ }
262
+ else if (entry.isFile() || entry.isSymbolicLink()) {
263
+ files.push(filePath);
264
+ }
265
+ }
266
+ return files;
267
+ }
268
+ function validateMoveTargets(moves) {
269
+ const targets = new Set();
270
+ for (const move of moves) {
271
+ const target = path.resolve(move.target);
272
+ if (targets.has(target) || fs.existsSync(target) || (0, utils_1.isSymlink)(target)) {
273
+ throw new Error(`cannot migrate project memory because the destination already exists: ${move.target}`);
274
+ }
275
+ targets.add(target);
276
+ }
277
+ }
278
+ function findGitWorktree(configRoot) {
279
+ const searchPath = process.env.AGENT_RUN_REAL_PATH ?? process.env.PATH ?? '';
280
+ const gitCommand = findCommandOnPath('git', searchPath);
281
+ if (gitCommand === null) {
282
+ return null;
283
+ }
284
+ const env = { ...process.env, PATH: searchPath };
285
+ const result = childProcess.spawnSync(gitCommand, ['-C', configRoot, 'rev-parse', '--show-toplevel'], {
286
+ encoding: 'utf8',
287
+ env,
288
+ shell: false
289
+ });
290
+ if (result.status !== 0) {
291
+ return null;
292
+ }
293
+ const root = result.stdout.trim();
294
+ return root ? { command: gitCommand, env, root: path.resolve(root) } : null;
295
+ }
296
+ function findCommandOnPath(command, searchPath) {
297
+ const extensions = process.platform === 'win32' ? ['.exe', '.cmd', '.bat', ''] : [''];
298
+ for (const dir of searchPath.split(path.delimiter).filter(Boolean)) {
299
+ for (const extension of extensions) {
300
+ const candidate = path.join(dir, `${command}${extension}`);
301
+ try {
302
+ fs.accessSync(candidate, process.platform === 'win32' ? fs.constants.F_OK : fs.constants.X_OK);
303
+ return candidate;
304
+ }
305
+ catch {
306
+ continue;
307
+ }
308
+ }
309
+ }
310
+ return null;
311
+ }
312
+ function isTrackedByGit(git, filePath) {
313
+ const relativePath = path.relative(git.root, filePath).replace(/\\/g, '/');
314
+ const result = childProcess.spawnSync(git.command, ['-C', git.root, '--literal-pathspecs', 'ls-files', '--error-unmatch', '--', relativePath], { encoding: 'utf8', env: git.env, shell: false });
315
+ return result.status === 0;
316
+ }
317
+ function runGitMove(git, move) {
318
+ const source = path.relative(git.root, move.source).replace(/\\/g, '/');
319
+ const target = path.relative(git.root, move.target).replace(/\\/g, '/');
320
+ const result = childProcess.spawnSync(git.command, ['-C', git.root, '--literal-pathspecs', 'mv', '--', source, target], {
321
+ encoding: 'utf8',
322
+ env: git.env,
323
+ shell: false
324
+ });
325
+ if (result.status !== 0) {
326
+ const detail = result.stderr.trim() || result.stdout.trim() || `git exited with status ${result.status}`;
327
+ throw new Error(`cannot migrate tracked project memory ${move.source}: ${detail}`);
328
+ }
329
+ }
330
+ function migrateLegacyCodexHome(profileDir) {
331
+ const source = path.join(profileDir, 'memories', 'codex-home');
332
+ if (!fs.existsSync(source) && !(0, utils_1.isSymlink)(source)) {
333
+ return 0;
334
+ }
335
+ const target = path.join(profileDir, constants_1.LIVE_DIR_NAME, 'memories', 'codex-home');
336
+ if (!fs.existsSync(target) && !(0, utils_1.isSymlink)(target)) {
337
+ fs.mkdirSync(path.dirname(target), { recursive: true });
338
+ fs.renameSync(source, target);
339
+ removeEmptyParents(path.dirname(source), profileDir);
340
+ (0, utils_1.verbose)(`moved Codex runtime ${source} -> ${target}`);
341
+ return 1;
342
+ }
343
+ const sourceIsDirectory = !(0, utils_1.isSymlink)(source) && fs.statSync(source).isDirectory();
344
+ const targetIsDirectory = !(0, utils_1.isSymlink)(target) && fs.statSync(target).isDirectory();
345
+ if (!sourceIsDirectory || !targetIsDirectory) {
346
+ const backupTarget = (0, utils_1.nextBackupPath)(target);
347
+ fs.renameSync(source, backupTarget);
348
+ removeEmptyParents(path.dirname(source), profileDir);
349
+ (0, utils_1.verbose)(`moved Codex runtime ${source} -> ${backupTarget}`);
350
+ return 1;
351
+ }
352
+ const moved = mergeRuntimeTree(source, target);
353
+ removeEmptyParents(path.dirname(source), profileDir);
354
+ return moved;
355
+ }
356
+ function mergeRuntimeTree(sourceDir, targetDir) {
357
+ let moved = 0;
358
+ fs.mkdirSync(targetDir, { recursive: true });
359
+ for (const entry of safeReadDir(sourceDir)) {
360
+ const source = path.join(sourceDir, entry.name);
361
+ let target = path.join(targetDir, entry.name);
362
+ if (entry.isDirectory() && !entry.isSymbolicLink()) {
363
+ const targetIsDirectory = fs.existsSync(target) && !(0, utils_1.isSymlink)(target) && fs.statSync(target).isDirectory();
364
+ if ((fs.existsSync(target) || (0, utils_1.isSymlink)(target)) && !targetIsDirectory) {
365
+ target = (0, utils_1.nextBackupPath)(target);
366
+ }
367
+ moved += mergeRuntimeTree(source, target);
368
+ continue;
369
+ }
370
+ const resolvedTarget = fs.existsSync(target) || (0, utils_1.isSymlink)(target) ? (0, utils_1.nextBackupPath)(target) : target;
371
+ fs.mkdirSync(path.dirname(resolvedTarget), { recursive: true });
372
+ fs.renameSync(source, resolvedTarget);
373
+ (0, utils_1.verbose)(`moved Codex runtime ${source} -> ${resolvedTarget}`);
374
+ moved += 1;
375
+ }
376
+ try {
377
+ fs.rmdirSync(sourceDir);
378
+ }
379
+ catch (error) {
380
+ if (!(error instanceof Error && 'code' in error && error.code === 'ENOENT')) {
381
+ throw error;
382
+ }
383
+ }
384
+ return moved;
385
+ }
386
+ function removeEmptyParents(startDir, stopDir) {
387
+ let dir = path.resolve(startDir);
388
+ const stop = path.resolve(stopDir);
389
+ while (dir !== stop && (0, utils_1.isSamePathOrDescendant)(dir, stop)) {
390
+ try {
391
+ fs.rmdirSync(dir);
392
+ }
393
+ catch (error) {
394
+ if (error instanceof Error && 'code' in error && ['ENOENT', 'ENOTEMPTY'].includes(String(error.code))) {
395
+ return;
396
+ }
397
+ throw error;
398
+ }
399
+ dir = path.dirname(dir);
400
+ }
401
+ }
153
402
  function starterConfigRootPath() {
154
403
  return path.resolve(__dirname, '..', 'examples', 'basic-config', 'agent-config');
155
404
  }
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.28';
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
@@ -3,10 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.defaultManifest = defaultManifest;
4
4
  exports.stringifyRootDefaults = stringifyRootDefaults;
5
5
  exports.defaultLocalTemplate = defaultLocalTemplate;
6
+ exports.defaultProjectMemoryIndex = defaultProjectMemoryIndex;
6
7
  exports.defaultGlobalTemplates = defaultGlobalTemplates;
7
8
  exports.defaultToolInstructionsTemplate = defaultToolInstructionsTemplate;
8
9
  exports.defaultCodexConfigContent = defaultCodexConfigContent;
9
10
  exports.defaultClaudeSettingsContent = defaultClaudeSettingsContent;
11
+ exports.defaultGeminiSettingsContent = defaultGeminiSettingsContent;
12
+ exports.defaultGrokConfigContent = defaultGrokConfigContent;
10
13
  exports.legacyDefaultClaudeSettingsContent = legacyDefaultClaudeSettingsContent;
11
14
  function defaultManifest(profile) {
12
15
  return {
@@ -21,8 +24,11 @@ function defaultManifest(profile) {
21
24
  },
22
25
  tools: {
23
26
  codex: true,
24
- claude: true
27
+ claude: true,
28
+ gemini: true,
29
+ grok: true
25
30
  },
31
+ mcp: { servers: {} },
26
32
  checks: ['agent-run check .', 'pnpm run cleanbuild'],
27
33
  guardrails: {
28
34
  blockGitWrite: true,
@@ -34,7 +40,8 @@ function defaultManifest(profile) {
34
40
  changesFile: '{{ projectRoot }}/CHANGES',
35
41
  reviewDir: '{{ profileDir }}/reviews',
36
42
  reviewFile: '{{ profileDir }}/reviews/REVIEW-{{ date }}.md',
37
- memoriesDir: '{{ agentDir }}/memories'
43
+ memoriesDir: '{{ agentDir }}/memories',
44
+ projectMemoryDir: '{{ profileDir }}/notes/memory'
38
45
  }
39
46
  };
40
47
  }
@@ -71,6 +78,19 @@ function defaultLocalTemplate() {
71
78
  ''
72
79
  ].join('\n');
73
80
  }
81
+ function defaultProjectMemoryIndex() {
82
+ return [
83
+ '# Project memory',
84
+ '',
85
+ 'This directory contains durable notes for this project across agent sessions and machines.',
86
+ 'Keep the notes short, factual, and free of secrets. Link additional topic files from this index.',
87
+ '',
88
+ '## Files',
89
+ '',
90
+ '- Add project memory files here as they are needed.',
91
+ ''
92
+ ].join('\n');
93
+ }
74
94
  function defaultGlobalTemplates() {
75
95
  return new Map([
76
96
  ['global/agents/code.md.njk', defaultCodeAgentTemplate()],
@@ -80,6 +100,8 @@ function defaultGlobalTemplates() {
80
100
  ['global/snippets/verification.md.njk', defaultVerificationSnippet()],
81
101
  ['global/tool-templates/codex-config.toml.njk', defaultCodexConfigTemplate()],
82
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()],
83
105
  ['global/skills/commit-workflow/SKILL.md.njk', defaultCommitWorkflowSkill()],
84
106
  ['global/skills/github-release/SKILL.md.njk', defaultGithubReleaseSkill()],
85
107
  ['global/skills/release-package-check/SKILL.md.njk', defaultReleasePackageCheckSkill()],
@@ -123,7 +145,7 @@ function defaultNoAiFilesSnippet() {
123
145
  return [
124
146
  '## Agent File Storage',
125
147
  '',
126
- '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.',
127
149
  'Generated agent-only files belong under {{ paths.liveDir }}.',
128
150
  'Source config files belong under {{ profileDir }}.',
129
151
  ''
@@ -196,7 +218,7 @@ function defaultReleasePackageCheckSkill() {
196
218
  '- The year in the top `CHANGES` version line appears in `package.json.copyright`.',
197
219
  '- The git `origin` remote matches `<org>/<repo>`.',
198
220
  '- The current branch has an upstream configured and is in sync with it after fetching.',
199
- '- 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`.',
200
222
  '- The repository root has a lockfile: `pnpm-lock.yaml`, `package-lock.json`, `yarn.lock`, `bun.lockb`, or `bun.lock`.',
201
223
  '- Every GitHub Actions `uses:` reference is pinned to a full commit SHA.',
202
224
  '- The pinned `actions/setup-node` release is v6-compatible and workflows use `node-version: 24`.',
@@ -293,6 +315,18 @@ function defaultToolInstructionsTemplate(isClaude) {
293
315
  '{{ paths.memoriesDir }}',
294
316
  '```',
295
317
  '',
318
+ 'Project memory directory:',
319
+ '',
320
+ '```text',
321
+ '{{ paths.projectMemoryDir }}',
322
+ '```',
323
+ '',
324
+ 'Native Codex memory directory:',
325
+ '',
326
+ '```text',
327
+ '{{ paths.nativeMemoryDir }}',
328
+ '```',
329
+ '',
296
330
  'Global memory directory:',
297
331
  '',
298
332
  '```text',
@@ -315,11 +349,11 @@ function defaultToolInstructionsTemplate(isClaude) {
315
349
  '',
316
350
  isClaude
317
351
  ? 'Claude receives {{ agentDir }}/CLAUDE.md through `--append-system-prompt-file` and loads generated skills as a local plugin.'
318
- : '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.',
319
353
  '',
320
354
  '## Mandatory Path Rule',
321
355
  '',
322
- '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.',
323
357
  'Generated agent-only files must be stored under the agent directory shown above.',
324
358
  'Source config files must be stored under the profile source directory shown above.',
325
359
  ''
@@ -337,7 +371,8 @@ function defaultCodexConfigTemplate() {
337
371
  'writable_roots = [',
338
372
  ' {{ projectRoot | dump }},',
339
373
  ' {{ paths.reviewDir | dump }},',
340
- ' {{ paths.memoriesDir | dump }}',
374
+ ' {{ paths.memoriesDir | dump }},',
375
+ ' {{ paths.projectMemoryDir | dump }}',
341
376
  ']',
342
377
  'network_access = false',
343
378
  ''
@@ -349,6 +384,7 @@ function defaultClaudeSettingsTemplate() {
349
384
  ' "AGENT_DIR": {{ agentDir | dump }},',
350
385
  ' "AGENT_PROFILE_DIR": {{ profileDir | dump }},',
351
386
  ' "AGENT_RUN_PROJECT_ROOT": {{ projectRoot | dump }},',
387
+ ' "AGENT_PROJECT_MEMORY_DIR": {{ paths.projectMemoryDir | dump }},',
352
388
  ' "AGENT_GLOBAL_MEMORY_DIR": {{ paths.globalMemoryDir | dump }}',
353
389
  ' }'
354
390
  ].join('\n');
@@ -365,6 +401,19 @@ function defaultClaudeSettingsTemplate() {
365
401
  '{% endif %}'
366
402
  ].join('\n');
367
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
+ }
368
417
  function defaultCodexConfigContent(context) {
369
418
  return [
370
419
  '# Generated by agent-run. Do not edit directly.',
@@ -377,7 +426,8 @@ function defaultCodexConfigContent(context) {
377
426
  'writable_roots = [',
378
427
  ` ${JSON.stringify(context.projectRoot)},`,
379
428
  ` ${JSON.stringify(context.paths.reviewDir)},`,
380
- ` ${JSON.stringify(context.paths.memoriesDir)}`,
429
+ ` ${JSON.stringify(context.paths.memoriesDir)},`,
430
+ ` ${JSON.stringify(context.paths.projectMemoryDir)}`,
381
431
  ']',
382
432
  'network_access = false',
383
433
  ''
@@ -389,6 +439,7 @@ function defaultClaudeSettingsContent(context) {
389
439
  AGENT_DIR: context.agentDir,
390
440
  AGENT_PROFILE_DIR: context.profileDir,
391
441
  AGENT_RUN_PROJECT_ROOT: context.projectRoot,
442
+ AGENT_PROJECT_MEMORY_DIR: context.paths.projectMemoryDir,
392
443
  AGENT_GLOBAL_MEMORY_DIR: context.paths.globalMemoryDir
393
444
  }
394
445
  };
@@ -397,6 +448,12 @@ function defaultClaudeSettingsContent(context) {
397
448
  }
398
449
  return `${JSON.stringify(settings, null, 2)}\n`;
399
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
+ }
400
457
  function legacyDefaultClaudeSettingsContent(context, agentDir) {
401
458
  return `${JSON.stringify({
402
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,
@@ -165,10 +173,39 @@ function normalizeManifest(manifest, profile) {
165
173
  reviewDir: normalizeLegacyReviewPath(paths.reviewDir ?? fallbackPaths.reviewDir ?? '{{ profileDir }}/reviews'),
166
174
  reviewFile: normalizeLegacyReviewPath(paths.reviewFile ?? fallbackPaths.reviewFile ?? '{{ profileDir }}/reviews/REVIEW-{{ date }}.md'),
167
175
  reviewConsolidatedFile: paths.reviewConsolidatedFile ?? fallbackPaths.reviewConsolidatedFile,
168
- memoriesDir: paths.memoriesDir ?? fallbackPaths.memoriesDir ?? '{{ agentDir }}/memories'
176
+ memoriesDir: paths.memoriesDir ?? fallbackPaths.memoriesDir ?? '{{ agentDir }}/memories',
177
+ projectMemoryDir: paths.projectMemoryDir ?? fallbackPaths.projectMemoryDir ?? '{{ profileDir }}/notes/memory'
169
178
  }
170
179
  };
171
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
+ }
172
209
  function normalizeInstalledSkills(names) {
173
210
  return [...new Set(names.map(normalizeSkillName))];
174
211
  }