Sloop is multi-provider. All per-provider knowledge lives in one place: the adapter manifest. Every provider-aware feature reads it; no feature hardcodes a tool name.
The rule: to support a new AI CLI, add one manifest. Never special-case a tool in code.
- Built-in:
internal/adapter/builtin/<tool>.yaml, embedded in the binary (go:embed). - User overrides / new tools:
~/.sloop/adapters/<tool>.yaml. A file with the same key overrides the built-in; a new key adds a tool. Loaded byadapter.Load()(internal/adapter/adapter.go).
Manifests are sloop's provider knowledge / plugin layer, not user config. Users normally
never touch them (enabled tools are chosen in .sloop/config.yaml; see docs/reference/CONFIG.md).
name: Claude Code # display name
detect: claude # binary name used to detect installation
launch: claude # binary to launch
context: # how AGENTS.md context reaches the tool
mode: pointer # "pointer" (write a pointer file) | "native" (reads AGENTS.md itself)
file: CLAUDE.md # pointer file name (pointer mode only)
skills: # how .sloop/skills is delivered
target: .claude/skills # dir to symlink skills into; "" = tool has no skills dir
hooks: # status hooks for `sloop ps` (see docs/guide/USAGE.md)
config: .claude/settings.local.json # where the tool configures hooks (repo-relative or ~/…)
install: settings-json # installer strategy: "settings-json" | "" (manual)
docs: https://… # link to the tool's hook docs
notes: "" # caveats for manual setup
events: # the tool's event name for each sloop state
working: UserPromptSubmit # "" if the tool can't signal that state
waiting: Notification
idle: Stop
run: # `sloop run` model/effort knobs (all optional); see docs/design/run.md
vendor: anthropic # model vendor this CLI natively serves
default_for: [anthropic] # vendors this CLI is the home launcher for (bare `sloop run opus`)
model_flag: "--model" # how to pass a model; "" = no model selection
effort_flag: "" # how to pass reasoning effort; "" = unsupported
effort_values: {} # low|medium|high → the CLI's own token (e.g. -c model_reasoning_effort=high)
models: [opus, sonnet] # aliases for completion + bare-model resolution (forwarded as-is)
prompt: positional # how `run -t "task"` is passed: "positional" | a flag | "" (none)
account: # OPTIONAL: how a 2nd account is selected (powers `profile add --config-dir`)
config_dir_env: CLAUDE_CONFIG_DIR # env var that points the tool at an alternate config dir
default_dir: ~/.claude # the tool's standard config dir (source to share from)
share: [plugins, agents, commands, skills, CLAUDE.md] # safe to symlink into the 2nd account (tooling)
share_state: [projects, todos] # conversation/session state; opt-in, lets a 2nd account resume
heuristics: # OPTIONAL fallback status markers (see below); usually omit
waiting: ["shall I apply"] # tool-specific phrasing the cross-tool defaults miss
working: ["indexing repo"]
readiness: # OPTIONAL extra `sloop check` best practices, sourced from the
docs: https://… # provider's own guidance (cited so it's not sloop's opinion)
checks: # only filesystem-detectable practices; behavioral tips don't belong
- id: subagents
label: "Subagents folder (.claude/agents)"
kind: dir-exists # "file-exists" | "dir-exists" | "git-tracked"
path: .claude/agents # repo-relative
fix: "sloop init --scaffold"
optional: true # missing → advisory info (•), not a counted gap (✗)| Feature | Reads | Field |
|---|---|---|
detect / sloop tools |
detect.Tools |
detect |
sloop run launch |
run.go |
launch |
sloop run model/effort |
run.go (planLaunch/buildRunArgs) |
run.* |
sloop sync context |
sync |
context.mode/file |
| skills symlink | sync |
skills.target |
sloop hooks + status |
hooks.go |
hooks.* |
sloop ps status fallback |
tmux.ClassifyStatus |
heuristics.* (additive; see below) |
sloop check readiness |
check.go |
readiness.* (optional; provider-sourced) |
sloop profile add --config-dir |
account.go |
account.* (optional; 2nd-account dir + sharing). .credentials.json is never shared. |
| shell completion | completion.go |
manifest keys |
The runtime view of all of this is sloop tools (capability matrix:
KEY NAME INSTALLED CONTEXT SKILLS HOOKS).
Each tool's manifest declares an install strategy; sloop auto-installs the hook config when possible,
otherwise prints instructions. The manifest's events block maps sloop states (waiting, working,
idle) to provider events using EventSpec: {event: "ProviderEventName", matcher: "discriminator"}.
The matcher is the provider's native discriminator (e.g., Copilot's notification types
permission_prompt/agent_idle, or Claude's tool-use name for post-edit hooks). For simple events
with no sub-types, matcher is omitted. Reserved future fields: cadence, decision/failClosed
(for v0.2.0 workflow hooks).
settings-json: mergeevents → "sloop hooks emit <state>"into a JSON settings file (mergeSettingsHooks/installSettingsHooks). Used by claude and gemini (identical nestedhooks[event] = [{hooks:[{type,command}]}]shape). Written idempotently, never clobbering keys.cursor-json: merge into.cursor/hooks.json, Cursor's flatter shape ({version, hooks[event]=[{command}]};mergeCursorHooks/installCursorHooks). Used by cursor. Note: Cursor has no clean "blocked on user" event (beforeShellExecutionfires on every command), so its manifest leaveswaiting: ""and the pane heuristic covers waiting.copilot-json: sloop owns the entire file~/.copilot/hooks/sloop.json(whole-file write;installCopilotHooks). Used by copilot. Copilot loads all*.jsonin~/.copilot/hooks/, so sloop gets a dedicated file rather than merging into a shared config.codex-toml: claims codex's singlenotifyslot in~/.codex/config.tomlonly when free; occupied → prints chaining guidance (installCodexHooks). Used by codex. Codex has onenotifyprogram for all events, so the installer writessloop hooks notify codex(a hidden router that dispatches toemit <state>based on the event payload).""(manual): no safe auto-writer yet;sloop hooks print <tool>shows the exact event→command wiring andsloop hooks listmarks itprint+paste. agy is the only current builtin that stays manual (it has no hooks at all).
Strategies dispatch through hookInstaller(strategy): add a case there + an install…Hooks
writer to teach a new config format.
sloop ps classifies each session as waiting / working / idle. The precise signal is the
hooks above (the tool tells sloop its state). Heuristics are the fallback when hooks aren't
installed: a non-invasive read of the tool's own pane text.
Most prompt conventions ((y/n), press enter, esc to interrupt, spinners, tokens) are
cross-tool defaults baked in once (defaultWaitingMarkers/defaultWorkingMarkers in
internal/tmux/status.go) and applied to every manifest. They are terminal conventions, not
per-provider knowledge, so they live in code, not duplicated into each YAML.
A manifest's optional heuristics block is additive on top of the defaults; add markers only
for phrasing a specific tool uses that the defaults genuinely miss. If a tool needs none (most don't,
e.g. claude), omit the block. Don't re-list default markers. If you want precision, install the
tool's hooks rather than growing heuristics.
- Drop
internal/adapter/builtin/<tool>.yaml(or~/.sloop/adapters/<tool>.yaml) with the schema above. Sethooks.events/installfrom the tool's hook docs. - Verify with
sloop tools(capabilities) andsloop hooks print <tool>. - If its hooks use the settings.json shape,
install: settings-jsonmakes it auto-install for free. Otherwise leaveinstall: ""and it's print-and-paste until a strategy is added.
No Go code changes are required for a new provider unless it needs a brand-new hook install strategy.