@vidit19sharma/opencode-cursor-auth 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +23 -8
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +112 -0
- package/dist/plugin.js +14 -1
- package/installation.md +12 -19
- package/package.json +8 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
4
4
|
|
|
5
|
+
## 0.3.2 — 2026-10-16
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `npx @vidit19sharma/opencode-cursor-auth setup` — adds the plugin to opencode config idempotently (`--dry-run`, `--config`).
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- The first request before `opencode auth login` hung in opencode's connect loop: without a `cursor` auth entry opencode skips the plugin's auth loader and dials the dummy `http://cursor.local/v1`. The plugin now seeds its auth entry (only when none exists), so the loader runs and returns a clear 401 telling you to run `opencode auth login`.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Docs no longer claim the Cursor CLI (`cursor-agent`) is required; `@cursor/sdk` talks to Cursor's backend directly.
|
|
18
|
+
|
|
5
19
|
## 0.3.1 — 2026-10-16
|
|
6
20
|
|
|
7
21
|
### Fixed
|
package/README.md
CHANGED
|
@@ -23,18 +23,22 @@ The plugin bridges opencode's OpenAI-style chat requests to the official [`@curs
|
|
|
23
23
|
## Requirements
|
|
24
24
|
|
|
25
25
|
- Node >= 22.13 (opencode runs plugins on Bun, which also works)
|
|
26
|
-
- [`cursor-agent` CLI](https://cursor.com/docs/cli) installed (`curl https://cursor.com/install -fsS | bash`) — the SDK's local executor uses it
|
|
27
26
|
- A Cursor subscription
|
|
28
27
|
|
|
29
28
|
## Install
|
|
30
29
|
|
|
31
|
-
|
|
30
|
+
All three methods end with `opencode auth login` (next section).
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
### 1. One command (npx)
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
npx @vidit19sharma/opencode-cursor-auth setup
|
|
35
36
|
```
|
|
36
37
|
|
|
37
|
-
|
|
38
|
+
Adds the plugin to the `plugin` array of your opencode config (`~/.config/opencode/opencode.json[c]`, or creates `opencode.json`); running it again is a no-op.
|
|
39
|
+
`--dry-run` shows the change without writing; `--config <path>` targets a specific file. A `.jsonc` file with comments is never rewritten — you get the one-line manual edit instead.
|
|
40
|
+
|
|
41
|
+
### 2. Manual
|
|
38
42
|
|
|
39
43
|
Add the plugin to `~/.config/opencode/opencode.json`:
|
|
40
44
|
|
|
@@ -44,7 +48,17 @@ Add the plugin to `~/.config/opencode/opencode.json`:
|
|
|
44
48
|
}
|
|
45
49
|
```
|
|
46
50
|
|
|
47
|
-
The plugin auto-registers
|
|
51
|
+
The plugin auto-registers the `cursor` provider — no manual provider config needed.
|
|
52
|
+
|
|
53
|
+
### 3. Let an AI agent do it
|
|
54
|
+
|
|
55
|
+
Paste this into any LLM agent:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
Install the @vidit19sharma/opencode-cursor-auth plugin and configure it by following: https://raw.githubusercontent.com/vidit19sharma/opencode-cursor-auth/main/installation.md
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
See [installation.md](installation.md) for the step-by-step agent instructions.
|
|
48
62
|
|
|
49
63
|
## Authenticate
|
|
50
64
|
|
|
@@ -115,7 +129,7 @@ The models cache is refetched once on upgrade.
|
|
|
115
129
|
## How it works
|
|
116
130
|
|
|
117
131
|
```
|
|
118
|
-
opencode ──(OpenAI chat JSON)──> plugin fetch() ──> @cursor/sdk Agent ──>
|
|
132
|
+
opencode ──(OpenAI chat JSON)──> plugin fetch() ──> @cursor/sdk Agent ──> Cursor backend
|
|
119
133
|
<─(SSE stream)──────── bridge <──(typed deltas: text / thinking / tool calls)
|
|
120
134
|
```
|
|
121
135
|
|
|
@@ -147,6 +161,8 @@ bun install
|
|
|
147
161
|
bun run build # tsc → dist/
|
|
148
162
|
bun run test # end-to-end bridge tests (requires Cursor login, uses real Cursor usage)
|
|
149
163
|
bun run spike # raw @cursor/sdk validation
|
|
164
|
+
bun run test:cli # exercises the npx setup command against temp config dirs (7 cases)
|
|
165
|
+
bun run smoke # packs the plugin and loads it in a pristine HOME (startup, auth login, first request before login)
|
|
150
166
|
```
|
|
151
167
|
|
|
152
168
|
The test suite runs 13 scenarios: plain streaming, a full tool loop on one held run, in-memory and disk-persisted session resume, AGENTS.md system-prompt delivery, mid-stream abort, error statuses, session scoping, context limits and key checks, model variants, the concurrency cap, retry semantics, and blocked models.
|
|
@@ -154,7 +170,6 @@ The test suite runs 13 scenarios: plain streaming, a full tool loop on one held
|
|
|
154
170
|
## Known limitations
|
|
155
171
|
|
|
156
172
|
- `systemPrompt` as a true harness replacement is feature-gated server-side by Cursor; system text is applied via `AGENTS.md` as a project rule (merged under Cursor's harness prompt).
|
|
157
|
-
- The SDK spawns a `cursor-agent` worker per turn (~1–2 s startup on resume).
|
|
158
173
|
- Cursor's catalog has no entitlement info, so models your plan or team blocks only disappear after the first 403.
|
|
159
174
|
- `limit.context` is per model, not per variant: picking a smaller-context variant (e.g. gpt-5.5 `272k`) keeps the base model's 1M limit in opencode's context indicator and auto-compaction.
|
|
160
175
|
- A run parked waiting for a tool result keeps its slot. Cursor-model sessions that spawn Cursor-model sub-agents nested deeper than `OPENCODE_CURSOR_MAX_ACTIVE` will stall; raise the limit if you fan out.
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
import process from "node:process";
|
|
6
|
+
const PKG = "@vidit19sharma/opencode-cursor-auth";
|
|
7
|
+
const ENTRY = `${PKG}@latest`;
|
|
8
|
+
const HELP = `Usage: npx ${PKG} [setup] [--dry-run] [--config <path>]
|
|
9
|
+
|
|
10
|
+
Adds "${ENTRY}" to the "plugin" array of your opencode config.
|
|
11
|
+
Idempotent: does nothing if any ${PKG} entry is already present.
|
|
12
|
+
|
|
13
|
+
--dry-run print what would change, write nothing
|
|
14
|
+
--config <path> edit this file instead of the auto-detected one
|
|
15
|
+
--help show this help
|
|
16
|
+
|
|
17
|
+
Config lookup: --config, $OPENCODE_CONFIG, then opencode.jsonc / opencode.json in
|
|
18
|
+
$OPENCODE_CONFIG_DIR, $XDG_CONFIG_HOME/opencode or ~/.config/opencode.
|
|
19
|
+
|
|
20
|
+
Exit codes: 0 ok or already installed, 2 cannot edit safely (manual edit printed), 1 error.`;
|
|
21
|
+
const NEXT = `Next steps:
|
|
22
|
+
1. Restart opencode
|
|
23
|
+
2. opencode auth login -> Cursor -> Log in with Cursor (browser)
|
|
24
|
+
3. opencode models | grep cursor/`;
|
|
25
|
+
const MANUAL = `Add this to the top-level "plugin" array of your opencode config (create it if missing):
|
|
26
|
+
"plugin": ["${ENTRY}"]`;
|
|
27
|
+
// Strips // and /* */ comments and trailing commas outside double-quoted strings; does not handle single-quoted strings or other JSON5 syntax.
|
|
28
|
+
const stripJsonc = (s) => s
|
|
29
|
+
.replace(/("(?:\\.|[^"\\])*")|\/\/[^\n]*|\/\*[\s\S]*?\*\//g, (_m, str) => str ?? "")
|
|
30
|
+
.replace(/("(?:\\.|[^"\\])*")|,(\s*[}\]])/g, (_m, str, close) => str ?? close);
|
|
31
|
+
function tryParse(s) {
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(s);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function main(argv) {
|
|
40
|
+
let dryRun = false;
|
|
41
|
+
let explicit;
|
|
42
|
+
for (let i = 0; i < argv.length; i++) {
|
|
43
|
+
const a = argv[i];
|
|
44
|
+
if (a === "--help" || a === "-h")
|
|
45
|
+
return console.log(HELP), 0;
|
|
46
|
+
else if (a === "--dry-run")
|
|
47
|
+
dryRun = true;
|
|
48
|
+
else if (a === "--config")
|
|
49
|
+
explicit = argv[++i];
|
|
50
|
+
else if (a !== "setup")
|
|
51
|
+
return console.error(`Unknown argument: ${a}\n\n${HELP}`), 1;
|
|
52
|
+
}
|
|
53
|
+
if (argv.includes("--config") && !explicit)
|
|
54
|
+
return console.error("--config needs a path"), 1;
|
|
55
|
+
const env = process.env;
|
|
56
|
+
const dir = env.OPENCODE_CONFIG_DIR || join(env.XDG_CONFIG_HOME || join(homedir(), ".config"), "opencode");
|
|
57
|
+
const file = resolve(explicit ||
|
|
58
|
+
env.OPENCODE_CONFIG ||
|
|
59
|
+
["opencode.jsonc", "opencode.json"].map((f) => join(dir, f)).find((f) => existsSync(f)) ||
|
|
60
|
+
join(dir, "opencode.json"));
|
|
61
|
+
const exists = existsSync(file);
|
|
62
|
+
const original = exists ? readFileSync(file, "utf8") : "";
|
|
63
|
+
const strict = exists ? tryParse(original) : { $schema: "https://opencode.ai/config.json" };
|
|
64
|
+
const config = (strict ?? tryParse(stripJsonc(original)));
|
|
65
|
+
console.log(`Config: ${file}${exists ? "" : " (will be created)"}`);
|
|
66
|
+
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
67
|
+
console.error(`Cannot parse ${file}; not editing it.\n${MANUAL}`);
|
|
68
|
+
return 2;
|
|
69
|
+
}
|
|
70
|
+
const leftovers = [];
|
|
71
|
+
const provider = config.provider;
|
|
72
|
+
if (provider && typeof provider === "object" && "cursor-acp" in provider)
|
|
73
|
+
leftovers.push(` - remove the "cursor-acp" block under "provider" in ${file}`);
|
|
74
|
+
const acp = join(dir, "plugin", "cursor-acp.js");
|
|
75
|
+
if (existsSync(acp))
|
|
76
|
+
leftovers.push(` - rm ${JSON.stringify(acp)}`);
|
|
77
|
+
if (leftovers.length)
|
|
78
|
+
console.log(`Notice: old open-cursor setup found (nothing was deleted). To remove it:\n${leftovers.join("\n")}\n - npm uninstall -g @rama_nigg/open-cursor`);
|
|
79
|
+
const plugins = config.plugin ?? [];
|
|
80
|
+
if (!Array.isArray(plugins)) {
|
|
81
|
+
console.error(`"plugin" in ${file} is not an array; not editing it.\n${MANUAL}`);
|
|
82
|
+
return 2;
|
|
83
|
+
}
|
|
84
|
+
if (plugins.some((p) => typeof p === "string" && p.startsWith(PKG))) {
|
|
85
|
+
console.log(`Already installed: ${PKG} is in "plugin". File left untouched.\n\n${NEXT}`);
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
if (strict === undefined) {
|
|
89
|
+
console.error(`${file} has comments or trailing commas; not rewriting it (that would drop them).\n${MANUAL}`);
|
|
90
|
+
return 2;
|
|
91
|
+
}
|
|
92
|
+
config.plugin = [...plugins, ENTRY];
|
|
93
|
+
const next = JSON.stringify(config, null, 2) + "\n";
|
|
94
|
+
if (dryRun) {
|
|
95
|
+
console.log(`Dry run: would ${exists ? "update" : "create"} ${file} with:\n${next}`);
|
|
96
|
+
return 0;
|
|
97
|
+
}
|
|
98
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
99
|
+
if (exists)
|
|
100
|
+
writeFileSync(`${file}.bak-cursor-auth`, original);
|
|
101
|
+
writeFileSync(`${file}.tmp`, next);
|
|
102
|
+
renameSync(`${file}.tmp`, file);
|
|
103
|
+
console.log(`Added "${ENTRY}" to "plugin"${exists ? ` (backup: ${file}.bak-cursor-auth)` : ""}.\n\n${NEXT}`);
|
|
104
|
+
return 0;
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
process.exitCode = main(process.argv.slice(2));
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
console.error(`Unexpected error: ${e instanceof Error ? e.message : e}`);
|
|
111
|
+
process.exitCode = 1;
|
|
112
|
+
}
|
package/dist/plugin.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { Cursor } from "@cursor/sdk";
|
|
2
2
|
import { errorResponse, handleChatCompletion } from "./bridge.js";
|
|
3
3
|
import { currentModels } from "./models.js";
|
|
4
|
+
import { readFile } from "node:fs/promises";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { join } from "node:path";
|
|
4
7
|
export { parseContext } from "./models.js";
|
|
5
8
|
const BASE_URL = "http://cursor.local/v1";
|
|
6
9
|
// Cursor publishes no output limit and the bridge never passes max_tokens to Cursor, so output is only an accounting reserve for opencode.
|
|
@@ -67,7 +70,17 @@ async function cursorFetch(input, init) {
|
|
|
67
70
|
}
|
|
68
71
|
return Response.json({ error: { message: `unsupported endpoint: ${url}` } }, { status: 404 });
|
|
69
72
|
}
|
|
70
|
-
|
|
73
|
+
async function seedAuthEntry(client) {
|
|
74
|
+
if (!client?.auth?.set)
|
|
75
|
+
return;
|
|
76
|
+
const file = join(process.env.XDG_DATA_HOME || join(homedir(), ".local", "share"), "opencode", "auth.json");
|
|
77
|
+
const entries = await readFile(file, "utf8").then(JSON.parse, (err) => (err.code === "ENOENT" ? {} : undefined));
|
|
78
|
+
if (!entries || typeof entries !== "object" || "cursor" in entries)
|
|
79
|
+
return;
|
|
80
|
+
await client.auth.set({ path: { id: "cursor" }, body: { type: "api", key: "cursor-sdk-store" } });
|
|
81
|
+
}
|
|
82
|
+
export const CursorAuthPlugin = async (input) => {
|
|
83
|
+
seedAuthEntry(input?.client).catch(() => { });
|
|
71
84
|
return {
|
|
72
85
|
config: async (config) => {
|
|
73
86
|
const providers = (config.provider ??= {});
|
package/installation.md
CHANGED
|
@@ -20,23 +20,9 @@ You should see `1.19` or newer. If older or missing, ask the user to install or
|
|
|
20
20
|
node --version
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
You should see `v22.13` or newer.
|
|
23
|
+
You should see `v22.13` or newer. `@cursor/sdk` requires Node >= 22.13. If older, ask the user to upgrade Node.
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
cursor-agent --version
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
If this prints a version, continue. If it fails with `command not found`, install it:
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
curl https://cursor.com/install -fsS | bash
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
Then open a new shell, or add `~/.local/bin` to `PATH`, and re-run `cursor-agent --version`.
|
|
38
|
-
|
|
39
|
-
A Cursor subscription (Pro, Business, or team) is required. `cursor-agent login` is not required (the plugin has its own login) but is harmless.
|
|
25
|
+
A Cursor subscription (Pro, Business, or team) is required.
|
|
40
26
|
|
|
41
27
|
## Installation
|
|
42
28
|
|
|
@@ -52,7 +38,15 @@ Skip this step if none of these exist.
|
|
|
52
38
|
|
|
53
39
|
### Step 2: Add the plugin to the opencode config
|
|
54
40
|
|
|
55
|
-
|
|
41
|
+
**Preferred, if you can run shell commands:**
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npx @vidit19sharma/opencode-cursor-auth setup
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
It adds the plugin idempotently (exit 0 = added or already present) and lists any open-cursor leftovers from Step 1 without deleting them. Exit 2 means it could not edit the file safely (e.g. a `.jsonc` with comments); it prints the exact edit, which you then make by hand as described below. Use `--dry-run` to preview.
|
|
48
|
+
|
|
49
|
+
**Fallback: edit the file by hand.** The config file is `~/.config/opencode/opencode.json`. Some users have `~/.config/opencode/opencode.jsonc` instead; edit whichever exists.
|
|
56
50
|
|
|
57
51
|
If neither exists, create `~/.config/opencode/opencode.json`:
|
|
58
52
|
|
|
@@ -123,9 +117,8 @@ The plugin is installed. Every model the user's Cursor plan includes is availabl
|
|
|
123
117
|
- **401 `Cursor SDK key missing/expired`**: run `opencode auth login`.
|
|
124
118
|
- **403 `permission_error` "Model Blocked"**: a team admin disabled that model for the account. It is hidden from `opencode models` from then on; delete `~/.cache/opencode-cursor-auth/models.json` to re-check.
|
|
125
119
|
- **400 `Cannot use this model`**: the model id is not in Cursor's catalog for this account.
|
|
126
|
-
-
|
|
120
|
+
- **Request hangs with no error**: `opencode auth login` was never run for Cursor; run it (0.3.2+ seeds the entry automatically so you get a 401 message instead).
|
|
127
121
|
- **Model list empty or only `cursor/auto`**: the catalog fetch failed (offline or not logged in). Fix auth, delete `~/.cache/opencode-cursor-auth/models.json`, restart opencode.
|
|
128
|
-
- **Stray `cursor-agent` processes**: check with `pgrep -fl cursor-agent`. A single `worker-server` is the SDK's idle singleton and is normal.
|
|
129
122
|
|
|
130
123
|
### Paths
|
|
131
124
|
|
package/package.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vidit19sharma/opencode-cursor-auth",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "OpenCode plugin that uses your Cursor subscription via the official @cursor/sdk \u2014 no proxy server, no separate login.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"opencode-cursor-auth": "dist/cli.js"
|
|
10
|
+
},
|
|
8
11
|
"files": [
|
|
9
12
|
"dist",
|
|
10
13
|
"README.md",
|
|
@@ -31,10 +34,12 @@
|
|
|
31
34
|
"auth"
|
|
32
35
|
],
|
|
33
36
|
"scripts": {
|
|
34
|
-
"build": "tsc",
|
|
37
|
+
"build": "tsc && chmod +x dist/cli.js",
|
|
35
38
|
"test": "bun spike/e2e-bridge.ts",
|
|
36
39
|
"spike": "bun spike/validate-sdk.ts",
|
|
37
|
-
"
|
|
40
|
+
"smoke": "bash scripts/fresh-home-smoke.sh",
|
|
41
|
+
"prepublishOnly": "npm run build",
|
|
42
|
+
"test:cli": "npm run build && bash spike/cli-setup.test.sh"
|
|
38
43
|
},
|
|
39
44
|
"dependencies": {
|
|
40
45
|
"@cursor/sdk": "^1.0.36"
|