@chatpanel/bridge 0.2.6 → 0.2.8
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 +13 -6
- package/package.json +1 -1
- package/scripts/install.ps1 +39 -0
- package/scripts/install.sh +28 -17
- package/src/engines/claude.js +30 -3
- package/src/engines/codex.js +5 -4
- package/src/engines/gemini.js +2 -2
- package/src/env.js +26 -0
- package/src/server.js +12 -2
package/README.md
CHANGED
|
@@ -14,16 +14,23 @@ the ones the bridge reports as available.
|
|
|
14
14
|
|
|
15
15
|
## Run it
|
|
16
16
|
|
|
17
|
-
### Option A — one-line install (
|
|
17
|
+
### Option A — one-line install (no Node.js needed)
|
|
18
18
|
|
|
19
|
+
**macOS / Linux:**
|
|
19
20
|
```bash
|
|
20
|
-
curl -fsSL https://
|
|
21
|
+
curl -fsSL https://dl.chatpanel.net/bridge/install.sh | bash
|
|
21
22
|
```
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
**Windows (PowerShell):**
|
|
25
|
+
```powershell
|
|
26
|
+
irm https://dl.chatpanel.net/bridge/install.ps1 | iex
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
This downloads the standalone binary for your OS, installs it, and sets it to
|
|
30
|
+
start at login. **Recommended** — installing this way avoids the macOS "damaged"
|
|
31
|
+
and Windows SmartScreen prompts that browser downloads trigger. Re-running it is a
|
|
32
|
+
clean in-place upgrade (no duplicate installs). Then open the ChatPanel side panel
|
|
33
|
+
and your agents appear.
|
|
27
34
|
|
|
28
35
|
Manage it: `chatpanel-bridge --status` · `--uninstall` · run with no flags to start
|
|
29
36
|
once in the foreground.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chatpanel/bridge",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Local bridge that exposes the AI coding agents installed on your machine — Claude Code (Agent SDK), Codex (CLI), and Gemini CLI — to the ChatPanel Chrome extension over a localhost SSE endpoint. Bring your own agent.",
|
|
6
6
|
"keywords": [
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# ChatPanel Bridge installer (Windows) - no Node.js required.
|
|
2
|
+
#
|
|
3
|
+
# irm https://dl.chatpanel.net/bridge/install.ps1 | iex
|
|
4
|
+
$ErrorActionPreference = 'Stop'
|
|
5
|
+
$ProgressPreference = 'SilentlyContinue' # IWR fallback: skip the slow progress bar
|
|
6
|
+
|
|
7
|
+
$url = 'https://dl.chatpanel.net/bridge/windows-x64.exe'
|
|
8
|
+
$dir = Join-Path $env:LOCALAPPDATA 'ChatPanel'
|
|
9
|
+
$bin = Join-Path $dir 'chatpanel-bridge.exe'
|
|
10
|
+
$tmp = "$bin.new"
|
|
11
|
+
|
|
12
|
+
Write-Host ""
|
|
13
|
+
Write-Host "Installing ChatPanel Bridge" -ForegroundColor Cyan
|
|
14
|
+
|
|
15
|
+
# Stop any running bridge + its scheduled task so the .exe isn't locked (clean
|
|
16
|
+
# in-place upgrade, no duplicate installs).
|
|
17
|
+
schtasks /End /TN ChatPanelBridge *> $null
|
|
18
|
+
Get-Process -Name 'chatpanel-bridge' -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
|
|
19
|
+
Start-Sleep -Milliseconds 600
|
|
20
|
+
|
|
21
|
+
New-Item -ItemType Directory -Force -Path $dir | Out-Null
|
|
22
|
+
|
|
23
|
+
Write-Host " Downloading the bridge (~60 MB)..." -ForegroundColor Gray
|
|
24
|
+
$curl = Get-Command curl.exe -ErrorAction SilentlyContinue
|
|
25
|
+
if ($curl) {
|
|
26
|
+
& curl.exe -fL --progress-bar -o $tmp $url # fast, with a real progress bar
|
|
27
|
+
} else {
|
|
28
|
+
Invoke-WebRequest -Uri $url -OutFile $tmp -UseBasicParsing
|
|
29
|
+
}
|
|
30
|
+
Unblock-File -Path $tmp -ErrorAction SilentlyContinue # no SmartScreen mark-of-the-web
|
|
31
|
+
Move-Item -Force $tmp $bin
|
|
32
|
+
|
|
33
|
+
Write-Host " Setting it to start at login..." -ForegroundColor Gray
|
|
34
|
+
& $bin --install
|
|
35
|
+
|
|
36
|
+
Write-Host ""
|
|
37
|
+
Write-Host "Done. ChatPanel Bridge is running and starts at login." -ForegroundColor Green
|
|
38
|
+
Write-Host "Open the ChatPanel side panel - your agents appear automatically."
|
|
39
|
+
Write-Host "Manage it: `"$bin`" --status | --uninstall"
|
package/scripts/install.sh
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# ChatPanel Bridge installer
|
|
2
|
+
# ChatPanel Bridge installer - downloads the standalone binary for your OS and
|
|
3
3
|
# sets it to start at login. No Node.js required.
|
|
4
4
|
#
|
|
5
5
|
# curl -fsSL https://raw.githubusercontent.com/chatpanel/chatpanel-bridge/main/scripts/install.sh | bash
|
|
@@ -8,38 +8,49 @@
|
|
|
8
8
|
# "damaged / unidentified developer" prompt that browser downloads trigger.
|
|
9
9
|
set -euo pipefail
|
|
10
10
|
|
|
11
|
-
REPO="chatpanel/chatpanel-bridge"
|
|
12
11
|
os="$(uname -s)"
|
|
13
12
|
arch="$(uname -m)"
|
|
13
|
+
asset=""
|
|
14
14
|
|
|
15
15
|
case "$os" in
|
|
16
16
|
Darwin)
|
|
17
|
-
if [ "$arch" = "arm64" ]; then
|
|
17
|
+
if [ "$arch" = "arm64" ]; then
|
|
18
|
+
asset="bridge/macos-arm64"
|
|
18
19
|
else
|
|
19
|
-
echo "Intel Mac detected
|
|
20
|
+
echo "Intel Mac detected - no x64 binary yet. Use: npx @chatpanel/bridge (needs Node.js 18+)"
|
|
20
21
|
exit 1
|
|
21
|
-
fi
|
|
22
|
-
|
|
22
|
+
fi
|
|
23
|
+
;;
|
|
24
|
+
Linux)
|
|
25
|
+
asset="bridge/linux-x64"
|
|
26
|
+
;;
|
|
23
27
|
*)
|
|
24
28
|
echo "Unsupported OS ($os). Use: npx @chatpanel/bridge (needs Node.js 18+)"
|
|
25
|
-
exit 1
|
|
29
|
+
exit 1
|
|
30
|
+
;;
|
|
26
31
|
esac
|
|
27
32
|
|
|
28
|
-
url="https://
|
|
29
|
-
dest="$HOME/.local/bin"
|
|
30
|
-
bin="$dest/chatpanel-bridge"
|
|
33
|
+
url="https://dl.chatpanel.net/${asset}"
|
|
34
|
+
dest="${HOME}/.local/bin"
|
|
35
|
+
bin="${dest}/chatpanel-bridge"
|
|
31
36
|
mkdir -p "$dest"
|
|
32
37
|
|
|
33
|
-
echo "
|
|
34
|
-
curl -
|
|
38
|
+
echo "Downloading ChatPanel Bridge (~60 MB)..."
|
|
39
|
+
curl -fL --progress-bar "$url" -o "$bin" # show a progress bar (not silent)
|
|
35
40
|
chmod +x "$bin"
|
|
36
|
-
xattr -c "$bin" 2>/dev/null || true
|
|
41
|
+
xattr -c "$bin" 2>/dev/null || true # belt-and-suspenders; curl files aren't quarantined
|
|
37
42
|
|
|
38
|
-
|
|
43
|
+
# Clean upgrade: stop any running bridge (incl. a stray npx one) so the new
|
|
44
|
+
# install replaces it in place — same path, same service, no duplicates.
|
|
45
|
+
pkill -f 'chatpanel-bridge' 2>/dev/null || true
|
|
46
|
+
sleep 1
|
|
47
|
+
|
|
48
|
+
echo "Installed to ${bin}"
|
|
39
49
|
"$bin" --install
|
|
40
50
|
echo
|
|
41
51
|
echo "ChatPanel Bridge is running and will start at login."
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
*)
|
|
52
|
+
|
|
53
|
+
case ":${PATH}:" in
|
|
54
|
+
*":${dest}:"*) : ;;
|
|
55
|
+
*) echo "Tip: add it to your PATH -> export PATH=\"\$HOME/.local/bin:\$PATH\"" ;;
|
|
45
56
|
esac
|
package/src/engines/claude.js
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
import os from 'node:os';
|
|
12
|
+
import { existsSync } from 'node:fs';
|
|
13
|
+
import { findAgentBin, isCompiledBinary } from '../env.js';
|
|
12
14
|
|
|
13
15
|
let sdkPromise = null;
|
|
14
16
|
function loadSdk() {
|
|
@@ -16,14 +18,37 @@ function loadSdk() {
|
|
|
16
18
|
return sdkPromise;
|
|
17
19
|
}
|
|
18
20
|
|
|
21
|
+
// Where the Claude Code CLI lives. The SDK ships a bundled cli.js, but inside a
|
|
22
|
+
// compiled binary that file is on a virtual FS that child processes can't reach
|
|
23
|
+
// (it fails on Windows as "B:\~BUN\cli.js"). So in a binary we point the SDK at
|
|
24
|
+
// the user's INSTALLED Claude Code instead — preferring the real cli.js next to
|
|
25
|
+
// the npm shim (modern Node won't spawn a .cmd directly).
|
|
26
|
+
function claudeExecutable() {
|
|
27
|
+
if (process.env.CHATPANEL_CLAUDE_PATH) return process.env.CHATPANEL_CLAUDE_PATH;
|
|
28
|
+
if (!isCompiledBinary()) return undefined; // under node/bun the bundled cli.js works
|
|
29
|
+
const bin = findAgentBin('claude');
|
|
30
|
+
if (!bin) return undefined;
|
|
31
|
+
const dir = path.dirname(bin);
|
|
32
|
+
const candidates = [
|
|
33
|
+
path.join(dir, 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js'),
|
|
34
|
+
path.join(dir, '..', 'lib', 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js'),
|
|
35
|
+
];
|
|
36
|
+
for (const c of candidates) if (existsSync(c)) return c;
|
|
37
|
+
return bin;
|
|
38
|
+
}
|
|
39
|
+
|
|
19
40
|
export async function available() {
|
|
20
41
|
const sdk = await loadSdk();
|
|
21
42
|
if (!sdk) {
|
|
22
43
|
return { ok: false, reason: 'Agent SDK not installed (npm i in bridge/)' };
|
|
23
44
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
45
|
+
// In a compiled binary the bundled CLI is unreachable, so Claude Code must be
|
|
46
|
+
// installed locally. (Under node/bun the bundled CLI works, so this is skipped.)
|
|
47
|
+
if (isCompiledBinary() && !process.env.CHATPANEL_CLAUDE_PATH && !findAgentBin('claude')) {
|
|
48
|
+
return {
|
|
49
|
+
ok: false,
|
|
50
|
+
reason: 'Claude Code not found. Install it (npm i -g @anthropic-ai/claude-code), or run the bridge with `npx @chatpanel/bridge`.',
|
|
51
|
+
};
|
|
27
52
|
}
|
|
28
53
|
return { ok: true };
|
|
29
54
|
}
|
|
@@ -85,6 +110,7 @@ export async function chat({ messages, system, options }, emit) {
|
|
|
85
110
|
? { type: 'preset', preset: 'claude_code', append: system }
|
|
86
111
|
: { type: 'preset', preset: 'claude_code' },
|
|
87
112
|
...(options.model ? { model: options.model } : {}),
|
|
113
|
+
...(claudeExecutable() ? { pathToClaudeCodeExecutable: claudeExecutable() } : {}),
|
|
88
114
|
...(process.env.CHATPANEL_MAX_TURNS ? { maxTurns: Number(process.env.CHATPANEL_MAX_TURNS) } : {}),
|
|
89
115
|
},
|
|
90
116
|
});
|
|
@@ -140,6 +166,7 @@ export async function complete({ prompt, system, model }) {
|
|
|
140
166
|
settingSources: [], // skip CLAUDE.md / MCP for a tiny completion
|
|
141
167
|
systemPrompt: system || "Continue the user's text briefly. Reply with only the continuation.",
|
|
142
168
|
model: model || 'haiku',
|
|
169
|
+
...(claudeExecutable() ? { pathToClaudeCodeExecutable: claudeExecutable() } : {}),
|
|
143
170
|
},
|
|
144
171
|
});
|
|
145
172
|
for await (const message of iterator) {
|
package/src/engines/codex.js
CHANGED
|
@@ -19,6 +19,7 @@ import { readFile, unlink } from 'node:fs/promises';
|
|
|
19
19
|
import { existsSync, mkdirSync, symlinkSync } from 'node:fs';
|
|
20
20
|
import os from 'node:os';
|
|
21
21
|
import path from 'node:path';
|
|
22
|
+
import { findAgentBin } from '../env.js';
|
|
22
23
|
|
|
23
24
|
const TIMEOUT_MS = Number(process.env.CHATPANEL_CODEX_TIMEOUT_MS) || 180_000;
|
|
24
25
|
const REASONING = process.env.CHATPANEL_CODEX_EFFORT ?? 'low'; // '' → respect config
|
|
@@ -61,13 +62,13 @@ function ensureIsolatedHome() {
|
|
|
61
62
|
let installed = false;
|
|
62
63
|
let lastProbe = 0;
|
|
63
64
|
export async function available() {
|
|
64
|
-
//
|
|
65
|
-
//
|
|
65
|
+
// Availability = "is codex findable on PATH", not "does `codex --version` exit
|
|
66
|
+
// 0" (which fails when the CLI just needs login). Cache positives; re-probe
|
|
67
|
+
// (throttled) while not found so it self-heals once codex appears on PATH.
|
|
66
68
|
if (!installed && Date.now() - lastProbe > 4000) {
|
|
67
69
|
lastProbe = Date.now();
|
|
68
70
|
try {
|
|
69
|
-
|
|
70
|
-
installed = r.status === 0 || (r.status === null && !r.error);
|
|
71
|
+
installed = !!findAgentBin('codex');
|
|
71
72
|
} catch {
|
|
72
73
|
installed = false;
|
|
73
74
|
}
|
package/src/engines/gemini.js
CHANGED
|
@@ -12,6 +12,7 @@ import { spawn, spawnSync } from 'node:child_process';
|
|
|
12
12
|
import { mkdirSync } from 'node:fs';
|
|
13
13
|
import os from 'node:os';
|
|
14
14
|
import path from 'node:path';
|
|
15
|
+
import { findAgentBin } from '../env.js';
|
|
15
16
|
|
|
16
17
|
const TIMEOUT_MS = Number(process.env.CHATPANEL_GEMINI_TIMEOUT_MS) || 180_000;
|
|
17
18
|
const SCRATCH = path.join(os.tmpdir(), 'chatpanel-gemini-scratch');
|
|
@@ -24,8 +25,7 @@ export async function available() {
|
|
|
24
25
|
if (!installed && Date.now() - lastProbe > 4000) {
|
|
25
26
|
lastProbe = Date.now();
|
|
26
27
|
try {
|
|
27
|
-
|
|
28
|
-
installed = r.status === 0 || (r.status === null && !r.error);
|
|
28
|
+
installed = !!findAgentBin('gemini');
|
|
29
29
|
} catch {
|
|
30
30
|
installed = false;
|
|
31
31
|
}
|
package/src/env.js
CHANGED
|
@@ -21,6 +21,32 @@ function onPath(name) {
|
|
|
21
21
|
return dirs.some((d) => d && (existsSync(path.join(d, name)) || existsSync(path.join(d, name + '.exe'))));
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
// Resolve an agent CLI to its absolute path: first on the (enriched) PATH, then
|
|
25
|
+
// by asking the login shell. Returns the path, or null. Used for availability —
|
|
26
|
+
// "is it installed/findable", NOT "does `--version` exit 0" (which can fail for
|
|
27
|
+
// reasons unrelated to installation, e.g. the CLI needs login).
|
|
28
|
+
export function findAgentBin(name) {
|
|
29
|
+
// On Windows, CLIs are usually <name>.cmd / .exe / .bat (npm shims).
|
|
30
|
+
const exts = process.platform === 'win32' ? ['', '.cmd', '.exe', '.bat', '.ps1'] : [''];
|
|
31
|
+
const dirs = (process.env.PATH || '').split(path.delimiter);
|
|
32
|
+
for (const d of dirs) {
|
|
33
|
+
if (!d) continue;
|
|
34
|
+
for (const ext of exts) {
|
|
35
|
+
const p = path.join(d, name + ext);
|
|
36
|
+
if (existsSync(p)) return p;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return shellWhich(name) || null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// True when running as a Bun/Node single-file compiled binary (not under a
|
|
43
|
+
// node/bun interpreter). Inside such a binary, bundled JS files live on a virtual
|
|
44
|
+
// FS that child processes can't reach — notably the Claude SDK's CLI on Windows.
|
|
45
|
+
export function isCompiledBinary() {
|
|
46
|
+
const base = path.basename(process.execPath).toLowerCase();
|
|
47
|
+
return !(base.startsWith('node') || base.startsWith('bun'));
|
|
48
|
+
}
|
|
49
|
+
|
|
24
50
|
// Ask the user's login shell to locate a command — no hardcoded locations, works
|
|
25
51
|
// wherever the user actually installed it.
|
|
26
52
|
function shellWhich(name) {
|
package/src/server.js
CHANGED
|
@@ -15,13 +15,14 @@
|
|
|
15
15
|
// Binds to 127.0.0.1 only and accepts requests from the extension origin.
|
|
16
16
|
|
|
17
17
|
import { createServer } from 'node:http';
|
|
18
|
+
import os from 'node:os';
|
|
18
19
|
import * as claude from './engines/claude.js';
|
|
19
20
|
import * as codex from './engines/codex.js';
|
|
20
21
|
import * as gemini from './engines/gemini.js';
|
|
21
22
|
import { installService, uninstallService, serviceStatus } from './service.js';
|
|
22
|
-
import { enrichPath } from './env.js';
|
|
23
|
+
import { enrichPath, findAgentBin } from './env.js';
|
|
23
24
|
|
|
24
|
-
const VERSION = '0.2.
|
|
25
|
+
const VERSION = '0.2.8';
|
|
25
26
|
const HOST = process.env.CHATPANEL_BRIDGE_HOST || '127.0.0.1';
|
|
26
27
|
const PORT = Number(process.env.CHATPANEL_BRIDGE_PORT) || 4319;
|
|
27
28
|
|
|
@@ -175,6 +176,15 @@ const server = createServer(async (req, res) => {
|
|
|
175
176
|
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
176
177
|
try {
|
|
177
178
|
if (req.method === 'GET' && url.pathname === '/health') return handleHealth(res);
|
|
179
|
+
if (req.method === 'GET' && url.pathname === '/debug') {
|
|
180
|
+
return json(res, 200, {
|
|
181
|
+
version: VERSION,
|
|
182
|
+
home: os.homedir(),
|
|
183
|
+
codex: findAgentBin('codex') || null,
|
|
184
|
+
gemini: findAgentBin('gemini') || null,
|
|
185
|
+
path: process.env.PATH,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
178
188
|
if (req.method === 'POST' && url.pathname === '/chat') return handleChat(req, res);
|
|
179
189
|
if (req.method === 'POST' && url.pathname === '/complete') return handleComplete(req, res);
|
|
180
190
|
json(res, 404, { error: 'Not found' });
|