# Charlie - Universal Agent Config Generator

**Define once in YAML/Markdown. Generate agent-specific commands, MCP config, and rules.**

Charlie is a universal agent configuration generator that produces agent-specific commands, MCP configurations, and rules from a single YAML/Markdown spec.

[![Tests](https://img.shields.io/badge/tests-350%20passed-green)]()
[![Coverage](https://img.shields.io/badge/coverage-84%25-brightgreen)]()
[![Python](https://img.shields.io/badge/python-3.11+-blue)]()

## Features

- ✨ **Single Definition**: Write settings once in YAML or Markdown
- 🤖 **Multi-Agent Support**: Generate for different AI agents (Claude, Cursor, GitHub Copilot, and OpenCode supported)
- ⚙️ **Slash Commands Integration**: Generate slash commands from a single definition.
- 🔌 **MCP Integration**: Generate MCP server configurations with tool schemas
- 📋 **Rules Generation**: Create agent-specific rules files with manual preservation
- 🕵️ **Subagent Support**: Define specialized AI sub-assistants once
- 🧠 **Skills Support**: Package domain knowledge as reusable skills agents invoke automatically
- 🎯 **Auto-Detection**: Automatically finds `charlie.yaml` or `.charlie/` directory
- ⚡ **Runtime Targeting**: Choose which agents to generate for at runtime
- 📦 **Library & CLI**: Use as CLI tool or import as Python library
- 🔗 **Configuration Inheritance**: Extend configurations from external Git repositories

## Quick Start

### Installation

#### Install via pip

```bash
pip install charlie-agents
```

#### Use Docker

Charlie is available as a Docker image, so you don't need to install Python dependencies:

```bash
# Pull the image from GitHub Container Registry
docker pull ghcr.io/henriquemoody/charlie:latest

# Run charlie commands (mount your project directory)
docker run --rm -v $(pwd):/workspace ghcr.io/henriquemoody/charlie list-agents

# Generate configuration
docker run --rm -v $(pwd):/workspace ghcr.io/henriquemoody/charlie generate claude
```

**Create an alias for convenience:**

Add to your `.bashrc` or `.zshrc`:

```bash
alias charlie='docker run --rm -v $(pwd):/workspace ghcr.io/henriquemoody/charlie'
```

Then use it like the native CLI:

```bash
charlie generate claude
charlie validate
charlie list-agents
```

## Configuration

For advanced features, Charlie supports two configuration approaches:

1. **Monolithic** - Single YAML file (good for small projects)
2. **Directory-Based** - Modular files in `.charlie/` directories (good for large projects)

### Monolithic Configuration

For advanced features, create `charlie.yaml` in your project:

```yaml
version: "1.0" # Optional: Schema version (defaults to "1.0")

extends: # Optional: Define it when you want to extend another repository's configuration
  - git@github.com:MyOrg/team-config.git#v1.0

project:
  name: "My project" # Optional: Inferred from directory name if omitted
  namespace: "my" # Optional: Used to prefix commands, rules, and MCP servers.

variables:
  mcp_api_token: ~ # It will ask the user to provide an API token, if the environment variable is not set

# Command definitions
commands:
  - name: "commit"
    description: "Analyze changes and create a high-quality git commit"
    prompt: "Check what changed, and commit your changes. The body of the message explains WHY it changed"

  - name: "command-handler"
    description: "Creates a command handler"
    prompt: "Create a command handler using src/examples/handler.py as an reference"

# MCP server definitions
mcp_servers:
  - name: "local_server"
    type: "stdio"
    command: "node"
    args: ["server.js"]
    env:
      KEY: "value"

  - name: "remote_server"
    url: "https://example.com/mcp"
    headers:
      Authorization: "Bearer {{var:mcp_api_token}}"
      Content-Type: "application/json"

# Rules configuration (rules)
rules:
  - description: "Commit message standards"
    prompt: "Use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)"

  - description: "Coding standards"
    prompt: "All code should follow PEP 8"

# Subagent definitions (optional)
subagents:
  - name: "code-reviewer"
    description: "Expert code reviewer. Use proactively after code changes."
    prompt: |
      You are a senior code reviewer. When invoked:
      1. Run git diff to see recent changes
      2. Review for quality, security, and best practices
      3. Provide feedback organized by priority
    metadata:
      tools: "Read, Grep, Glob, Bash"
      model: sonnet

# Skill definitions (optional)
skills:
  - name: "explain-code"
    description: "Explains code with visual diagrams and analogies. Use when explaining how code works."
    prompt: |
      When explaining code, always include:
      1. An analogy from everyday life
      2. An ASCII diagram showing the flow
      3. A step-by-step walkthrough
      4. A common gotcha or misconception

  - name: "deploy"
    description: "Deploy the application to production"
    prompt: |
      Deploy the application:
      1. Run the test suite
      2. Build the application
      3. Push to the deployment target
      4. Verify the deployment succeeded
    metadata:
      disable-model-invocation: true

# Ignore patterns (optional) -- will be added to agent-specific ignore files
ignore_patterns:
  - "*.log"
  - "tmp/"
  - "node_modules/"
```

Charlie will also read `charlie.dist.yaml`, unless you have a `charlie.yaml` in the directory.

See [`examples/`](examples/) directory for complete examples:

- [`examples/simple/`](examples/simple/) - Basic configuration
- [`examples/speckit/`](examples/speckit/) - Spec-kit inspired configuration

### Configuration Inheritance (`extends`)

Charlie supports inheriting configuration from external Git repositories. This allows you to share common configurations across multiple projects or organizations:

```yaml
extends:
  - https://github.com/MyOrg/shared-agent-config
  - git@github.com:MyOrg/team-config.git#v1.0

# Local commands, rules, etc. will be merged with extended configs
commands:
  - name: "local-command"
    description: "A project-specific command"
    prompt: "Do something specific to this project"
```

**How it works:**

1. Charlie fetches each repository in the `extends` list (in order)
2. Configurations are merged sequentially - later entries override earlier ones
3. Your local configuration is merged last, taking highest precedence
4. Duplicate items (commands, rules, MCP servers, variables) are overwritten with a warning

When duplicates are detected, Charlie displays a warning:

```
⚠ Overwriting command 'init' from https://github.com/MyOrg/shared-config
⚠ Overwriting rule 'coding-standards' from https://github.com/MyOrg/shared-config
```

**Version/Branch Support:**

Use URL fragments to specify a branch or tag:

- `https://github.com/Org/Config#main` - use the `main` branch
- `https://github.com/Org/Config#v1.0.0` - use the `v1.0.0` tag
- `git@github.com:Org/Config.git#feature-branch` - use a specific branch

### Directory-Based Configuration

For better organization and collaboration, use the directory-based approach. The `charlie.yaml` file is **optional** - if you only have a `.charlie/` directory, Charlie will infer the project name from the directory:

```
project/
├── charlie.yaml                  # Optional: Project metadata (name inferred if omitted)
├── .charlieignore                # Optional: Patterns to exclude from AI agents
└── .charlie/
    ├── commands/
    │   ├── init.yaml             # One file per command (Markdown or YAML supported)
    │   └── deploy.md
    ├── rules/
    │   ├── commit-messages.yaml  # One file per rule (Markdown or YAML supported)
    │   └── code-style.md
    ├── agents/
    │   ├── code-reviewer.md      # One file per subagent (Markdown with YAML frontmatter)
    │   └── debugger.md
    ├── skills/
    │   ├── explain-code.md       # Flat file skill (Markdown with YAML frontmatter)
    │   └── deploy/               # Directory-based skill
    │       ├── SKILL.md          # Skill definition (required)
    │       ├── deploy.sh         # Companion files (copied to output)
    │       └── templates/
    │           └── config.yaml
    └── mcp-servers/
        └── local-tools.yaml      # MCP servers in YAML
```

See [`examples/directory-based/`](examples/directory-based/) for a complete example.

**Benefits:**

- Clear organization (one file per command/rule)
- No merge conflicts on single file
- Easy to add/remove components
- Better for version control diffs
- Native markdown support for rich documentation

### Generate Agent-specific Configuration

```bash
# Generate configuration files for a specific agent (generates commands, MCP, and rules by default)
charlie generate claude
```

### Placeholders

Charlie supports these universal placeholders in commands, rules, and MCP configurations:

**Project Placeholders:**

- `{{project_dir}}` → Resolves to the project root directory
- `{{project_name}}` → Replaced with the project name (e.g., `My Project`)
- `{{project_namespace}}` → Replaced with the project namespace (e.g., `my`)

**Agent Placeholders:**

- `{{agent_name}}` → Replaced with the agent's full name (e.g., `Claude Code`, `Cursor`)
- `{{agent_shortname}}` → Replaced with the agent's short identifier (e.g., `claude`, `cursor`)
- `{{agent_dir}}` → Resolves to agent's base directory (e.g., `.claude`, `.cursor`)
- `{{commands_shorthand_injection}}` → Agent-specific command shorthand (e.g., `$ARGUMENTS` for supported agents)

**Agent Path Placeholders:**

- `{{commands_dir}}` → Resolves to agent's commands directory (e.g., `.claude/skills`, `.cursor/commands`)
- `{{rules_dir}}` → Resolves to agent's rules directory (e.g., `.claude/rules/`)
- `{{rules_file}}` → Resolves to agent's rules file path (e.g., `CLAUDE.md`, `.cursor/rules`)
- `{{subagents_dir}}` → Resolves to agent's subagents directory (e.g., `.claude/agents`)
- `{{skills_dir}}` → Resolves to agent's skills directory (e.g., `.claude/skills`)
- `{{mcp_file}}` → Resolves to agent's MCP configuration file name (e.g., `mcp.json`)
- `{{assets_dir}}` → Resolves to agent's assets directory (e.g., `.claude/assets`)

**Variable Placeholders:**

- `{{var:VARIABLE_NAME}}` → Replaced with the value of a variable defined in your `charlie.yaml`
  - Variables can be defined in the `variables:` section
  - Use `~` as value to prompt user for input if not set as environment variable
  - Example: `{{var:mcp_api_token}}`
  - Charlie will prompt user for input if variable is not set.

**Environment Variable Placeholders:**

- `{{env:VAR_NAME}}` → Replaced with the value of the environment variable
  - Loads from system environment or `.env` file in root directory
  - Raises `EnvironmentVariableNotFoundError` if variable doesn't exist
  - System environment variables take precedence over `.env` file

**Custom Replacements:**

- Custom placeholders can be defined per-command or per-rule using the `replacements` field
- See the Library API section for examples

These placeholders work in commands, rules, and MCP server configurations (command, args, URL, and headers fields).

## Usage

### CLI Commands

#### `charlie generate <agent>`

Setup agent-specific configurations (generates commands, MCP config, and rules by default):

```bash
# Auto-detect charlie.yaml (generates all artifacts)
charlie generate claude

# Setup without MCP config
charlie generate cursor --no-mcp

# Setup without rules (rules)
charlie generate claude --no-rules

# Setup without commands
charlie generate claude --no-commands

# Setup without subagents
charlie generate claude --no-subagents

# Setup without skills
charlie generate claude --no-skills

# Explicit config file
charlie generate cursor --config my-config.yaml

# Custom output directory
charlie generate cursor --output ./build
```

#### `charlie validate`

Validate YAML configuration:

```bash
# Auto-detect charlie.yaml
charlie validate

# Specific file
charlie validate my-config.yaml
```

#### `charlie list-agents`

List all supported AI agents:

```bash
charlie list-agents
```

#### `charlie info <agent>`

Show detailed information about an agent:

```bash
charlie info claude
charlie info cursor
```

### Library API

Use Charlie programmatically in Python:

```python
from charlie import AgentRegistry, AgentConfiguratorFactory, Tracker
from charlie.schema import Project, Command, Rule, Skill, Subagent, HttpMCPServer, StdioMCPServer, ValueReplacement
from charlie.enums import RuleMode

# Initialize registry and get agent
registry = AgentRegistry()
agent = registry.get("claude")

# Create project configuration
project = Project(
    name="My Project",
    namespace="my",
    dir="/path/to/project",
)

# Create configurator
configurator = AgentConfiguratorFactory.create(
    agent=agent,
    project=project,
    tracker=Tracker()
)

# Generate commands
configurator.commands([
    Command(
        name="commit",
        description="Analyze changes and create a high-quality git commit",
        prompt="Check what changed, and commit your changes. The body of the message explains WHY it changed",
        metadata={
            "allowed-tools": "Bash(git add:*), Bash(git status:*), Bash(git commit:*)"
        },
        replacements={}
    ),
    Command(
        name="deploy",
        description="Deploy the application",
        prompt="Run {{script}}",
        metadata={},
        replacements={
            "script": ValueReplacement(
                type="value",
                value=".claude/assets/deploy.sh"
            )
        }
    )
])

# Generate MCP configuration
configurator.mcp_servers([
    HttpMCPServer(
        name="my-http-server",
        type="http",
        url="https://example.com/mcp",
        headers={
            "Authorization": "Bearer F8417EA8-94F3-447C-A108-B0AD7E428BE6",
            "Content-Type": "application/json"
        },
    ),
    StdioMCPServer(
        name="my-stdio-server",
        type="stdio",
        command="node",
        args=["server.js"],
        env={
            "API_TOKEN": "84EBB71B-0FF8-49D8-84C8-55FF9550CA2C"
        },
    ),
])

# Generate rules (rules)
configurator.rules(
    [
        Rule(
            name="commit-messages",
            description="Commit message standards",
            prompt="Use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)",
            metadata={
                "alwaysApply": True,
            },
            replacements={}
        ),
        Rule(
            name="coding-standards",
            description="Coding standards",
            prompt="All code should follow {{standard}}",
            metadata={},
            replacements={
                "standard": ValueReplacement(
                    type="value",
                    value="PEP 8"
                )
            }
        )
    ],
    RuleMode.MERGED
)

# Generate subagents
configurator.subagents([
    Subagent(
        name="code-reviewer",
        description="Expert code reviewer. Use proactively after code changes.",
        prompt="You are a senior code reviewer. Analyze code for quality, security, and best practices.",
        metadata={
            "tools": "Read, Grep, Glob, Bash",
            "model": "sonnet",
        },
    )
])

# Generate skills
configurator.skills([
    Skill(
        name="explain-code",
        description="Explains code with visual diagrams and analogies. Use when explaining how code works.",
        prompt="When explaining code, always include an analogy, an ASCII diagram, and a step-by-step walkthrough.",
    ),
    Skill(
        name="deploy",
        description="Deploy the application to production",
        prompt="Deploy the application: run tests, build, push, verify.",
        metadata={
            "disable-model-invocation": True,
            "allowed-tools": "Bash(./deploy.sh)",
        },
    ),
])

# Copy assets to the agent's directory
configurator.assets([
    ".charlie/assets/deploy.sh",
])
```

## Supported Agents

Charlie currently supports the following AI agents:

- **Claude Code** (`claude`) - Claude's AI coding assistant
- **Cursor** (`cursor`) - AI-powered code editor
- **GitHub Copilot** (`copilot`) - GitHub's AI pair programmer
- **OpenCode** (`opencode`) - Open-source AI coding agent

Run `charlie list-agents` to see all available agents.

> **Note:** Claude Code has [merged custom commands into skills](https://code.claude.com/docs/en/skills). Charlie generates Claude commands as `.claude/skills/{name}/SKILL.md` (the same format as skills). OpenCode also treats commands as skills, generating them as `.opencode/skills/{name}/SKILL.md`. For Cursor, commands are still generated as `.cursor/commands/{name}.md`.

### Metadata support

Charlie uses **pass-through metadata** - add any agent-specific metadata to your commands or rules, and Charlie will include them in generated output:

Charlie extracts these fields and includes them in agent-specific output (YAML frontmatter for Markdown agents, TOML fields for TOML agents). See [`AGENT_FIELDS.md`](AGENT_FIELDS.md) for details on which agents support which fields.

### Rules Generation Modes

Rules (rules) can be generated in two modes:

**Merged Mode** (default) - Single file with all sections:

```bash
charlie generate cursor --rules-mode merged
```

**Separate Mode** - One file per section:

```bash
charlie generate cursor --rules-mode separate
```

Use merged mode for simple projects, separate mode for better organization in complex projects.

## Subagents

Subagents are specialized AI sub-assistants that handle specific types of tasks. Charlie lets you define them once and generate the appropriate files for each supported tool.

### Define subagents

**Directory-based** — create `.charlie/agents/<name>.md`:

```markdown
---
description: Expert code reviewer. Use proactively after code changes.
tools: Read, Grep, Glob, Bash
model: sonnet
---

You are a senior code reviewer. When invoked:

1. Run git diff to see recent changes
2. Review for quality, security, and best practices
3. Provide feedback organized by priority: Critical / Warnings / Suggestions
```

The filename becomes the subagent name (e.g., `code-reviewer.md` → `code-reviewer`). Override it with a `name:` field in the frontmatter.

**YAML inline** — add a `subagents:` section to `charlie.yaml`:

```yaml
subagents:
  - name: code-reviewer
    description: Expert code reviewer. Use proactively after code changes.
    prompt: |
      You are a senior code reviewer...
    metadata:
      tools: "Read, Grep, Glob, Bash"
      model: sonnet
```

### Generated output

| Agent          | Output                       | Supported metadata                                                                                                                      |
| -------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| Claude Code    | `.claude/agents/{name}.md`   | `tools`, `disallowedTools`, `model`, `permissionMode`, `maxTurns`, `skills`, `mcpServers`, `hooks`, `memory`, `background`, `isolation` |
| Cursor         | `.cursor/agents/{name}.md`   | `model`, `readonly`, `is_background`                                                                                                    |
| GitHub Copilot | — (skipped)                  | —                                                                                                                                       |
| OpenCode       | `.opencode/agents/{name}.md` | `description`, `tools`, `model`, `permission`                                                                                           |

See [`AGENT_FIELDS.md`](AGENT_FIELDS.md) for a full metadata field reference.

### Namespace support

When a `namespace` is set, subagent filenames are prefixed:

- Claude: `.claude/agents/myapp-code-reviewer.md`
- Cursor: `.cursor/agents/myapp.code-reviewer.md`

## Skills

Skills are portable, version-controlled packages that teach agents how to perform domain-specific tasks. Unlike commands (which users invoke explicitly), skills can be loaded automatically by the agent when relevant to the conversation.

### Define skills

Charlie supports three ways to define skills:

**Flat file** — create `.charlie/skills/<name>.md`:

```markdown
---
description: Explains code with visual diagrams and analogies. Use when explaining how code works.
---

When explaining code, always include:

1. **Start with an analogy**: Compare the code to something from everyday life
2. **Draw a diagram**: Use ASCII art to show the flow, structure, or relationships
3. **Walk through the code**: Explain step-by-step what happens
4. **Highlight a gotcha**: What's a common mistake or misconception?
```

The filename becomes the skill name (e.g., `explain-code.md` → `explain-code`). Override it with a `name:` field in the frontmatter.

**Directory-based** — create `.charlie/skills/<name>/SKILL.md`:

```
.charlie/skills/deploy/
├── SKILL.md              # Skill definition (required)
├── deploy.sh             # Companion files (copied to output)
└── templates/
    └── config.yaml
```

```markdown
---
description: Deploy the application to production
disable-model-invocation: true
---

Deploy the application:

1. Run the test suite
2. Build using the template at `{{skills_dir}}/deploy/templates/config.yaml`
3. Execute `{{skills_dir}}/deploy/deploy.sh`
4. Verify the deployment succeeded
```

The directory name becomes the skill name (e.g., `deploy/` → `deploy`). Any files alongside `SKILL.md` are automatically copied to the output skill directory, preserving the relative path structure. This lets skills bundle scripts, templates, or reference material that the agent can access at runtime.

**YAML inline** — add a `skills:` section to `charlie.yaml`:

```yaml
skills:
  - name: explain-code
    description: Explains code with visual diagrams and analogies. Use when explaining how code works.
    prompt: |
      When explaining code, always include an analogy, an ASCII diagram, and a step-by-step walkthrough.
  - name: deploy
    description: Deploy the application to production
    prompt: |
      Deploy the application:
      1. Run the test suite
      2. Build the application
      3. Push to the deployment target
    metadata:
      disable-model-invocation: true
```

Both flat and directory-based skills can coexist in the same `.charlie/skills/` folder.

### Generated output

| Agent          | Output                             | Supported metadata                                                                                                                  |
| -------------- | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| Claude Code    | `.claude/skills/{name}/SKILL.md`   | `description`, `argument-hint`, `disable-model-invocation`, `user-invocable`, `allowed-tools`, `model`, `context`, `agent`, `hooks` |
| Cursor         | `.cursor/skills/{name}/SKILL.md`   | `description`, `disable-model-invocation`, `license`, `compatibility`                                                               |
| GitHub Copilot | — (skipped)                        | —                                                                                                                                   |
| OpenCode       | `.opencode/skills/{name}/SKILL.md` | `description`, `license`, `compatibility`                                                                                           |

Each skill is output as a directory containing a `SKILL.md` file, following the [Agent Skills](https://agentskills.io) open standard. Companion files from directory-based skills are copied alongside `SKILL.md` in the output.

### Namespace support

When a `namespace` is set, skill directory names are prefixed:

- Claude: `.claude/skills/myapp-explain-code/SKILL.md`
- Cursor: `.cursor/skills/myapp.explain-code/SKILL.md`

### Invocation control

Use `disable-model-invocation: true` in metadata to prevent the agent from loading the skill automatically — useful for workflows with side effects (deploys, commits, etc.) that you want to trigger manually with `/skill-name`.

## Development

### Use Dev Container (optional)

Open the project in VS Code with the [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers):

1. Open the project in VS Code
2. Press `F1` and select "Dev Containers: Reopen in Container"
3. Wait for the container to build (first time only)
4. Start coding with all tools pre-configured (pytest, mypy, ruff)

The Dev Container provides a consistent development environment with all dependencies and VS Code extensions pre-installed.

### Developer commands

```bash
# Install dev dependencies
pip install -e ".[dev]"

# Run tests
pytest # or `make test`

# Run with coverage
pytest --cov=charlie # or `make test-coverage`

# Run ruff
ruff check . # or `make lint` (`make format` to format the code)

# Run mypy
mypy --install-types --non-interactive src/charlie # or `make analyze`
```

## Contributing

Contributions welcome! Key areas:

- Adding support for new AI agents
- Improving documentation
- Adding more examples
- Bug fixes and tests

## License

MIT

## Acknowledgments

Charlie was inspired by the need to maintain consistent command definitions across multiple AI agents in the [Spec Kit](https://github.com/github/spec-kit) project.
