Generate tool-specific AI config files from shared templates. Write your AI instructions, skills, agents, hooks, and MCP server configs once — generate config for Claude Code, GitHub Copilot, Cursor, and OpenAI Codex automatically.
Table of Contents
- Why
- Install
- Quick Start
- Template Structure
- Writing Templates
- Per-Target Overrides
- EJS Template Variables
- Configuration
- CLI Reference
- Output Paths
- Complete Template Reference
- Programmatic API
- Adding a New Target
Why
AI coding tools each have their own config formats stored in .claude/, .github/, .cursor/, .codex/ (plus root AGENTS.md and .agents/skills/ for Codex). Teams want shared AI config but each developer may use a different tool. This CLI generates target-specific config files from shared templates in .universal-ai-config/, so the tool-specific folders can be gitignored and each dev generates only what they need.
Install & Run
npm install -D universal-ai-config npm exec uac <command>
Non-JS projects: Use
npx universal-ai-configto run commands without installing, e.g.npx universal-ai-config generate -t claude.
Quick Start
# Scaffold template directory with meta-instructions and config uac init # Generate config for all targets uac generate # Generate for specific targets uac generate -t claude,cursor # Or generate just Codex uac generate -t codex # Preview without writing files uac generate --dry-run # Seed example templates (instruction, skill, agent, hook) uac seed examples
Template Structure
your-project/
├── universal-ai-config.config.ts # Shared config (committed)
├── universal-ai-config.overrides.config.ts # Personal overrides (gitignored)
└── .universal-ai-config/
├── instructions/ # Rules/instructions (markdown + EJS)
│ ├── react-patterns.md
│ └── security.md
├── skills/ # One folder per skill
│ └── test-generation/
│ └── SKILL.md
├── agents/ # Agent/subagent definitions
│ └── code-reviewer.md
├── hooks/ # Hook configs (JSON)
│ ├── security.json
│ └── quality.json
└── mcp/ # MCP server configs (JSON)
└── github.json
Writing Templates
Markdown templates use YAML frontmatter and EJS for conditional content. JSON templates (hooks, MCP) support {{variableName}} interpolation from config variables — when the entire value is a placeholder (e.g. "args": "{{myArgs}}"), it resolves to the raw typed value (arrays, objects, etc.), enabling environment-specific configs via overrides.ts.
Instructions
--- description: TypeScript specific rules globs: ["**/*.ts", "**/*.tsx"] --- Use strict TypeScript. Prefer interfaces over type aliases for object shapes.
--- description: Always applied coding standards alwaysApply: true --- Follow the project's coding standards at all times. <% if (target === 'claude') { -%> Use the Read tool to check existing patterns before creating new code. <% } else { -%> Check existing patterns before creating new code. <% } -%>
Frontmatter Fields
| Field | Type | Description |
|---|---|---|
description |
string |
What this instruction does |
globs |
string | string[] |
File patterns this applies to |
alwaysApply |
boolean |
Apply to all files regardless of context |
excludeAgent |
string |
Copilot-only: exclude from specific agent (e.g. "code-review") |
Codex routing: Codex consolidates instructions.
alwaysApply: true(or templates with no globs / leading-wildcard globs) → concatenated into rootAGENTS.md. Resolvable-prefix globs likepackages/frontend/**→<dir>/AGENTS.override.md. See the UAC Template Guide for full Codex caveats.
Skills
Skills live in subdirectories with a SKILL.md file:
--- name: test-generation description: Generate tests for code disableAutoInvocation: true userInvocable: /test --- Generate comprehensive tests using vitest for the given code.
Frontmatter Fields
| Field | Type | Description |
|---|---|---|
name |
string |
Skill identifier |
description |
string |
What this skill does |
disableAutoInvocation |
boolean |
Prevent automatic triggering |
userInvocable |
boolean | string |
Slash command trigger (Claude/Copilot) |
allowedTools |
string[] |
Tools this skill can use (Claude only) |
model |
string |
Model to use (Claude only) |
subagentType |
string |
Agent type (Claude only) |
forkContext |
boolean |
Fork context (Claude only) |
argumentHint |
string |
Hint for arguments (Claude/Copilot) |
license |
string |
License info (Copilot/Cursor/Codex) |
compatibility |
string |
Compatibility info (Copilot/Cursor/Codex) |
metadata |
object |
Extra metadata (Copilot/Cursor/Codex) |
hooks |
object |
Hook definitions (Claude only) |
version |
string |
Semver-style version (Codex SKILL.md spec; passthrough on others) |
author |
string |
Skill author attribution (Codex SKILL.md spec; passthrough on others) |
codex |
object |
Codex-only nested UI/policy/dependency metadata — drives agents/openai.yaml sidecar emission (see template guide) |
Agents
--- name: code-reviewer description: Reviews code for quality model: sonnet tools: ["read", "grep", "glob"] --- You are a code reviewer. Check for bugs and best practice violations.
Frontmatter Fields
| Field | Type | Description |
|---|---|---|
name |
string |
Agent identifier |
description |
string |
What this agent does |
model |
string |
Model to use |
tools |
string[] |
Available tools |
disallowedTools |
string[] |
Blocked tools (Claude only) |
permissionMode |
string |
Permission mode (Claude only) |
skills |
string[] |
Available skills (Claude only) |
hooks |
object |
Hook definitions (Claude only) |
memory |
string |
Memory scope (Claude only) |
target |
string |
Target description (Copilot only) |
mcpServers |
object |
MCP server config (Claude/Copilot/Codex) |
handoffs |
string[] |
Handoff targets (Copilot only) |
nicknameCandidates |
string[] |
Display nickname pool for spawned worker copies (Codex only) |
sandboxMode |
string |
Sandbox mode for Codex: read-only, workspace-write, danger-full-access (Codex only) |
Note: Cursor does not support agents. The CLI will warn and skip agent generation for the
cursortarget. Codex emits each agent as a standalone.codex/agents/<name>.tomlfile (body becomesdeveloper_instructions). For Codex, the existingeffortfield auto-maps tomodel_reasoning_effortwhen its value is in{minimal, low, medium, high, xhigh};maxdrops with a warning.
Hooks
Hooks are JSON files that define automated scripts running at lifecycle events (pre-tool-use, session start, etc.). Unlike other template types, hooks use pure JSON with no EJS templating. Multiple .json files in the hooks/ directory are deep-merged by event name.
{
"hooks": {
"preToolUse": [
{
"matcher": "Bash",
"command": ".hooks/block-rm.sh",
"timeout": 30
}
],
"postToolUse": [
{
"command": ".hooks/lint.sh",
"timeout": 60
}
]
}
}Handler Fields
| Field | Type | Required | Description |
|---|---|---|---|
command |
string |
yes | Shell command or script path |
args |
string[] |
no | Argument list (Claude/Codex). Codex flattens command + args into a single shell-escaped string. |
matcher |
string |
no | Regex pattern to filter when hook fires |
timeout |
number |
no | Timeout in seconds |
statusMessage |
string |
no | Status message displayed while hook runs (Claude/Codex) |
description |
string |
no | Human-readable description |
Codex: supports only
type: "command"handlers. Other handler types (http,mcp_tool,prompt,agent) drop with a warning. Codex hook events are a strict PascalCase subset of Claude's (see the event table below).
Universal Event Names
Use camelCase event names. The CLI maps them to each target's format and silently drops unsupported events.
| Universal | Claude | Cursor | Copilot | Codex |
|---|---|---|---|---|
sessionStart |
SessionStart |
sessionStart |
sessionStart |
SessionStart |
sessionEnd |
SessionEnd |
sessionEnd |
sessionEnd |
— |
userPromptSubmit |
UserPromptSubmit |
beforeSubmitPrompt |
userPromptSubmitted |
UserPromptSubmit |
preToolUse |
PreToolUse |
preToolUse |
preToolUse |
PreToolUse |
postToolUse |
PostToolUse |
postToolUse |
postToolUse |
PostToolUse |
postToolUseFailure |
PostToolUseFailure |
postToolUseFailure |
— | — |
stop |
Stop |
stop |
— | Stop |
subagentStart |
SubagentStart |
subagentStart |
— | — |
subagentStop |
SubagentStop |
subagentStop |
— | — |
preCompact |
PreCompact |
preCompact |
— | — |
permissionRequest |
PermissionRequest |
— | — | PermissionRequest |
notification |
Notification |
— | — | — |
errorOccurred |
— | — | errorOccurred |
— |
Cursor-specific events (beforeShellExecution, afterFileEdit, etc.) can be used directly — they pass through to Cursor and are dropped for other targets.
MCP Servers
MCP server configs define external tool servers available to AI assistants. Like hooks, they use JSON format with typed {{variable}} interpolation support. Multiple .json files in the mcp/ directory are merged by server name. Use exact-match placeholders like "args": "{{myArgs}}" to substitute entire typed values (arrays, objects) from config variables.
{
"mcpServers": {
"github": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_TOKEN": "${GITHUB_TOKEN}"
}
}
}
}Server Fields
| Field | Type | Required | Description |
|---|---|---|---|
command |
string |
yes* | Command to launch the server (stdio) |
args |
string[] |
no | Arguments for the command |
type |
string |
no | Transport type ("stdio" or "sse"). Codex: dropped — transport inferred from command vs url |
env |
Record<string, string> |
no | Environment variables |
url |
string |
yes* | Server URL (SSE/HTTP transport) |
headers |
Record<string, string> |
no | HTTP headers (SSE/HTTP transport). Codex: renamed to http_headers in TOML output |
*A server must have either command or url. If neither is present after per-target override resolution, the server is dropped for that target.
Codex-specific MCP fields (universal camelCase → snake_case in TOML): cwd, envVars, enabledTools, disabledTools, bearerTokenEnvVar, envHttpHeaders, startupTimeoutSec, startupTimeoutMs, toolTimeoutSec, enabled, required, oauthResource, scopes, experimentalEnvironment. See the UAC Template Guide for the full field reference. Codex's authentication uses bearerTokenEnvVar + oauthResource + scopes — Claude's oauth and Cursor's auth fields drop with a warning when emitting for Codex.
Server fields support per-target overrides (same syntax as hooks):
{
"mcpServers": {
"my-server": {
"command": { "default": "npx", "cursor": "node" },
"args": { "default": ["-y", "@my/server"], "cursor": ["./mcp-server.js"] }
}
}
}For Copilot, an optional inputs array provides interactive secret prompts. It's included in Copilot output only:
{
"mcpServers": { ... },
"inputs": [
{ "type": "promptString", "id": "github-token", "description": "GitHub PAT", "password": true }
]
}Per-Target Overrides
Any frontmatter field, hook handler field, or MCP server field can accept per-target values instead of a single value. If a field's value is an object where every key is a target name (claude, copilot, cursor) or default, it's resolved to the matching target's value during generation. If the target isn't listed, the default value is used. If neither is present, the field is omitted.
Frontmatter
--- description: claude: Use Claude Code conventions copilot: Use Copilot conventions cursor: Use Cursor conventions codex: Use Codex conventions tools: default: ["read", "grep", "glob"] claude: ["Read", "Grep", "Glob"] model: default: sonnet claude: opus copilot: gpt-4o codex: gpt-5.4 permissionMode: claude: acceptEdits sandboxMode: codex: workspace-write ---
When generating for Claude: tools: ["Read", "Grep", "Glob"], model: opus, permissionMode: acceptEdits. For Copilot: tools: ["read", "grep", "glob"] (default), model: gpt-4o, permissionMode omitted. For Cursor: tools: ["read", "grep", "glob"] (default), model: sonnet (default), permissionMode omitted. For Codex: model: gpt-5.4, sandboxMode: workspace-write, permissionMode dropped (Codex-incompatible), tools dropped (Codex uses per-MCP-server enabledTools instead).
You can mix per-target and plain values freely — plain values apply to all targets:
--- name: my-skill description: claude: Claude-specific description copilot: Copilot-specific description license: MIT ---
Hooks
Hook handler fields (command, matcher, timeout, description) support the same syntax:
{
"hooks": {
"preToolUse": [
{
"command": {
"claude": ".hooks/claude-check.sh",
"copilot": ".hooks/copilot-check.sh",
"cursor": ".hooks/cursor-check.sh"
},
"matcher": {
"claude": "Bash",
"cursor": "Bash"
},
"timeout": 30
}
]
}
}If command resolves to undefined for a target (i.e. that target isn't listed), the entire handler is skipped for that target.
Note: Objects with non-target keys (e.g.
metadata: { category: "devops" }) are not treated as overrides — they pass through unchanged.
EJS Template Variables
All templates have access to these variables:
| Variable | Type | Description |
|---|---|---|
target |
'claude' | 'copilot' | 'cursor' | 'codex' |
Current output target |
type |
'instructions' | 'skills' | 'agents' |
Template type being rendered (hooks/MCP don't use EJS) |
config |
ResolvedConfig |
Full resolved config object |
...config.variables |
Record<string, unknown> |
Custom user variables spread into scope |
Path Helpers
Templates have access to path helper functions. All name parameters are optional — omit to get the directory path.
Output path helpers — resolve to the target-specific output path:
| Function | Returns |
|---|---|
instructionPath(name?) |
Target-specific output path (or directory) for an instruction |
skillPath(name?) |
Target-specific output path (or directory) for a skill |
agentPath(name?) |
Target-specific output path (or directory) for an agent |
Template path helpers — resolve to the source template path in the templates directory:
| Function | Returns |
|---|---|
instructionTemplatePath(name?) |
Template source path (or directory) for an instruction |
skillTemplatePath(name?) |
Template source path (or directory) for a skill |
agentTemplatePath(name?) |
Template source path (or directory) for an agent |
hookTemplatePath(name?) |
Template source path (or directory) for a hook |
mcpTemplatePath(name?) |
Template source path (or directory) for an MCP config |
MCP reference helpers — produce the target-appropriate syntax for referencing an MCP server tool. Use in instruction/skill bodies so the AI knows the right reference string for its platform without writing <% if (target...) %> conditionals:
| Function | Returns |
|---|---|
mcpToolRef(server, tool) |
Fully-qualified reference to a specific MCP tool |
mcpToolRef(server) |
Wildcard reference to all tools on a server |
Per-target output for mcpToolRef('github', 'list_issues') / mcpToolRef('github'):
| Target | Specific tool (mcpToolRef('github', 'list_issues')) |
Wildcard (mcpToolRef('github')) |
|---|---|---|
| Claude | mcp__github__list_issues |
mcp__github__* |
| Codex | mcp__github__list_issues |
mcp__github__.* (regex pattern for hook matchers) |
| Copilot | github/list_issues |
github/* |
| Cursor | MCP:list_issues |
MCP:.* (no server qualifier — Cursor hooks don't filter by server) |
For example, <%= instructionPath('coding-style') %> renders to:
| Target | Output |
|---|---|
| Claude | .claude/rules/coding-style.md |
| Copilot | .github/instructions/coding-style.instructions.md |
| Cursor | .cursor/rules/coding-style.mdc |
| Codex | depends on alwaysApply / globs — see Codex caveats below |
And <%= instructionPath() %> (no argument) renders to:
| Target | Output |
|---|---|
| Claude | .claude/rules |
| Copilot | .github/instructions |
| Cursor | .cursor/rules |
| Codex | (root — AGENTS.md) |
Template path helpers are target-independent: <%= instructionTemplatePath('coding-style') %> always renders to .universal-ai-config/instructions/coding-style.md (or the configured templatesDir).
Configuration
Base config (universal-ai-config.config.ts) — committed
import { defineConfig } from "universal-ai-config"; export default defineConfig({ // Where templates live (default: '.universal-ai-config') templatesDir: ".universal-ai-config", // Additional directories to discover templates from (default: []) // Supports absolute paths, relative paths, and ~ for home directory additionalTemplateDirs: ["~/.universal-ai-config"], // Which targets to generate (default: all four) targets: ["claude", "copilot", "cursor", "codex"], // Which types to generate (default: all) types: ["instructions", "skills", "agents", "hooks", "mcp"], // Custom EJS variables available in templates variables: { projectName: "my-app", useStrictMode: true, }, // Override default output directories outputDirs: { claude: ".claude", copilot: ".github", cursor: ".cursor", codex: ".codex", }, // Exclude templates by glob pattern (optional) exclude: ["agents/internal-only.md"], });
Overrides config (universal-ai-config.overrides.config.ts) — (ideally gitignored)
import { defineConfig } from "universal-ai-config"; export default defineConfig({ // I only use Claude and Cursor targets: ["claude", "cursor"], // Extra variables for my local setup variables: { myPreferredStyle: "functional", }, });
Template Exclusion
The exclude option accepts glob patterns to skip specific templates during generation. Patterns match against input template paths relative to the templates directory (e.g., instructions/my-rule.md, skills/deploy-helper/SKILL.md, hooks/debug.json, mcp/internal.json) — not output paths.
For instructions/skills/agents one input file maps to one output, so exclusion is 1:1. For hooks and MCP, multiple input JSON files merge into a single output file per target: excluding hooks/debug.json or mcp/internal.json drops every handler/server that file declared. The exclude option does not target individual hook handlers or named MCP servers — only the whole input file containing it. For MCP specifically, see MCP Opt-In Filtering below for server-name-level control.
Array form — same exclusions for all targets:
export default defineConfig({ exclude: ["agents/security-checker.md", "hooks/debug.json"], });
Per-target form — different exclusions per target:
export default defineConfig({ exclude: { claude: ["agents/copilot-reviewer.md"], copilot: ["skills/**"], cursor: ["hooks/**"], codex: ["agents/codex-incompatible.md"], default: [], }, });
Supported glob syntax: * (single segment), ** (recursive), ? (single char), {a,b} (alternatives).
MCP Opt-In Filtering
MCP servers can heavily affect agent performance — every server adds tools, context, and latency. The exclude option is file-level; if a single mcp/*.json template declares multiple servers, excluding the whole file is all-or-nothing. For finer control, use the mcp config block to opt-in by server name:
export default defineConfig({ mcp: { forceOptIn: true, mcpServers: ["github", "playwright"], }, });
When forceOptIn is true for a target, only servers whose names appear in mcpServers are emitted, no matter how many input files declare them. When forceOptIn is false or unset (the default), all discovered servers pass through.
Both fields accept the standard per-target shape:
export default defineConfig({ mcp: { forceOptIn: { claude: true, codex: true, default: false }, mcpServers: { claude: ["github"], copilot: ["github", "playwright"], codex: ["github", "playwright"], default: [], }, }, });
mcpServers: []withforceOptIn: true→ no servers for that target, MCP output file skipped.- Unknown names emit a
[uac]warning at generate time; generation continues with the matched subset. - Server-level filtering is additive with file-level
exclude— you can use both.
Additional Template Directories
The additionalTemplateDirs option lets you load templates from extra directories alongside the project-local templatesDir. This is useful for sharing common templates (e.g., MCP servers, coding standards) across projects from a central location like your home directory.
export default defineConfig({ additionalTemplateDirs: ["~/.universal-ai-config"], });
- Paths can be absolute, relative to the project root, or use
~for the home directory - The main
templatesDiralways takes priority — if a template with the same name and type exists in both, the main dir's version is used - Within
additionalTemplateDirs, earlier entries take priority over later ones - The
excludeoption works the same way — patterns match against type-relative paths (e.g.,instructions/foo.md) regardless of source directory - Generated files are always written to the project's output directories
Merge Behavior
- Arrays (
targets,types,exclude,additionalTemplateDirs): overrides replace entirely - Objects (
variables,outputDirs): deep-merged - Scalars (
templatesDir): overrides replace
To concatenate array fields (like exclude) instead of replacing, use the mergeField helper:
// universal-ai-config.overrides.ts import { defineConfig, mergeField } from "universal-ai-config"; import base from "./universal-ai-config.config"; export default defineConfig({ exclude: mergeField(base.exclude, ["additional-pattern/**"]), });
mergeField handles all type combinations — plain arrays, per-target objects, and mixed. When mixing types, plain arrays are treated as the default value, and target-specific keys fall back to default when absent.
Resolution Order (later wins)
- Built-in defaults
universal-ai-config.{ts,js,mjs,cjs}(base)universal-ai-config.overrides.{ts,js,mjs,cjs}(personal)- CLI flags (
--target,--type, etc.)
CLI Reference
uac generate
Generate config files for specified targets.
| Flag | Short | Description | Default |
|---|---|---|---|
--target |
-t |
Comma-separated targets | All from config |
--type |
Comma-separated types | All from config | |
--config |
-c |
Config file path | Auto-detected |
--root |
-r |
Project root | cwd |
--dry-run |
-d |
Preview without writing | false |
--clean |
Remove existing files first | false |
uac init
Scaffold .universal-ai-config/ directory with meta-instruction templates and a universal-ai-config.config.ts config file. Seeds instruction and skill templates that teach AI tools how to manage universal-ai-config templates. Adds universal-ai-config.overrides.* to .gitignore.
| Flag | Short | Description | Default |
|---|---|---|---|
--root |
-r |
Project root | cwd |
uac clean
Remove all generated config directories.
| Flag | Short | Description | Default |
|---|---|---|---|
--target |
-t |
Comma-separated targets to clean | All |
--root |
-r |
Project root | cwd |
uac seed
Seed the templates directory with pre-built template sets. Available seed types:
meta-instructions— Instruction and skill templates that teach AI tools how to create, update, and manage universal-ai-config templates. This bootstraps the AI's ability to extend its own configuration. Also seeded automatically byuac init.examples— Example templates (instruction, skill, agent, hook) demonstrating template structure and frontmatter fields. Good for learning the format.gitignore— Updates.gitignorewith patterns for all generated output files (target config dirs, MCP files, etc.). Also run automatically byuac init.
# Seed meta-instructions (also done by uac init) uac seed meta-instructions # Seed example templates uac seed examples # Update .gitignore with output patterns uac seed gitignore # Seed with custom project root uac seed meta-instructions --root ./my-project
| Flag | Short | Description | Default |
|---|---|---|---|
--root |
-r |
Project root | cwd |
The meta-instructions seed creates 12 files in the templates directory:
| File | Purpose |
|---|---|
instructions/uac-usage.md |
How to use uac CLI commands (always applied) |
instructions/uac-template-guide.md |
Full template authoring guide |
skills/update-ai-config/SKILL.md |
Main entry point for creating or updating any template |
skills/import-existing-ai-config/SKILL.md |
Import existing target-specific configs as universal templates |
skills/ai-config-compress/SKILL.md |
Compress LLM instructions to reduce token count |
update-ai-config is the primary skill users should invoke — it analyzes intent and auto-delegates to internal type-specific skills (update-instruction, update-skill, update-agent, update-hook, update-mcp).
Existing files are overwritten to ensure templates stay up to date.
Output Paths
Claude (.claude/)
| Type | Output Path |
|---|---|
| Instructions | .claude/rules/<name>.md |
| Skills | .claude/skills/<name>/SKILL.md |
| Agents | .claude/agents/<name>.md |
| Hooks | .claude/settings.json (merged into hooks key) |
| MCP | .mcp.json |
Copilot (.github/)
| Type | Output Path |
|---|---|
| Instructions | .github/instructions/<name>.instructions.md |
Instructions (alwaysApply) |
.github/copilot-instructions.md |
| Skills | .github/skills/<name>/SKILL.md |
| Agents | .github/agents/<name>.agent.md |
| Hooks | .github/hooks/hooks.json |
| MCP | .vscode/mcp.json |
Cursor (.cursor/)
| Type | Output Path |
|---|---|
| Instructions | .cursor/rules/<name>.mdc |
| Skills | .cursor/skills/<name>/SKILL.md |
| Agents | Not supported |
| Hooks | .cursor/hooks.json |
| MCP | .cursor/mcp.json |
Codex (.codex/ + root + .agents/)
Codex emits files in multiple locations outside outputDir to align with Codex's auto-discovery conventions:
| Type | Output Path | Notes |
|---|---|---|
| Instructions | AGENTS.md (root) + <dir>/AGENTS.override.md |
Multiple instructions consolidate into a single root file or per-directory override files |
| Skills | .agents/skills/<name>/SKILL.md (+ agents/openai.yaml when needed) |
Root-relative, per the open Agent Skills standard. Sidecar agents/openai.yaml emitted for codex.* metadata |
| Agents | .codex/agents/<name>.toml |
Standalone TOML files (body becomes developer_instructions) |
| Hooks | .codex/hooks.json |
JSON, wholly uac-managed |
| MCP | .codex/config.toml |
TOML — [mcp_servers.*] table is uac-owned, other top-level keys preserved |
File ownership for Codex
.codex/config.tomlis shared with user content. uac only writes the[mcp_servers.*]table. Users can hand-author[profiles.*],[model_providers.*],[permissions.*],personality,[memories],[tui], OTel config, etc. directly in this file and uac preserves those sections across regenerates.uac clean --target codexonly removes themcp_serverskey from this file.- All other Codex output files (
AGENTS.md,AGENTS.override.md,.codex/agents/*.toml,.codex/hooks.json,.agents/skills/) are wholly uac-managed — plain overwrite on each regenerate, same as.claude/rules/,.cursor/rules/, etc.
Codex caveats
- Instructions consolidate.
alwaysApply: true(or templates with no globs / leading-wildcard globs like**/*.ts) → concatenated into rootAGENTS.md. Templates with resolvable-prefix globs likepackages/frontend/**→<dir>/AGENTS.override.md. Templates targeting the same dir get alpha-sorted and concatenated. - Agents are TOML. Each agent template →
.codex/agents/<name>.tomlwith body content indeveloper_instructions. Codex auto-discovers these files; no[agents.*]registration inconfig.tomlis needed. - Per-target overrides are required for
model(Claude vs Codex model IDs),permissionMode→sandboxMode(different vocabularies), andtools(Codex has no agent-level tool restriction — use per-MCPenabledToolsinstead). uac warns when these mismatches are detected. effortauto-maps to Codex'smodel_reasoning_effortwhen the value is in{minimal, low, medium, high, xhigh}. Claude-onlymaxdrops with a warning.disableAutoInvocationauto-maps toagents/openai.yamlpolicy.allow_implicit_invocation: !disableAutoInvocation.
See the UAC Template Guide for the complete reference.
Complete Template Reference
For the most up-to-date reference on all frontmatter fields, available tools per platform (Claude, Copilot, Cursor), MCP tool syntax, hook matcher patterns, and per-target overrides, see the UAC Template Guide. This guide is also seeded into your project via uac seed meta-instructions and made available to your AI tools as a rule/instruction.
Programmatic API
import { generate, writeGeneratedFiles, cleanTargetFiles, loadProjectConfig, } from "universal-ai-config"; // Generate files (returns GeneratedFile[] without writing to disk) const files = await generate({ root: process.cwd(), targets: ["claude"], // Inline config overrides — no config file needed overrides: { variables: { projectName: "my-app" }, exclude: ["agents/**"], outputDirs: { claude: ".custom-claude" }, }, }); // Write generated files to disk await writeGeneratedFiles(files, process.cwd()); // Clean generated files before regenerating await cleanTargetFiles(process.cwd(), ["claude"]); // Load resolved config directly (for advanced use) const config = await loadProjectConfig({ root: process.cwd() });
generate(options?)
| Option | Type | Description |
|---|---|---|
root |
string |
Project root (default: cwd) |
targets |
Target[] |
Override targets (highest priority) |
types |
TemplateType[] |
Override types (highest priority) |
config |
string |
Config file path |
overrides |
UserConfig |
Inline config overrides (see below) |
The overrides option accepts all config file fields (templatesDir, additionalTemplateDirs, variables, outputDirs, exclude, targets, types). Priority order: defaults → config file → overrides file → inline overrides → CLI-level targets/types.
writeGeneratedFiles(files, root)
Writes GeneratedFile[] to disk. Handles mergeKey for JSON merge targets (e.g., Claude hooks into settings.json).
cleanTargetFiles(root, targets?)
Removes generated config files for specified targets (or all targets if omitted).
loadProjectConfig(options?)
Loads and resolves the full config chain. Accepts inlineOverrides for programmatic config.
Adding a New Target
Create a single file in src/targets/ implementing TargetDefinition:
import { defineTarget } from "../define-target.js"; export default defineTarget({ name: "zed", outputDir: ".zed", supportedTypes: ["instructions"], instructions: { frontmatterMap: { globs: "path", }, getOutputPath: (name) => `prompts/${name}.md`, }, });
Then add one line to src/targets/index.ts:
import zed from "./zed/index.js"; export const targets = { claude, copilot, cursor, codex, zed };
License
MIT