Custom Adapters
Add support for a new AI coding CLI by writing a declarative adapter — no app build required.
Gent talks to AI coding CLIs (Claude Code, Codex, Copilot, …) through adapters. An adapter describes how to launch a CLI, what to show in its model / effort / slash-command pickers, how to label its tools, and how to turn its output into Gent's chat. Most of an adapter is data, not code — so you can add a new CLI by writing a JSON file.
Custom adapters are part of Gent's runtime-extensible adapter system. This page documents the authoring format; availability ships with the rollout of the new adapter engine.
How it works
Two things are always true:
- The desktop host runs the adapter. A paired phone is a viewer — it never spawns a CLI; it just mirrors what the host produces. Get it working on your Mac/PC and it works on your phone automatically.
- Pure-data adapters can't execute anything. When a CLI genuinely needs parsing code, that code runs in a sandbox (WASM) or as a signed first-party driver — never as unsandboxed downloaded code.
Pick a tier
How you build the adapter depends on how the CLI talks.
Declarative (most CLIs)
The CLI prints a simple format (e.g. one JSON object per line). You write only a JSON spec. No app build.
Sandboxed WASM
The CLI needs real parsing code. You ship a JSON spec plus a .wasm module that runs in a sandbox on the host. No app build.
Bundled driver
Rare: a protocol needing full host trust. Requires a first-party code change shipped in an app release.
Write a declarative adapter
Create the spec
A complete, working adapter for a CLI that emits one JSON object per line:
{
"id": "acme-cli",
"name": "Acme Coder",
"version": "1.0.0",
"schemaVersion": 1,
"minAppVersion": "0.0.49",
"spawn": {
"command": "acme",
"args": [
{ "flag": "--model", "value": "{model}", "when": { "var": "model", "op": "set" } },
{ "flag": "--json" },
{ "value": "{prompt}" }
],
"oneShot": false
},
"models": [
{ "id": "acme-small", "label": "Acme Small" },
{ "id": "acme-large", "label": "Acme Large" }
],
"tools": {
"categories": { "edit": ["write_file"], "read": ["read_file"], "bash": ["run_shell"] },
"display": { "read_file": { "label": "Read $path" } }
},
"driver": { "type": "stdio_json_lines" },
"parsing": [
{ "match": { "jsonPath": "type", "equals": "assistant_text" },
"extract": { "text": ["text"] },
"emit": { "event": "assistantDelta", "text": "$text" } },
{ "match": { "jsonPath": "type", "equals": "tool_call" },
"extract": { "toolName": ["tool"], "input": ["args"] },
"emit": { "event": "toolStart", "toolName": "$toolName", "input": "$input" } },
{ "match": { "jsonPath": "type", "equals": "tool_result" },
"extract": { "id": ["call_id"], "output": ["result"] },
"emit": { "event": "toolDone", "toolId": "$id", "output": "$output" } }
]
}Install it with Add Adapter
Open Add Adapter in Gent, paste your adapter JSON (or a GitHub repo URL). Gent validates it and — if it spawns a command — shows you the exact command before anything runs. Approve to install.
Test it
Start a new agent chat with your adapter selected and send a prompt. Your model picker, tools, and messages should appear just like a built-in CLI.
Models, effort, and slash commands
These are catalogs — lists that drive the pickers. The picker shows the label; the
id is what gets sent to the CLI. Omit a catalog and its picker disappears.
"models": [
{ "id": "acme-large", "label": "Acme Large", "contextWindow": 200000 }
],
"effortLevels": [
{ "id": "low", "label": "Low", "flag": "--effort" },
{ "id": "high", "label": "High", "flag": "--effort" },
{ "id": "xhigh", "label": "Extra High", "flag": "--effort" }
],
"slashCommands": [
{ "command": "/review", "description": "Review the current diff",
"action": "prompt", "promptStarter": "/review " }
]When the user picks a model or effort, its id becomes the variable {model} / {effort}.
You choose where it goes — and that depends on the CLI:
Most CLIs take model/effort as command-line arguments. Reference the variable in
spawn.args. Changing the selection respawns the process.
"args": [
{ "flag": "--model", "value": "{model}", "when": { "var": "model", "op": "set" } },
{ "flag": "--effort", "value": "{effort}", "when": { "var": "effort", "op": "set" },
"validValues": ["low", "medium", "high", "xhigh", "max"] }
]validValues rejects an unknown effort; when skips the flag when nothing is selected.
CLIs like Codex/Copilot accept model/effort with each turn, so they can change
mid-session without restarting. Set perTurnSettings and put the variables in the
message template instead of the spawn args.
"capabilities": { "perTurnSettings": true },
"encoding": {
"mode": "jsonrpc",
"userMessage": {
"method": "turn/start",
"paramsTemplate": { "model": "{model}", "effort": "{effort}", "prompt": "{prompt}" }
}
}The models list is a fallback. If your CLI can report its real models at runtime, the
driver uses those and overrides the list.
Turning output into chat
The parsing rules run against each line of the CLI's output; the first match wins. The
vocabulary is small on purpose — match, extract, emit, a shallow setFlag, and
escalate:
match— pick lines by JSON path, value, regex, or field presence.extract— pull named fields (with ordered fallbacks).emit— produce a Gent event (assistantDelta,toolStart,toolDone,turnComplete, …).escalate— for anything stateful, hand off to a WASM or bundled driver instead of growing the rules.
If your CLI needs real, cross-line parsing logic, that's the cue for a sandboxed WASM
adapter (driver.type: "wasm" with a hash-pinned module) rather than stretching the
declarative rules.
Publish to the registry
To share an adapter with everyone, open a pull request to mqtik/mate-adapters adding
your specs/<id>.json (and the .wasm for a WASM adapter). Once merged and signed, every
Gent install picks it up on the next refresh — no app update. Fixes ship as a new immutable
version, so rollback is a one-line change.
Tips
- Namespace your
id; the built-inclaude/codex/copilotids are protected. - Put every tool the CLI emits into
tools.categories, or it shows up uncategorized. - Tool categories can tighten approval, never relax it — unknown tools always ask.
- A bad or missing adapter degrades to a clear "needs update" state; it can't break the app.
See also: Custom Connectors for adding MCP servers (a different extension point — tools for an agent, rather than a new CLI).