GitHub - fabis94/universal-ai-config: Generate tool-specific AI config files from shared templates. Write your AI instructions, skills, and agents once — generate config for Claude Code, GitHub Copilot, and other targets automatically.

19 min read Original article ↗

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

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-config to 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 root AGENTS.md. Resolvable-prefix globs like packages/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 cursor target. Codex emits each agent as a standalone .codex/agents/<name>.toml file (body becomes developer_instructions). For Codex, the existing effort field auto-maps to model_reasoning_effort when its value is in {minimal, low, medium, high, xhigh}; max drops 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: [] with forceOptIn: 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 templatesDir always 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 exclude option 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)

  1. Built-in defaults
  2. universal-ai-config.{ts,js,mjs,cjs} (base)
  3. universal-ai-config.overrides.{ts,js,mjs,cjs} (personal)
  4. 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 by uac init.
  • examples — Example templates (instruction, skill, agent, hook) demonstrating template structure and frontmatter fields. Good for learning the format.
  • gitignore — Updates .gitignore with patterns for all generated output files (target config dirs, MCP files, etc.). Also run automatically by uac 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.toml is 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 codex only removes the mcp_servers key 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 root AGENTS.md. Templates with resolvable-prefix globs like packages/frontend/**<dir>/AGENTS.override.md. Templates targeting the same dir get alpha-sorted and concatenated.
  • Agents are TOML. Each agent template → .codex/agents/<name>.toml with body content in developer_instructions. Codex auto-discovers these files; no [agents.*] registration in config.toml is needed.
  • Per-target overrides are required for model (Claude vs Codex model IDs), permissionModesandboxMode (different vocabularies), and tools (Codex has no agent-level tool restriction — use per-MCP enabledTools instead). uac warns when these mismatches are detected.
  • effort auto-maps to Codex's model_reasoning_effort when the value is in {minimal, low, medium, high, xhigh}. Claude-only max drops with a warning.
  • disableAutoInvocation auto-maps to agents/openai.yaml policy.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