@xtruder/opencode-claude-max-plugin 0.4.4 → 2.0.0-rc.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xtruder/opencode-claude-max-plugin",
3
- "version": "0.4.4",
3
+ "version": "2.0.0-rc.1",
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,11 +10,12 @@
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
21
  "main": "build/index.js",
@@ -25,23 +26,19 @@
25
26
  "types": "./build/index.d.ts"
26
27
  },
27
28
  "./server": {
28
- "import": "./build/server.js",
29
- "config": {}
29
+ "import": "./build/server.js"
30
30
  },
31
31
  "./tui": {
32
- "import": "./src/tui.tsx",
33
- "config": {
34
- "enabled": "true",
35
- "sidebar": "true",
36
- "poll_interval": "60"
37
- }
32
+ "import": "./build/tui.js"
38
33
  }
39
34
  },
40
35
  "scripts": {
41
- "build": "bun build ./src/index.ts ./src/server.ts --outdir ./build --target node --format esm --packages external && bun x tsc --emitDeclarationOnly --declaration --outDir build",
42
- "dev": "bun run dev:config && bun build ./src/index.ts --outdir ./build --target node --format esm --packages external --watch",
43
- "dev:config": "bun scripts/dev-config.ts",
44
- "test": "bun test --concurrency 1 src/*.test.ts",
36
+ "build": "vite build && tsc --emitDeclarationOnly --declaration --outDir build",
37
+ "dev": "vite build --watch",
38
+ "test": "npm run build && vitest run",
39
+ "typecheck": "tsc --noEmit",
40
+ "test:live": "CLAUDE_LIVE_TESTS=1 npm test",
41
+ "test:tui-smoke": "uv run --with pexpect --with pyte python scripts/tui-smoke.py",
45
42
  "lint": "oxlint src",
46
43
  "lint:fix": "oxlint --fix src",
47
44
  "format": "oxfmt --write .",
@@ -50,45 +47,34 @@
50
47
  },
51
48
  "dependencies": {
52
49
  "@anthropic-ai/sdk": "0.94.0",
50
+ "@opentui/core": "0.5.10",
51
+ "@opentui/solid": "0.5.10",
52
+ "solid-js": "1.9.12",
53
53
  "xxhash-wasm": "^1.1.0"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@ai-sdk/provider": "3.0.8",
57
57
  "@ai-sdk/provider-utils": "3.0.2",
58
- "@opencode-ai/plugin": "1.15.11",
59
- "@opentui/solid": "^0.1.96",
58
+ "@opencode/plugin": "2.0.16",
59
+ "@opencode/theme": "2.0.16",
60
+ "@types/node": "^26.6.2",
60
61
  "ai": "^6.0.146",
61
- "bun-types": "^1.3.11",
62
62
  "oxfmt": "^0.41.0",
63
63
  "oxlint": "^1.56.0",
64
64
  "simple-git-hooks": "^2.13.1",
65
- "typescript": "^5.7.0"
65
+ "typescript": "^5.7.0",
66
+ "vite": "^8.3.1",
67
+ "vite-plugin-solid": "^2.11.14",
68
+ "vitest": "^5.0.2"
66
69
  },
67
70
  "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
- }
71
+ "@ai-sdk/provider": ">=2.0.0"
87
72
  },
88
73
  "simple-git-hooks": {
89
- "pre-commit": "bun run lint && bun run format:check"
74
+ "pre-commit": "npm run lint && npm run format:check"
90
75
  },
91
76
  "engines": {
92
- "opencode": ">=1.3.13"
77
+ "node": ">=26.4.0",
78
+ "opencode": ">=2.0.16"
93
79
  }
94
80
  }
@@ -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
- }