Add a New Vendor Adapter
Use this skill when ynh needs to support a new AI coding CLI that has no
internal/vendor/<name>.go yet — e.g. adding GitHub Copilot CLI, Gemini CLI,
or any future vendor. If the vendor already has an adapter and you're just
reacting to a spec change, use the vendor-adapters skill instead — it owns
the per-vendor documentation links and format-mapping tables that this skill
depends on.
Before writing code: research
- Research the vendor CLI's actual behavior — install method, config file layout (project-level and user-level), plugin/extension system (if any), skills/agents/rules/commands support, MCP support, hooks, instructions file, and CLI flags for interactive/non-interactive/initial-prompt launch. Cite official docs, not blog posts, wherever possible — vendor CLIs change fast and secondary sources go stale.
- Add a new section to
.claude/skills/vendor-adapters/SKILL.md's documentation-links table and format-mapping tables for the new vendor. That skill is the single source of truth for vendor doc links — do not duplicate them here. - Decide the vendor's launch strategy up front — this drives most other
decisions:
- Native plugin loading (like Claude's
--plugin-dir):syscall.Exec, no symlinks,NeedsSymlinks() == false. - No native plugin loading (like Codex, Cursor): symlink install into
the project's vendor config dir + managed child process with signal
forwarding,
NeedsSymlinks() == true.
- Native plugin loading (like Claude's
Checklist: files to touch
Every one of these was touched when Cursor was added; treat it as the
canonical touch-list. Search the codebase for cursor (case-insensitive) at
any point to cross-check completeness — grep -rli cursor --include="*.go" --include="*.md" .
Core adapter
-
internal/vendor/<name>.go— new file implementing the fullAdapterinterface (internal/vendor/adapter.go). Self-register ininit()viaRegister(&<Name>{}). No other wiring needed for the adapter itself to be reachable by name. -
internal/vendor/<name>_test.go— table-driven tests for every method, matching the depth ofinternal/vendor/cursor_test.go. - If the vendor needs symlinks, reuse
installSymlinks/cleanSymlinksfrominternal/vendor/symlinks.go— don't reimplement. - If the vendor launches as a managed child process, reuse
runChildProcessfrominternal/vendor/process.gofor signal forwarding.
Exporter (vendor-native plugin distribution)
-
internal/exporter/exporter.go— add the vendor's export layout (skills/agents/rules/commands directory names may differ from runtimeArtifactDirs()— see Codex's.agents/skills/vs runtime.codex/). -
GeneratePluginManifestininternal/vendor/<vendor>.go— the vendor-native plugin manifest. There is nointernal/exporter/manifest.go; the exporter calls the adapter (exporter.go:190and:330), and so does marketplace generation (marketplace.go:305). One implementation serves both.
Marketplace
internal/marketplace/marketplace.go— add the vendor to the default vendor list inBuild(search for[]string{"claude", "cursor", "codex", "copilot"}).GenerateMarketplaceIndexininternal/vendor/<vendor>.go— the index content itself, called frominternal/marketplace/index.go:41. Each adapter declares its own anonymous structs;Codexis the worked example of a schema that genuinely differs (interface.displayName,source{source,path},policy.installation).Do not copy Claude's implementation and rename it.
Cursordid exactly that, and shipped the wrong shape until ynh#301 was fixed: Cursor's documented format nestsdescriptionundermetadata, carries no per-pluginversion, and uses a bare"plugin-name"source, none of which the copied implementation had. Nothing caught it, because it compiled and produced valid JSON — it was just the wrong vendor's JSON.Write it from
references/<vendor>.md, which is hand-tested, and check the result against that file rather than against another adapter.Note
codexMarketplaceJSONinmarketplace.gois a test unmarshalling helper, not the generator — do not add a sibling struct there expecting it to produce output.
ynh agent run backend (if the CLI supports scriptable turn-by-turn sessions)
-
internal/agent/<name>.go— implementWorkerBackend(internal/agent/worker.go) if the vendor CLI can run non-interactively with session resume (seeinternal/agent/cursor.go,codex.go,claude.gofor the three existing resume strategies — they differ significantly, don't assume one pattern fits). -
internal/agent/loop.go— register inselectBackend. - This is a separate surface from
vendor.Adapter— a vendor can have one without the other, but if the CLI supports non-interactive prompting at all, prefer adding both in the same PR since they share research.
Docker image support
-
cmd/ynh/image.go— addCOPY --linkline for the new vendor's staged config, and confirm the default-vendor fallback logic still makes sense.
Documentation
-
docs/vendors.md— vendor capability table. -
docs/hooks.md— hook event mapping table. -
docs/mcp.md— MCP config format table. -
docs/marketplace.md— marketplace format table. -
docs/artifacts.md,docs/skills-standard.md— skills/agents/rules/ commands format notes if the vendor has quirks (e.g. Claude's plugin loader demoting certain frontmatter fields). -
docs/getting-started.md,docs/harness-engineering.md,README.md,AGENTS.md— mentions of the supported-vendor list. -
docs/tutorial/vendors-and-symlinks.mdand any other tutorial that enumerates vendors by name. -
docs/tutorial/manual-test-plan.md— add vendor-specific manual test steps. -
.github/CONTRIBUTING.md— the format-mapping tables in the "Vendor Adapters" section quote every vendor inline; add a column. -
.claude/CLAUDE.md,.claude/agents/ynh-contributor.md,.claude/agents/evals.md,.claude/skills/ynh-dev/references/architecture.md— project-level mentions of the vendor list.
Tests
- Unit tests alongside every changed file (errcheck is strict — check all returned errors).
-
test/e2e/— add the new vendor to the end-to-end matrix once the adapter is stable enough to exercise against real fixtures (this is the release gate, not a per-PR gate — see.github/CONTRIBUTING.md§ E2E test suite).
Order of operations
- Research + update
vendor-adaptersskill docs/tables (this makes the adapter code review-able against a written spec, not tribal knowledge). - Write the adapter + its test, get
make test FILE=./internal/vendorgreen in isolation. - Wire exporter + marketplace + docker image.
- Wire
ynh agent runbackend if applicable. - Update all documentation touch points.
make check(full CI: deps, format, lint, test, build).- Manual smoke test:
make install, create a throwaway harness,ynh run -v <name>against the real vendor CLI, confirm launch, skills/rules/MCP/hooks actually take effect. - Only then: commit, push, PR into
developper the branching model and pre-remote gates (.claude/rules/branching.md,.claude/rules/pre-remote.md).
Common pitfalls
- Don't force a lowest-common-denominator design. Per
.github/CONTRIBUTING.md§ Design Stance, "vendor-neutral" means every feature translates cleanly to every vendor — not that every vendor must implement every feature identically. It's fine forSupportsExportDelegates()orExportArtifactDirs()to return "not supported" for a vendor that genuinely lacks the concept; document the gap, don't fake support. - Don't assume the CLI's flags are stable. Vendor CLIs that are under
active development (weekly releases) explicitly warn that flags change —
verify against the CLI's own
--helpoutput at implementation time, not just docs pulled during research. - Don't invent a new artifact directory shape unless the vendor genuinely
requires it — reuse
DefaultArtifactDirs()(skills,agents,rules,commands) unless the vendor's own conventions differ enough to justify a customArtifactDirs()override (see Codex's.agents/skills/handling). - Skills-as-directories vs skills-as-files: some vendors (Claude, Cursor)
read
SKILL.mdunder vendor-owned directories; some read it under cross-vendor directories like.agents/skills/or even another vendor's directory natively (a vendor recognizing.claude/skills/directly, for example) — check whether the new vendor's own docs list other vendors' conventions as inputs it accepts natively before assuming it needs its own private copy.