coc-bridge 0.0.0-stage → 1.0.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/CHANGELOG.md ADDED
@@ -0,0 +1,39 @@
1
+ # Changelog
2
+
3
+ All notable changes to coc-bridge are documented here.
4
+
5
+ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6
+ Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ---
9
+
10
+ ## [1.0.1] — 2026-10-14
11
+
12
+ ### Fixed
13
+ - Fixed global CLI bin symlink path in `package.json` for seamless `npm install -g coc-bridge` installation
14
+ - Dynamic model discovery from `opencode models`
15
+ - Added `coc-bridge models` and `coc-bridge use <model-id>` commands
16
+ - Full daemon backgrounding with `coc-bridge start/stop/status/restart/logs`
17
+
18
+ ## [1.0.0] — 2026-10-14
19
+
20
+ ### Added
21
+ - Initial public release 🎉
22
+ - Zero-dependency HTTP proxy serving the Anthropic `/v1/messages` and `/v1/models` API
23
+ - Streaming SSE support (`message_start → content_block_delta → message_stop`)
24
+ - Non-streaming JSON response support
25
+ - Model discovery endpoint (`GET /v1/models`) for Claude Code's `/model` picker
26
+ - 9 OpenCode free models available out of the box
27
+ - Model alias table: short names (`cat-jam`, `nemotron`, `muse-spark`, etc.) and Claude tier names (`opus`, `sonnet`, `haiku`, `fable`) all resolve correctly
28
+ - Auto-detection of `opencode` binary from `$PATH`, NVM locations, and common paths
29
+ - `coc-bridge setup` — wraps the `claude` binary to auto-start the bridge and inject all env vars
30
+ - `coc-bridge doctor` — checks Node.js version, opencode presence, claude wrapper status, bridge health, and log directory
31
+ - `coc-bridge help` — full CLI help with all env vars and model list
32
+ - 5-minute request timeout with `BRIDGE_TIMEOUT_MS` override
33
+ - 50MB payload cap with `413` response
34
+ - Client disconnect detection — kills orphan opencode child processes immediately
35
+ - `tool_use` block support in `buildPrompt()` — preserves full agent tool-call history across turns
36
+ - CORS headers for VS Code extension compatibility
37
+ - `npm test` four-test suite: health, model discovery, non-streaming, streaming SSE
38
+
39
+ [1.0.0]: https://github.com/allannuwamanya/coc-bridge/releases/tag/v1.0.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Allan Nuwamanya
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,178 @@
1
- # Temporary Holding Version
1
+ # coc-bridge 🔌
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > **Claude Code ↔ OpenCode. Zero API keys. All free.**
4
+
5
+ [![npm](https://img.shields.io/npm/v/coc-bridge.svg)](https://www.npmjs.com/package/coc-bridge)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
+ [![Node ≥18](https://img.shields.io/badge/Node-%3E%3D18-brightgreen.svg)](https://nodejs.org)
8
+
9
+ `coc-bridge` is a tiny local proxy that lets **Claude Code** use **OpenCode's free hosted models** — no Anthropic API key, no billing, no friction.
10
+
11
+ ```
12
+ Claude Code ──( Anthropic format )──► coc-bridge ──► opencode run -m <model>
13
+ Claude Code ◄──( SSE stream )─── coc-bridge ◄── model output
14
+ ```
15
+
16
+ ---
17
+
18
+ ## ✨ Features
19
+
20
+ - **Zero dependencies** — pure Node.js, nothing to `npm install`
21
+ - **Drop-in replacement** — Claude Code doesn't know the difference
22
+ - **9 free models** out of the box (Space Bunny, Cat Jam, Nemotron Ultra, Muse Spark, and more)
23
+ - **Full streaming** — Anthropic SSE events, same as the real API
24
+ - **VS Code model picker** — custom model names show up in the sidebar dropdown
25
+ - **Auto-start** — the bridge starts itself when you run `claude`, so you never have to think about it
26
+ - **`coc-bridge doctor`** — one command checks everything is working
27
+
28
+ ---
29
+
30
+ ## 🚀 Quick start (3 steps)
31
+
32
+ ### Step 1 — Install opencode
33
+
34
+ ```bash
35
+ npm install -g opencode
36
+ ```
37
+
38
+ > opencode is the only runtime requirement. coc-bridge itself has zero dependencies.
39
+
40
+ ### Step 2 — Install coc-bridge
41
+
42
+ ```bash
43
+ npm install -g coc-bridge
44
+ ```
45
+
46
+ ### Step 3 — Run setup
47
+
48
+ ```bash
49
+ coc-bridge setup
50
+ ```
51
+
52
+ That's it. From now on, running `claude` will automatically start the bridge and use free OpenCode models. 🎉
53
+
54
+ ---
55
+
56
+ ## 🧪 Verify it's working
57
+
58
+ ```bash
59
+ coc-bridge doctor
60
+ ```
61
+
62
+ ```
63
+ coc-bridge doctor 🩺
64
+
65
+ ✔ Node.js v24.21.0
66
+ ✔ opencode 0.3.x
67
+ ✔ claude wrapper /usr/local/bin/claude (patched ✓)
68
+ ✔ Bridge server http://127.0.0.1:4045 ✓
69
+ ✔ Default model opencode/space-bunny-free
70
+ ✔ Available models 9 models
71
+ ✔ Log directory /home/you/.coc-bridge
72
+
73
+ Everything looks great! 🎉
74
+ ```
75
+
76
+ Or run the full test suite:
77
+
78
+ ```bash
79
+ npm test
80
+ ```
81
+
82
+ ---
83
+
84
+ ## 🎛️ Available models
85
+
86
+ | Short name | Full OpenCode ID | Best for |
87
+ | :--------------- | :------------------------------------------------ | :------------------------------ |
88
+ | `space-bunny` | `opencode/space-bunny-free` | General coding (default) |
89
+ | `cat-jam` | `opencode/cat-jam-free` | Fast edits & quick answers |
90
+ | `muse-spark` | `opencode/muse-spark-1.3-contributor-free` | Reasoning & architecture |
91
+ | `nemotron` | `opencode/nemotron-3-ultra-free` | Deep reasoning (NVIDIA 500B) |
92
+ | `nemotron-lightning` | `opencode/nemotron-3.5-lightning-free` | Fast NVIDIA model |
93
+ | `ling-flash` | `opencode/ling-3.1-flash-free` | High-throughput tasks |
94
+ | `longcat` | `opencode/longcat-2.5-preview-free` | Long-context tasks |
95
+ | `mimo` | `opencode/mimo-v2.6-flash-free` | Multimodal flash |
96
+ | `big-pickle` | `opencode/big-pickle` | 🥒 |
97
+
98
+ **VS Code dropdown** maps to:
99
+ | Claude Code menu | Resolves to |
100
+ | :--------------- | :--------------------------------------------------- |
101
+ | Opus | `opencode/nemotron-3-ultra-free` (Nemotron 3 Ultra) |
102
+ | Sonnet | `opencode/muse-spark-1.3-contributor-free` (Muse Spark 1.3) |
103
+ | Haiku | `opencode/cat-jam-free` (Cat Jam) |
104
+ | Fable | `opencode/ling-3.1-flash-free` (Ling 3.1 Flash) |
105
+ | Custom | `opencode/space-bunny-free` (Space Bunny) |
106
+
107
+ ---
108
+
109
+ ## ⚙️ Configuration
110
+
111
+ All configuration is via environment variables — no config files.
112
+
113
+ | Variable | Default | Description |
114
+ | :------------------- | :----------------------------- | :------------------------------------------- |
115
+ | `BRIDGE_PORT` | `4045` | Port the bridge listens on |
116
+ | `OPENCODE_MODEL` | `opencode/space-bunny-free` | Default model when none is requested |
117
+ | `OPENCODE_BIN` | auto-detected | Path to the `opencode` binary |
118
+ | `BRIDGE_TIMEOUT_MS` | `300000` (5 min) | Max time per request before killing the child process |
119
+
120
+ ### Use a custom port
121
+
122
+ ```bash
123
+ BRIDGE_PORT=5000 coc-bridge
124
+ ```
125
+
126
+ ### Use a specific model as default
127
+
128
+ ```bash
129
+ OPENCODE_MODEL=opencode/nemotron-3-ultra-free coc-bridge
130
+ ```
131
+
132
+ ---
133
+
134
+ ## 🛠️ Manual setup (without `coc-bridge setup`)
135
+
136
+ If you prefer not to let the installer touch your `claude` binary:
137
+
138
+ ```bash
139
+ # Terminal 1 — start the bridge
140
+ coc-bridge
141
+
142
+ # Terminal 2 — set env vars and launch Claude Code
143
+ export ANTHROPIC_BASE_URL="http://127.0.0.1:4045"
144
+ export ANTHROPIC_AUTH_TOKEN="coc-local"
145
+ claude
146
+ ```
147
+
148
+ ---
149
+
150
+ ## 🗂️ Project structure
151
+
152
+ ```
153
+ coc-bridge/
154
+ ├── bin/
155
+ │ └── coc-bridge.js # CLI entry point
156
+ ├── src/
157
+ │ └── server.js # Bridge proxy server
158
+ ├── scripts/
159
+ │ ├── setup-claude.js # Wraps the claude binary
160
+ │ └── doctor.js # Health check utility
161
+ ├── test/
162
+ │ └── test-client.js # Test suite
163
+ ├── CHANGELOG.md
164
+ ├── CONTRIBUTING.md
165
+ └── LICENSE
166
+ ```
167
+
168
+ ---
169
+
170
+ ## 🤝 Contributing
171
+
172
+ See [CONTRIBUTING.md](CONTRIBUTING.md). PRs welcome — especially new model aliases and better error messages.
173
+
174
+ ---
175
+
176
+ ## 📄 License
177
+
178
+ [MIT](LICENSE) © Allan Nuwamanya
@@ -0,0 +1,360 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * coc-bridge CLI entry point
4
+ *
5
+ * Default behaviour: daemonize the server in the background, confirm it's
6
+ * connected, then return the terminal prompt immediately.
7
+ *
8
+ * Commands:
9
+ * (none / start) Start daemon in background and confirm connection
10
+ * stop Stop the running daemon
11
+ * status Show whether the daemon is running
12
+ * restart Stop then start
13
+ * logs Tail the live log file
14
+ * server Run the server IN the foreground (for debugging)
15
+ * setup Wire Claude Code to auto-start the bridge
16
+ * doctor Health-check the full installation
17
+ * help / --help Show this help
18
+ * --version / -v Show version
19
+ */
20
+ 'use strict';
21
+
22
+ const path = require('path');
23
+ const fs = require('fs');
24
+ const http = require('http');
25
+ const os = require('os');
26
+ const { spawn, execSync } = require('child_process');
27
+
28
+ // ─── paths ────────────────────────────────────────────────────────────────────
29
+ const PORT = parseInt(process.env.BRIDGE_PORT || '4045', 10);
30
+ const LOG_DIR = path.join(os.homedir(), '.coc-bridge');
31
+ const LOG_FILE = path.join(LOG_DIR, 'bridge.log');
32
+ const PID_FILE = path.join(LOG_DIR, 'bridge.pid');
33
+ const CONFIG_FILE = path.join(LOG_DIR, 'config.json');
34
+ const SERVER_JS = path.join(__dirname, '..', 'src', 'server.js');
35
+
36
+ function readConfig() {
37
+ try { return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')); } catch (_) { return {}; }
38
+ }
39
+
40
+ // ─── colours ──────────────────────────────────────────────────────────────────
41
+ const G = '\x1b[32m', Y = '\x1b[33m', R = '\x1b[31m', B = '\x1b[34m';
42
+ const BOLD = '\x1b[1m', DIM = '\x1b[2m', RESET = '\x1b[0m';
43
+
44
+ // ─── helpers ──────────────────────────────────────────────────────────────────
45
+ function ensureLogDir() {
46
+ if (!fs.existsSync(LOG_DIR)) fs.mkdirSync(LOG_DIR, { recursive: true });
47
+ }
48
+
49
+ function readPid() {
50
+ try { return parseInt(fs.readFileSync(PID_FILE, 'utf8').trim(), 10); } catch (_) { return null; }
51
+ }
52
+
53
+ function writePid(pid) {
54
+ ensureLogDir();
55
+ fs.writeFileSync(PID_FILE, String(pid));
56
+ }
57
+
58
+ function clearPid() {
59
+ try { fs.unlinkSync(PID_FILE); } catch (_) {}
60
+ }
61
+
62
+ function isProcessAlive(pid) {
63
+ if (!pid) return false;
64
+ try { process.kill(pid, 0); return true; } catch (_) { return false; }
65
+ }
66
+
67
+ function healthCheck() {
68
+ return new Promise(resolve => {
69
+ const req = http.get(`http://127.0.0.1:${PORT}/health`, { timeout: 2000 }, res => {
70
+ let body = '';
71
+ res.on('data', d => body += d);
72
+ res.on('end', () => {
73
+ try { resolve(JSON.parse(body)); } catch (_) { resolve(null); }
74
+ });
75
+ });
76
+ req.on('error', () => resolve(null));
77
+ req.on('timeout', () => { req.destroy(); resolve(null); });
78
+ });
79
+ }
80
+
81
+ async function waitForBridge(timeoutMs = 8000) {
82
+ const start = Date.now();
83
+ while (Date.now() - start < timeoutMs) {
84
+ const health = await healthCheck();
85
+ if (health && health.status === 'ok') return health;
86
+ await new Promise(r => setTimeout(r, 150));
87
+ }
88
+ return null;
89
+ }
90
+
91
+ // ─── commands ─────────────────────────────────────────────────────────────────
92
+
93
+ async function cmdStart() {
94
+ // Already running?
95
+ const existingPid = readPid();
96
+ if (isProcessAlive(existingPid)) {
97
+ const health = await healthCheck();
98
+ if (health) {
99
+ console.log(`\n${G}${BOLD}✔ coc-bridge already running${RESET} ${DIM}(pid ${existingPid}, port ${PORT})${RESET}`);
100
+ console.log(` ${DIM}Default model: ${health.defaultModel}${RESET}`);
101
+ console.log(` ${DIM}${health.availableModels.length} models available${RESET}\n`);
102
+ return;
103
+ }
104
+ // PID alive but bridge not responding — stale, clean up
105
+ clearPid();
106
+ }
107
+
108
+ // Spawn the daemon — stdout/stderr both go to the log file
109
+ ensureLogDir();
110
+ const logFd = fs.openSync(LOG_FILE, 'a');
111
+ const child = spawn(process.execPath, [SERVER_JS], {
112
+ detached: true,
113
+ stdio: ['ignore', logFd, logFd],
114
+ env: { ...process.env, COC_BRIDGE_DAEMON: '1' },
115
+ });
116
+ fs.closeSync(logFd);
117
+ child.unref();
118
+
119
+ writePid(child.pid);
120
+
121
+ process.stdout.write(`\n Starting coc-bridge`);
122
+ const health = await waitForBridge(10000);
123
+
124
+ if (!health) {
125
+ process.stdout.write(`\n${R}${BOLD}✖ Bridge failed to start${RESET}\n`);
126
+ console.log(` Check logs: ${LOG_FILE}\n`);
127
+ clearPid();
128
+ process.exit(1);
129
+ }
130
+
131
+ console.log(`\r${G}${BOLD}✔ coc-bridge connected${RESET} ${DIM}http://127.0.0.1:${PORT} (pid ${child.pid})${RESET}`);
132
+ console.log(` ${DIM}Default model : ${health.defaultModel}${RESET}`);
133
+ console.log(` ${DIM}Models : ${health.availableModels.length} available${RESET}`);
134
+ console.log(` ${DIM}Logs : ${LOG_FILE}${RESET}`);
135
+ console.log(`\n ${DIM}Run ${RESET}${BOLD}coc-bridge stop${RESET}${DIM} to shut it down.${RESET}\n`);
136
+ }
137
+
138
+ async function cmdStop() {
139
+ const pid = readPid();
140
+ if (!pid || !isProcessAlive(pid)) {
141
+ console.log(`\n${Y} coc-bridge is not running.${RESET}\n`);
142
+ clearPid();
143
+ return;
144
+ }
145
+ try {
146
+ process.kill(pid, 'SIGTERM');
147
+ // Wait up to 3s for process to die
148
+ for (let i = 0; i < 30; i++) {
149
+ await new Promise(r => setTimeout(r, 100));
150
+ if (!isProcessAlive(pid)) break;
151
+ }
152
+ clearPid();
153
+ console.log(`\n${G} ✔ coc-bridge stopped.${RESET}\n`);
154
+ } catch (e) {
155
+ console.log(`\n${R} ✖ Could not stop process ${pid}: ${e.message}${RESET}\n`);
156
+ }
157
+ }
158
+
159
+ async function cmdStatus() {
160
+ const pid = readPid();
161
+ const alive = isProcessAlive(pid);
162
+ const health = await healthCheck();
163
+
164
+ console.log('');
165
+ if (alive && health) {
166
+ console.log(`${G}${BOLD} ● coc-bridge is running${RESET}`);
167
+ console.log(` pid : ${pid}`);
168
+ console.log(` port : ${PORT}`);
169
+ console.log(` model : ${health.defaultModel}`);
170
+ console.log(` models : ${health.availableModels.length} available`);
171
+ console.log(` log : ${LOG_FILE}`);
172
+ } else if (alive && !health) {
173
+ console.log(`${Y}${BOLD} ⚠ Process alive (pid ${pid}) but not responding on port ${PORT}${RESET}`);
174
+ } else {
175
+ console.log(`${R}${BOLD} ○ coc-bridge is not running${RESET}`);
176
+ console.log(` Run ${BOLD}coc-bridge${RESET} to start it.`);
177
+ }
178
+ console.log('');
179
+ }
180
+
181
+ async function cmdRestart() {
182
+ await cmdStop();
183
+ await new Promise(r => setTimeout(r, 500));
184
+ await cmdStart();
185
+ }
186
+
187
+ function cmdLogs() {
188
+ ensureLogDir();
189
+ if (!fs.existsSync(LOG_FILE)) {
190
+ console.log(`\n${Y} No log file yet. Start the bridge first: coc-bridge${RESET}\n`);
191
+ return;
192
+ }
193
+ console.log(`\n${DIM} Tailing ${LOG_FILE} (Ctrl+C to exit)${RESET}\n`);
194
+ // Print last 20 lines then follow
195
+ try {
196
+ const lines = fs.readFileSync(LOG_FILE, 'utf8').split('\n').slice(-20).join('\n');
197
+ if (lines.trim()) console.log(lines);
198
+ } catch (_) {}
199
+ const tail = spawn('tail', ['-f', LOG_FILE], { stdio: 'inherit' });
200
+ tail.on('exit', () => process.exit(0));
201
+ process.on('SIGINT', () => { tail.kill(); process.exit(0); });
202
+ }
203
+
204
+ function cmdServer() {
205
+ // Foreground mode — just run the server directly, no daemonizing
206
+ console.log(`\n${B}${BOLD} coc-bridge — foreground mode${RESET} ${DIM}(Ctrl+C to stop)${RESET}\n`);
207
+ require(SERVER_JS);
208
+ }
209
+
210
+ // ── coc-bridge models ──────────────────────────────────────────────────────────
211
+ function cmdModels() {
212
+ const { spawnSync } = require('child_process');
213
+ const NODE = process.execPath;
214
+
215
+ // Ask the bridge's own health endpoint first (if running)
216
+ http.get(`http://127.0.0.1:${PORT}/v1/models`, { timeout: 2000 }, res => {
217
+ let body = '';
218
+ res.on('data', d => body += d);
219
+ res.on('end', () => {
220
+ try {
221
+ const data = JSON.parse(body);
222
+ printModelList(data.data, '(from running bridge)');
223
+ } catch (_) {
224
+ fetchAndPrint();
225
+ }
226
+ });
227
+ }).on('error', () => fetchAndPrint());
228
+
229
+ function fetchAndPrint() {
230
+ // Bridge not running — call opencode models directly
231
+ let opencodebin;
232
+ try {
233
+ const { execSync } = require('child_process');
234
+ opencodebin = execSync('which opencode', { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
235
+ } catch (_) {
236
+ const candidates = [
237
+ path.join(os.homedir(), '.nvm', 'versions', 'node', 'v24.21.0', 'bin', 'opencode'),
238
+ path.join(os.homedir(), '.local', 'bin', 'opencode'),
239
+ '/usr/local/bin/opencode',
240
+ ];
241
+ opencodebin = candidates.find(c => { try { fs.accessSync(c, fs.constants.X_OK); return true; } catch (_) { return false; } });
242
+ }
243
+ if (!opencodebin) {
244
+ console.log(`\n${R} Cannot find opencode. Install it: npm install -g opencode${RESET}\n`);
245
+ return;
246
+ }
247
+ const r = spawnSync(opencodebin, ['models'], { encoding: 'utf8', timeout: 10000, stdio: ['ignore', 'pipe', 'ignore'] });
248
+ if (r.status !== 0 || !r.stdout) {
249
+ console.log(`\n${R} opencode models failed.${RESET}\n`);
250
+ return;
251
+ }
252
+ const models = r.stdout.trim().split('\n').filter(Boolean).map(id => ({ id, display_name: id }));
253
+ printModelList(models, '(from opencode)');
254
+ }
255
+
256
+ function printModelList(models, source) {
257
+ const cfg = readConfig();
258
+ const current = cfg.defaultModel || 'opencode/space-bunny-free';
259
+ console.log(`\n${BOLD} Available OpenCode models ${DIM}${source}${RESET}\n`);
260
+ for (const m of models) {
261
+ const isDefault = m.id === current;
262
+ const tag = isDefault ? ` ${G}← current default${RESET}` : '';
263
+ console.log(` ${isDefault ? G + BOLD : DIM}${m.id}${RESET}${tag}`);
264
+ }
265
+ console.log(`\n ${DIM}Set default: ${RESET}${BOLD}coc-bridge use <model-id>${RESET}\n`);
266
+ }
267
+ }
268
+
269
+ // ── coc-bridge use <model> ─────────────────────────────────────────────────────
270
+ function cmdUse(modelId) {
271
+ if (!modelId) {
272
+ console.log(`\n${R} Usage: coc-bridge use <model-id>${RESET}`);
273
+ console.log(` Example: coc-bridge use opencode/cat-jam-free\n`);
274
+ console.log(` Run ${BOLD}coc-bridge models${RESET} to see what's available.\n`);
275
+ return;
276
+ }
277
+ ensureLogDir();
278
+ const cfg = readConfig();
279
+ const previous = cfg.defaultModel || 'opencode/space-bunny-free';
280
+ cfg.defaultModel = modelId;
281
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2));
282
+ console.log(`\n${G}${BOLD} ✔ Default model updated${RESET}`);
283
+ console.log(` ${DIM}Was:${RESET} ${previous}`);
284
+ console.log(` ${DIM}Now:${RESET} ${G}${modelId}${RESET}`);
285
+ console.log(`\n ${DIM}Restart the bridge to apply: ${RESET}${BOLD}coc-bridge restart${RESET}\n`);
286
+ }
287
+
288
+ function showHelp() {
289
+ const pkg = require('../package.json');
290
+ console.log(`
291
+ ${BOLD}╔══════════════════════════════════════════════════════╗
292
+ ║ coc-bridge 🔌 v${pkg.version} — Claude ↔ OpenCode ║
293
+ ╚══════════════════════════════════════════════════════╝${RESET}
294
+
295
+ ${BOLD}USAGE${RESET}
296
+ coc-bridge [command]
297
+
298
+ ${BOLD}COMMANDS${RESET}
299
+ ${G}(default)${RESET} Start the bridge in the background, return your prompt
300
+ ${G}stop${RESET} Stop the running background bridge
301
+ ${G}restart${RESET} Stop then start again
302
+ ${G}status${RESET} Show whether the bridge is running
303
+ ${G}logs${RESET} Tail the live log file
304
+ ${G}models${RESET} List all available OpenCode models
305
+ ${G}use <id>${RESET} Set the default model (saved to ~/.coc-bridge/config.json)
306
+ ${G}server${RESET} Run in the foreground (useful for debugging)
307
+ ${G}setup${RESET} Wire Claude Code to auto-start the bridge on login
308
+ ${G}doctor${RESET} Check that Node, opencode, and claude are all wired up
309
+
310
+ ${BOLD}ENVIRONMENT VARIABLES${RESET}
311
+ BRIDGE_PORT Port to listen on (default: 4045)
312
+ OPENCODE_MODEL Default model (overrides config.json)
313
+ OPENCODE_BIN Path to opencode binary (auto-detected)
314
+ BRIDGE_TIMEOUT_MS Max ms per request (default: 300000)
315
+
316
+ ${BOLD}EXAMPLES${RESET}
317
+ coc-bridge # Start daemon, get prompt back
318
+ coc-bridge models # See all models you can use
319
+ coc-bridge use opencode/cat-jam-free # Switch default model
320
+ coc-bridge restart # Apply model change
321
+ coc-bridge stop # Shut it down
322
+ coc-bridge status # Is it running?
323
+ coc-bridge logs # Watch live logs
324
+ coc-bridge setup # One-time Claude Code wiring
325
+ BRIDGE_PORT=5000 coc-bridge # Custom port
326
+ `);
327
+ }
328
+
329
+ // ─── router ───────────────────────────────────────────────────────────────────
330
+ const cmd = process.argv[2] || 'start';
331
+
332
+ (async () => {
333
+ switch (cmd) {
334
+ case 'start': await cmdStart(); break;
335
+ case 'stop': await cmdStop(); break;
336
+ case 'status': await cmdStatus(); break;
337
+ case 'restart': await cmdRestart(); break;
338
+ case 'logs': cmdLogs(); break;
339
+ case 'server': cmdServer(); break;
340
+ case 'models': cmdModels(); break;
341
+ case 'use': cmdUse(process.argv[3]); break;
342
+ case 'setup': require(path.join(__dirname, '..', 'scripts', 'setup-claude.js')); break;
343
+ case 'doctor': require(path.join(__dirname, '..', 'scripts', 'doctor.js')); break;
344
+ case '--version':
345
+ case '-v': {
346
+ const pkg = require('../package.json');
347
+ console.log(`coc-bridge v${pkg.version}`);
348
+ break;
349
+ }
350
+ case 'help':
351
+ case '--help':
352
+ case '-h':
353
+ showHelp();
354
+ break;
355
+ default:
356
+ console.log(`\n${R} Unknown command: ${cmd}${RESET}\n Run ${BOLD}coc-bridge help${RESET} for available commands.\n`);
357
+ process.exit(1);
358
+ }
359
+ })();
360
+
package/package.json CHANGED
@@ -1,6 +1,56 @@
1
1
  {
2
2
  "name": "coc-bridge",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.1",
4
+ "description": "A zero-dependency local bridge that connects Claude Code to OpenCode's free hosted models — no API keys required.",
5
+ "main": "src/server.js",
6
+ "bin": {
7
+ "coc-bridge": "./bin/coc-bridge.js",
8
+ "coc-bridge-start": "./bin/coc-bridge.js"
9
+ },
10
+ "scripts": {
11
+ "start": "node src/server.js",
12
+ "dev": "BRIDGE_LOG_LEVEL=debug node src/server.js",
13
+ "test": "node test/test-client.js",
14
+ "install-claude": "node scripts/setup-claude.js",
15
+ "doctor": "node scripts/doctor.js"
16
+ },
17
+ "keywords": [
18
+ "claude-code",
19
+ "opencode",
20
+ "anthropic",
21
+ "proxy",
22
+ "bridge",
23
+ "ai",
24
+ "llm",
25
+ "free",
26
+ "claude",
27
+ "opencode-bridge"
28
+ ],
29
+ "author": {
30
+ "name": "Allan Nuwamanya",
31
+ "email": "allan.info.comp@gmail.com",
32
+ "url": "https://github.com/allannuwamanya"
33
+ },
34
+ "license": "MIT",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/allannuwamanya/coc-bridge.git"
38
+ },
39
+ "bugs": {
40
+ "url": "https://github.com/allannuwamanya/coc-bridge/issues"
41
+ },
42
+ "homepage": "https://github.com/allannuwamanya/coc-bridge#readme",
43
+ "engines": {
44
+ "node": ">=18.0.0"
45
+ },
46
+ "files": [
47
+ "bin/",
48
+ "src/",
49
+ "scripts/",
50
+ "test/",
51
+ "README.md",
52
+ "LICENSE",
53
+ "CHANGELOG.md"
54
+ ],
55
+ "preferGlobal": true
56
+ }