@xtruder/opencode-claude-max-plugin 0.4.5 → 2.0.0-rc.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 +109 -124
- package/build/index.d.ts +514 -19
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2 -1818
- package/build/model.d.ts +0 -5
- package/build/model.d.ts.map +1 -1
- package/build/server.d.ts +4 -11
- package/build/server.d.ts.map +1 -1
- package/build/server.js +68 -1818
- package/build/src-Dvxl6Bub.js +1527 -0
- package/build/tui-state.d.ts +12 -0
- package/build/tui-state.d.ts.map +1 -0
- package/build/tui.d.ts +11 -3
- package/build/tui.d.ts.map +1 -1
- package/build/tui.js +239 -0
- package/build/usage-pZdEiX94.js +336 -0
- package/build/usage.d.ts +1 -1
- package/build/usage.d.ts.map +1 -1
- package/package.json +34 -44
- package/src/credentials.ts +0 -156
- package/src/tui.tsx +0 -579
- package/src/usage.ts +0 -331
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xtruder/opencode-claude-max-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "2.0.0-rc.2",
|
|
4
4
|
"description": "OpenCode plugin for Claude Max/Pro subscription via @anthropic-ai/sdk - uses Claude Code OAuth credentials",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-sdk",
|
|
@@ -10,38 +10,39 @@
|
|
|
10
10
|
"provider"
|
|
11
11
|
],
|
|
12
12
|
"license": "MIT",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/xtruder/opencode-claude-max-plugin.git"
|
|
16
|
+
},
|
|
13
17
|
"files": [
|
|
14
|
-
"build"
|
|
15
|
-
"src/tui.tsx",
|
|
16
|
-
"src/usage.ts",
|
|
17
|
-
"src/credentials.ts"
|
|
18
|
+
"build"
|
|
18
19
|
],
|
|
19
20
|
"type": "module",
|
|
20
|
-
"main": "build/
|
|
21
|
-
"types": "build/
|
|
21
|
+
"main": "build/server.js",
|
|
22
|
+
"types": "build/server.d.ts",
|
|
22
23
|
"exports": {
|
|
23
24
|
".": {
|
|
25
|
+
"import": "./build/server.js",
|
|
26
|
+
"types": "./build/server.d.ts"
|
|
27
|
+
},
|
|
28
|
+
"./provider": {
|
|
24
29
|
"import": "./build/index.js",
|
|
25
30
|
"types": "./build/index.d.ts"
|
|
26
31
|
},
|
|
27
32
|
"./server": {
|
|
28
|
-
"import": "./build/server.js"
|
|
29
|
-
"config": {}
|
|
33
|
+
"import": "./build/server.js"
|
|
30
34
|
},
|
|
31
35
|
"./tui": {
|
|
32
|
-
"import": "./
|
|
33
|
-
"config": {
|
|
34
|
-
"enabled": "true",
|
|
35
|
-
"sidebar": "true",
|
|
36
|
-
"poll_interval": "60"
|
|
37
|
-
}
|
|
36
|
+
"import": "./build/tui.js"
|
|
38
37
|
}
|
|
39
38
|
},
|
|
40
39
|
"scripts": {
|
|
41
|
-
"build": "
|
|
42
|
-
"dev": "
|
|
43
|
-
"
|
|
44
|
-
"
|
|
40
|
+
"build": "vite build && tsc --emitDeclarationOnly --declaration --outDir build",
|
|
41
|
+
"dev": "vite build --watch",
|
|
42
|
+
"test": "npm run build && vitest run",
|
|
43
|
+
"typecheck": "tsc --noEmit",
|
|
44
|
+
"test:live": "CLAUDE_LIVE_TESTS=1 npm test",
|
|
45
|
+
"test:tui-smoke": "uv run --with pexpect --with pyte python scripts/tui-smoke.py",
|
|
45
46
|
"lint": "oxlint src",
|
|
46
47
|
"lint:fix": "oxlint --fix src",
|
|
47
48
|
"format": "oxfmt --write .",
|
|
@@ -50,45 +51,34 @@
|
|
|
50
51
|
},
|
|
51
52
|
"dependencies": {
|
|
52
53
|
"@anthropic-ai/sdk": "0.94.0",
|
|
54
|
+
"@opentui/core": "0.5.10",
|
|
55
|
+
"@opentui/solid": "0.5.10",
|
|
56
|
+
"solid-js": "1.9.12",
|
|
53
57
|
"xxhash-wasm": "^1.1.0"
|
|
54
58
|
},
|
|
55
59
|
"devDependencies": {
|
|
56
60
|
"@ai-sdk/provider": "3.0.8",
|
|
57
61
|
"@ai-sdk/provider-utils": "3.0.2",
|
|
58
|
-
"@opencode
|
|
59
|
-
"@
|
|
62
|
+
"@opencode/plugin": "2.0.16",
|
|
63
|
+
"@opencode/theme": "2.0.16",
|
|
64
|
+
"@types/node": "^26.6.2",
|
|
60
65
|
"ai": "^6.0.146",
|
|
61
|
-
"bun-types": "^1.3.11",
|
|
62
66
|
"oxfmt": "^0.41.0",
|
|
63
67
|
"oxlint": "^1.56.0",
|
|
64
68
|
"simple-git-hooks": "^2.13.1",
|
|
65
|
-
"typescript": "^5.7.0"
|
|
69
|
+
"typescript": "^5.7.0",
|
|
70
|
+
"vite": "^8.3.1",
|
|
71
|
+
"vite-plugin-solid": "^2.11.14",
|
|
72
|
+
"vitest": "^5.0.2"
|
|
66
73
|
},
|
|
67
74
|
"peerDependencies": {
|
|
68
|
-
"@ai-sdk/provider": ">=2.0.0"
|
|
69
|
-
"@opencode-ai/plugin": "*",
|
|
70
|
-
"@opentui/core": "*",
|
|
71
|
-
"@opentui/solid": "*",
|
|
72
|
-
"solid-js": "*"
|
|
73
|
-
},
|
|
74
|
-
"peerDependenciesMeta": {
|
|
75
|
-
"@opencode-ai/plugin": {
|
|
76
|
-
"optional": true
|
|
77
|
-
},
|
|
78
|
-
"@opentui/core": {
|
|
79
|
-
"optional": true
|
|
80
|
-
},
|
|
81
|
-
"@opentui/solid": {
|
|
82
|
-
"optional": true
|
|
83
|
-
},
|
|
84
|
-
"solid-js": {
|
|
85
|
-
"optional": true
|
|
86
|
-
}
|
|
75
|
+
"@ai-sdk/provider": ">=2.0.0"
|
|
87
76
|
},
|
|
88
77
|
"simple-git-hooks": {
|
|
89
|
-
"pre-commit": "
|
|
78
|
+
"pre-commit": "npm run lint && npm run format:check"
|
|
90
79
|
},
|
|
91
80
|
"engines": {
|
|
92
|
-
"
|
|
81
|
+
"node": ">=26.4.0",
|
|
82
|
+
"opencode": ">=2.0.16"
|
|
93
83
|
}
|
|
94
84
|
}
|
package/src/credentials.ts
DELETED
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
import { execSync } from "node:child_process"
|
|
2
|
-
import { existsSync, readFileSync } from "node:fs"
|
|
3
|
-
import { homedir } from "node:os"
|
|
4
|
-
import { join } from "node:path"
|
|
5
|
-
|
|
6
|
-
export interface ClaudeOAuthCredentials {
|
|
7
|
-
accessToken: string
|
|
8
|
-
refreshToken: string
|
|
9
|
-
expiresAt: number
|
|
10
|
-
scopes: string[]
|
|
11
|
-
subscriptionType: string | null
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
interface CredentialsFile {
|
|
15
|
-
claudeAiOauth?: ClaudeOAuthCredentials
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* How long to cache credentials in memory before re-reading from disk.
|
|
20
|
-
*/
|
|
21
|
-
const CREDENTIAL_CACHE_TTL_MS = 30_000
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Buffer before expiry at which we consider the token "near expiry"
|
|
25
|
-
* and trigger a CLI refresh. 60 seconds matches opencode-claude-auth.
|
|
26
|
-
*/
|
|
27
|
-
const EXPIRY_BUFFER_MS = 60_000
|
|
28
|
-
|
|
29
|
-
let cachedCreds: ClaudeOAuthCredentials | null = null
|
|
30
|
-
let cachedAt = 0
|
|
31
|
-
let cachedPath: string | undefined
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Default path to Claude Code credentials file.
|
|
35
|
-
*/
|
|
36
|
-
export function getCredentialsPath(): string {
|
|
37
|
-
return join(homedir(), ".claude", ".credentials.json")
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Read OAuth credentials from Claude Code's credentials file.
|
|
42
|
-
* Returns null if the file doesn't exist or can't be parsed.
|
|
43
|
-
*/
|
|
44
|
-
export function readClaudeCredentials(path?: string): ClaudeOAuthCredentials | null {
|
|
45
|
-
const credPath = path ?? getCredentialsPath()
|
|
46
|
-
|
|
47
|
-
if (!existsSync(credPath)) return null
|
|
48
|
-
|
|
49
|
-
try {
|
|
50
|
-
const raw = readFileSync(credPath, "utf-8")
|
|
51
|
-
const parsed: CredentialsFile = JSON.parse(raw)
|
|
52
|
-
if (!parsed.claudeAiOauth?.accessToken) return null
|
|
53
|
-
return parsed.claudeAiOauth
|
|
54
|
-
} catch {
|
|
55
|
-
return null
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Check if credentials are expired (with 60s buffer).
|
|
61
|
-
*/
|
|
62
|
-
export function isExpired(creds: ClaudeOAuthCredentials): boolean {
|
|
63
|
-
return Date.now() > creds.expiresAt - EXPIRY_BUFFER_MS
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Run the Claude CLI to trigger an OAuth token refresh.
|
|
68
|
-
* The CLI writes refreshed credentials to ~/.claude/.credentials.json.
|
|
69
|
-
* Retries once on failure (matching opencode-claude-auth behavior).
|
|
70
|
-
*/
|
|
71
|
-
export function refreshViaCli(): boolean {
|
|
72
|
-
const maxAttempts = 2
|
|
73
|
-
for (let i = 0; i < maxAttempts; i++) {
|
|
74
|
-
try {
|
|
75
|
-
execSync("claude -p . --model haiku", {
|
|
76
|
-
timeout: 60_000,
|
|
77
|
-
encoding: "utf-8",
|
|
78
|
-
env: { ...process.env, TERM: "dumb" },
|
|
79
|
-
stdio: "ignore",
|
|
80
|
-
})
|
|
81
|
-
return true
|
|
82
|
-
} catch {
|
|
83
|
-
// Non-fatal: retry once, then give up
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return false
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Read credentials, refreshing via Claude CLI if the token is near expiry.
|
|
91
|
-
* Returns null only if credentials are completely unavailable.
|
|
92
|
-
*/
|
|
93
|
-
export function refreshIfNeeded(path?: string): ClaudeOAuthCredentials | null {
|
|
94
|
-
const creds = readClaudeCredentials(path)
|
|
95
|
-
if (!creds) return null
|
|
96
|
-
|
|
97
|
-
// Token still fresh
|
|
98
|
-
if (!isExpired(creds)) return creds
|
|
99
|
-
|
|
100
|
-
// Token near expiry — run Claude CLI to refresh
|
|
101
|
-
console.warn("[anthropic-sdk-provider] OAuth token near expiry, refreshing via Claude CLI...")
|
|
102
|
-
const refreshed = refreshViaCli()
|
|
103
|
-
if (!refreshed) {
|
|
104
|
-
console.warn("[anthropic-sdk-provider] CLI refresh failed. Token may be expired.")
|
|
105
|
-
// Return the existing creds anyway — they might still work for a bit
|
|
106
|
-
return creds
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Re-read from disk after CLI refresh
|
|
110
|
-
const fresh = readClaudeCredentials(path)
|
|
111
|
-
if (fresh && !isExpired(fresh)) {
|
|
112
|
-
console.warn("[anthropic-sdk-provider] Token refreshed successfully.")
|
|
113
|
-
return fresh
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// CLI ran but didn't produce a fresh token — return what we have
|
|
117
|
-
return fresh ?? creds
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Get cached credentials with automatic refresh.
|
|
122
|
-
* Uses in-memory caching with a 30-second TTL to avoid
|
|
123
|
-
* hitting the filesystem on every request.
|
|
124
|
-
* When the cached token is near expiry, triggers a CLI refresh.
|
|
125
|
-
*/
|
|
126
|
-
export function getCachedCredentials(path?: string): ClaudeOAuthCredentials | null {
|
|
127
|
-
const now = Date.now()
|
|
128
|
-
const resolvedPath = path ?? cachedPath
|
|
129
|
-
|
|
130
|
-
// Return cached if still fresh and not near expiry
|
|
131
|
-
if (cachedCreds && now - cachedAt < CREDENTIAL_CACHE_TTL_MS && !isExpired(cachedCreds)) {
|
|
132
|
-
return cachedCreds
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Read and potentially refresh
|
|
136
|
-
const fresh = refreshIfNeeded(resolvedPath)
|
|
137
|
-
if (fresh) {
|
|
138
|
-
cachedCreds = fresh
|
|
139
|
-
cachedAt = now
|
|
140
|
-
cachedPath = resolvedPath
|
|
141
|
-
} else {
|
|
142
|
-
cachedCreds = null
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return cachedCreds
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Clear the in-memory credential cache.
|
|
150
|
-
* Forces the next getCachedCredentials() call to re-read from disk.
|
|
151
|
-
*/
|
|
152
|
-
export function clearCredentialCache(): void {
|
|
153
|
-
cachedCreds = null
|
|
154
|
-
cachedAt = 0
|
|
155
|
-
cachedPath = undefined
|
|
156
|
-
}
|