opencode-with-claude 1.1.3 → 1.1.7
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 +19 -143
- package/dist/index.d.ts +5 -2
- package/dist/index.js +1 -52
- package/package.json +5 -4
- package/dist/logger.d.ts +0 -7
- package/dist/logger.js +0 -8
- package/dist/proxy.d.ts +0 -29
- package/dist/proxy.js +0 -91
package/README.md
CHANGED
|
@@ -4,6 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
Use [OpenCode](https://opencode.ai) with your [Claude Max](https://claude.ai) subscription.
|
|
6
6
|
|
|
7
|
+
## What this is
|
|
8
|
+
|
|
9
|
+
An [OpenCode](https://opencode.ai) plugin that runs [opencode-claude-max-proxy](https://github.com/rynfar/opencode-claude-max-proxy) for you: **start OpenCode once** and the proxy comes up with it; **quit OpenCode** and the proxy stops. No separate proxy CLI or Docker container to manage.
|
|
10
|
+
|
|
11
|
+
**Compared to running the proxy yourself:**
|
|
12
|
+
|
|
13
|
+
- **One process to think about** — OpenCode owns the proxy lifecycle (start/stop) instead of you juggling two things.
|
|
14
|
+
- **Several OpenCode windows at once** — each instance gets its own proxy on an OS-assigned port, so ports do not collide and you avoid session issues from sharing one proxy across instances.
|
|
15
|
+
- **Explicit session headers** — the plugin adds session tracking on outgoing API calls, so the proxy does not have to infer sessions from fingerprints alone.
|
|
16
|
+
|
|
7
17
|
## How It Works
|
|
8
18
|
|
|
9
19
|
```
|
|
@@ -14,15 +24,9 @@ Use [OpenCode](https://opencode.ai) with your [Claude Max](https://claude.ai) su
|
|
|
14
24
|
└─────────────┘ └────────────────────┘ └─────────────────┘
|
|
15
25
|
```
|
|
16
26
|
|
|
17
|
-
[OpenCode](https://opencode.ai) speaks the Anthropic REST API. Claude Max provides access via the [Claude Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk) (not the REST API). The [opencode-claude-max-proxy](https://github.com/rynfar/opencode-claude-max-proxy) bridges the gap — it accepts API requests from OpenCode and translates them into Agent SDK calls using your Claude Max session.
|
|
18
|
-
|
|
19
27
|
## Quick Start
|
|
20
28
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
### Option A: OpenCode Plugin (recommended)
|
|
24
|
-
|
|
25
|
-
The plugin manages the proxy lifecycle automatically — it starts the proxy when OpenCode launches, configures the Anthropic provider, and cleans up on exit. Each OpenCode instance gets its own proxy on an OS-assigned port, so multiple instances can run simultaneously without conflicts.
|
|
29
|
+
The plugin hooks into OpenCode's plugin system. When OpenCode launches, it starts the proxy, configures the Anthropic provider, and cleans everything up on exit.
|
|
26
30
|
|
|
27
31
|
**1. Authenticate with Claude (one-time)**
|
|
28
32
|
|
|
@@ -50,7 +54,7 @@ Global (`~/.config/opencode/opencode.json`) or project-level:
|
|
|
50
54
|
}
|
|
51
55
|
```
|
|
52
56
|
|
|
53
|
-
The `apiKey` is a
|
|
57
|
+
> **Note:** The `apiKey` is a placeholder — authentication goes through your Claude Max session via `claude login`, not an API key. The `baseURL` is the default proxy port. If port 3456 is already in use (e.g., another OpenCode instance), the plugin automatically starts the proxy on a different port and overrides the `baseURL` at runtime.
|
|
54
58
|
|
|
55
59
|
**3. Run OpenCode**
|
|
56
60
|
|
|
@@ -58,106 +62,6 @@ The `apiKey` is a dummy value — authentication goes through your Claude Max se
|
|
|
58
62
|
opencode
|
|
59
63
|
```
|
|
60
64
|
|
|
61
|
-
### Option B: Standalone Installer (`oc` launcher)
|
|
62
|
-
|
|
63
|
-
A one-liner that installs all dependencies and gives you the `oc` command — no config files to edit.
|
|
64
|
-
|
|
65
|
-
```bash
|
|
66
|
-
curl -fsSL https://raw.githubusercontent.com/ianjwhite99/opencode-with-claude/main/install.sh | bash
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
This installs:
|
|
70
|
-
- [Claude Code CLI](https://www.npmjs.com/package/@anthropic-ai/claude-code) — authentication with Claude
|
|
71
|
-
- [OpenCode](https://www.npmjs.com/package/opencode-ai) — the coding assistant
|
|
72
|
-
- [opencode-claude-max-proxy](https://www.npmjs.com/package/opencode-claude-max-proxy) — bridges OpenCode to Claude Max
|
|
73
|
-
- **`oc`** — launcher that ties it all together
|
|
74
|
-
|
|
75
|
-
Then run:
|
|
76
|
-
|
|
77
|
-
```bash
|
|
78
|
-
cd your-project
|
|
79
|
-
oc
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
The `oc` command starts the proxy in the background, waits for it to be ready, and launches OpenCode.
|
|
83
|
-
|
|
84
|
-
### Option C: Docker
|
|
85
|
-
|
|
86
|
-
Run everything in a container with the OpenCode web UI exposed on port 4096.
|
|
87
|
-
|
|
88
|
-
```bash
|
|
89
|
-
git clone https://github.com/ianjwhite99/opencode-with-claude.git
|
|
90
|
-
cd opencode-with-claude
|
|
91
|
-
|
|
92
|
-
# Build and start
|
|
93
|
-
docker compose -f docker/docker-compose.yml up -d
|
|
94
|
-
|
|
95
|
-
# Authenticate (first time only)
|
|
96
|
-
docker exec -it -u opencode opencode-with-claude claude login
|
|
97
|
-
|
|
98
|
-
# Open the web UI
|
|
99
|
-
open http://localhost:4096
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
The container runs the proxy and OpenCode web UI together. Your `~/workspace` directory is mounted into the container, and Claude auth, OpenCode data, and config are persisted across restarts via Docker volumes.
|
|
103
|
-
|
|
104
|
-
## Prerequisites
|
|
105
|
-
|
|
106
|
-
- **Node.js >= 18** — [nodejs.org](https://nodejs.org) (or Bun/Deno) — not needed for Docker
|
|
107
|
-
- **Claude Max subscription** — the $100/mo plan on [claude.ai](https://claude.ai)
|
|
108
|
-
|
|
109
|
-
## `oc` Launcher Reference
|
|
110
|
-
|
|
111
|
-
The `oc` launcher handles everything — starts the proxy, waits for health, launches OpenCode, and cleans up on exit:
|
|
112
|
-
|
|
113
|
-
```bash
|
|
114
|
-
oc # Start OpenCode TUI in current directory
|
|
115
|
-
oc web # Start OpenCode web UI on port 4096
|
|
116
|
-
oc update # Update all components to latest versions
|
|
117
|
-
oc --help # Show help
|
|
118
|
-
oc --version # Show component versions
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
All arguments are passed through to `opencode`, so anything that works with `opencode` works with `oc`.
|
|
122
|
-
|
|
123
|
-
### Installer Options
|
|
124
|
-
|
|
125
|
-
```bash
|
|
126
|
-
# Skip the Claude login prompt
|
|
127
|
-
curl -fsSL ... | bash -s -- --no-auth
|
|
128
|
-
|
|
129
|
-
# Don't modify shell PATH
|
|
130
|
-
curl -fsSL ... | bash -s -- --no-modify-path
|
|
131
|
-
|
|
132
|
-
# Show help
|
|
133
|
-
curl -fsSL ... | bash -s -- --help
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
### Uninstalling
|
|
137
|
-
|
|
138
|
-
Remove the `oc` launcher and clean up PATH entries:
|
|
139
|
-
|
|
140
|
-
```bash
|
|
141
|
-
curl -fsSL https://raw.githubusercontent.com/ianjwhite99/opencode-with-claude/main/install.sh | bash -s -- --uninstall
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
This removes the `oc` launcher from `~/.opencode/bin` and cleans up any PATH entries added to your shell config. To also remove the underlying tools:
|
|
145
|
-
|
|
146
|
-
```bash
|
|
147
|
-
npm uninstall -g @anthropic-ai/claude-code opencode-ai opencode-claude-max-proxy
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
## Configuration
|
|
151
|
-
|
|
152
|
-
### Environment Variables
|
|
153
|
-
|
|
154
|
-
| Variable | Default | Description |
|
|
155
|
-
|----------|---------|-------------|
|
|
156
|
-
| `CLAUDE_PROXY_PORT` | `3456` | Preferred port for the proxy (falls back to a random port if in use) |
|
|
157
|
-
| `CLAUDE_PROXY_WORKDIR` | `$PWD` | Working directory for the proxy |
|
|
158
|
-
| `OC_SKIP_AUTH_CHECK` | unset | Set to `1` to skip Claude auth check on `oc` launch |
|
|
159
|
-
| `OC_AUTO_UPDATE` | unset | Set to `true` or `1` to auto-update components on Docker container start |
|
|
160
|
-
|
|
161
65
|
## Troubleshooting
|
|
162
66
|
|
|
163
67
|
### "Claude Code CLI not found"
|
|
@@ -180,19 +84,6 @@ This opens a browser for OAuth. Your Claude Max subscription credentials are nee
|
|
|
180
84
|
2. Ensure your internet connection is working
|
|
181
85
|
3. If using a manual port override, check if it's in use: `lsof -i :$CLAUDE_PROXY_PORT`
|
|
182
86
|
|
|
183
|
-
### Updating components
|
|
184
|
-
|
|
185
|
-
```bash
|
|
186
|
-
# oc launcher
|
|
187
|
-
oc update
|
|
188
|
-
|
|
189
|
-
# Plugin / manual
|
|
190
|
-
npm install -g @anthropic-ai/claude-code opencode-ai opencode-claude-max-proxy
|
|
191
|
-
|
|
192
|
-
# Docker
|
|
193
|
-
docker compose -f docker/docker-compose.yml build --no-cache && docker compose -f docker/docker-compose.yml up -d
|
|
194
|
-
```
|
|
195
|
-
|
|
196
87
|
## Development
|
|
197
88
|
|
|
198
89
|
### Project Structure
|
|
@@ -203,13 +94,6 @@ opencode-with-claude/
|
|
|
203
94
|
│ ├── index.ts # Plugin entry point
|
|
204
95
|
│ ├── proxy.ts # Proxy lifecycle management
|
|
205
96
|
│ └── logger.ts # Plugin logger
|
|
206
|
-
├── bin/
|
|
207
|
-
│ └── oc # Standalone launcher
|
|
208
|
-
├── docker/
|
|
209
|
-
│ ├── Dockerfile # All-in-one Docker image
|
|
210
|
-
│ ├── docker-compose.yml # Docker Compose config
|
|
211
|
-
│ └── entrypoint.sh # Docker entrypoint
|
|
212
|
-
├── install.sh # curl | bash installer
|
|
213
97
|
├── test/
|
|
214
98
|
│ ├── run.sh # Test runner
|
|
215
99
|
│ └── opencode.json # Test config
|
|
@@ -235,27 +119,19 @@ npm run build
|
|
|
235
119
|
|
|
236
120
|
**Do I need an Anthropic API key?**
|
|
237
121
|
|
|
238
|
-
No.
|
|
239
|
-
|
|
240
|
-
**What happens if my Claude Max subscription expires?**
|
|
241
|
-
|
|
242
|
-
The proxy will fail to authenticate. Run `claude auth status` to check. You'll need an active Claude Max ($100/mo) or Claude Max with Team ($200/mo) subscription.
|
|
243
|
-
|
|
244
|
-
**Plugin, `oc`, or Docker — which should I use?**
|
|
245
|
-
|
|
246
|
-
The **plugin** is recommended if you already use OpenCode — it integrates with OpenCode's plugin system and requires no extra commands. Use the **`oc` launcher** if you want a one-command install from scratch or prefer not to edit config files. Use **Docker** if you want an isolated environment or want to run the web UI as a service.
|
|
122
|
+
No. Claude Max is not authenticated with API keys here. Run `claude login` once; the proxy uses that session (Agent SDK via OAuth). OpenCode still expects an `apiKey` field, so the plugin supplies a placeholder — it is not used for real auth.
|
|
247
123
|
|
|
248
|
-
**
|
|
124
|
+
**What if my Claude Max subscription lapses?**
|
|
249
125
|
|
|
250
|
-
|
|
126
|
+
The proxy will fail to authenticate. Run `claude auth status`. You need an active Claude Max plan; see [claude.ai](https://claude.ai) for current options and pricing.
|
|
251
127
|
|
|
252
|
-
**
|
|
128
|
+
**Can I run several OpenCode instances at once?**
|
|
253
129
|
|
|
254
|
-
|
|
130
|
+
Yes. The first instance uses port **3456** by default; others get a free OS-assigned port, so nothing extra to configure.
|
|
255
131
|
|
|
256
|
-
**
|
|
132
|
+
**Is this the same as using the Anthropic API directly?**
|
|
257
133
|
|
|
258
|
-
|
|
134
|
+
Not exactly. OpenCode speaks Anthropic-style HTTP to the local proxy; the proxy maps requests to the Claude Agent SDK and your Claude Max session. Usage limits follow your Max subscription, not Anthropic API billing tiers.
|
|
259
135
|
|
|
260
136
|
## Disclaimer
|
|
261
137
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Plugin } from '@opencode-ai/plugin';
|
|
2
|
+
|
|
2
3
|
/**
|
|
3
4
|
* OpenCode plugin that manages the Claude Max proxy lifecycle.
|
|
4
5
|
*
|
|
@@ -9,4 +10,6 @@ import type { Plugin } from "@opencode-ai/plugin";
|
|
|
9
10
|
* 4. Returns a `config` hook that injects the proxy's baseURL into
|
|
10
11
|
* the Anthropic provider so each opencode instance gets its own proxy.
|
|
11
12
|
*/
|
|
12
|
-
|
|
13
|
+
declare const ClaudeMaxPlugin: Plugin;
|
|
14
|
+
|
|
15
|
+
export { ClaudeMaxPlugin };
|
package/dist/index.js
CHANGED
|
@@ -1,52 +1 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { registerCleanup, startProxy } from "./proxy.js";
|
|
3
|
-
/**
|
|
4
|
-
* OpenCode plugin that manages the Claude Max proxy lifecycle.
|
|
5
|
-
*
|
|
6
|
-
* On init:
|
|
7
|
-
* 1. Verifies the Claude CLI is installed and authenticated
|
|
8
|
-
* 2. Starts the proxy (port 3456, or falls back to a random port if in use)
|
|
9
|
-
* 3. Registers cleanup handlers to stop the proxy on exit
|
|
10
|
-
* 4. Returns a `config` hook that injects the proxy's baseURL into
|
|
11
|
-
* the Anthropic provider so each opencode instance gets its own proxy.
|
|
12
|
-
*/
|
|
13
|
-
export const ClaudeMaxPlugin = async ({ client, $, directory }) => {
|
|
14
|
-
const log = createLogger(client);
|
|
15
|
-
// 1. Verify Claude CLI is installed
|
|
16
|
-
try {
|
|
17
|
-
await $ `claude --version`;
|
|
18
|
-
}
|
|
19
|
-
catch {
|
|
20
|
-
throw new Error("Claude Code CLI not found. Install it with: npm install -g @anthropic-ai/claude-code");
|
|
21
|
-
}
|
|
22
|
-
// 2. Verify authentication
|
|
23
|
-
let authOutput;
|
|
24
|
-
try {
|
|
25
|
-
authOutput = await $ `claude auth status`.text();
|
|
26
|
-
}
|
|
27
|
-
catch {
|
|
28
|
-
throw new Error("Failed to check Claude auth status. Run: claude login");
|
|
29
|
-
}
|
|
30
|
-
if (!authOutput.includes('"loggedIn": true')) {
|
|
31
|
-
throw new Error("Claude not authenticated. Run: claude login");
|
|
32
|
-
}
|
|
33
|
-
await log("info", "Claude authentication verified");
|
|
34
|
-
// 3. Start the proxy
|
|
35
|
-
const port = parseInt(process.env.CLAUDE_PROXY_PORT || "", 10) || undefined;
|
|
36
|
-
await log("info", "Starting Claude Max proxy...");
|
|
37
|
-
const proxy = await startProxy({ port, log });
|
|
38
|
-
const baseURL = `http://127.0.0.1:${proxy.port}`;
|
|
39
|
-
await log("info", `Claude Max proxy ready at ${baseURL}`);
|
|
40
|
-
// 4. Register cleanup handlers
|
|
41
|
-
registerCleanup(proxy);
|
|
42
|
-
// 5. Configure the Anthropic provider to route through the proxy
|
|
43
|
-
return {
|
|
44
|
-
async config(input) {
|
|
45
|
-
input.provider ??= {};
|
|
46
|
-
input.provider.anthropic ??= {};
|
|
47
|
-
input.provider.anthropic.options ??= {};
|
|
48
|
-
input.provider.anthropic.options.baseURL = baseURL;
|
|
49
|
-
input.provider.anthropic.options.apiKey = "claude-max-proxy";
|
|
50
|
-
},
|
|
51
|
-
};
|
|
52
|
-
};
|
|
1
|
+
function l(n){return(t,o)=>n.app.log({body:{service:"opencode-with-claude",level:t,message:o}})}import{startProxyServer as d}from"opencode-claude-max-proxy";var y=process.platform==="win32",f=3456;async function u(n){let{port:t=f,log:o}=n,e=console.error;console.error=(...r)=>{let a=r.map(String).join(" ");if(a.startsWith("[PROXY]")){o("debug",a);return}e.apply(console,r)};let c=async r=>{try{return await d({port:r,host:"127.0.0.1",silent:!0})}catch(a){if(r!==0&&a instanceof Error&&"code"in a&&a.code==="EADDRINUSE")return await o("info",`Port ${r} in use, starting on a random port instead...`),d({port:0,host:"127.0.0.1",silent:!0});throw a}},i;try{i=await c(t)}catch(r){throw console.error=e,r}let s=i.server.address().port;return await o("info",`Claude Max proxy running on port ${s}`),{port:s,close:async()=>{console.error=e,await i.close()}}}function g(n){let t=!1,o=()=>{t||(t=!0,n.close())};process.on("exit",o),process.on("SIGINT",o),y||process.on("SIGTERM",o)}var v=async({client:n,$:t,directory:o})=>{let e=l(n);try{await t`claude --version`}catch{throw new Error("Claude Code CLI not found. Install it with: npm install -g @anthropic-ai/claude-code")}let c;try{c=await t`claude auth status`.text()}catch{throw new Error("Failed to check Claude auth status. Run: claude login")}if(!c.includes('"loggedIn": true'))throw new Error("Claude not authenticated. Run: claude login");await e("info","Claude authentication verified");let i=parseInt(process.env.CLAUDE_PROXY_PORT||"",10)||void 0;await e("info","Starting Claude Max proxy...");let p=await u({port:i,log:e}),s=`http://127.0.0.1:${p.port}`;return await e("info",`Claude Max proxy ready at ${s}`),g(p),{async config(r){r.provider??={},r.provider.anthropic??={},r.provider.anthropic.options??={},r.provider.anthropic.options.baseURL=s,r.provider.anthropic.options.apiKey="claude-max-proxy"}}};export{v as ClaudeMaxPlugin};
|
package/package.json
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-with-claude",
|
|
3
3
|
"description": "OpenCode plugin to use your Claude Max subscription via local proxy",
|
|
4
|
-
"version": "1.1.
|
|
4
|
+
"version": "1.1.7",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"opencode-claude-max-proxy": "^1.
|
|
9
|
+
"opencode-claude-max-proxy": "^1.17.0"
|
|
10
10
|
},
|
|
11
11
|
"devDependencies": {
|
|
12
12
|
"@opencode-ai/plugin": "^1.3.0",
|
|
13
13
|
"@types/node": "^25.5.0",
|
|
14
|
-
"
|
|
14
|
+
"tsup": "^8.4.0",
|
|
15
|
+
"typescript": "^6.0.2"
|
|
15
16
|
},
|
|
16
17
|
"scripts": {
|
|
17
|
-
"build": "
|
|
18
|
+
"build": "tsup src/index.ts --format esm --dts --clean --minify",
|
|
18
19
|
"test": "./test/run.sh",
|
|
19
20
|
"test:clean": "./test/run.sh --clean",
|
|
20
21
|
"prepublishOnly": "npm run build"
|
package/dist/logger.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { Plugin } from "@opencode-ai/plugin";
|
|
2
|
-
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
3
|
-
export type LogFn = (level: LogLevel, message: string) => Promise<unknown>;
|
|
4
|
-
/**
|
|
5
|
-
* Create a logger bound to the plugin's client.
|
|
6
|
-
*/
|
|
7
|
-
export declare function createLogger(client: Parameters<Plugin>[0]["client"]): LogFn;
|
package/dist/logger.js
DELETED
package/dist/proxy.d.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import type { LogFn } from "./logger.js";
|
|
2
|
-
export interface StartProxyOptions {
|
|
3
|
-
port?: number;
|
|
4
|
-
log: LogFn;
|
|
5
|
-
}
|
|
6
|
-
export interface ProxyHandle {
|
|
7
|
-
port: number;
|
|
8
|
-
close(): Promise<void>;
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Start the Claude Max proxy using the programmatic API.
|
|
12
|
-
*
|
|
13
|
-
* Tries the preferred port first (default 3456). If that port is already
|
|
14
|
-
* in use, falls back to port 0 so the OS assigns a free port. This keeps
|
|
15
|
-
* the port predictable for single-instance users while allowing multiple
|
|
16
|
-
* opencode instances to coexist without conflicts.
|
|
17
|
-
*
|
|
18
|
-
* The upstream proxy unconditionally writes `[PROXY]` lines to
|
|
19
|
-
* console.error, so we patch it for the duration of the call and
|
|
20
|
-
* redirect those messages through the plugin logger instead.
|
|
21
|
-
*/
|
|
22
|
-
export declare function startProxy(opts: StartProxyOptions): Promise<ProxyHandle>;
|
|
23
|
-
/**
|
|
24
|
-
* Register cross-platform cleanup handlers that stop the proxy on exit.
|
|
25
|
-
*
|
|
26
|
-
* - `exit` and `SIGINT` work on all platforms.
|
|
27
|
-
* - `SIGTERM` is only available on POSIX systems.
|
|
28
|
-
*/
|
|
29
|
-
export declare function registerCleanup(proxy: ProxyHandle): void;
|
package/dist/proxy.js
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import { startProxyServer } from "opencode-claude-max-proxy";
|
|
2
|
-
const IS_WINDOWS = process.platform === "win32";
|
|
3
|
-
const DEFAULT_PORT = 3456;
|
|
4
|
-
/**
|
|
5
|
-
* Start the Claude Max proxy using the programmatic API.
|
|
6
|
-
*
|
|
7
|
-
* Tries the preferred port first (default 3456). If that port is already
|
|
8
|
-
* in use, falls back to port 0 so the OS assigns a free port. This keeps
|
|
9
|
-
* the port predictable for single-instance users while allowing multiple
|
|
10
|
-
* opencode instances to coexist without conflicts.
|
|
11
|
-
*
|
|
12
|
-
* The upstream proxy unconditionally writes `[PROXY]` lines to
|
|
13
|
-
* console.error, so we patch it for the duration of the call and
|
|
14
|
-
* redirect those messages through the plugin logger instead.
|
|
15
|
-
*/
|
|
16
|
-
export async function startProxy(opts) {
|
|
17
|
-
const { port = DEFAULT_PORT, log } = opts;
|
|
18
|
-
const origError = console.error;
|
|
19
|
-
console.error = (...args) => {
|
|
20
|
-
const msg = args.map(String).join(" ");
|
|
21
|
-
if (msg.startsWith("[PROXY]")) {
|
|
22
|
-
void log("debug", msg);
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
origError.apply(console, args);
|
|
26
|
-
};
|
|
27
|
-
const attempt = async (p) => {
|
|
28
|
-
try {
|
|
29
|
-
return await startProxyServer({
|
|
30
|
-
port: p,
|
|
31
|
-
host: "127.0.0.1",
|
|
32
|
-
silent: true,
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
catch (err) {
|
|
36
|
-
if (p !== 0 &&
|
|
37
|
-
err instanceof Error &&
|
|
38
|
-
"code" in err &&
|
|
39
|
-
err.code === "EADDRINUSE") {
|
|
40
|
-
await log("info", `Port ${p} in use, starting on a random port instead...`);
|
|
41
|
-
return startProxyServer({
|
|
42
|
-
port: 0,
|
|
43
|
-
host: "127.0.0.1",
|
|
44
|
-
silent: true,
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
throw err;
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
let proxy;
|
|
51
|
-
try {
|
|
52
|
-
proxy = await attempt(port);
|
|
53
|
-
}
|
|
54
|
-
catch (err) {
|
|
55
|
-
console.error = origError;
|
|
56
|
-
throw err;
|
|
57
|
-
}
|
|
58
|
-
const addr = proxy.server.address();
|
|
59
|
-
const actualPort = addr.port;
|
|
60
|
-
await log("info", `Claude Max proxy running on port ${actualPort}`);
|
|
61
|
-
return {
|
|
62
|
-
port: actualPort,
|
|
63
|
-
close: async () => {
|
|
64
|
-
console.error = origError;
|
|
65
|
-
await proxy.close();
|
|
66
|
-
},
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
// ---------------------------------------------------------------------------
|
|
70
|
-
// Process cleanup
|
|
71
|
-
// ---------------------------------------------------------------------------
|
|
72
|
-
/**
|
|
73
|
-
* Register cross-platform cleanup handlers that stop the proxy on exit.
|
|
74
|
-
*
|
|
75
|
-
* - `exit` and `SIGINT` work on all platforms.
|
|
76
|
-
* - `SIGTERM` is only available on POSIX systems.
|
|
77
|
-
*/
|
|
78
|
-
export function registerCleanup(proxy) {
|
|
79
|
-
let cleaned = false;
|
|
80
|
-
const cleanup = () => {
|
|
81
|
-
if (cleaned)
|
|
82
|
-
return;
|
|
83
|
-
cleaned = true;
|
|
84
|
-
void proxy.close();
|
|
85
|
-
};
|
|
86
|
-
process.on("exit", cleanup);
|
|
87
|
-
process.on("SIGINT", cleanup);
|
|
88
|
-
if (!IS_WINDOWS) {
|
|
89
|
-
process.on("SIGTERM", cleanup);
|
|
90
|
-
}
|
|
91
|
-
}
|