DEV Community

Lukas Wolfsteiner
Lukas Wolfsteiner

Posted on

One Skills Brain for Codex, Claude, Cursor, and Copilot with Chezmoi

One Skills Brain for Codex, Claude, Cursor, and Copilot with Chezmoi

When you use multiple coding agents across multiple machines, the same prompt often behaves differently for a simple reason: your agent skills and MCP wiring drift over time.

Typical failure modes:

  • a skill is updated in one tool but not the others
  • MCP servers are configured differently per machine
  • one tool reads user-level config, another reads project-level config, and behavior diverges

The initial problem is not just dotfiles sync. It is maintaining one consistent agent behavior contract across Codex, Claude, Cursor, and Copilot despite different config formats and file locations.

The solution in this guide is to separate concerns:

  • one canonical skills directory: ~/.agent-skills/skills
  • one canonical MCP intent (shared definitions rendered per tool)
  • one dotfiles manager (chezmoi) to materialize tool-native adapters

Why this works:

  • skills stay identical because they live in one place
  • each tool keeps its native schema, so updates are compatible with official behavior
  • chezmoi templates and symlinks remove manual copy/paste and reduce drift
  • path differences are explicit and auditable

All path references below follow:

Path contract (from agent-storage-map)

  1. Codex
    User config: ~/.codex/config.toml
    Project config: .codex/config.toml
    Skills: .agents/skills and SKILL.md folders

  2. Claude Code
    Main MCP config: ~/.claude.json
    Dedicated MCP file: ~/.claude/mcp_servers.json (lower precedence than ~/.claude.json)
    Settings: ~/.claude/settings.json, .claude/settings.json, .claude/settings.local.json
    Sub-agents: ~/.claude/agents/, .claude/agents/

  3. Cursor
    MCP config: ~/.cursor/mcp.json, .cursor/mcp.json
    Skills: ~/.cursor/skills/, .cursor/skills/
    Compatibility skills paths include ~/.codex/skills/, ~/.claude/skills/

  4. GitHub Copilot
    MCP config: ~/.copilot/mcp-config.json
    Skills: ~/.copilot/skills/, .copilot/skills/

Why symlinks (not hard links)

Use symlinks for shared skills directories.

  • explicit target path (readlink)
  • works natively with chezmoi symlink_ files
  • easier operationally than hard links for directory trees

Chezmoi source tree (concrete)

This is the chezmoi source-state tree ($(chezmoi source-path)):

~/.local/share/chezmoi/
├── dot_agent-skills/
│   ├── README.md
│   ├── skills/
│   │   ├── commit-style/
│   │   │   └── SKILL.md
│   │   └── test-triage/
│   │       └── SKILL.md
│   └── mcp/
│       ├── codex-mcp.toml.tmpl
│       ├── generic-mcp.json.tmpl
│       └── copilot-mcp.json.tmpl
├── dot_codex/
│   ├── config.toml.tmpl
│   └── symlink_skills.tmpl
├── private_dot_claude.json.tmpl
├── dot_claude/
│   └── settings.json.tmpl
├── dot_cursor/
│   ├── mcp.json.tmpl
│   └── symlink_skills.tmpl
└── dot_copilot/
    ├── mcp-config.json.tmpl
    └── symlink_skills.tmpl
Enter fullscreen mode Exit fullscreen mode

Chezmoi attribute reminders:

  • dot_ -> leading dot in target
  • private_ -> stricter permissions in target
  • symlink_ -> create a symlink target
  • .tmpl -> template rendering

Command set

First machine

chezmoi init git@github.com:<you>/<dotfiles>.git
cd "$(chezmoi source-path)"

# Optional: import existing files
chezmoi add ~/.codex/config.toml
chezmoi add ~/.claude.json
chezmoi add ~/.claude/settings.json
chezmoi add ~/.cursor/mcp.json
chezmoi add ~/.copilot/mcp-config.json

chezmoi diff
chezmoi apply -v

git add .
git commit -m "feat: unify skills and mcp adapters"
git push
Enter fullscreen mode Exit fullscreen mode

Additional machines

chezmoi init git@github.com:<you>/<dotfiles>.git
chezmoi update -v
Enter fullscreen mode Exit fullscreen mode

Daily flow

chezmoi edit ~/.codex/config.toml
chezmoi diff
chezmoi apply -v
Enter fullscreen mode Exit fullscreen mode

Shared skills content

Create all skills once in dot_agent-skills/skills.

Example dot_agent-skills/skills/commit-style/SKILL.md:

# commit-style

## Goal
Create clear conventional commits.

## Rules
- Keep subject under 50 chars.
- Use imperative present tense.
- Explain user impact in body.
Enter fullscreen mode Exit fullscreen mode

After apply, this becomes ~/.agent-skills/skills/... and is your canonical skill bank.

Shared MCP templates

Keep one MCP intent and render per tool schema.

dot_agent-skills/mcp/codex-mcp.toml.tmpl

[mcp_servers.filesystem]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "{{ .homeDir }}/.agent-skills/skills"]

[mcp_servers.git]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-git", "--repository", "{{ .homeDir }}/Developer"]
Enter fullscreen mode Exit fullscreen mode

dot_agent-skills/mcp/generic-mcp.json.tmpl (Claude/Cursor style)

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "{{ .homeDir }}/.agent-skills/skills"]
    },
    "git": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-git", "--repository", "{{ .homeDir }}/Developer"]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

dot_agent-skills/mcp/copilot-mcp.json.tmpl

{
  "mcpServers": {
    "filesystem": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "{{ .homeDir }}/.agent-skills/skills"]
    },
    "git": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-git", "--repository", "{{ .homeDir }}/Developer"]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Per-tool templates

1) Codex

dot_codex/config.toml.tmpl

model = "gpt-5-codex"
approval_policy = "on-request"

[mcp_servers.filesystem]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "{{ .homeDir }}/.agent-skills/skills"]

[mcp_servers.git]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-git", "--repository", "{{ .homeDir }}/Developer"]
Enter fullscreen mode Exit fullscreen mode

dot_codex/symlink_skills.tmpl

{{ .homeDir }}/.agent-skills/skills
Enter fullscreen mode Exit fullscreen mode

Result: ~/.codex/skills -> ~/.agent-skills/skills
Use this mainly as a compatibility anchor for tools that read Codex-style skills paths.
For Codex itself, keep project skills in .agents/skills when you want repository-local behavior.

2) Claude Code

private_dot_claude.json.tmpl

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "{{ .homeDir }}/.agent-skills/skills"]
    },
    "git": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-git", "--repository", "{{ .homeDir }}/Developer"]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

dot_claude/settings.json.tmpl

{
  "permissions": {
    "allow": [],
    "deny": []
  }
}
Enter fullscreen mode Exit fullscreen mode

For project-level MCP, use .mcp.json with the same filesystem path.
For agent logic, prefer documented .claude/agents/ over .claude/skills/.

3) Cursor

dot_cursor/mcp.json.tmpl

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "{{ .homeDir }}/.agent-skills/skills"]
    },
    "git": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-git", "--repository", "{{ .homeDir }}/Developer"]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

dot_cursor/symlink_skills.tmpl

{{ .homeDir }}/.agent-skills/skills
Enter fullscreen mode Exit fullscreen mode

Result: ~/.cursor/skills -> ~/.agent-skills/skills

4) GitHub Copilot

dot_copilot/mcp-config.json.tmpl

{
  "mcpServers": {
    "filesystem": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "{{ .homeDir }}/.agent-skills/skills"]
    },
    "git": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-git", "--repository", "{{ .homeDir }}/Developer"]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

dot_copilot/symlink_skills.tmpl

{{ .homeDir }}/.agent-skills/skills
Enter fullscreen mode Exit fullscreen mode

Result: ~/.copilot/skills -> ~/.agent-skills/skills

Validation

readlink ~/.codex/skills
readlink ~/.cursor/skills
readlink ~/.copilot/skills
ls ~/.agent-skills/skills

chezmoi status
chezmoi diff
chezmoi verify
Enter fullscreen mode Exit fullscreen mode

Expected: all symlinks resolve to ~/.agent-skills/skills, and each MCP file targets that same path.

Feature parity with skills-sync

I reviewed skills-sync feature usage and mapped it to chezmoi primitives.

  1. Single source workspace
    Chezmoi equivalent: one source state with canonical dot_agent-skills/skills.

  2. Symlink distribution
    Chezmoi equivalent: symlink_*.tmpl entries per tool.

  3. Profile switching (use <profile>)
    Chezmoi equivalent: .chezmoidata/profiles.yaml + template conditionals.

  4. Runtime materialization (sync)
    Chezmoi equivalent: chezmoi apply -v.

  5. Seed content (init --seed)
    Chezmoi equivalent: commit baseline skills/templates in source state.

  6. Upstream ingestion (add-upstream, add-skill)
    Chezmoi equivalent: external_ or scripted run_onchange_ sync jobs.

  7. Drift/inventory (agents drift, agents inventory)
    Chezmoi equivalent: chezmoi status/diff/verify plus symlink checks.

Profile-aware template example

Create .chezmoidata/profiles.yaml:

active_profile: personal
profiles:
  personal:
    dev_root: "~/Developer"
  work:
    dev_root: "~/Work"
Enter fullscreen mode Exit fullscreen mode

Use in template:

{{- $p := index .profiles .active_profile -}}
[mcp_servers.git]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-git", "--repository", "{{ $p.dev_root }}"]
Enter fullscreen mode Exit fullscreen mode

Switch profile by changing active_profile, then:

chezmoi apply -v
Enter fullscreen mode Exit fullscreen mode

Upstream sync example

run_onchange_after_50-sync-upstream-skills.sh.tmpl

#!/usr/bin/env bash
set -eu

ROOT="$HOME/.agent-skills/skills/upstream"
mkdir -p "$ROOT"

sync_repo() {
  name="$1"
  url="$2"
  dir="$ROOT/$name"
  if [ -d "$dir/.git" ]; then
    git -C "$dir" pull --ff-only
  else
    git clone "$url" "$dir"
  fi
}

sync_repo "matlab_skills" "https://github.com/matlab-deep-learning/llm-agents-with-mcp.git"
Enter fullscreen mode Exit fullscreen mode

Apply with:

chezmoi apply -v
Enter fullscreen mode Exit fullscreen mode

Security and portability notes

  • Keep tokens in env vars, not committed config files.
  • Use private_ for files that may carry sensitive values.
  • Use {{ .homeDir }} in templates for cross-machine portability.

This pattern gives one skill bank, one MCP intent, four adapters, and minimal drift.

Cheers,
Lukas

Top comments (0)