Repository navigation
Add Cursor and OpenCode adapters - #25
Merged
Merged
Conversation
Cursor's agent hooks are a flat `hooks.json` of per-event command entries with a `matcher` field on the entry itself, and its permission-bearing hooks answer with a top-level `permission` of allow/deny/ask. That is close enough to the Codex adapter's config-file shape to reuse its installer idioms, but the event set and response schema are Cursor's own, so the parsing lives in a new module. The supported slice stays deliberately narrow: `beforeShellExecution` for commands Cursor does not sandbox, `preToolUse` matching `Write` and `Delete` for the mutating file tools, and `stop` for completion. Cursor runs the shell hook for every command rather than only when it would prompt, so this is a strict pre-tool gate with no native policy; sandboxed executions are skipped because Cursor never prompts for those. Cursor documents `preToolUse` `tool_input` as an open object, which for a write can hold the whole proposed file body, so the summary names the action from the tool type and speaks only a path it can resolve — never the argument object. `stop` carries no final assistant text, so completion is announced and no turn is ever continued. Every parse, transport, and broker failure emits no hook output, which is also Cursor's documented default for a failed hook. Wire format from https://cursor.com/docs/agent/hooks; fixtures in the tests are written from that reference rather than recorded from a pinned client release. Co-Authored-By: Claude Fable 5 <[email protected]>
Matches the existing integration UX: TapQ-managed entries only, unrelated hook data preserved, a restrictive backup before an existing file changes, and repair on rerun. Cursor has no hook-trust step and no local command that reports hook activation, so status validates TapQ's file layout and then states the documented client coverage instead of executing a Cursor binary. That coverage matters: the desktop app fires every installed hook, while `cursor-agent` does not fire `preToolUse`, leaving writes and deletes native there. Co-Authored-By: Claude Fable 5 <[email protected]>
Adds the integration-guide and CLI-reference sections plus a manual test plan modeled on the Codex one. The plan carries extra weight here: this adapter ships no versioned Cursor fixture corpus, so it is the only place the real payload shape is confirmed against a live client, and several cases ask the tester to record observed field names. Co-Authored-By: Claude Fable 5 <[email protected]>
OpenCode has no hook-registration file comparable to Claude Code's settings.json or Codex's hooks.json. Its documented extension surface is a JavaScript plugin loaded from the OpenCode configuration directory at startup, so the unit of installation is one file TapQ owns end to end. TapQOpenCodeAdapter therefore generates and installs that plugin, and tapq-opencode-hook is the executable edge it relays to. The plugin observes OpenCode's permission.asked and session-idle bus events, pipes a small TapQ relay envelope to the hook, and applies the returned decision through OpenCode's own permission API. All policy, broker authentication, and speech rendering stay in Swift, so the JavaScript layer carries no security-relevant logic and a bus-event rename is a one-line plugin edit. The plugin's permission.ask hook is deliberately unused: it is declared in @opencode-ai/plugin's types but no OpenCode code path triggers it (anomalyco/opencode#7006), so a plugin built on it would silently never run. Replies prefer POST /permission/{requestID}/reply and fall back to the deprecated session-scoped route on the injected SDK client. The plugin is embedded as source text rather than an SPM resource because package-runtime-app.sh assembles TapQRuntime.app from executables alone, so a resource bundle would be absent exactly where the installer normally runs. Embedding also makes install, repair detection, and idempotence a plain string comparison. The supported slice stays narrow. Allow becomes a one-time `once` reply and deny becomes `reject`; the remembered `always` reply is never sent, because a hands-free answer is scoped to the prompt it was spoken for. bash, edit, and webfetch get kind-specific speech from documented scalar metadata, with a URL contributing only its host, and every other kind is spoken from its name alone, so a permission's open-ended metadata object never reaches speech. There is no question interception or final-response continuation, because OpenCode exposes no equivalent surface. Every parse, transport, and broker failure applies no reply at all, leaving OpenCode's own prompt pending and answerable on screen. Co-Authored-By: Claude Fable 5 <[email protected]>
The command matches the existing integration UX: TapQ-managed content only, a restrictive timestamped backup before changing an existing file, atomic replacement, and repair-on-rerun for a plugin left stale by a moved checkout or a hand edit. A plugin whose contents already match is a byte-for-byte no-op that creates no backup. Because TapQ owns the whole file here rather than merging entries into a shared one, ownership is explicit: only a file carrying TapQ's marker is rewritten or removed. Install refuses to overwrite anything else at the managed path and reports it instead, and uninstall leaves such a file alone, so neighbouring plugins in the same directory are never touched. The default path resolves the OpenCode configuration directory the way OpenCode does — OPENCODE_CONFIG_DIR, then XDG_CONFIG_HOME/opencode, then ~/.config/opencode — and ignores a non-absolute value in either variable so the plugin cannot land somewhere OpenCode will not scan. Co-Authored-By: Claude Fable 5 <[email protected]>
package-runtime-app.sh now builds, copies, and signs tapq-opencode-hook alongside the other adapters, so the installer running from TapQRuntime.app can point at a hook that exists beside it. check-public-boundary.sh adds the new module to the portable target list. Co-Authored-By: Claude Fable 5 <[email protected]>
Adds an OpenCode section to the integration guide and the CLI reference, a manual test plan modeled on the Codex one, and the roadmap, README, and changelog updates. The docs are explicit about what the automated coverage does and does not claim: the process contracts drive the real hook against a real broker, but they do not start OpenCode, load the plugin into its runtime, or prove that OpenCode accepted the reply the plugin issues. The manual plan owns those boundaries, along with the once-per-turn completion announcement, the speech boundary for permission metadata, and answering on screen while TapQ is still speaking. Co-Authored-By: Claude Fable 5 <[email protected]>
Resolves the adjacent-entry conflicts between claude/cursor-adapter and claude/opencode-adapter: both adapter products, targets, CLI cases, docs sections, and packaging steps are kept, the README support sentence and integration help text now name all four agents, and the roadmap drops the superseded "Cursor next" item. Full suite passes: 1563 tests, 0 failures. Co-Authored-By: Claude Fable 5 <[email protected]>
The only conflict was the Unreleased changelog section; main's general entries now lead Added, followed by the Cursor and OpenCode adapter groups and main's Changed section. Full suite passes after the merge: 1583 tests, 0 failures. Co-Authored-By: Claude Fable 5 <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two new agent adapters connect Cursor and OpenCode to the existing agent-neutral broker, following the deliberately narrow slice the Codex adapter established: native approval prompts, completion announcements, and fail-open on every error path so the agent's own on-screen flow always remains in control.
Cursor (
TapQCursorAdapter,tapq-cursor-hook)tapq integration cursor install|status|uninstallmanages TapQ-owned entries in~/.cursor/hooks.json, preserving unrelated hook data and snapshotting an existing file to a restrictive backup before changing it.beforeShellExecutionfor non-sandboxed commands andpreToolUsefor theWriteandDeletefile tools;stopannounces completion. Cursor exposes no hookable question tool and no final assistant text onstop, so question routing is absent by design.docs/CURSOR_ADAPTER_MANUAL_TEST_PLAN.mdpins what to confirm against a real client (tool_inputkeys,agent_messagecasing,sandboxflag).OpenCode (
TapQOpenCodeAdapter,tapq-opencode-hook)tapq integration opencode installwrites one dependency-free plugin TapQ owns end to end at<config>/plugins/tapq.js; install refuses to overwrite a file TapQ did not write, and uninstall removes only TapQ's own plugin.permission.askedbus event and applies the hands-free answer through OpenCode's permission API — allow becomes a one-timeonce, deny becomesreject, and the rememberedalwaysreply is never sent. The documentedpermission.askplugin hook is deliberately unused: it is declared in@opencode-ai/pluginbut never triggered by OpenCode (permission.askplugin hook is defined but not triggered anomalyco/opencode#7006).1.18.15source; process-contract tests drive the real hook executable against a real broker, anddocs/OPENCODE_ADAPTER_MANUAL_TEST_PLAN.mdcovers the boundaries only a live OpenCode can prove.Verification
swift buildclean;swift test: 1563 tests, 0 failures (baseline 1455 onmain).scripts/check-public-boundary.shpasses;scripts/package-runtime-app.shbuilds and signs both new hook binaries intoTapQRuntime.app.The two adapters were developed on independent branches (
claude/cursor-adapter,claude/opencode-adapter) and merged here with conflicts resolved; per-adapter history is preserved in the merge.🤖 Generated with Claude Code