#!/usr/bin/env node 'use strict'; const fs = require('fs'); const path = require('path'); const TOOL_NAME_MAP = new Map([ ['Read', 'read_file'], ['Write', 'write_file'], ['Edit', 'replace'], ['Bash', 'run_shell_command'], ['Grep', 'grep_search'], ['Glob', 'glob'], ['WebSearch', 'google_web_search'], ['WebFetch', 'web_fetch'], ]); function usage() { return [ 'Adapt ECC agent frontmatter for Gemini CLI.', '', 'Usage:', ' node scripts/gemini-adapt-agents.js [agents-dir]', '', 'Defaults to .gemini/agents under the current working directory.', 'Rewrites tools: to Gemini-compatible tool names and removes unsupported color: metadata.' ].join('\n'); } function parseArgs(argv) { if (argv.includes('--help') || argv.includes('-h')) { return { help: true }; } const positional = argv.filter(arg => !arg.startsWith('-')); if (positional.length > 1) { throw new Error('Expected at most one agents directory argument'); } return { help: false, agentsDir: path.resolve(positional[0] || path.join(process.cwd(), '.gemini', 'agents')), }; } function ensureDirectory(dirPath) { if (!fs.existsSync(dirPath)) { throw new Error(`Agents directory not found: ${dirPath}`); } if (!fs.statSync(dirPath).isDirectory()) { throw new Error(`Expected a directory: ${dirPath}`); } } function stripQuotes(value) { return value.trim().replace(/^['"]|['"]$/g, ''); } function parseToolList(line) { const match = line.match(/^(\s*tools\s*:\s*)\[(.*)\]\s*$/); if (!match) { return null; } const rawItems = match[2].trim(); if (!rawItems) { return []; } return rawItems .split(',') .map(part => stripQuotes(part)) .filter(Boolean); } function adaptToolName(toolName) { const mapped = TOOL_NAME_MAP.get(toolName); if (mapped) { return mapped; } if (toolName.startsWith('mcp__')) { return toolName .replace(/^mcp__/, 'mcp_') .replace(/__/g, '_') .replace(/[^A-Za-z0-9_]/g, '_') .toLowerCase(); } return toolName; } function formatToolLine(tools) { return `tools: [${tools.map(tool => JSON.stringify(tool)).join(', ')}]`; } function adaptFrontmatter(text) { const match = text.match(/^---\n([\s\S]*?)\n---(\n|$)/); if (!match) { return { text, changed: false }; } let changed = false; const updatedLines = []; for (const line of match[1].split('\n')) { if (/^\s*color\s*:/.test(line)) { changed = true; continue; } const tools = parseToolList(line); if (tools) { const adaptedTools = []; const seen = new Set(); for (const tool of tools.map(adaptToolName)) { if (seen.has(tool)) { continue; } seen.add(tool); adaptedTools.push(tool); } const updatedLine = formatToolLine(adaptedTools); if (updatedLine !== line) { changed = true; } updatedLines.push(updatedLine); continue; } updatedLines.push(line); } if (!changed) { return { text, changed: false }; } return { text: `---\n${updatedLines.join('\n')}\n---${match[2]}${text.slice(match[0].length)}`, changed: true, }; } function adaptAgents(dirPath) { ensureDirectory(dirPath); let updated = 0; let unchanged = 0; for (const entry of fs.readdirSync(dirPath, { withFileTypes: true })) { if (!entry.isFile() || !entry.name.endsWith('.md')) { continue; } const filePath = path.join(dirPath, entry.name); const original = fs.readFileSync(filePath, 'utf8'); const adapted = adaptFrontmatter(original); if (adapted.changed) { fs.writeFileSync(filePath, adapted.text); updated += 1; } else { unchanged += 1; } } return { updated, unchanged }; } function main() { const options = parseArgs(process.argv.slice(2)); if (options.help) { console.log(usage()); return; } const result = adaptAgents(options.agentsDir); console.log(`Updated ${result.updated} agent file(s); ${result.unchanged} already compatible`); } try { main(); } catch (error) { console.error(error.message); process.exit(1); }