op-anthropic-auth-v2 0.1.0 → 0.1.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/README.md +31 -13
- package/index.js +186 -89
- package/package.json +11 -2
package/README.md
CHANGED
|
@@ -1,34 +1,45 @@
|
|
|
1
1
|
# op-anthropic-auth-v2
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/op-anthropic-auth-v2)
|
|
4
|
+
|
|
3
5
|
OpenCode 2 plugin for Anthropic OAuth with Claude Pro/Max.
|
|
4
6
|
|
|
7
|
+
## Origin and attribution
|
|
8
|
+
|
|
9
|
+
This package is adapted from
|
|
10
|
+
[op-anthropic-auth](https://github.com/leohenon/op-anthropic-auth) version `0.1.4`,
|
|
11
|
+
created by [leohenon](https://github.com/leohenon) and released under the MIT
|
|
12
|
+
License. The original project's authentication approach, request shaping, and
|
|
13
|
+
plugin design form the basis of this package.
|
|
14
|
+
|
|
15
|
+
This adaptation ports that work to the OpenCode 2 plugin API. It is maintained as
|
|
16
|
+
a separate package and is not an official OpenCode 2 release from the original
|
|
17
|
+
author. The [license](./LICENSE) retains credit for the original work.
|
|
18
|
+
|
|
5
19
|
> [!WARNING]
|
|
6
20
|
> This is an unofficial compatibility plugin. Anthropic may restrict third-party use
|
|
7
21
|
> of Claude subscription OAuth credentials, and using it could put your account at
|
|
8
22
|
> risk. Review Anthropic's current terms and use it at your own discretion.
|
|
9
23
|
|
|
10
|
-
Port of [op-anthropic-auth](https://github.com/leohenon/op-anthropic-auth) (MIT, by
|
|
11
|
-
leohenon) to the [OpenCode 2](https://opencode.ai/v2/docs) plugin API. The original
|
|
12
|
-
plugin targets OpenCode 1 and cannot load in v2 — this package is the v2 equivalent.
|
|
13
|
-
|
|
14
24
|
## What it does
|
|
15
25
|
|
|
16
26
|
OpenCode 2 beta sends Anthropic OAuth requests as-is, and Anthropic rejects them with
|
|
17
27
|
an opaque `429` (`{"message":"Error"}`). This plugin rewrites every request to
|
|
18
28
|
`api.anthropic.com` so it passes OAuth gating:
|
|
19
29
|
|
|
30
|
+
- registers a `Claude Pro/Max` browser login using OAuth and PKCE
|
|
31
|
+
- lets OpenCode 2 store credentials and refresh expired tokens
|
|
20
32
|
- sets the `claude-cli` user-agent and required `anthropic-beta` headers
|
|
21
33
|
- adds the `x-anthropic-billing-header` system block
|
|
22
34
|
- prepends the Claude Code identity to the system prompt and relocates remaining
|
|
23
35
|
system text into the first user message
|
|
24
36
|
- prefixes tool names with `mcp_` and rewrites the response stream back
|
|
25
|
-
- refreshes the OAuth token on expiry and persists it to the shared
|
|
26
|
-
`~/.local/share/opencode/auth.json`
|
|
27
37
|
|
|
28
38
|
## Install
|
|
29
39
|
|
|
30
40
|
```sh
|
|
31
41
|
opencode2 plugin add op-anthropic-auth-v2
|
|
42
|
+
opencode2 auth login anthropic --method oauth
|
|
32
43
|
```
|
|
33
44
|
|
|
34
45
|
or add it to `~/.config/opencode/opencode.jsonc`:
|
|
@@ -39,22 +50,29 @@ or add it to `~/.config/opencode/opencode.jsonc`:
|
|
|
39
50
|
}
|
|
40
51
|
```
|
|
41
52
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
53
|
+
The login command opens Anthropic's authorization page. Complete authorization, then
|
|
54
|
+
paste the returned code into OpenCode 2. Users with a plain API key are unaffected;
|
|
55
|
+
the plugin leaves non-OAuth connections untouched.
|
|
45
56
|
|
|
46
|
-
|
|
47
|
-
|
|
57
|
+
This adaptation only targets OpenCode 2. OpenCode 1 users should use the original
|
|
58
|
+
[op-anthropic-auth](https://github.com/leohenon/op-anthropic-auth) package.
|
|
48
59
|
|
|
49
60
|
## Compatibility
|
|
50
61
|
|
|
51
|
-
Built against `opencode2` `0.0.0-beta-
|
|
52
|
-
|
|
62
|
+
Built against `opencode2` `0.0.0-beta-18743`. Version `0.1.2` reports Claude Code
|
|
63
|
+
`2.1.257` to meet Anthropic's model compatibility check. The v2 plugin API is beta
|
|
64
|
+
and may change; pin accordingly.
|
|
65
|
+
|
|
66
|
+
The runtime uses `@openauthjs/openauth` for PKCE generation. Development uses the
|
|
67
|
+
matching `@opencode-ai/plugin` beta types, TypeScript, and Node.js types to check the
|
|
68
|
+
published JavaScript against the OpenCode 2 plugin API.
|
|
53
69
|
|
|
54
70
|
## Development
|
|
55
71
|
|
|
56
72
|
```sh
|
|
73
|
+
npm install
|
|
57
74
|
npm test
|
|
75
|
+
npm run typecheck
|
|
58
76
|
npm run check
|
|
59
77
|
npm run pack:dry-run
|
|
60
78
|
```
|
package/index.js
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
// Anthropic OAuth (Claude Pro/Max) support for OpenCode 2.
|
|
2
2
|
// Port of op-anthropic-auth@0.1.4 to the v2 plugin API.
|
|
3
3
|
// v1 loads op-anthropic-auth itself; this file targets opencode2 only.
|
|
4
|
-
import { createHash } from "node:crypto";
|
|
5
|
-
import {
|
|
4
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
5
|
+
import { chmodSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
7
|
import { join } from "node:path";
|
|
8
|
+
import { generatePKCE } from "@openauthjs/openauth/pkce";
|
|
8
9
|
|
|
9
10
|
const CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
11
|
+
const AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
|
|
10
12
|
const TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
|
|
13
|
+
const CALLBACK_URL = "https://platform.claude.com/oauth/code/callback";
|
|
14
|
+
const AUTH_METHOD_ID = "oauth";
|
|
15
|
+
const AUTH_SCOPE =
|
|
16
|
+
"org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload";
|
|
11
17
|
const REQUIRED_BETAS = ["oauth-2025-04-20", "interleaved-thinking-2025-05-14"];
|
|
12
18
|
const TOOL_PREFIX = "mcp_";
|
|
13
19
|
const OPENCODE_IDENTITY_PREFIX = "You are OpenCode";
|
|
@@ -20,117 +26,191 @@ const PARAGRAPH_REMOVAL_ANCHORS = [
|
|
|
20
26
|
const TEXT_REPLACEMENTS = [
|
|
21
27
|
{ match: "if OpenCode honestly", replacement: "if the assistant honestly" },
|
|
22
28
|
];
|
|
23
|
-
const CLAUDE_CODE_VERSION = "2.1.
|
|
29
|
+
const CLAUDE_CODE_VERSION = "2.1.257";
|
|
24
30
|
const CLAUDE_CODE_ENTRYPOINT = "sdk-cli";
|
|
25
31
|
const BILLING_HEADER_PREFIX = "x-anthropic-billing-header:";
|
|
26
32
|
const CCH_SALT = "59cf53e54c78";
|
|
27
33
|
const CCH_POSITIONS = [4, 7, 20];
|
|
28
|
-
const REQUEST_USER_AGENT =
|
|
34
|
+
const REQUEST_USER_AGENT = `claude-cli/${CLAUDE_CODE_VERSION} (external, cli)`;
|
|
29
35
|
const TOKEN_USER_AGENT = "axios/1.13.6";
|
|
30
36
|
|
|
31
|
-
const AUTH_FILE =
|
|
32
|
-
process.env.XDG_DATA_HOME != null
|
|
33
|
-
? join(process.env.XDG_DATA_HOME, "opencode", "auth.json")
|
|
34
|
-
: join(homedir(), ".local", "share", "opencode", "auth.json");
|
|
35
|
-
|
|
36
37
|
function isRecord(value) {
|
|
37
38
|
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
function
|
|
41
|
+
function isPluginOAuthCredential(value) {
|
|
41
42
|
return (
|
|
42
43
|
isRecord(value) &&
|
|
43
44
|
value.type === "oauth" &&
|
|
45
|
+
value.methodID === AUTH_METHOD_ID &&
|
|
44
46
|
typeof value.refresh === "string" &&
|
|
47
|
+
value.refresh.length > 0 &&
|
|
48
|
+
typeof value.access === "string" &&
|
|
49
|
+
value.access.length > 0 &&
|
|
45
50
|
typeof value.expires === "number"
|
|
46
51
|
);
|
|
47
52
|
}
|
|
48
53
|
|
|
49
|
-
|
|
54
|
+
function migrateLegacyCredential() {
|
|
55
|
+
const authFile =
|
|
56
|
+
process.env.XDG_DATA_HOME != null
|
|
57
|
+
? join(process.env.XDG_DATA_HOME, "opencode", "auth.json")
|
|
58
|
+
: join(homedir(), ".local", "share", "opencode", "auth.json");
|
|
59
|
+
try {
|
|
60
|
+
const all = JSON.parse(readFileSync(authFile, "utf8"));
|
|
61
|
+
const credential = all?.anthropic;
|
|
62
|
+
if (
|
|
63
|
+
!isRecord(credential) ||
|
|
64
|
+
credential.type !== "oauth" ||
|
|
65
|
+
credential.methodID != null ||
|
|
66
|
+
typeof credential.refresh !== "string" ||
|
|
67
|
+
credential.refresh.length === 0 ||
|
|
68
|
+
!Number.isSafeInteger(credential.expires)
|
|
69
|
+
) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const hasAccess = typeof credential.access === "string" && credential.access.length > 0;
|
|
74
|
+
all.anthropic = {
|
|
75
|
+
...credential,
|
|
76
|
+
methodID: AUTH_METHOD_ID,
|
|
77
|
+
access: hasAccess ? credential.access : "",
|
|
78
|
+
expires: hasAccess ? credential.expires : 0,
|
|
79
|
+
};
|
|
80
|
+
const temporary = `${authFile}.tmp-${process.pid}`;
|
|
81
|
+
writeFileSync(temporary, JSON.stringify(all, null, 2), { mode: 0o600 });
|
|
82
|
+
chmodSync(temporary, 0o600);
|
|
83
|
+
renameSync(temporary, authFile);
|
|
84
|
+
return true;
|
|
85
|
+
} catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
50
89
|
|
|
51
|
-
function
|
|
90
|
+
function parseAuthorizationCode(input) {
|
|
91
|
+
const text = input.trim();
|
|
52
92
|
try {
|
|
53
|
-
|
|
93
|
+
const url = new URL(text);
|
|
94
|
+
const code = url.searchParams.get("code");
|
|
95
|
+
const state = url.searchParams.get("state");
|
|
96
|
+
if (code && state) return { code, state };
|
|
54
97
|
} catch {
|
|
55
|
-
|
|
98
|
+
// The callback may be a code-state pair instead of a URL.
|
|
56
99
|
}
|
|
100
|
+
|
|
101
|
+
const [code, state, ...rest] = text.split("#");
|
|
102
|
+
if (code && state && rest.length === 0) return { code, state };
|
|
103
|
+
|
|
104
|
+
const params = new URLSearchParams(text);
|
|
105
|
+
const queryCode = params.get("code");
|
|
106
|
+
const queryState = params.get("state");
|
|
107
|
+
if (queryCode && queryState) return { code: queryCode, state: queryState };
|
|
108
|
+
throw new Error("Authorization callback must include a code and state");
|
|
57
109
|
}
|
|
58
110
|
|
|
59
|
-
function
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
111
|
+
function makeAuthorizationUrl(challenge, state) {
|
|
112
|
+
const url = new URL(AUTHORIZE_URL);
|
|
113
|
+
url.searchParams.set("code", "true");
|
|
114
|
+
url.searchParams.set("client_id", CLIENT_ID);
|
|
115
|
+
url.searchParams.set("response_type", "code");
|
|
116
|
+
url.searchParams.set("redirect_uri", CALLBACK_URL);
|
|
117
|
+
url.searchParams.set("scope", AUTH_SCOPE);
|
|
118
|
+
url.searchParams.set("code_challenge", challenge);
|
|
119
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
120
|
+
url.searchParams.set("state", state);
|
|
121
|
+
return url.toString();
|
|
66
122
|
}
|
|
67
123
|
|
|
68
|
-
|
|
124
|
+
async function requestTokens(body, action, requireRefreshToken = true) {
|
|
125
|
+
const response = await fetch(TOKEN_URL, {
|
|
126
|
+
method: "POST",
|
|
127
|
+
headers: {
|
|
128
|
+
Accept: "application/json, text/plain, */*",
|
|
129
|
+
"Content-Type": "application/json",
|
|
130
|
+
"User-Agent": TOKEN_USER_AGENT,
|
|
131
|
+
},
|
|
132
|
+
body: JSON.stringify(body),
|
|
133
|
+
});
|
|
134
|
+
if (!response.ok) {
|
|
135
|
+
const detail = await response.text().catch(() => "");
|
|
136
|
+
throw new Error(`${action} failed: ${response.status}${detail ? ` ${detail}` : ""}`);
|
|
137
|
+
}
|
|
69
138
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
},
|
|
85
|
-
body: JSON.stringify({
|
|
86
|
-
grant_type: "refresh_token",
|
|
87
|
-
refresh_token: refreshToken,
|
|
88
|
-
client_id: CLIENT_ID,
|
|
89
|
-
}),
|
|
90
|
-
});
|
|
91
|
-
if (!response.ok) {
|
|
92
|
-
if (response.status >= 500 && attempt < maxRetries) {
|
|
93
|
-
await response.body?.cancel();
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
// On 401, another process may have already rotated the refresh token.
|
|
97
|
-
if (response.status === 401 && attempt < maxRetries) {
|
|
98
|
-
const updated = readAuthFile()?.anthropic;
|
|
99
|
-
if (isOAuthAuth(updated) && updated.refresh !== refreshToken) {
|
|
100
|
-
await response.body?.cancel();
|
|
101
|
-
if (updated.access && updated.expires > Date.now()) {
|
|
102
|
-
return updated.access;
|
|
103
|
-
}
|
|
104
|
-
refreshToken = updated.refresh;
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
const body = await response.text().catch(() => "");
|
|
109
|
-
throw new Error(`Token refresh failed: ${response.status} — ${body}`);
|
|
110
|
-
}
|
|
111
|
-
const json = await response.json();
|
|
112
|
-
const entry = {
|
|
113
|
-
type: "oauth",
|
|
114
|
-
refresh: json.refresh_token,
|
|
115
|
-
access: json.access_token,
|
|
116
|
-
expires: Date.now() + json.expires_in * 1000,
|
|
117
|
-
};
|
|
118
|
-
writeAuthEntry(entry);
|
|
119
|
-
return entry.access;
|
|
139
|
+
const result = await response.json();
|
|
140
|
+
if (
|
|
141
|
+
!isRecord(result) ||
|
|
142
|
+
typeof result.access_token !== "string" ||
|
|
143
|
+
result.access_token.length === 0 ||
|
|
144
|
+
!Number.isSafeInteger(result.expires_in) ||
|
|
145
|
+
result.expires_in <= 0 ||
|
|
146
|
+
(requireRefreshToken &&
|
|
147
|
+
(typeof result.refresh_token !== "string" || result.refresh_token.length === 0)) ||
|
|
148
|
+
(!requireRefreshToken &&
|
|
149
|
+
result.refresh_token != null &&
|
|
150
|
+
(typeof result.refresh_token !== "string" || result.refresh_token.length === 0))
|
|
151
|
+
) {
|
|
152
|
+
throw new Error(`${action} failed: malformed token response`);
|
|
120
153
|
}
|
|
121
|
-
|
|
154
|
+
return result;
|
|
122
155
|
}
|
|
123
156
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
})
|
|
132
|
-
}
|
|
133
|
-
|
|
157
|
+
function toCredential(tokens, metadata, fallbackRefreshToken) {
|
|
158
|
+
return /** @type {import("@opencode-ai/plugin").Credential.OAuth} */ ({
|
|
159
|
+
type: /** @type {"oauth"} */ ("oauth"),
|
|
160
|
+
methodID: AUTH_METHOD_ID,
|
|
161
|
+
refresh: tokens.refresh_token ?? fallbackRefreshToken,
|
|
162
|
+
access: tokens.access_token,
|
|
163
|
+
expires: Date.now() + tokens.expires_in * 1000,
|
|
164
|
+
...(metadata == null ? {} : { metadata }),
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function authorize() {
|
|
169
|
+
const pkce = await generatePKCE();
|
|
170
|
+
const state = randomBytes(16).toString("hex");
|
|
171
|
+
return {
|
|
172
|
+
url: makeAuthorizationUrl(pkce.challenge, state),
|
|
173
|
+
instructions: "Authorize Claude Pro/Max, then paste the returned code here.",
|
|
174
|
+
mode: /** @type {"code"} */ ("code"),
|
|
175
|
+
async callback(input) {
|
|
176
|
+
const parsed = parseAuthorizationCode(input);
|
|
177
|
+
if (parsed.state !== state) throw new Error("Authorization state does not match");
|
|
178
|
+
const tokens = await requestTokens(
|
|
179
|
+
{
|
|
180
|
+
code: parsed.code,
|
|
181
|
+
state: parsed.state,
|
|
182
|
+
grant_type: "authorization_code",
|
|
183
|
+
client_id: CLIENT_ID,
|
|
184
|
+
redirect_uri: CALLBACK_URL,
|
|
185
|
+
code_verifier: pkce.verifier,
|
|
186
|
+
},
|
|
187
|
+
"Token exchange",
|
|
188
|
+
);
|
|
189
|
+
return toCredential(tokens);
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function refreshCredential(
|
|
195
|
+
/** @type {import("@opencode-ai/plugin").Credential.OAuth} */ credential,
|
|
196
|
+
) {
|
|
197
|
+
const tokens = await requestTokens(
|
|
198
|
+
{
|
|
199
|
+
grant_type: "refresh_token",
|
|
200
|
+
refresh_token: credential.refresh,
|
|
201
|
+
client_id: CLIENT_ID,
|
|
202
|
+
},
|
|
203
|
+
"Token refresh",
|
|
204
|
+
false,
|
|
205
|
+
);
|
|
206
|
+
return toCredential(tokens, credential.metadata, credential.refresh);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function currentCredential(ctx) {
|
|
210
|
+
const connection = await ctx.integration.connection.active("anthropic");
|
|
211
|
+
if (!connection) return null;
|
|
212
|
+
const credential = await ctx.integration.connection.resolve(connection);
|
|
213
|
+
return isPluginOAuthCredential(credential) ? credential : null;
|
|
134
214
|
}
|
|
135
215
|
|
|
136
216
|
// ---- request rewriting ----
|
|
@@ -386,9 +466,24 @@ function isAnthropicApi(url) {
|
|
|
386
466
|
|
|
387
467
|
// ---- plugin ----
|
|
388
468
|
|
|
389
|
-
|
|
469
|
+
/** @type {import("@opencode-ai/plugin").Plugin.Plugin} */
|
|
470
|
+
const plugin = {
|
|
390
471
|
id: "anthropic-oauth-v2",
|
|
391
472
|
async setup(ctx) {
|
|
473
|
+
migrateLegacyCredential();
|
|
474
|
+
await ctx.integration.transform((draft) => {
|
|
475
|
+
draft.method.update({
|
|
476
|
+
integrationID: "anthropic",
|
|
477
|
+
method: {
|
|
478
|
+
id: AUTH_METHOD_ID,
|
|
479
|
+
type: "oauth",
|
|
480
|
+
label: "Claude Pro/Max",
|
|
481
|
+
},
|
|
482
|
+
authorize,
|
|
483
|
+
refresh: refreshCredential,
|
|
484
|
+
});
|
|
485
|
+
});
|
|
486
|
+
|
|
392
487
|
await ctx.session.hook(
|
|
393
488
|
"http.request",
|
|
394
489
|
async (event) => {
|
|
@@ -400,11 +495,11 @@ export default {
|
|
|
400
495
|
}
|
|
401
496
|
if (!isAnthropicApi(url)) return;
|
|
402
497
|
|
|
403
|
-
const
|
|
404
|
-
if (!
|
|
498
|
+
const credential = await currentCredential(ctx);
|
|
499
|
+
if (!credential) return;
|
|
405
500
|
|
|
406
501
|
const headers = new Headers(event.request.headers);
|
|
407
|
-
headers.set("authorization", `Bearer ${access}`);
|
|
502
|
+
headers.set("authorization", `Bearer ${credential.access}`);
|
|
408
503
|
headers.set("anthropic-beta", mergeBetaHeaders(headers));
|
|
409
504
|
headers.set("user-agent", REQUEST_USER_AGENT);
|
|
410
505
|
headers.delete("x-api-key");
|
|
@@ -423,7 +518,6 @@ export default {
|
|
|
423
518
|
method: event.request.method,
|
|
424
519
|
headers,
|
|
425
520
|
body,
|
|
426
|
-
duplex: "half",
|
|
427
521
|
signal: event.request.signal,
|
|
428
522
|
});
|
|
429
523
|
},
|
|
@@ -432,7 +526,8 @@ export default {
|
|
|
432
526
|
|
|
433
527
|
await ctx.session.hook(
|
|
434
528
|
"http.response",
|
|
435
|
-
(event) => {
|
|
529
|
+
async (event) => {
|
|
530
|
+
if (event.request.headers.get("user-agent") !== REQUEST_USER_AGENT) return;
|
|
436
531
|
// Rewrite unless the response URL is present and provably non-Anthropic.
|
|
437
532
|
try {
|
|
438
533
|
const url = new URL(event.response.url);
|
|
@@ -446,3 +541,5 @@ export default {
|
|
|
446
541
|
);
|
|
447
542
|
},
|
|
448
543
|
};
|
|
544
|
+
|
|
545
|
+
export default plugin;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "op-anthropic-auth-v2",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "OpenCode 2 plugin for Anthropic OAuth with Claude Pro/Max",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.js",
|
|
@@ -36,7 +36,16 @@
|
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"test": "node --test",
|
|
39
|
-
"
|
|
39
|
+
"typecheck": "tsc",
|
|
40
|
+
"check": "node --check index.js && npm run typecheck && npm test",
|
|
40
41
|
"pack:dry-run": "npm pack --dry-run"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@openauthjs/openauth": "0.4.3"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@opencode-ai/plugin": "0.0.0-beta-18743",
|
|
48
|
+
"@types/node": "24.6.1",
|
|
49
|
+
"typescript": "5.9.3"
|
|
41
50
|
}
|
|
42
51
|
}
|