storyboard-bridge 0.3.0 → 0.3.3
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/index.mjs +33 -8
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -26,13 +26,13 @@
|
|
|
26
26
|
// --cdp <url> CDP_HTTP the debug-Chrome endpoint (default http://localhost:9222)
|
|
27
27
|
// -- CLAUDE_BIN/GEMINI_BIN path to the CLI binary if not on PATH
|
|
28
28
|
import { spawn, execFile } from 'node:child_process';
|
|
29
|
-
import { writeFile, readFile, unlink, stat, mkdir } from 'node:fs/promises';
|
|
29
|
+
import { writeFile, readFile, unlink, stat, mkdir, mkdtemp, rm } from 'node:fs/promises';
|
|
30
30
|
import { join, resolve, dirname, sep } from 'node:path';
|
|
31
31
|
import { fileURLToPath } from 'node:url';
|
|
32
32
|
import { tmpdir } from 'node:os';
|
|
33
33
|
import { WebSocket } from 'ws';
|
|
34
34
|
|
|
35
|
-
const VERSION = '0.3.
|
|
35
|
+
const VERSION = '0.3.3';
|
|
36
36
|
|
|
37
37
|
// ---- config ----
|
|
38
38
|
const arg = (name, fallback) => {
|
|
@@ -71,17 +71,19 @@ function wsUrl(httpUrl) {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
// ---- the same CLI contract the backend uses: `<bin> -p <prompt> --output-format json [--model m]` ----
|
|
74
|
-
function runCli(bin, args) {
|
|
74
|
+
function runCli(bin, args, stdinInput, cwd) {
|
|
75
75
|
return new Promise((resolve, reject) => {
|
|
76
76
|
// On Windows the CLI is usually a `.cmd` shim (claude.cmd) that spawn() can't launch without a
|
|
77
77
|
// shell; shell:true uses PATHEXT to resolve it. POSIX (Linux/WSL/macOS) spawns the binary directly.
|
|
78
|
-
const child = spawn(bin, args, { timeout: JOB_TIMEOUT_MS, shell: IS_WIN });
|
|
78
|
+
const child = spawn(bin, args, { timeout: JOB_TIMEOUT_MS, shell: IS_WIN, cwd });
|
|
79
79
|
let out = '';
|
|
80
80
|
let err = '';
|
|
81
81
|
child.stdout.on('data', (d) => (out += d));
|
|
82
82
|
child.stderr.on('data', (d) => (err += d));
|
|
83
83
|
child.on('error', reject);
|
|
84
84
|
child.on('close', (code) => (code === 0 ? resolve(out) : reject(new Error(`${bin} exited ${code}: ${err.slice(0, 300)}`))));
|
|
85
|
+
// Feed the prompt via STDIN (not argv) — see runJob for why this matters on Windows.
|
|
86
|
+
if (stdinInput != null) child.stdin.write(stdinInput);
|
|
85
87
|
child.stdin.end();
|
|
86
88
|
});
|
|
87
89
|
}
|
|
@@ -96,11 +98,34 @@ function extractText(stdout) {
|
|
|
96
98
|
}
|
|
97
99
|
return stdout;
|
|
98
100
|
}
|
|
99
|
-
async function runJob(prompt) {
|
|
100
|
-
|
|
101
|
+
async function runJob(prompt, images) {
|
|
102
|
+
// Pass the prompt via STDIN, not argv. On Windows the bridge spawns through cmd.exe (shell:true, to
|
|
103
|
+
// resolve claude.cmd), and a big multi-line prompt as an arg gets mangled — cmd splits it on newlines,
|
|
104
|
+
// breaking the command ("'claude' is not recognized"). stdin sidesteps shell parsing entirely; `claude
|
|
105
|
+
// -p` / `gemini -p` read the prompt from stdin. (POSIX worked either way, but stdin is correct on both.)
|
|
106
|
+
const args = ['-p'];
|
|
101
107
|
if (USE_JSON) args.push('--output-format', 'json'); // claude wraps as JSON; gemini prints raw text
|
|
102
108
|
if (MODEL) args.push('--model', MODEL);
|
|
103
|
-
|
|
109
|
+
if (images?.length) {
|
|
110
|
+
// vision: write the attached image(s) into a temp dir, then point the prompt's `@name` refs at each
|
|
111
|
+
// file's ABSOLUTE path. Claude/Gemini resolve a bare `@name` against their own workspace/home (e.g.
|
|
112
|
+
// C:\Users\<you>), NOT our spawn cwd — so a relative ref silently misses and the model can't see the
|
|
113
|
+
// image (it goes looking for "image.png" and reports it can't find it). Absolute paths are cwd-independent.
|
|
114
|
+
const dir = await mkdtemp(join(tmpdir(), 'sbvis-'));
|
|
115
|
+
try {
|
|
116
|
+
let p = prompt;
|
|
117
|
+
for (const im of images) {
|
|
118
|
+
const name = String(im.name);
|
|
119
|
+
const abs = join(dir, name);
|
|
120
|
+
await writeFile(abs, Buffer.from(String(im.data), 'base64'));
|
|
121
|
+
p = p.split('@' + name).join('@' + abs);
|
|
122
|
+
}
|
|
123
|
+
return extractText(await runCli(BIN, args, p, dir));
|
|
124
|
+
} finally {
|
|
125
|
+
await rm(dir, { recursive: true, force: true });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return extractText(await runCli(BIN, args, prompt));
|
|
104
129
|
}
|
|
105
130
|
|
|
106
131
|
// is a CLI installed / runnable? (best-effort; a logged-out CLI still answers --version)
|
|
@@ -224,7 +249,7 @@ function connect() {
|
|
|
224
249
|
} else if (msg.type === 'job' && msg.id) {
|
|
225
250
|
const t0 = Date.now();
|
|
226
251
|
try {
|
|
227
|
-
const text = await runJob(String(msg.prompt ?? ''));
|
|
252
|
+
const text = await runJob(String(msg.prompt ?? ''), Array.isArray(msg.images) ? msg.images : null);
|
|
228
253
|
ws.send(JSON.stringify({ type: 'result', id: msg.id, text }));
|
|
229
254
|
log(`job ${msg.id} ✓ (${Date.now() - t0}ms)`);
|
|
230
255
|
} catch (e) {
|
package/package.json
CHANGED