opencode-fastdraw
v1.2.0
Published
Quick-draw model switching for opencode agents — bind models to roles/agents from the TUI, snapshot them as presets, preview, share, and hot-swap instantly
Maintainers
Readme
opencode-fastdraw
Quick-draw model switching for opencode agents. Bind any configured model to any agent — OMO roles (sisyphus, oracle, explore, …) and OMO task categories (deep, quick, ultrabrain, …), built-ins (build, plan, general), or your own custom agents — from a TUI dialog or agent tool calls. Snapshot bindings into named presets, preview them before applying, export/import them as portable JSON files, and hot-swap the whole setup instantly.
Built for heavy oh-my-openagent (OMO) setups, works with any opencode config.
Features
- Assign models from the TUI —
/fastdrawor<leader>m→ pick agent → pick model. Agents grouped by OMO Roles / OMO Categories / Overrideable / Custom; models grouped by provider. After each binding you land back on the agent list with the new model shown. - Presets — save the current assignment set as a named preset; loading a preset shows a full preview of every role's binding before you confirm.
- Export / Import — share presets as portable JSON files (
fastdraw-preset-<name>.json), or import a whole preset store at once. - Hot-apply —
fastdraw_*tools mutate the live config immediately for opencode-side agents (agents not in a freshly loaded preset revert to their original models). OMO roles/categories bind in OMO's own config file and take effect on next start. TUI changes persist and apply on restart. - Config-respecting — assignments live in
~/.config/opencode/.fastdraw.json; presets infastdraw-presets.json. Youropencode.jsoncis never modified by assignment — the only write path is load preset in a restore mode (global/original/path), and even then only for custom/built-in roles. OMO roles and categories are bound in~/.omo/omo.jsonc(oromo.json, matching OMO's detection order) and every file is backed up as<file>.bak-<timestamp>before being touched.
Install
Dual-file registration (read this first)
opencode loads plugins from two separate config files. FastDraw has a server part (config hook + fastdraw_* tools) and a TUI part (the /fastdraw command + <leader>m keybind). Both files must list the plugin — if only one does, the other half loads silently absent (commonly the TUI half: tools keep working but /fastdraw and the keybind vanish).
| Config file | Loads | What you lose if missing |
|---|---|---|
| ~/.config/opencode/opencode.jsonc (or .json) | server plugins | fastdraw_* agent tools |
| ~/.config/opencode/tui.json | TUI plugins | /fastdraw command + <leader>m keybind |
Every install method below writes to BOTH. When registering manually, add the same entry to both files' "plugin" arrays.
1. npm (recommended)
Add the plugin to plugin in both files — opencode installs and caches the package itself at startup; there is no separate install step, and npm install -g is not a discovery path (opencode never scans the global npm prefix):
// ~/.config/opencode/opencode.jsonc
{ "plugin": ["opencode-fastdraw@latest", /* …your other plugins… */] }// ~/.config/opencode/tui.json
{ "plugin": ["opencode-fastdraw@latest"] }@latest never rots into a stale pin; if you prefer a frozen running surface, replace it with an exact version (note that a floating spec makes every startup re-resolve and execute potentially new code). Optional pre-warm for a first offline start: cd ~/.config/opencode && bun add opencode-fastdraw@latest.
Restart opencode after installing. The /fastdraw command and <leader>m binding appear in the TUI; the fastdraw_* tools become available to agents.
Usage
TUI
Press <leader>m or type /fastdraw:
FastDraw — Model Assignments & Presets
Assign Model Bind a model to an agent
Save Current as Preset Snapshot all current assignments
Load Preset Preview bindings, then apply
Import Preset from File Load preset(s) from a JSON file
Export Preset to File Share a preset as JSON
Delete Preset Remove a saved presetAgent tools
| Tool | What it does |
|---|---|
| fastdraw_assign | agent, model → bind (effective immediately) |
| fastdraw_remove | remove an agent's override, revert to default |
| fastdraw_list | show all agents and their current models |
| fastdraw_save_preset | name, description? → snapshot current assignments |
| fastdraw_load_preset | name → replace all assignments (hot-swap, with revert) |
| fastdraw_list_presets | list presets with full binding previews |
| fastdraw_delete_preset | name → delete a preset |
| fastdraw_export_preset | name, path? → write portable JSON |
| fastdraw_import_preset | path, name? → import from JSON (single or bulk) |
Ask any agent: "use fastdraw to put oracle on provider/model and save it as preset 'reasoning'".
Preset file formats
Bindings are stored in two sections: omo (standard OMO roles — sisyphus, oracle, explore, prometheus, …) and custom (your own agents plus opencode built-ins like build/plan/general). A preset saved on a machine with custom agents loads fine on a machine without them — missing custom roles are skipped with a warning listing exactly which roles were skipped.
Store (~/.config/opencode/fastdraw-presets.json):
{
"presets": {
"reasoning": {
"schemaVersion": 2,
"description": "Heavy reasoning setup",
"createdAt": "2026-08-18T10:00:00.000Z",
"omo": {
"oracle": {
"model": "provider/model",
"origin": { "layer": "state", "file": "${CONFIG_DIR}/.fastdraw.json" }
}
},
"custom": {
"my-custom-agent": { "model": "provider/model" }
}
}
}
}Portable export (fastdraw-preset-reasoning.json):
{
"fastdraw": 1,
"schemaVersion": 2,
"name": "reasoning",
"description": "Heavy reasoning setup",
"exportedAt": "2026-08-18T10:05:00.000Z",
"omo": { "oracle": { "model": "provider/model" } },
"custom": { "my-custom-agent": { "model": "provider/model" } }
}Each binding is { "model": "provider/model", "origin": … }; origin records where the binding came from as a portable placeholder path (${CONFIG_DIR}, ${PROJECT}, ${HOME}) so presets stay machine-portable. OMO-side bindings carry origin.layer: "omo" with the ~/.omo/omo.jsonc path — on load they are always restored into the OMO config regardless of restore mode. Legacy v1 presets — flat "agents": { "oracle": "provider/model" } or { "model": … } values — load fine and are normalized to v2 on the next save. Import accepts either format (single export or whole store); loading a preset reports origin resolution conflicts and shows exactly where each role would be written in restore modes.
How it works
Bindings are routed by where the role actually lives:
- OMO roles and categories (
oracle,deep, …) are defined in OMO's own config, not in opencode'sagentsection. FastDraw binds them by surgically editing~/.omo/omo.jsonc(comments and surrounding entries preserved). The two kinds follow different runtime rules (oh-my-openagent 4.19.4): categories resolve through theirmodelsarray —models[0]is the effective primary,models.slice(1)the fallback chain, and the legacymodel/fallback_modelskeys are ignored once the array exists. Every FastDraw category write therefore canonicalizes the entry in place: the chain is preserved, only its primary is swapped (an object head keeps its thinking/temperature/etc. settings), and the superseded scalar keys are dropped so nothing can drift. Agents consume only the.modelscalar — FastDraw changes exactly that and nothing else. The pre-fastdraw model ANDmodels[]chain are recorded in.fastdraw.jsonunderomo, so remove/preset-load restore the chain verbatim (canonical entry, no scalar keys).fastdraw_listflags hand-edited drift:⚠ legacy scalar/fallback_models — canonicalized to the chain on next applyfor categories,⚠ .models array on an agent — ignored at runtimefor agents. Writing these names into opencode'scfg.agentwould just create phantom roles next to the real ones — FastDraw never does that. If a project-level.omo/omo.jsoncshadows the user file you get a warning (project configs are never auto-edited). - Custom agents and opencode built-ins (
build,plan,general,agents/*.md) bind through opencode's config: the server plugin'sconfig()hook overlays~/.config/opencode/.fastdraw.jsonontocfg.agentafter other plugins load, and tool-triggered changes mutate the same live config object, sofastdraw_assign/fastdraw_load_presettake effect immediately. The first-seen model of every overridden agent is snapshotted so un-override restores it exactly.
State files written by older FastDraw versions (OMO roles sitting in the flat agents map) are migrated into the OMO config on first start. Config files on disk are touched only by OMO writes and by load preset in a restore mode, which first backs each file up as <file>.bak-<timestamp>.
Home-reachability & hardlink safety
Incident 2026-09-09 (wiki: dev/tools/omo-home-pollution-cadence): a Cadence installer set the Windows user HOME to Documents\Cadence\SPB_Data. OMO resolves its config home as env.HOME ?? env.USERPROFILE ?? cwd, so it silently stopped reading the ~/.omo/omo.jsonc that FastDraw and the user edit — and the user's hardlink mirror of that file was destroyed by editors' atomic save (write temp + rename = new inode), leaving two diverged configs.
FastDraw now guards both failure modes (warn-only, never blocks a write):
- Hardlink-aware writes. All config writes (
.fastdraw.json, preset store, exports,~/.omo/omo.jsonc, preset-restore targets) go throughsafeWriteFile: when the target hasnlink > 1it is rewritten in place, so link partners keep seeing the same content; unlinked files keep the atomic temp+rename path. Each linked path is recorded in~/.config/opencode/fastdraw-links.json. - Status warnings.
fastdraw_listand the TUI menu additionally show⚠ home-reach: ...lines whenHOMElooks hijacked (differs from the OS home andUSERPROFILE), when a managed OMO config sits outside the resolved home, or when a registered link partner vanished / changed inode / diverged from its mirror.
Tip: if an external tool hardlinks your OMO config, disable the editor's atomic save where offered (some tools call the option saveAtomic: false) — an atomic replace detaches every hardlink partner silently; FastDraw will at least flag it afterwards.
Development
npm install
npm test # esbuild-transpiles server.ts, runs preset round-trip tests
npm publish # after updating the versionLicense
MIT
