Imported from amtiYo/agents (
AGENTS.md). Install upstream withnpx skills add amtiYo/agents. Copyright stays with the author.
AGENTS.md
Instructions for AI coding agents working on the @agents-dev/cli project.
Project Overview
@agents-dev/cli is a CLI tool that provides a practical standard layer for multi-LLM development. It solves the configuration fragmentation problem by syncing MCP servers, skills and instructions across 18 AI coding tools from a single source of truth, and packages the result as an Agent Plugins bundle.
Key Principle: One .agents/ folder syncs to all tools. Add an MCP server once, it appears everywhere.
Tech Stack
- Language: TypeScript (strict mode)
- Runtime: Node.js 20.12+
- Build: tsc (TypeScript compiler)
- Tests: Vitest
- CLI Framework: Commander.js
- Prompts: @clack/prompts
- Package Manager: npm
Project Structure
agents/
├── src/
│ ├── cli.ts # CLI entry point
│ ├── commands/ # Command implementations
│ │ ├── start.ts # Setup wizard
│ │ ├── sync.ts # Config sync
│ │ ├── mcp-add.ts # Add MCP server
│ │ └── ...
│ ├── core/ # Core logic
│ │ ├── sync.ts # Sync orchestration
│ │ ├── mcp.ts # MCP management
│ │ ├── renderers.ts # Tool-specific config generators
│ │ ├── trust.ts # Codex trust management
│ │ ├── ui.ts # CLI output helpers (colors, spinners)
│ │ └── ...
│ ├── integrations/ # Tool integrations
│ │ ├── codex.ts
│ │ ├── claude.ts
│ │ ├── cursor.ts
│ │ └── ...
│ └── types.ts # TypeScript types
├── tests/ # Test files
│ ├── *.test.ts # Unit tests
│ └── *.integration.test.ts # Integration tests
├── templates/ # Scaffold templates
│ └── agents/ # .agents/ directory templates
├── docs/ # Public documentation
└── bin/agents # CLI wrapper script
Development Workflow
Setup
npm install
npm run build
npm link
Development Commands
npm run dev -- <command> # Run CLI in dev mode
npm run build # Compile TypeScript
npm test # Run all tests
npm test tests/sync.test.ts # Run specific test
npm test -- --watch # Watch mode
npm run lint # Run ESLint
Testing
- Unit tests: Test individual functions in isolation
- Integration tests: Test full command flows with temp directories
- Use
mkdtempfor temporary test directories - Always clean up in
afterEachhooks - Mock external CLI calls when appropriate
Code Style
- TypeScript strict mode is enabled
- Follow existing patterns and conventions
- Use descriptive variable names
- Add JSDoc comments for exported functions
- Keep functions focused (single responsibility)
- Prefer pure functions where possible
Key Concepts
1. Project Structure (core/project.ts)
Projects have:
.agents/agents.json— MCP servers, shared config.agents/local.json— Secrets (gitignored).agents/skills/— Reusable workflows.agents/generated/— Auto-generated files (gitignored)AGENTS.md— Instructions for all tools
2. Sync Process (core/sync.ts)
- Read
.agents/agents.jsonand.agents/local.json - Merge configs (local overrides shared)
- Generate tool-specific configs via renderers
- Write atomically (temp file + rename)
- Acquire sync lock to prevent race conditions
3. Renderers (core/renderers.ts)
Each tool has a renderer that converts .agents/agents.json to tool-specific format:
- Codex: TOML (
.codex/config.toml) - Claude Code: JSON (
.mcp.json, project scope, shared with Copilot CLI) - Gemini: JSON (
.gemini/settings.json) - Cursor: JSON (
.cursor/mcp.json) - Copilot VS Code: JSON (
.vscode/mcp.json) - Antigravity: JSON (
.agents/mcp_config.json) - Grok Build: TOML (
.grok/config.toml), same dialect as Codex with aheaderstable - Amp: JSON (
.amp/settings.json, keyamp.mcpServers) - Droid / Devin: JSON (
.factory/mcp.json,.devin/mcp_config.json) - Kilo: JSONC (
.kilo/kilo.jsonc, keymcp) - Zed: JSONC (
.zed/settings.json, keycontext_servers) - Goose: YAML (
~/.config/goose/config.yaml, keyextensions)
Two renderers are shared: Codex and Grok use renderMcpToml with different options,
OpenCode and Kilo use the same local/remote shape.
4. MCP Server Management (core/mcp.ts)
MCP servers have:
- Transport:
stdio(command-based) orhttp/sse(URL-based).sseis deprecated by the MCP 2026-07-28 specification and flagged byagents doctor. - Config: command, args, env, headers, cwd, plus the optional
timeout,connectTimeout,tools,disabledTools,oauth,headersHelper,bearerTokenEnvVarandenvFile, each rendered only where the tool supports it - Secrets: Stored in
.agents/local.json, never exported into a plugin - Validation: Keys must be shell-safe (env) or HTTP token format (headers)
- Profiles:
profilesandactiveProfileselect a subset of servers for a sync
5. Trust Management (core/trust.ts)
Codex ignores a project-local .codex/config.toml unless the project is trusted, and
there is no codex trust command to do it. Trust lives in the [projects."<path>"]
section of the global ~/.codex/config.toml, which we edit in place:
- Read the section with a line scan, so a syntax error elsewhere does not hide it
- Refuse to touch a file Codex itself cannot parse;
agents doctorreports the error - Refuse to append when the project is recorded in another valid TOML shape, which would create a duplicate key and break the file
- Never reserialize the whole config: it carries comments and settings we do not own
6. File Safety (core/fs.ts)
Critical operations use atomic writes:
- Write to temp file
- Rename to target (atomic on most filesystems)
- Prevents data corruption if process crashes
Common Tasks
Adding a New Command
- Create
src/commands/your-command.ts - Export async function matching command signature
- Register in
src/cli.tswith Commander - Add tests in
tests/your-command.test.ts - Update README.md command reference
- Update CHANGELOG.md
Adding a New Tool Integration
Before writing code: confirm the config path and schema in the vendor's own documentation, and run the tool's CLI against a generated file if it is installed. Integrations verified that way are marked in the README table; the rest say so.
- Create
src/integrations/your-tool.ts - Implement integration interface (id, name, paths)
- Add renderer in
src/core/renderers.ts - Add the paths it needs in
src/core/paths.ts - Register in
src/integrations/registry.ts, including theconfigdescriptor: the path key, the format and the label.status,doctor,resetandstartread the descriptor, so this is the only place that list lives. A tool whose file depends on an option or the platform has no descriptor and is handled where that choice is made — add it toWITHOUT_DESCRIPTORintests/integration-registry.test.tswith the reason. - Add a sync hook in
src/integrations/syncHooks.ts - Add the skills bridge to
SKILL_BRIDGESinsrc/core/skills.ts, or setnativeSkillswhen the tool reads.agents/skillsitself - Add tests
- Update README supported tools table
Fixing a Bug
- Write a failing test that reproduces the bug
- Fix the bug
- Verify test passes
- Add edge case tests
- Update CHANGELOG.md (## Unreleased > ### Fixed)
Adding a Feature
- Discuss in GitHub issue first (if major)
- Write tests for new behavior
- Implement feature
- Update documentation
- Update CHANGELOG.md (## Unreleased > ### Added)
Important Patterns
Error Handling
// Throw descriptive errors
throw new Error(`MCP server "${name}" not found in .agents/agents.json`)
// User-facing errors should be clear and actionable
console.error(`Error: Invalid env key "${key}". Use format: [A-Za-z_][A-Za-z0-9_]*`)
process.exitCode = 1
File Operations
// Always use atomic writes for critical files
import { writeJsonAtomic } from './core/fs.js'
await writeJsonAtomic(path, data)
External CLI Calls
// Use spawnSync for external commands
import { spawnSync } from 'node:child_process'
const result = spawnSync('codex', ['trust', 'list'], {
encoding: 'utf-8',
stdio: ['ignore', 'pipe', 'pipe']
})
if (result.status !== 0) {
// Handle error
}
CLI Output (UI Module)
Use src/core/ui.ts for all user-facing output:
import { ui } from './core/ui.js'
// Status messages
ui.success('Config synced')
ui.error('Failed to connect')
ui.warning('Server not responding')
ui.info('Checking configuration...')
// Formatted output
ui.keyValue('Status', 'connected')
ui.list(['item1', 'item2'])
ui.section('MCP Servers')
ui.hint('Run `agents sync` to apply changes')
// Spinners for async operations
const result = await ui.spin('Syncing...', async () => {
return await doAsyncWork()
})
// Context-aware (respects --json, --quiet, NO_COLOR)
ui.json(data) // Only outputs if --json flag is set
Rules:
- Never use raw
console.log()for user-facing output - Use
ui.spin()for any operation > 500ms - Colors are minimal: green (success), red (error), yellow (warning), cyan (info)
- Unicode symbols have ASCII fallbacks for non-Unicode terminals
Temp Directories in Tests
import { mkdtemp, rm } from 'node:fs/promises'
import { join } from 'node:path'
import { tmpdir } from 'node:os'
describe('my test', () => {
const tempDirs: string[] = []
afterEach(async () => {
await Promise.all(tempDirs.map(dir => rm(dir, { recursive: true, force: true })))
tempDirs.length = 0
})
it('does something', async () => {
const dir = await mkdtemp(join(tmpdir(), 'agents-test-'))
tempDirs.push(dir)
// test logic
})
})
Testing Guidelines
What to Test
- ✅ All public functions
- ✅ Error conditions
- ✅ Edge cases (empty arrays, null values, etc.)
- ✅ Integration flows (full command execution)
- ✅ File I/O (read/write operations)
- ❌ Don't test third-party libraries
Test Structure
describe('feature', () => {
it('should do X when Y', () => {
// Arrange
const input = { ... }
// Act
const result = myFunction(input)
// Assert
expect(result).toBe(expected)
})
})
Flaky Tests
If a test is flaky (timing-dependent):
- Increase timeout:
it('test', { timeout: 10000 }, async () => { ... }) - Add retry logic if appropriate
- Mock external dependencies that cause flakiness
Release Process
(For maintainers only - see .docs-internal/PUBLISHING.md)
- Update CHANGELOG.md
- Bump version:
npm version patch|minor|major - Build and test:
npm run build && npm test - Publish:
npm publish --access public - Create GitHub release with tag
Common Pitfalls
1. Forgetting Atomic Writes
Wrong:
await writeFile(path, JSON.stringify(data))
Right:
await writeJsonAtomic(path, data)
2. Not Cleaning Up Temp Dirs in Tests
Always use afterEach to clean up, or tests will leave garbage in /tmp.
3. Hardcoding Paths
Wrong:
const codexConfig = '/Users/me/.codex/config.toml'
Right:
import { getCodexConfigPath } from './core/paths.js'
const codexConfig = getCodexConfigPath()
4. Not Validating User Input
Always validate:
- MCP server names (no spaces, special chars)
- Env keys (shell-safe format)
- Header keys (HTTP token format)
- File paths (absolute, not relative)
5. Assuming External CLIs Are Available
Check if CLI is installed before calling:
const result = spawnSync('codex', ['--version'], { encoding: 'utf-8' })
if (result.status !== 0) {
console.warn('Codex CLI not found. Skipping Codex integration.')
return
}
Security Considerations
- Secrets are stored in
.agents/local.json(gitignored) - Never log secrets to console
- Use
--secret-envand--secret-headerflags for sensitive values - Validate all user input (especially paths and shell commands)
- Don't execute arbitrary code from config files
Performance Tips
- Use
sync --checkfor drift detection (faster than full sync) - Use
status --fastto skip slow external CLI probes - Cache external CLI results when appropriate
- Use
Promise.all()for parallel operations
Documentation Standards
When updating docs:
- Keep README.md concise (high-level overview)
- Put details in
docs/files - Use code examples for clarity
- Include expected output
- Add troubleshooting sections
Git Workflow
# Create feature branch
git checkout -b feature/my-feature
# Make changes, commit
git add .
git commit -m "feat: add new feature"
# Push and create PR
git push origin feature/my-feature
Commit Message Format:
feat:— New featurefix:— Bug fixdocs:— Documentationtest:— Testsrefactor:— Code refactoringchore:— Maintenance
Questions?
- Check existing code for patterns
- Read tests for examples
- Ask in GitHub Discussions
- Open an issue if stuck
Key Files Reference
- Entry point:
src/cli.ts - Sync logic:
src/core/sync.ts - MCP management:
src/core/mcp.ts,src/core/mcpCrud.ts - Renderers:
src/core/renderers.ts - Project MCP (.mcp.json):
src/core/projectMcp.ts - Agent Plugins:
src/core/agentPlugin.ts - MCP client for budget:
src/core/mcpProbe.ts - Types:
src/types.ts - File I/O:
src/core/fs.ts - Paths:
src/core/paths.ts - UI helpers:
src/core/ui.ts
Remember: The goal is to make multi-LLM development simple. Every feature should reduce friction, not add complexity.