npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

cursor-oauth-opencode

v0.22.1

Published

OpenCode plugin that connects Cursor's API to OpenCode via OAuth, model discovery, and a local OpenAI-compatible proxy.

Readme

cursor-oauth-opencode

OpenCode plugin that connects to Cursor's API, giving you access to Cursor models inside OpenCode with full tool-calling support.

Install

npx cursor-oauth-opencode setup --global
opencode auth login --provider cursor

For project-local setup:

npx cursor-oauth-opencode setup --project
opencode auth login --provider cursor

The setup command is idempotent. It adds the npm plugin entry and fallback model registries for both provider.cursor and provider.cursor-code in opencode.json, preserving existing user model overrides. Login stays on the primary provider only (opencode auth login --provider cursor).

The package supports the OpenCode v1 hooks loader and the unreleased OpenCode v2 plugin API from one install. Its ./server entry is dual-shaped: v1 invokes server, while v2 schema-decodes and invokes setup. OpenCode v2 normalizes the legacy plugin / provider config keys used by setup, so the same config works on both runtimes. v2 support remains provisional until upstream ships a versioned release.

Providers

The plugin registers two OpenCode providers that share one OAuth login, proxy, and model catalog. Mode is selected in the model picker:

| Provider | Picker path | Behavior | |----------|-------------|----------| | Cursor | cursor/<model> | OpenCode-backed native tools plus residual MCP tools (code mode OFF) | | Cursor Code | cursor-code/<model> | Single code sandbox tool (code mode ON) |

Breaking default: cursor/* is native-tools / code mode OFF. To keep the previous sandbox-collapse behavior, pick cursor-code/<model>. There is no auto-rewrite of a previously selected cursor/* model.

Fleet-wide kill switch (forces OFF even for cursor-code/*): set CURSOR_OPENCODE_CODE_MODE=0 or provider.cursor.plugin.codeMode.enabled: false. That knob is read only under provider.cursor.plugin — a block under provider.cursor-code.plugin has no effect.

OpenCode skill routing (lowercase skill tool) is on by default and constrained to the system <available_skills> catalog. Disable with CURSOR_OPENCODE_SKILLS=0 or provider.cursor.plugin.skills.enabled: false. Missing/empty/malformed catalogs fail closed (skill tool stripped), not free-string.

Normal cursor/* Runs derive one capability plan from the current OpenCode tool catalog. Exact, schema-compatible built-ins use Cursor's fixed native protocol; tools without a safe native equivalent remain ordinary MCP declarations:

| OpenCode tool | Cursor-native carriers | |---------------|------------------------| | read | Read, Ls, PI Read, PI Ls | | grep | Grep, PI Grep | | bash | Shell, streaming shell, mini-SWE bash, PI Bash | | glob | Glob (grepArgs files-with-matches carrier) | | question | AskQuestion | | task | Task/subagent with OpenCode's dynamic agent catalog | | lsp | ReadLints diagnostics capture facade (production gate closed); general LSP stays MCP-visible | | apply_patch | Delete only; full apply_patch remains MCP-visible |

Promotion requires the exact lowercase built-in name, its trusted OpenCode schema, and one unambiguous owner. Aliases, namespaced tools, incompatible schemas, and custom/plugin tools stay MCP. A Run has zero MCP declarations only when it has no residual tools. OpenCode still executes every mapped operation and owns its permission checks.

Native Glob uses Cursor's live globToolCall, which executes through a glob-shaped grepArgs / grepResult wire while OpenCode still owns the exact glob operation. The protocol's fetchToolCall has a complete fetchArgs / fetchResult client Exec pair, but grok-4.5-fast low does not list or emit it even when explicitly allowed, so webfetch remains MCP-visible. The separate approval-only webFetchToolCall also stays disabled because it cannot carry OpenCode's result. Native Task is enabled only when the exact task description contains OpenCode's parseable, sorted dynamic agent catalog; otherwise task stays MCP-declared. The capture-only ReadLints facade requires a canonical lsp schema with a diagnostics operation and maps zero-based ranges, LSP severity 1-4, file/permission/error variants, and exact Exec/tool identities to Cursor's typed diagnosticsResult. Production promotion remains disabled: OpenCode 1.18.4 exposes no public diagnostics operation, and the pinned live Grok low probe completed readLintsToolCall without sending the client diagnosticsArgs/diagnosticsResult handoff. PI Edit stays disabled in transparent mode: two low-effort grok-4.5-fast attempts produced MCP or no ToolCall when it was allowed, and hiding raw apply_patch would remove add, move, and multi-file operations that PI Edit cannot represent. Standard editToolCall stays disabled because it has no client Exec/result lane. Default write remains MCP because live capture produced only unpaired standard writeArgs/writeResult traffic toward Cursor agent-tools, with no complete standard or PI ToolCall identity and target sequence.

Cursor-native WebSearch is enabled only when OpenCode did not advertise its own lowercase websearch tool. cursor-code/* is unchanged: its protocol allowlist remains exactly the single MCP code tool.

Reasoning variants

Reasoning-tier Cursor models are listed once in the picker; effort is chosen in OpenCode's Select variant menu (not as separate model slugs):

| Picker model | Variants | Notes | |--------------|----------|--------| | grok-4.6 / grok-4.6-fast | low / medium / high / xhigh | Cursor default is high; Fast is a separate picker family (2× price). Wire slugs are cursor-grok-4.6-{effort}[-fast] | | grok-4.5-fast | low / medium / high | Cursor slug suffixes are shifted (high…-xhigh); non-fast grok-4.5 is omitted | | claude-4.6-opus, gpt-5.4, glm-5.2, … | only levels Cursor exposes | e.g. opus → high, gpt-5.4 → medium | | composer-2.5 / composer-2.5-fast | default only | Non-effort axis; stay as separate models |

Legacy full slugs (grok-4.5-fast-high, …) still resolve as aliases. After upgrade, re-run setup so opencode.json seeds the collapsed base ids.

Manual config

If you do not want to use the setup command, add this to ~/.config/opencode/opencode.json:

{
  "$schema": "https://opencode.ai/config.json",
  "plugin": [
    "cursor-oauth-opencode@latest"
  ],
  "provider": {
    "cursor": {
      "name": "Cursor",
      "npm": "@ai-sdk/openai-compatible",
      "api": "http://127.0.0.1:65535/v1",
      "models": {
        "composer-2.5-fast": {
          "name": "Composer 2.5 Fast",
          "reasoning": true,
          "temperature": true,
          "attachment": false,
          "tool_call": true,
          "limit": {
            "context": 200000,
            "output": 64000
          }
        }
      }
    },
    "cursor-code": {
      "name": "Cursor Code",
      "npm": "@ai-sdk/openai-compatible",
      "api": "http://127.0.0.1:65535/v1/code",
      "models": {
        "composer-2.5-fast": {
          "name": "Composer 2.5 Fast",
          "reasoning": true,
          "temperature": true,
          "attachment": false,
          "tool_call": true,
          "limit": {
            "context": 200000,
            "output": 64000
          }
        }
      }
    }
  }
}

The fallback API URLs are placeholders so OpenCode can list the providers before login. After auth, the plugin starts a local proxy and replaces Cursor models with live Cursor model discovery. cursor-code must keep the /v1/code path suffix (merge-safety if a request falls back to provider.api).

OpenCode v2's config normalizer accepts this v1-shaped plugin / provider document and projects it into plugins / providers. If a v2-native plugins entry already registers this package, setup recognizes its string or { "package": "cursor-oauth-opencode" } form and does not add a duplicate.

Authenticate

opencode auth login --provider cursor

This opens Cursor OAuth in the browser. OpenCode v1 stores tokens in ~/.local/share/opencode/auth.json; v2 stores them as the cursor integration credential. Both refresh automatically, and both providers share that one Cursor login.

When refreshing a published plugin install, remove only ~/.cache/opencode/packages/cursor-oauth-opencode@latest. Do not delete ~/.local/share/opencode/auth.json; plugin cache cleanup does not require a new OAuth login.

Use

Start OpenCode and select a model under Cursor (native tools) or Cursor Code (local code tool). The plugin starts a local OpenAI-compatible proxy on demand and routes requests through Cursor's gRPC API.

How it works

  1. OAuth — browser-based login to Cursor via PKCE (cursor provider only).
  2. Model discovery — queries Cursor's gRPC API for all available models.
  3. Local proxy — translates chat completions into Cursor's protobuf/HTTP/2 Connect protocol. Path selects mode: /v1/* (native) vs /v1/code/* (sandbox).
  4. Code mode (cursor-code) — advertises only the MCP code tool to the Cursor model (allowed-tools hard-forced to mcpToolCall). The model writes one async JavaScript program as code({script,files?}); the proxy runs it in the in-proxy Node child sandbox (code-sandbox.mjs) and drives NDJSON client-tool waves (await tools.X(args) / callTool). Native read/list/grep/mutations are rejected with coaching that points back at code. The streaming tool-call accumulator is inert in this mode so the outer code name is never validated against the inner client catalog. cursor/* uses the request-aware native mapping above and declares only residual tools through MCP.
  5. Request continuity — every plugin window and OpenCode session carries an immutable proxy scope. Parked tool calls route by session plus pending call ids. A valid Cursor checkpoint is authoritative when workspace identity and client-history prefix match; missing, corrupt, incomplete, cross-workspace, or history-diverged checkpoints append one model-visible recovery marker and rebuild from the client ledger/history instead of borrowing another workspace's state.

Code mode kill switch: provider.cursor.plugin.codeMode.enabled (default true). Set false or CURSOR_OPENCODE_CODE_MODE=0 to force normal mode even on /v1/code.

Rollback: disable code mode with provider.cursor.plugin.codeMode.enabled: false or CURSOR_OPENCODE_CODE_MODE=0 (normal Cursor tools only). Do not change Cursor headers, native tool allowlists, protobufs, or normal provider behavior as part of rollback.

HTTP/2 transport runs through a Node child process (h2-daemon.mjs / legacy h2-bridge.mjs) because Bun's node:http2 support is not reliable against Cursor's API.

Protocol version and tool policy

These knobs live under provider.cursor.plugin (env overrides last). They describe how this plugin talks to Cursor's private Agent Run protocol — that surface is not a stable public API and can change without notice.

| Knob | Default | Env | Behavior | |------|---------|-----|----------| | (client version) | manifest pin cli-2026.07.20-8cc9c0b | CURSOR_OPENCODE_CLIENT_VERSION | Advertised x-cursor-client-version is the checked-in protocol manifest pin, not "newest installed cursor-agent". The env value is an explicit development override only. | | requestContext.inline | true | CURSOR_OPENCODE_INLINE_REQUEST_CONTEXT | When on, the initial action and checkpoint ResumeAction carry the same immutable RequestContext snapshot (also used for refresh replies). Set false / 0 to omit both action carriers and roll back to refresh-only context. | | nativePolicy | transparent | CURSOR_OPENCODE_NATIVE_POLICY | transparent: exact compatible built-ins use request-bound native carriers and only residual tools are MCP-declared. dedupe-canary: preserve the earlier read/list/grep dedupe behavior. legacy: keep the complete client catalog MCP-visible as an operator or circuit-breaker rollback. Code mode always declares only code. | | codeMode.enabled | true | CURSOR_OPENCODE_CODE_MODE | true enables code mode on /v1/code; false / 0 forces normal mode even when path is /v1/code. | | (Run retries) | 3 | CURSOR_OPENCODE_RUN_RETRIES | Maximum automatic retries for zero-byte transport failures and retryable Connect errors. Before output, the original action is replayed; after output, a safe latest checkpoint is resumed with ResumeAction. Set 0 to disable. | | (retry backoff) | 500 ms | CURSOR_OPENCODE_RUN_RETRY_BACKOFF_MS | Delay before retrying resource_exhausted / unavailable Connect failures. |

Declaration vs execution catalogs: one immutable plan owns the model-visible residual MCP catalog, complete internal execution catalog, exact native bindings, RequestContext instructions, and allowed-tools header for a physical Run. Refresh, retry, and checkpoint resume reuse that snapshot.

Filesystem ownership: Cursor-native Read and Ls delegate to the advertised OpenCode read tool; Cursor-native Grep delegates to OpenCode grep. The proxy translates their results back into Cursor's native result messages but does not perform the operation. OpenCode therefore owns read, grep, and external_directory permissions, including for valid absolute paths outside the workspace.

Shell cwd boundary: existing model-supplied shell directories are canonicalized and passed to OpenCode, including external directories and symlink targets; OpenCode owns external_directory approval. Missing paths and files still fail as typed tool errors before process spawn. Missing absolute prefixes may use the workspace suffix rescue, and commands are never silently relocated.

Circuit breaker: if transparent or dedupe-canary sees an impossible unbound native exec, unhandled case, or duplicate/closed identity, new Runs in that proxy context fall back to legacy declarations. In-flight execs still finish with their matching native result/error type.

Exec lifecycle / cancel: each controlled exec gets a heartbeat while active, a typed result or throw, and exactly one streamClose. Client abort (Esc) sends protocol CancelAction and tears down the owned bridge, execs, timers, and code sandbox.

Tool ownership (normal cursor/*): OpenCode remains the operation and permission owner. Native dispatch uses the exact binding captured in the Run plan, never alias search over residual MCP declarations. Unsupported native-only options return case-correct typed errors rather than being dropped. Fetch, edit, full patch, resources, skill, todo, plan/general LSP, image/background bridges, and all custom tools stay MCP unless a separate contract-complete native adapter is released. A capture-only ReadLints diagnostics adapter is present but its production gate is closed; navigation, symbols, hover, and call hierarchy never use it. The native-skill decision, live candidate matrix, and reconsideration gate are documented in docs/native-skill-carrier.md. Unsupported cloud subagents, agent stores, SCM/PR/VM/environment mutation, MCP auth, custom modes, native background, image, and standard native Write remain typed rejections/errors instead of being advertised optimistically. Semantic code search remains the plugin tool cursor_codebase_search. It is local-always with a bounded local result on every call; Cursor cloud is optional semantic enrichment only if implementation is available after the concurrent lane and, until then, local remains the sole serving path. Cursor's semSearchToolCall carries args and result inline on server interaction updates (ToolCall field 16; args/result fields 1/2), but the pinned protocol has no matching ExecServerMessage request or ExecClientMessage result case. It therefore cannot transparently return the plugin-owned cloud/local result or preserve OpenCode permissions. Code mode (cursor-code/*) still advertises exactly one MCP code tool and hard-forces allowed-tools to mcpToolCall only.

Codebase search status: every result starts with these fields in order: Cursor index adapter session:, Cloud readiness:, Cloud freshness:, Cloud query outcome:, Serving source:, and Scope:. Readiness is durable cloud state (disabled, initializing, ready, degraded, or unavailable), while query outcome and serving source describe only the current call. A valid cloud query with zero hits therefore remains ready even when local fallback serves matches. Freshness becomes stale-after-local-change after a successful bootstrap followed by a relevant watcher change; watcher changes do not claim continuous cloud upload.

targetDirectories accepts existing workspace-relative directories. Cloud retrieval remains workspace-wide top-K and decoded paths are post-filtered, so scoped cloud calls report post-filtered cloud top-K. If every supplied target is invalid, search does not broaden to the workspace. Local scan, cloud upload, and returned cloud paths share the same exclusions: ignored build/dependency directories, every .env*, .npmrc, .pypirc, credential/secret JSON names, and private-key/certificate extensions (.pem, .key, .p12, .pfx, .crt, .cer, .der). Local evidence is always available and never depends on cloud; cloud failure never suppresses local results. When cloud enrichment is available after the concurrent lane, merged results deduplicate by canonical relative path and label source truthfully; until then, local remains the sole serving path.

Cloud bootstrap is adapter-owned and non-blocking for provider load. Concurrent callers join one flight, each RPC resolves the current OAuth token, successful ensure outcomes are memoized, transient failures retry after a bounded cooldown, and manager disposal aborts owned RPCs and waits. The local lane runs concurrently and does not block on cloud.

Search prompt isolation and compaction: codebase search is exposed only as the explicit cursor_codebase_search tool. The plugin does not add search guidance or search results to system prompts. After compaction, old Cursor checkpoints are reused only when compacted client history still has the checkpoint prefix and every system blob can be safely rebased to the current system prompt and workspace identity; otherwise state is rebuilt from the compacted summary and retained tail. Same-Run transport retry still uses only a safe latest checkpoint with ResumeAction; the plugin does not invoke Cursor pre-compact or summarization messages.

Tool bridges live only under provider.cursor.plugin.toolBridges:

| Bridge | Config | Env | |--------|--------|-----| | Generate image | toolBridges.generateImage.enabled (+ modelId) | CURSOR_OPENCODE_GENERATE_IMAGE, CURSOR_OPENCODE_GENERATE_IMAGE_MODEL | | Native Write | toolBridges.nativeWrite.enabled | CURSOR_OPENCODE_NATIVE_WRITE | | Conversation search (default on) | toolBridges.conversationSearch.enabled | CURSOR_OPENCODE_CONVERSATION_SEARCH | | Background shell | toolBridges.backgroundShell.enabled | CURSOR_OPENCODE_BACKGROUND_SHELL |

Conversation search is limited to OpenCode sessions in the current workspace; it serves title hits immediately and builds a bounded local message index in the background. It never searches another workspace or Cursor's cloud cache.

Current capture status (release truth): C-IMAGE is NO_IMAGE_CAPABLE_MODEL / no pass fixture; C-MEDIA has a valid pass fixture captured from installed OpenCode 1.18.4, but media projection remains immutable-gated off pending a reviewed release decision; C-WRITE is NATIVE_WRITE_WIRE_INCOMPLETE: standard writeArgs/writeResult pairs appeared, but no matching standard or PI ToolCall start/completion identity appeared, and the emitted writes targeted Cursor agent-tools paths outside the isolated workspace with nonmatching bytes. Flags do not bypass those gates — cursor_generate_image and native Write remain unregistered / runtime-unavailable, and native media projection remains disabled, even if the flags are set true. C-SEM-SEARCH was attempted through both a direct AgentService Run and pinned Cursor Agent 2026.07.20-8cc9c0b with only semSearchToolCall allowed on grok-4.5-fast low. The account usage gate stopped both attempts before any ToolCall, so the fixture is unsupported, not a fabricated wire pass. Native SemSearch remains disabled unless a future pinned descriptor and live capture prove a client Exec/result handoff that can carry the exact plugin result.

Opt-in MCP background pair (toolBridges.backgroundShell.enabled / CURSOR_OPENCODE_BACKGROUND_SHELL=1): registers cursor_background_shell and cursor_await. Requires OpenCode bash permission (and external_directory when the cwd is outside the worktree). Output is <project>/terminals/<taskId>.txt. Defaults: 1 MiB output cap, 30 min runtime (hard max 6 h), 8 jobs/session, await up to 120 s (default 30 s), completed-job TTL 6 h. Await is same-session only; cancel/dispose tears down the process group. Registry is process-local — OpenCode/plugin restart loses in-flight jobs. Native Cursor background/Await stay disabled regardless.

Prompt shaping (caveman + reasoning containment)

On every outbound Cursor Run the proxy can:

  • Caveman-compress tool-description prose and the OpenCode system prompt (plugin.caveman.level / CURSOR_OPENCODE_CAVEMAN, default full; system inherits via plugin.caveman.system / CURSOR_OPENCODE_CAVEMAN_SYSTEM).
  • Append a verbatim reasoning guard for runaway-reasoning models (claude-fable-5, claude-opus-4-8; CURSOR_OPENCODE_REASONING_GUARD=0).
  • Inject a coalesced harness appendix (interstitial text, overthink <system-reminder>, throttled continuity, single-shot [reasoning tail] / [thinking exhaustion] notes) for those same models.

Savings counters live under CURSOR_OPENCODE_SAVINGS=1 (plugin log file).

Architecture

OpenCode v1 ./server `server` --> hooks (`auth.loader`, `chat.params`)
OpenCode v2 ./server `setup`  --> domains (`integration`, `catalog`, `session`)
                                  |
                                  v
OpenCode  -->  /v1/chat/completions       (cursor = code OFF)
          -->  /v1/code/chat/completions  (cursor-code = code ON)
                                               |
                                    Bun.serve (proxy)
                                              |
                                    Node child (h2-daemon / h2-bridge)
                                              |
                                     HTTP/2 Connect stream
                                              |
                                     api2.cursor.sh gRPC
                                       /agent.v1.AgentService/Run

Both current loaders prefer one dual ./server module; the root export remains an older-v1 fallback. The shared runtime lives in src/plugin-core.ts; src/index.ts is the thin v1 adapter and src/v2-entry.ts is the v2 Promise-domain adapter. v2 pins the live proxy URL through session.hook("model.request"), so it does not use the v1 :65535 fetch rewrite. The current v2 plugin tool context exposes neither the v1 permission prompt nor abort/directory fields, so v2 registers only the read-only cursor_codebase_search plugin tool; permission-sensitive background-shell and image bridges remain v1-only until upstream exposes an equivalent contract. v2 currently uses the host process cwd as workspace identity because its plugin context does not expose directory/worktree.

Tool call flow (code mode, cursor-code)

1. Proxy advertises ONE code tool to the Cursor model (its description embeds
   OpenCode's real tool catalog)
2. Model emits code({script}); proxy runs the script in a Node child sandbox
3. Each await tools.X() wave -> proxy emits OpenAI tool_calls SSE, parks BOTH
   the Cursor bridge and the sandbox
4. OpenCode executes the tools, sends results in the follow-up request
5. Proxy feeds results to the parked sandbox; the next wave repeats step 3
6. Script returns -> its value becomes the code tool result; the Cursor bridge
   resumes and streams the model's final answer

With cursor/* (or the kill switch forcing OFF) the proxy passes each client tool through directly: the model calls a tool via mcpArgs, the proxy emits OpenAI tool_calls, pauses the H2 stream, and resumes with mcpResult on the follow-up request.

OpenCode todowrite deliberately stays on that MCP path. Cursor's native updateTodosToolCall and readTodosToolCall carry their arguments and results inside interaction updates and mutate Cursor's checkpoint-owned todo state; the current protocol exposes no matching client Exec request/result pair. The plugin therefore never hides OpenCode todowrite, fabricates native results, or maintains a second todo ledger. Re-run the fail-closed live ownership probe after a pinned protocol upgrade:

bun scripts/probe-tool-bridges.ts --case native-todo --model grok-4.5-fast --effort low

The expected current-build outcome is a nonzero exit plus a sanitized native-todo-sequence.json fixture documenting CURSOR_OWNS_TODO_STATE_NO_CLIENT_HANDOFF.

Develop locally

bun install
bun run build
bun run test
bun run test:serial  # serial diagnostics
bun run test:smoke   # parallel smoke groups only

The default test gate runs quality fixtures (including protocol-snapshot / proto:check) alongside a parallel repository-gate phase: header/surface parity plus seven Bun smoke groups (five-way concurrency). Tests sharing module state remain serial within a group. Run one smoke group with bun run test:group <name>; names: core, index-context, code-mode, streaming-proxy, streaming-lifecycle, transport, utilities.

Logs

Plugin diagnostics never print to the terminal (they would paint over the OpenCode TUI). They land in ~/.cache/cursor-oauth-opencode/plugin.log. Override the path with CURSOR_OPENCODE_LOG_FILE; set CURSOR_OPENCODE_LOG_STDERR=1 to also mirror lines to stderr when debugging outside the TUI.

Requirements