Multi-Tool Platform Support

Status: Living design document. This is published in the open on purpose — it records why cmx supports the platforms it does, the way it does, so users and contributors can see our design goals and trade-offs rather than having to reverse-engineer them. Last substantive update: 2026-05.

Why this document exists

cmx installs agents (markdown system-prompt files) and skills (directories containing SKILL.md + supporting files) into the per-tool locations that AI coding assistants read. As we expanded beyond Claude Code, we needed to know — concretely, per tool — where artifacts go, what format each tool expects, and which of cmx's artifact kinds each tool can even consume.

This note records that research (so we don't re-derive it), the structural patterns it revealed, the abstraction options we weighed, and the scope decisions that produced the shipped design.

Design principles

These are the durable commitments behind the decisions on this page. We publish them so you can judge whether cmx's direction fits your values — not just whether it works today.

  • Honesty over the appearance of coverage. If a tool can't consume an artifact kind, cmx fails with a clear error rather than writing files the tool will never read. (→ supports() / skip-unsupported.)
  • Favor open, cross-tool standards. We invest first in the shared .agents/skills/ standard because it serves many tools at once; per-tool quirks are the long tail. (→ the skills cohort.)
  • Layer on top; don't replace. cmx adds version-tracking and cross-tool management over each tool's native system. It won't try to become a tool's plugin system, manage its secrets, or own its config. (→ file-drop-only; config-merge/MCP deferred; charter non-goals.)
  • Lean by default. The default build carries no needless dependencies or advisory surface; we'll hand-roll a small, well-tested thing before pulling in a crate for it. (→ the hand-rolled Codex TOML emitter.)
  • Provenance and integrity are first-class. Every install records source, version, and checksum in a per-platform lockfile, so you can always see what's installed, from where, and whether it has drifted.
  • One curated set, projected outward. You manage a single curated set of artifacts; cmx projects it into many tools' native locations and formats. You curate once, not once per tool.

Direction

The phasing below is the roadmap, and the scope decisions mark the current boundaries — deliberately, not permanently. The deferred items (a Command artifact kind, a config-merge/MCP engine, more tools) are open questions where those boundaries may be revisited. If your needs press on one of them, that signal is welcome — it's part of how the direction gets set.

TL;DR — the three findings that matter

  1. .agents/skills/ is a real cross-tool standard. The agentskills.io SKILL.md-in-a-directory format, read from project .agents/skills/ and user ~/.agents/skills/, is consumed natively by opencode, Codex, Pi, Crush, Amp, Zed, and OpenHands. It is byte compatible with cmx's existing skill model. One skill install serves the whole cohort at once — this is cmx's highest-leverage surface.

  2. "Agent" is the un-portable dimension, and often isn't a file at all. A portable markdown agent only works for Claude and opencode. Codex needs TOML (we transform it). Cline/Crush/Amp/Goose/Zed/Hermes have no file-droppable agent — they use rules, recipes, TS plugins, settings-embedded tool-gating profiles, or runtime delegation. So a cmx "agent" maps to different things per tool, or to nothing.

  3. A second install mechanism exists that cmx deliberately does not model: structured config-merge. Skills/commands are file-drop (copy → checksum → lockfile — cmx's model). MCP servers, Zed profiles, Goose extensions, and Crush/Continue config are merge a key into a shared JSON/YAML/TOML file — no per-artifact file to checksum, requires deep-merge without clobbering hand-edits. Decision: cmx stays file-drop only (see Scope decisions).

Capability matrix

Legend: ✓✓ native/first-class · ✓ supported · ✗ none/unsupported. "Reads .agents/skills?" = consumes the shared cross-tool skill location.

ToolSkill = SKILL.md dir?Reads .agents/skills?Agent-as-file?CommandsMCP configPlugin/registrycmx status
Claude Code✓✓(uses .claude/skills)✓ md✓ .claude/commands✓.claude-plugin/ marketplaceimplemented
GitHub Copilot✓✗✓ md——externalimplemented
Cursor✓✗ (.cursor/skills)✓ md—✓externalimplemented
Windsurf✓✗✓ md—✓externalimplemented
Gemini CLI✓✗✓ md—✓externalimplemented
opencode✓✓✓✓ md (.opencode/agent)✓✓✗ (npm/JS)implemented
Codex CLI✓✓✓✓ TOML (transform)✓ prompts (deprecated)✓✗implemented
Pi✓✓✓✗✗✓tap (GitHub repos)implemented
Crush✓✓✓✗ (internal only)skills w/ flag✓ crush.json✗implemented
Amp✓✓✓ (user: ~/.config/agents/skills)✗ (TS plugins)✓ .agents/commands✓ settings.json✗ (TS plugins)implemented
Zed✓✓✓ (flat only)✗ (settings.json profiles)skills via /name✓ context_serversextension.toml (not agents/skills)implemented
OpenHands✓✓✓agents are triggered skills✗ distinct✓✓ extensions registry (npm)implemented
Hermes✓✓✓ (opt-in external_dirs; user: ~/.hermes/skills)✗ (SOUL.md + runtime delegate)built-in slash only✓ config.yamltap + consumes claude-marketplaceimplemented
Aider✗✗✗ (fixed modes; --read a md file)✗ (built-in only)✗ native✗researched, not impl
Cline✓ (.claude/skills, ~/.cline/skills)✗✗ (Plan/Act + rules)✓ workflows .md✓✓ + marketplace✗ (MCP marketplace only)researched, not impl
Continue✗ (typed "blocks")✗YAML assistants / CLI md+frontmatter✓ prompts✓✓✓ Continue Hub (uses: owner/name@ver)researched, not impl
Goose✗ (use MCP ext)✗recipes (YAML)recipe-backed✓✓ (extensions are MCP)goose:// deeplink registryresearched, not impl

Install path reference (implemented tools)

Project paths are relative to the repo root; user paths to $HOME.

ToolProject agentsUser agentsProject skillsUser skillsLock slug
claude.claude/agents/~/.claude/agents/.claude/skills/~/.claude/skills/(none)
copilot.github/agents/~/.copilot/agents/.github/skills/~/.copilot/skills/copilot
cursor.cursor/agents/~/.cursor/agents/.cursor/skills/~/.cursor/skills/cursor
windsurf.windsurf/agents/~/.codeium/windsurf/agents/.windsurf/skills/~/.codeium/windsurf/skills/windsurf
gemini.gemini/agents/~/.gemini/agents/.gemini/skills/~/.gemini/skills/gemini
opencode.opencode/agent/~/.config/opencode/agent/.agents/skills/~/.agents/skills/opencode
codex.codex/agents/ (TOML)~/.codex/agents/ (TOML).agents/skills/~/.agents/skills/codex
pi——.agents/skills/~/.agents/skills/pi
crush——.agents/skills/~/.agents/skills/crush
amp——.agents/skills/~/.config/agents/skills/amp
zed——.agents/skills/~/.agents/skills/zed
openhands——.agents/skills/~/.agents/skills/openhands
hermes——.agents/skills/~/.hermes/skills/hermes
devin——.agents/skills/~/.agents/skills/devin

Notes:

  • Codex agents are transformed, not copied: cmx parses the source markdown frontmatter + body and emits a Codex subagent TOML (name, description, developer_instructions, plus model, model_reasoning_effort, and sandbox_mode when set). Codex parses its agent files with deny_unknown_fields, so nothing else may be a key; the rest of the frontmatter (metadata.version included) rides along as a # ----fenced comment block that cmx_core::agent::preserved_frontmatter reads back for list and doctor. See cmx-core/src/agent.rs.
  • Amp resolves user-scoped skills under XDG (~/.config/agents/skills/), not ~/.agents/skills/. Project skills use the shared path.
  • Hermes is global-centric: its auto-read source of truth is ~/.hermes/skills/. It reads .agents/skills/ only if you add it to skills.external_dirs in ~/.hermes/config.yaml.
  • Shared-directory caveat: because .agents/skills/ is shared, uninstalling a skill under one of these platforms removes the directory for all tools that read it (each platform still keeps its own lockfile).

The abstraction we considered (and why the lean version won)

The richest model on the table was an Artifact × Target capability descriptor with pluggable install strategies:

#![allow(unused)]
fn main() {
enum InstallStrategy {
    FileDrop    { dir, transform: Option<Transform> }, // copy/transform a file or dir
    ConfigMerge { file, pointer, entry_schema },        // merge a key into shared JSON/YAML/TOML
    Reference   { registry, slug_fmt },                 // tool's own package manager (e.g. Continue Hub)
    Unsupported { reason },
}
// Platform::target(kind, scope) -> Target { strategy, .. }
}

…plus growing ArtifactKind from {Agent, Skill} toward {Agent, Skill, Command, McpServer}.

We did not build this. Once the scope decision ruled out config-merge, the only live strategies were FileDrop and Unsupported — which the existing design already models cleanly via:

  • Platform::install_subpath(kind, scope) -> PathBuf — per-(platform, kind, scope) file-drop location,
  • Platform::supports(kind) -> bool — the Unsupported gate,
  • Platform::agent_extension() + Platform::transforms_agent_to_toml() — the one Transform we need (Codex).

Introducing the full enum would have been speculative abstraction (YAGNI) for a single strategy. The capability descriptor is the right shape to revisit if and when config-merge is in scope.

Scope decisions

DecisionChoiceRationale
Unsupported artifact kindsSkip with a clear errorEach platform declares supported kinds; e.g. cmx agent install --platform pi fails loudly rather than writing where the tool never reads.
Manifest generation for new toolsOut of scopeNone of opencode/codex/pi/crush/amp/zed/openhands/hermes has a Claude-style plugin/marketplace manifest; generating .X-plugin/ dirs would be dead files.
Codex agentsTransform md → TOML nowCodex subagents are TOML; a verbatim copy wouldn't work. Hand-rolled emitter, no new dependency.
Codex / cohort skill locationShared .agents/skills/Matches official docs and the cross-tool standard; serves multiple tools at once.
Config-merge / MCP serversOut of scope (file-drop only)MCP/Zed-profile/Goose/Crush config is a merge into a shared file, not a file install. It's a different engine and brushes the charter non-goal "managing LLM API keys" (MCP entries can carry secrets). Revisit deliberately if pursued.
Continue Hub (Reference)Out of scopeContinue has its own package manager; cmx would be redundant/parallel to it.

Phasing

  • Phase 1 (done): opencode, codex, pi (commit 308523f); then crush, amp, zed, openhands, hermes (commit 71021dd). All file-drop.
  • Phase 2 (not started): a Command artifact kind — portable markdown slash-commands (Amp .agents/commands/, Cline workflows, codex/Continue prompts). File-droppable and fairly portable.
  • Phase 3 (deferred, needs charter call): a ConfigMerge strategy + an McpServer artifact kind. Highest value for "manage the complexity," but a different engine and a scope expansion.

Known future snags

  • Cline breaks the shared-path pattern. It reads skills from .claude/skills / ~/.cline/skills, not .agents/skills. Adding Cline means a tool-specific skill target (and the .agents-cohort assumption no longer being universal).
  • Editor-embedded config is brittle to locate. Cline's MCP settings live under VS Code globalStorage/<publisher>/…, which varies per editor fork (Code, Insiders, VSCodium, Cursor, Windsurf). Any future config-merge work must detect the host editor.
  • Version-dialect drift. OpenHands has three coexisting skill layouts (.agents/skills, deprecated .openhands/skills, deprecated .openhands/microagents) and two frontmatter dialects. We target only the current standard.

Per-tool research summaries & sources

Each tool was researched against an identical capability template (identity, config dirs, agents, skills, commands, instruction files, MCP, plugin manifest, distinctive strengths, abstraction friction).

Implemented

Researched, not implemented

Source code map

  • cmx/src/platform.rs — the Platform enum and all per-tool knowledge (install_subpath, supports, agent_extension, transforms_agent_to_toml, slug, manifest_dir, targets).
  • cmx-core/src/agent.rs — markdown → Codex TOML transform (pure functional core, shared by cmx and cmf).
  • cmx/src/paths.rs — ConfigPaths::install_dir, installed_artifact_path (platform-aware filename), ensure_supports.
  • book/src/reference/platforms.md — user-facing platform path & lockfile tables.