@corenel/sidecar 0.1.1 → 0.1.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/README.md +40 -40
- package/dist/cli.js +113 -36
- package/package.json +41 -41
package/README.md
CHANGED
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
# @corenel/sidecar
|
|
2
|
-
|
|
3
|
-
A small local capability server for [Prompd](https://prompd.app). Run it next to the
|
|
4
|
-
web app and the browser can reach your machine — most usefully, your **local LLMs**
|
|
5
|
-
(Ollama, LM Studio, vLLM) **without CORS**, plus your filesystem and (opt-in) shell.
|
|
6
|
-
|
|
7
|
-
It runs only while the command is open, binds to loopback by default, and pairs with
|
|
8
|
-
a token the app reads once.
|
|
9
|
-
|
|
10
|
-
## Use
|
|
11
|
-
|
|
12
|
-
In Prompd, open **Connect a sidecar** and follow the steps, or run it directly:
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
npx @corenel/sidecar --allow-origin https://prompd.app
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
It prints a pairing token; paste that into the app. The `--allow-origin` flag must
|
|
19
|
-
match the site you're connecting from (the app fills it in for you).
|
|
20
|
-
|
|
21
|
-
## Options
|
|
22
|
-
|
|
23
|
-
| Flag | Default | What |
|
|
24
|
-
|------|---------|------|
|
|
25
|
-
| `--allow-origin <url>` | _(none)_ | Browser origin allowed to connect. Required for a web page. Repeatable. |
|
|
26
|
-
| `--port <n>` | `4858` | Port to listen on. |
|
|
27
|
-
| `--root <dir>` | cwd | Root the file tools are confined to. |
|
|
28
|
-
| `--allow-shell` | off | Enable the `shell_exec` tool (RCE on your machine — opt-in). |
|
|
29
|
-
| `--host <addr>` | `127.0.0.1` | Bind address. Non-loopback is reachable off-box; keep the token secret. |
|
|
30
|
-
| `--tls-cert <f>` `--tls-key <f>` | _(none)_ | Serve `wss://` (needed for a remote sidecar from an https page). |
|
|
31
|
-
|
|
32
|
-
## Security
|
|
33
|
-
|
|
34
|
-
- Loopback by default; a browser connection requires both the pairing token **and** an
|
|
35
|
-
allowed Origin.
|
|
36
|
-
- The HTTP proxy (for local LLMs) only reaches **loopback** hosts — it can't be used as
|
|
37
|
-
an SSRF pivot to internal services.
|
|
38
|
-
- `shell_exec` is off unless you pass `--allow-shell`.
|
|
39
|
-
|
|
40
|
-
Licensed under Elastic-2.0.
|
|
1
|
+
# @corenel/sidecar
|
|
2
|
+
|
|
3
|
+
A small local capability server for [Prompd](https://prompd.app). Run it next to the
|
|
4
|
+
web app and the browser can reach your machine — most usefully, your **local LLMs**
|
|
5
|
+
(Ollama, LM Studio, vLLM) **without CORS**, plus your filesystem and (opt-in) shell.
|
|
6
|
+
|
|
7
|
+
It runs only while the command is open, binds to loopback by default, and pairs with
|
|
8
|
+
a token the app reads once.
|
|
9
|
+
|
|
10
|
+
## Use
|
|
11
|
+
|
|
12
|
+
In Prompd, open **Connect a sidecar** and follow the steps, or run it directly:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npx @corenel/sidecar --allow-origin https://prompd.app
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
It prints a pairing token; paste that into the app. The `--allow-origin` flag must
|
|
19
|
+
match the site you're connecting from (the app fills it in for you).
|
|
20
|
+
|
|
21
|
+
## Options
|
|
22
|
+
|
|
23
|
+
| Flag | Default | What |
|
|
24
|
+
|------|---------|------|
|
|
25
|
+
| `--allow-origin <url>` | _(none)_ | Browser origin allowed to connect. Required for a web page. Repeatable. |
|
|
26
|
+
| `--port <n>` | `4858` | Port to listen on. |
|
|
27
|
+
| `--root <dir>` | cwd | Root the file tools are confined to. |
|
|
28
|
+
| `--allow-shell` | off | Enable the `shell_exec` tool (RCE on your machine — opt-in). |
|
|
29
|
+
| `--host <addr>` | `127.0.0.1` | Bind address. Non-loopback is reachable off-box; keep the token secret. |
|
|
30
|
+
| `--tls-cert <f>` `--tls-key <f>` | _(none)_ | Serve `wss://` (needed for a remote sidecar from an https page). |
|
|
31
|
+
|
|
32
|
+
## Security
|
|
33
|
+
|
|
34
|
+
- Loopback by default; a browser connection requires both the pairing token **and** an
|
|
35
|
+
allowed Origin.
|
|
36
|
+
- The HTTP proxy (for local LLMs) only reaches **loopback** hosts — it can't be used as
|
|
37
|
+
an SSRF pivot to internal services.
|
|
38
|
+
- `shell_exec` is off unless you pass `--allow-shell`.
|
|
39
|
+
|
|
40
|
+
Licensed under Elastic-2.0.
|
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,8 @@ import { createRequire as __cr } from 'node:module'; const require = __cr(import
|
|
|
3
3
|
|
|
4
4
|
// src/cli.ts
|
|
5
5
|
import { resolve as resolve2 } from "node:path";
|
|
6
|
-
import { readFileSync } from "node:fs";
|
|
6
|
+
import { readFileSync, statSync } from "node:fs";
|
|
7
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
7
8
|
|
|
8
9
|
// src/server.ts
|
|
9
10
|
import { WebSocketServer } from "ws";
|
|
@@ -278,6 +279,7 @@ function startSidecar(opts) {
|
|
|
278
279
|
wss2.on("connection", onConnection);
|
|
279
280
|
return new Promise((resolveHandle, reject) => {
|
|
280
281
|
httpsServer.on("error", reject);
|
|
282
|
+
wss2.on("error", reject);
|
|
281
283
|
httpsServer.listen(opts.port, host, () => {
|
|
282
284
|
const addr = httpsServer.address();
|
|
283
285
|
const boundPort = typeof addr === "object" && addr ? addr.port : opts.port;
|
|
@@ -317,42 +319,47 @@ function withinRoot(root, abs) {
|
|
|
317
319
|
const rel = relative(root, abs);
|
|
318
320
|
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel) && rel.split(sep)[0] !== "..";
|
|
319
321
|
}
|
|
320
|
-
function
|
|
321
|
-
const
|
|
322
|
-
|
|
322
|
+
function activeRoot(roots, rootArg) {
|
|
323
|
+
const r = typeof rootArg === "string" ? rootArg : "";
|
|
324
|
+
return roots.includes(r) ? r : roots[0];
|
|
325
|
+
}
|
|
326
|
+
function confine(roots, rootArg, p) {
|
|
327
|
+
const abs = resolve(activeRoot(roots, rootArg), p);
|
|
328
|
+
if (!roots.some((root) => withinRoot(root, abs))) throw new Error(`path escapes the sidecar roots: ${p}`);
|
|
323
329
|
return abs;
|
|
324
330
|
}
|
|
325
|
-
async function assertRealWithinRoot(
|
|
326
|
-
const
|
|
331
|
+
async function assertRealWithinRoot(roots, abs) {
|
|
332
|
+
const realRoots = await Promise.all(roots.map((r) => fs.realpath(r)));
|
|
327
333
|
let probe = abs;
|
|
328
334
|
for (; ; ) {
|
|
329
335
|
try {
|
|
330
336
|
const real = await fs.realpath(probe);
|
|
331
|
-
if (!withinRoot(
|
|
337
|
+
if (!realRoots.some((rr) => withinRoot(rr, real))) throw new Error(`path escapes the sidecar roots via symlink: ${abs}`);
|
|
332
338
|
return;
|
|
333
339
|
} catch (e) {
|
|
334
340
|
if (e instanceof Error && e.message.includes("via symlink")) throw e;
|
|
335
341
|
const parent = dirname(probe);
|
|
336
|
-
if (parent === probe) throw new Error(`cannot resolve path under the sidecar
|
|
342
|
+
if (parent === probe) throw new Error(`cannot resolve path under the sidecar roots: ${abs}`);
|
|
337
343
|
probe = parent;
|
|
338
344
|
}
|
|
339
345
|
}
|
|
340
346
|
}
|
|
347
|
+
var ROOT_PARAM = { type: "string", description: "which pre-approved root to resolve the path against (its absolute path; defaults to the primary root)" };
|
|
341
348
|
var MAX_READ_BYTES = 16 * 1024 * 1024;
|
|
342
|
-
function nodeFsTools(
|
|
349
|
+
function nodeFsTools(roots) {
|
|
343
350
|
return [
|
|
344
351
|
{
|
|
345
352
|
name: "fs_read_file",
|
|
346
|
-
description: "Read a UTF-8 text file from the sidecar machine, relative to
|
|
353
|
+
description: "Read a UTF-8 text file from the sidecar machine, relative to a pre-approved root.",
|
|
347
354
|
parameters: {
|
|
348
355
|
type: "object",
|
|
349
|
-
properties: { path: { type: "string", description: "path relative to the
|
|
356
|
+
properties: { path: { type: "string", description: "path relative to the chosen root" }, root: ROOT_PARAM },
|
|
350
357
|
required: ["path"]
|
|
351
358
|
},
|
|
352
359
|
namespace: "fs",
|
|
353
360
|
async run(args) {
|
|
354
|
-
const abs = confine(root, String(args.path ?? ""));
|
|
355
|
-
await assertRealWithinRoot(
|
|
361
|
+
const abs = confine(roots, args.root, String(args.path ?? ""));
|
|
362
|
+
await assertRealWithinRoot(roots, abs);
|
|
356
363
|
const st = await fs.stat(abs);
|
|
357
364
|
if (st.size > MAX_READ_BYTES) {
|
|
358
365
|
throw new Error(`file too large to read (${st.size} bytes; max ${MAX_READ_BYTES})`);
|
|
@@ -362,36 +369,56 @@ function nodeFsTools(root) {
|
|
|
362
369
|
},
|
|
363
370
|
{
|
|
364
371
|
name: "fs_write_file",
|
|
365
|
-
description: "Write a UTF-8 text file on the sidecar machine, relative to
|
|
372
|
+
description: "Write a UTF-8 text file on the sidecar machine, relative to a pre-approved root.",
|
|
366
373
|
parameters: {
|
|
367
374
|
type: "object",
|
|
368
375
|
properties: {
|
|
369
|
-
path: { type: "string", description: "path relative to the
|
|
370
|
-
content: { type: "string" }
|
|
376
|
+
path: { type: "string", description: "path relative to the chosen root" },
|
|
377
|
+
content: { type: "string" },
|
|
378
|
+
root: ROOT_PARAM
|
|
371
379
|
},
|
|
372
380
|
required: ["path", "content"]
|
|
373
381
|
},
|
|
374
382
|
namespace: "fs",
|
|
375
383
|
mutates: true,
|
|
376
384
|
async run(args) {
|
|
377
|
-
const abs = confine(root, String(args.path ?? ""));
|
|
385
|
+
const abs = confine(roots, args.root, String(args.path ?? ""));
|
|
378
386
|
await fs.mkdir(dirname(abs), { recursive: true });
|
|
379
|
-
await assertRealWithinRoot(
|
|
387
|
+
await assertRealWithinRoot(roots, abs);
|
|
380
388
|
await fs.writeFile(abs, String(args.content ?? ""), "utf8");
|
|
381
389
|
return `wrote ${args.path}`;
|
|
382
390
|
}
|
|
383
391
|
},
|
|
392
|
+
{
|
|
393
|
+
name: "fs_read_bytes",
|
|
394
|
+
description: "Read a file from the sidecar machine as base64 (for previewing media/binary the text read would mangle), relative to a pre-approved root.",
|
|
395
|
+
parameters: {
|
|
396
|
+
type: "object",
|
|
397
|
+
properties: { path: { type: "string", description: "path relative to the chosen root" }, root: ROOT_PARAM },
|
|
398
|
+
required: ["path"]
|
|
399
|
+
},
|
|
400
|
+
namespace: "fs",
|
|
401
|
+
async run(args) {
|
|
402
|
+
const abs = confine(roots, args.root, String(args.path ?? ""));
|
|
403
|
+
await assertRealWithinRoot(roots, abs);
|
|
404
|
+
const st = await fs.stat(abs);
|
|
405
|
+
if (st.size > MAX_READ_BYTES) {
|
|
406
|
+
throw new Error(`file too large to read (${st.size} bytes; max ${MAX_READ_BYTES})`);
|
|
407
|
+
}
|
|
408
|
+
return (await fs.readFile(abs)).toString("base64");
|
|
409
|
+
}
|
|
410
|
+
},
|
|
384
411
|
{
|
|
385
412
|
name: "fs_list_dir",
|
|
386
|
-
description: "List the entries of a directory on the sidecar machine, relative to
|
|
413
|
+
description: "List the entries of a directory on the sidecar machine, relative to a pre-approved root.",
|
|
387
414
|
parameters: {
|
|
388
415
|
type: "object",
|
|
389
|
-
properties: { path: { type: "string", description: 'directory relative to the
|
|
416
|
+
properties: { path: { type: "string", description: 'directory relative to the chosen root (default ".")' }, root: ROOT_PARAM }
|
|
390
417
|
},
|
|
391
418
|
namespace: "fs",
|
|
392
419
|
async run(args) {
|
|
393
|
-
const abs = confine(root, String(args.path ?? "."));
|
|
394
|
-
await assertRealWithinRoot(
|
|
420
|
+
const abs = confine(roots, args.root, String(args.path ?? "."));
|
|
421
|
+
await assertRealWithinRoot(roots, abs);
|
|
395
422
|
const entries = await fs.readdir(abs, { withFileTypes: true });
|
|
396
423
|
return entries.map((e) => e.isDirectory() ? `${e.name}/` : e.name).join("\n");
|
|
397
424
|
}
|
|
@@ -401,15 +428,16 @@ function nodeFsTools(root) {
|
|
|
401
428
|
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
402
429
|
var MAX_TIMEOUT_MS = 3e5;
|
|
403
430
|
var MAX_OUTPUT = 1e5;
|
|
404
|
-
function nodeShellTool(
|
|
431
|
+
function nodeShellTool(roots) {
|
|
405
432
|
return {
|
|
406
433
|
name: "shell_exec",
|
|
407
|
-
description: "Run a shell command on the sidecar machine (commands a browser cannot spawn). Runs in
|
|
434
|
+
description: "Run a shell command on the sidecar machine (commands a browser cannot spawn). Runs in a pre-approved root by default; returns the exit status plus combined stdout/stderr.",
|
|
408
435
|
parameters: {
|
|
409
436
|
type: "object",
|
|
410
437
|
properties: {
|
|
411
438
|
command: { type: "string", description: "the shell command line to run" },
|
|
412
|
-
cwd: { type: "string", description: 'working directory relative to the
|
|
439
|
+
cwd: { type: "string", description: 'working directory relative to the chosen root (default ".")' },
|
|
440
|
+
root: ROOT_PARAM,
|
|
413
441
|
timeout_ms: { type: "number", description: `max run time in ms (default ${DEFAULT_TIMEOUT_MS}, max ${MAX_TIMEOUT_MS})` }
|
|
414
442
|
},
|
|
415
443
|
required: ["command"]
|
|
@@ -420,8 +448,8 @@ function nodeShellTool(root) {
|
|
|
420
448
|
async run(args) {
|
|
421
449
|
const command = String(args.command ?? "").trim();
|
|
422
450
|
if (!command) return "Error: no command given";
|
|
423
|
-
const cwd = confine(root, String(args.cwd ?? "."));
|
|
424
|
-
await assertRealWithinRoot(
|
|
451
|
+
const cwd = confine(roots, args.root, String(args.cwd ?? "."));
|
|
452
|
+
await assertRealWithinRoot(roots, cwd);
|
|
425
453
|
const timeout = Math.min(Math.max(Number(args.timeout_ms) || DEFAULT_TIMEOUT_MS, 1), MAX_TIMEOUT_MS);
|
|
426
454
|
return runShell(command, cwd, timeout);
|
|
427
455
|
}
|
|
@@ -675,7 +703,7 @@ function extractTunnelUrl(s) {
|
|
|
675
703
|
}
|
|
676
704
|
var START_TIMEOUT_MS2 = 25e3;
|
|
677
705
|
function tunnelArgs(port, host = "localhost") {
|
|
678
|
-
return ["tunnel", "--no-autoupdate", "--url", `http://${host}:${port}`];
|
|
706
|
+
return ["tunnel", "--no-autoupdate", "--protocol", "http2", "--url", `http://${host}:${port}`];
|
|
679
707
|
}
|
|
680
708
|
var tail2 = (s, n = 6) => s.trim().split("\n").slice(-n).join("\n");
|
|
681
709
|
function cloudflaredBins() {
|
|
@@ -780,12 +808,12 @@ registerTunnel(zrokProvider);
|
|
|
780
808
|
|
|
781
809
|
// src/cli.ts
|
|
782
810
|
function parseArgs(argv) {
|
|
783
|
-
const out = { port: 4858, host: "127.0.0.1",
|
|
811
|
+
const out = { port: 4858, host: "127.0.0.1", roots: [], allowOrigins: [], allowShell: false, allowLan: false, selfSigned: false, open: false, appUrl: "https://prompd.app" };
|
|
784
812
|
for (let i = 0; i < argv.length; i++) {
|
|
785
813
|
const a = argv[i];
|
|
786
814
|
if (a === "--port") out.port = Number(argv[++i]);
|
|
787
815
|
else if (a === "--host") out.host = argv[++i];
|
|
788
|
-
else if (a === "--root") out.
|
|
816
|
+
else if (a === "--root") out.roots.push(resolve2(argv[++i]));
|
|
789
817
|
else if (a === "--allow-origin") out.allowOrigins.push(argv[++i]);
|
|
790
818
|
else if (a === "--allow-shell") out.allowShell = true;
|
|
791
819
|
else if (a === "--allow-lan") out.allowLan = true;
|
|
@@ -794,13 +822,53 @@ function parseArgs(argv) {
|
|
|
794
822
|
else if (a === "--self-signed") out.selfSigned = true;
|
|
795
823
|
else if (a === "--tunnel") out.tunnel = argv[++i];
|
|
796
824
|
else if (a === "--tunnel-name") out.tunnelName = argv[++i];
|
|
825
|
+
else if (a === "--open") out.open = true;
|
|
826
|
+
else if (a === "--app-url") out.appUrl = argv[++i];
|
|
827
|
+
else if (a === "--sidecar" || a === "start") {
|
|
828
|
+
} else throw new Error(`unknown argument: ${a}
|
|
829
|
+
valid flags: ${KNOWN_FLAGS.join(" ")}`);
|
|
797
830
|
}
|
|
798
831
|
return out;
|
|
799
832
|
}
|
|
833
|
+
var KNOWN_FLAGS = [
|
|
834
|
+
"--port",
|
|
835
|
+
"--host",
|
|
836
|
+
"--root",
|
|
837
|
+
"--allow-origin",
|
|
838
|
+
"--allow-shell",
|
|
839
|
+
"--allow-lan",
|
|
840
|
+
"--tls-cert",
|
|
841
|
+
"--tls-key",
|
|
842
|
+
"--self-signed",
|
|
843
|
+
"--tunnel",
|
|
844
|
+
"--tunnel-name",
|
|
845
|
+
"--open",
|
|
846
|
+
"--app-url"
|
|
847
|
+
];
|
|
800
848
|
var isIp2 = (s) => /^\d{1,3}(?:\.\d{1,3}){3}$/.test(s);
|
|
849
|
+
function openBrowser(url) {
|
|
850
|
+
const p = process.platform;
|
|
851
|
+
const cmd = p === "darwin" ? "open" : p === "win32" ? "cmd" : "xdg-open";
|
|
852
|
+
const args = p === "win32" ? ["/c", "start", "", url] : [url];
|
|
853
|
+
try {
|
|
854
|
+
spawn4(cmd, args, { stdio: "ignore", detached: true }).unref();
|
|
855
|
+
} catch {
|
|
856
|
+
}
|
|
857
|
+
}
|
|
801
858
|
var DEFAULT_ALLOWED_ORIGINS = ["https://prompd.app", "https://www.prompd.app", "https://prmd.ai", "https://www.prmd.ai"];
|
|
802
859
|
async function main() {
|
|
803
860
|
const args = parseArgs(process.argv.slice(2));
|
|
861
|
+
if (args.roots.length === 0) args.roots = [process.cwd()];
|
|
862
|
+
args.roots = [...new Set(args.roots)];
|
|
863
|
+
for (const r of args.roots) {
|
|
864
|
+
let ok = false;
|
|
865
|
+
try {
|
|
866
|
+
ok = statSync(r).isDirectory();
|
|
867
|
+
} catch {
|
|
868
|
+
ok = false;
|
|
869
|
+
}
|
|
870
|
+
if (!ok) throw new Error(`--root is not a directory: ${r}`);
|
|
871
|
+
}
|
|
804
872
|
const token = await loadOrCreateToken();
|
|
805
873
|
if (args.tlsCert && !args.tlsKey || !args.tlsCert && args.tlsKey) {
|
|
806
874
|
throw new Error("TLS needs both --tls-cert and --tls-key");
|
|
@@ -831,12 +899,12 @@ async function main() {
|
|
|
831
899
|
port: args.port,
|
|
832
900
|
host: args.host,
|
|
833
901
|
token,
|
|
834
|
-
tools: [...nodeFsTools(args.
|
|
902
|
+
tools: [...nodeFsTools(args.roots), ...args.allowShell ? [nodeShellTool(args.roots)] : []],
|
|
835
903
|
allowedOrigins,
|
|
836
904
|
tls,
|
|
837
|
-
// Advertised in the handshake so the host can show the working
|
|
838
|
-
//
|
|
839
|
-
info: { root: args.
|
|
905
|
+
// Advertised in the handshake so the host can show the available working
|
|
906
|
+
// directories and let the user pick one (confined to the union of roots).
|
|
907
|
+
info: { root: args.roots[0], roots: args.roots, platform: process.platform },
|
|
840
908
|
log: slog,
|
|
841
909
|
allowLan: args.allowLan
|
|
842
910
|
});
|
|
@@ -855,7 +923,7 @@ async function main() {
|
|
|
855
923
|
process.stdout.write(
|
|
856
924
|
[
|
|
857
925
|
`corenel sidecar listening on ${scheme}://${handle.host}:${handle.port}`,
|
|
858
|
-
` root: ${args.
|
|
926
|
+
args.roots.length === 1 ? ` root: ${args.roots[0]}` : ` roots: ${args.roots.join("\n ")} (${args.roots.length} approved; pick the active one in the app)`,
|
|
859
927
|
` tools: fs_read/write/list${args.allowShell ? " + shell_exec" : " (shell off; pass --allow-shell to enable)"}`,
|
|
860
928
|
` token: ${token} (also at ${tokenPath()})`,
|
|
861
929
|
tunnelWss ? ` connect at: ${tunnelWss} (public ${args.tunnel} tunnel${args.tunnelName ? ", stable" : ""} \u2014 pair from any device with this URL + the token)` : ` connect at: ${connectUrls.join(" ")}`,
|
|
@@ -866,6 +934,13 @@ async function main() {
|
|
|
866
934
|
""
|
|
867
935
|
].filter((l) => l !== null).join("\n")
|
|
868
936
|
);
|
|
937
|
+
if (args.open) {
|
|
938
|
+
const loopback = `${handle.secure ? "wss" : "ws"}://127.0.0.1:${handle.port}`;
|
|
939
|
+
const target = `${args.appUrl.replace(/\/$/, "")}/studio#sidecar=${encodeURIComponent(loopback)}&token=${encodeURIComponent(token)}`;
|
|
940
|
+
process.stdout.write(` opening ${args.appUrl} (auto-pairing)\u2026
|
|
941
|
+
`);
|
|
942
|
+
openBrowser(target);
|
|
943
|
+
}
|
|
869
944
|
const shutdown = () => {
|
|
870
945
|
const closeServer = () => handle.close().then(() => process.exit(0));
|
|
871
946
|
if (tunnel) void tunnel.stop().catch(() => {
|
|
@@ -876,7 +951,9 @@ async function main() {
|
|
|
876
951
|
process.on("SIGTERM", shutdown);
|
|
877
952
|
}
|
|
878
953
|
main().catch((e) => {
|
|
879
|
-
|
|
954
|
+
const code = e?.code;
|
|
955
|
+
const msg = code === "EADDRINUSE" ? `port already in use \u2014 another sidecar may be running. Stop it, or pass --port <n>.` : e instanceof Error ? e.message : String(e);
|
|
956
|
+
process.stderr.write(`corenel-sidecar: ${msg}
|
|
880
957
|
`);
|
|
881
958
|
process.exit(1);
|
|
882
959
|
});
|
package/package.json
CHANGED
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@corenel/sidecar",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"type": "module",
|
|
5
|
-
"license": "Elastic-2.0",
|
|
6
|
-
"description": "Prompd sidecar — a local capability server. Run it next to Prompd (prompd.app) so the browser app can reach your machine: local LLMs (Ollama/LM Studio/vLLM) without CORS, your filesystem, and (opt-in) shell.",
|
|
7
|
-
"keywords": ["prompd", "corenel", "sidecar", "ollama", "local-llm", "cors"],
|
|
8
|
-
"bin": {
|
|
9
|
-
"corenel-sidecar": "dist/cli.js"
|
|
10
|
-
},
|
|
11
|
-
"files": [
|
|
12
|
-
"dist",
|
|
13
|
-
"README.md"
|
|
14
|
-
],
|
|
15
|
-
"engines": {
|
|
16
|
-
"node": ">=18"
|
|
17
|
-
},
|
|
18
|
-
"publishConfig": {
|
|
19
|
-
"access": "public"
|
|
20
|
-
},
|
|
21
|
-
"scripts": {
|
|
22
|
-
"build": "node build.mjs",
|
|
23
|
-
"prepublishOnly": "node build.mjs",
|
|
24
|
-
"start": "tsx src/cli.ts",
|
|
25
|
-
"typecheck": "tsc --noEmit",
|
|
26
|
-
"verify": "tsx src/verify.ts"
|
|
27
|
-
},
|
|
28
|
-
"dependencies": {
|
|
29
|
-
"selfsigned": "^2.4.1",
|
|
30
|
-
"ws": "^8.18.0"
|
|
31
|
-
},
|
|
32
|
-
"devDependencies": {
|
|
33
|
-
"@corenel/protocol": "workspace:*",
|
|
34
|
-
"@corenel/harness": "workspace:*",
|
|
35
|
-
"@types/node": "^24.13.1",
|
|
36
|
-
"@types/ws": "^8.5.13",
|
|
37
|
-
"esbuild": "^0.21.5",
|
|
38
|
-
"tsx": "^4.19.2",
|
|
39
|
-
"typescript": "^5.9.3"
|
|
40
|
-
}
|
|
41
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@corenel/sidecar",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "Elastic-2.0",
|
|
6
|
+
"description": "Prompd sidecar — a local capability server. Run it next to Prompd (prompd.app) so the browser app can reach your machine: local LLMs (Ollama/LM Studio/vLLM) without CORS, your filesystem, and (opt-in) shell.",
|
|
7
|
+
"keywords": ["prompd", "corenel", "sidecar", "ollama", "local-llm", "cors"],
|
|
8
|
+
"bin": {
|
|
9
|
+
"corenel-sidecar": "dist/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=18"
|
|
17
|
+
},
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "node build.mjs",
|
|
23
|
+
"prepublishOnly": "node build.mjs",
|
|
24
|
+
"start": "tsx src/cli.ts",
|
|
25
|
+
"typecheck": "tsc --noEmit",
|
|
26
|
+
"verify": "tsx src/verify.ts"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"selfsigned": "^2.4.1",
|
|
30
|
+
"ws": "^8.18.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@corenel/protocol": "workspace:*",
|
|
34
|
+
"@corenel/harness": "workspace:*",
|
|
35
|
+
"@types/node": "^24.13.1",
|
|
36
|
+
"@types/ws": "^8.5.13",
|
|
37
|
+
"esbuild": "^0.21.5",
|
|
38
|
+
"tsx": "^4.19.2",
|
|
39
|
+
"typescript": "^5.9.3"
|
|
40
|
+
}
|
|
41
|
+
}
|