@corenel/sidecar 0.1.1 → 0.1.2
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 +91 -35
- 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";
|
|
@@ -317,42 +318,47 @@ function withinRoot(root, abs) {
|
|
|
317
318
|
const rel = relative(root, abs);
|
|
318
319
|
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel) && rel.split(sep)[0] !== "..";
|
|
319
320
|
}
|
|
320
|
-
function
|
|
321
|
-
const
|
|
322
|
-
|
|
321
|
+
function activeRoot(roots, rootArg) {
|
|
322
|
+
const r = typeof rootArg === "string" ? rootArg : "";
|
|
323
|
+
return roots.includes(r) ? r : roots[0];
|
|
324
|
+
}
|
|
325
|
+
function confine(roots, rootArg, p) {
|
|
326
|
+
const abs = resolve(activeRoot(roots, rootArg), p);
|
|
327
|
+
if (!roots.some((root) => withinRoot(root, abs))) throw new Error(`path escapes the sidecar roots: ${p}`);
|
|
323
328
|
return abs;
|
|
324
329
|
}
|
|
325
|
-
async function assertRealWithinRoot(
|
|
326
|
-
const
|
|
330
|
+
async function assertRealWithinRoot(roots, abs) {
|
|
331
|
+
const realRoots = await Promise.all(roots.map((r) => fs.realpath(r)));
|
|
327
332
|
let probe = abs;
|
|
328
333
|
for (; ; ) {
|
|
329
334
|
try {
|
|
330
335
|
const real = await fs.realpath(probe);
|
|
331
|
-
if (!withinRoot(
|
|
336
|
+
if (!realRoots.some((rr) => withinRoot(rr, real))) throw new Error(`path escapes the sidecar roots via symlink: ${abs}`);
|
|
332
337
|
return;
|
|
333
338
|
} catch (e) {
|
|
334
339
|
if (e instanceof Error && e.message.includes("via symlink")) throw e;
|
|
335
340
|
const parent = dirname(probe);
|
|
336
|
-
if (parent === probe) throw new Error(`cannot resolve path under the sidecar
|
|
341
|
+
if (parent === probe) throw new Error(`cannot resolve path under the sidecar roots: ${abs}`);
|
|
337
342
|
probe = parent;
|
|
338
343
|
}
|
|
339
344
|
}
|
|
340
345
|
}
|
|
346
|
+
var ROOT_PARAM = { type: "string", description: "which pre-approved root to resolve the path against (its absolute path; defaults to the primary root)" };
|
|
341
347
|
var MAX_READ_BYTES = 16 * 1024 * 1024;
|
|
342
|
-
function nodeFsTools(
|
|
348
|
+
function nodeFsTools(roots) {
|
|
343
349
|
return [
|
|
344
350
|
{
|
|
345
351
|
name: "fs_read_file",
|
|
346
|
-
description: "Read a UTF-8 text file from the sidecar machine, relative to
|
|
352
|
+
description: "Read a UTF-8 text file from the sidecar machine, relative to a pre-approved root.",
|
|
347
353
|
parameters: {
|
|
348
354
|
type: "object",
|
|
349
|
-
properties: { path: { type: "string", description: "path relative to the
|
|
355
|
+
properties: { path: { type: "string", description: "path relative to the chosen root" }, root: ROOT_PARAM },
|
|
350
356
|
required: ["path"]
|
|
351
357
|
},
|
|
352
358
|
namespace: "fs",
|
|
353
359
|
async run(args) {
|
|
354
|
-
const abs = confine(root, String(args.path ?? ""));
|
|
355
|
-
await assertRealWithinRoot(
|
|
360
|
+
const abs = confine(roots, args.root, String(args.path ?? ""));
|
|
361
|
+
await assertRealWithinRoot(roots, abs);
|
|
356
362
|
const st = await fs.stat(abs);
|
|
357
363
|
if (st.size > MAX_READ_BYTES) {
|
|
358
364
|
throw new Error(`file too large to read (${st.size} bytes; max ${MAX_READ_BYTES})`);
|
|
@@ -362,36 +368,56 @@ function nodeFsTools(root) {
|
|
|
362
368
|
},
|
|
363
369
|
{
|
|
364
370
|
name: "fs_write_file",
|
|
365
|
-
description: "Write a UTF-8 text file on the sidecar machine, relative to
|
|
371
|
+
description: "Write a UTF-8 text file on the sidecar machine, relative to a pre-approved root.",
|
|
366
372
|
parameters: {
|
|
367
373
|
type: "object",
|
|
368
374
|
properties: {
|
|
369
|
-
path: { type: "string", description: "path relative to the
|
|
370
|
-
content: { type: "string" }
|
|
375
|
+
path: { type: "string", description: "path relative to the chosen root" },
|
|
376
|
+
content: { type: "string" },
|
|
377
|
+
root: ROOT_PARAM
|
|
371
378
|
},
|
|
372
379
|
required: ["path", "content"]
|
|
373
380
|
},
|
|
374
381
|
namespace: "fs",
|
|
375
382
|
mutates: true,
|
|
376
383
|
async run(args) {
|
|
377
|
-
const abs = confine(root, String(args.path ?? ""));
|
|
384
|
+
const abs = confine(roots, args.root, String(args.path ?? ""));
|
|
378
385
|
await fs.mkdir(dirname(abs), { recursive: true });
|
|
379
|
-
await assertRealWithinRoot(
|
|
386
|
+
await assertRealWithinRoot(roots, abs);
|
|
380
387
|
await fs.writeFile(abs, String(args.content ?? ""), "utf8");
|
|
381
388
|
return `wrote ${args.path}`;
|
|
382
389
|
}
|
|
383
390
|
},
|
|
391
|
+
{
|
|
392
|
+
name: "fs_read_bytes",
|
|
393
|
+
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.",
|
|
394
|
+
parameters: {
|
|
395
|
+
type: "object",
|
|
396
|
+
properties: { path: { type: "string", description: "path relative to the chosen root" }, root: ROOT_PARAM },
|
|
397
|
+
required: ["path"]
|
|
398
|
+
},
|
|
399
|
+
namespace: "fs",
|
|
400
|
+
async run(args) {
|
|
401
|
+
const abs = confine(roots, args.root, String(args.path ?? ""));
|
|
402
|
+
await assertRealWithinRoot(roots, abs);
|
|
403
|
+
const st = await fs.stat(abs);
|
|
404
|
+
if (st.size > MAX_READ_BYTES) {
|
|
405
|
+
throw new Error(`file too large to read (${st.size} bytes; max ${MAX_READ_BYTES})`);
|
|
406
|
+
}
|
|
407
|
+
return (await fs.readFile(abs)).toString("base64");
|
|
408
|
+
}
|
|
409
|
+
},
|
|
384
410
|
{
|
|
385
411
|
name: "fs_list_dir",
|
|
386
|
-
description: "List the entries of a directory on the sidecar machine, relative to
|
|
412
|
+
description: "List the entries of a directory on the sidecar machine, relative to a pre-approved root.",
|
|
387
413
|
parameters: {
|
|
388
414
|
type: "object",
|
|
389
|
-
properties: { path: { type: "string", description: 'directory relative to the
|
|
415
|
+
properties: { path: { type: "string", description: 'directory relative to the chosen root (default ".")' }, root: ROOT_PARAM }
|
|
390
416
|
},
|
|
391
417
|
namespace: "fs",
|
|
392
418
|
async run(args) {
|
|
393
|
-
const abs = confine(root, String(args.path ?? "."));
|
|
394
|
-
await assertRealWithinRoot(
|
|
419
|
+
const abs = confine(roots, args.root, String(args.path ?? "."));
|
|
420
|
+
await assertRealWithinRoot(roots, abs);
|
|
395
421
|
const entries = await fs.readdir(abs, { withFileTypes: true });
|
|
396
422
|
return entries.map((e) => e.isDirectory() ? `${e.name}/` : e.name).join("\n");
|
|
397
423
|
}
|
|
@@ -401,15 +427,16 @@ function nodeFsTools(root) {
|
|
|
401
427
|
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
402
428
|
var MAX_TIMEOUT_MS = 3e5;
|
|
403
429
|
var MAX_OUTPUT = 1e5;
|
|
404
|
-
function nodeShellTool(
|
|
430
|
+
function nodeShellTool(roots) {
|
|
405
431
|
return {
|
|
406
432
|
name: "shell_exec",
|
|
407
|
-
description: "Run a shell command on the sidecar machine (commands a browser cannot spawn). Runs in
|
|
433
|
+
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
434
|
parameters: {
|
|
409
435
|
type: "object",
|
|
410
436
|
properties: {
|
|
411
437
|
command: { type: "string", description: "the shell command line to run" },
|
|
412
|
-
cwd: { type: "string", description: 'working directory relative to the
|
|
438
|
+
cwd: { type: "string", description: 'working directory relative to the chosen root (default ".")' },
|
|
439
|
+
root: ROOT_PARAM,
|
|
413
440
|
timeout_ms: { type: "number", description: `max run time in ms (default ${DEFAULT_TIMEOUT_MS}, max ${MAX_TIMEOUT_MS})` }
|
|
414
441
|
},
|
|
415
442
|
required: ["command"]
|
|
@@ -420,8 +447,8 @@ function nodeShellTool(root) {
|
|
|
420
447
|
async run(args) {
|
|
421
448
|
const command = String(args.command ?? "").trim();
|
|
422
449
|
if (!command) return "Error: no command given";
|
|
423
|
-
const cwd = confine(root, String(args.cwd ?? "."));
|
|
424
|
-
await assertRealWithinRoot(
|
|
450
|
+
const cwd = confine(roots, args.root, String(args.cwd ?? "."));
|
|
451
|
+
await assertRealWithinRoot(roots, cwd);
|
|
425
452
|
const timeout = Math.min(Math.max(Number(args.timeout_ms) || DEFAULT_TIMEOUT_MS, 1), MAX_TIMEOUT_MS);
|
|
426
453
|
return runShell(command, cwd, timeout);
|
|
427
454
|
}
|
|
@@ -675,7 +702,7 @@ function extractTunnelUrl(s) {
|
|
|
675
702
|
}
|
|
676
703
|
var START_TIMEOUT_MS2 = 25e3;
|
|
677
704
|
function tunnelArgs(port, host = "localhost") {
|
|
678
|
-
return ["tunnel", "--no-autoupdate", "--url", `http://${host}:${port}`];
|
|
705
|
+
return ["tunnel", "--no-autoupdate", "--protocol", "http2", "--url", `http://${host}:${port}`];
|
|
679
706
|
}
|
|
680
707
|
var tail2 = (s, n = 6) => s.trim().split("\n").slice(-n).join("\n");
|
|
681
708
|
function cloudflaredBins() {
|
|
@@ -780,12 +807,12 @@ registerTunnel(zrokProvider);
|
|
|
780
807
|
|
|
781
808
|
// src/cli.ts
|
|
782
809
|
function parseArgs(argv) {
|
|
783
|
-
const out = { port: 4858, host: "127.0.0.1",
|
|
810
|
+
const out = { port: 4858, host: "127.0.0.1", roots: [], allowOrigins: [], allowShell: false, allowLan: false, selfSigned: false, open: false, appUrl: "https://prompd.app" };
|
|
784
811
|
for (let i = 0; i < argv.length; i++) {
|
|
785
812
|
const a = argv[i];
|
|
786
813
|
if (a === "--port") out.port = Number(argv[++i]);
|
|
787
814
|
else if (a === "--host") out.host = argv[++i];
|
|
788
|
-
else if (a === "--root") out.
|
|
815
|
+
else if (a === "--root") out.roots.push(resolve2(argv[++i]));
|
|
789
816
|
else if (a === "--allow-origin") out.allowOrigins.push(argv[++i]);
|
|
790
817
|
else if (a === "--allow-shell") out.allowShell = true;
|
|
791
818
|
else if (a === "--allow-lan") out.allowLan = true;
|
|
@@ -794,13 +821,35 @@ function parseArgs(argv) {
|
|
|
794
821
|
else if (a === "--self-signed") out.selfSigned = true;
|
|
795
822
|
else if (a === "--tunnel") out.tunnel = argv[++i];
|
|
796
823
|
else if (a === "--tunnel-name") out.tunnelName = argv[++i];
|
|
824
|
+
else if (a === "--open") out.open = true;
|
|
825
|
+
else if (a === "--app-url") out.appUrl = argv[++i];
|
|
797
826
|
}
|
|
798
827
|
return out;
|
|
799
828
|
}
|
|
800
829
|
var isIp2 = (s) => /^\d{1,3}(?:\.\d{1,3}){3}$/.test(s);
|
|
830
|
+
function openBrowser(url) {
|
|
831
|
+
const p = process.platform;
|
|
832
|
+
const cmd = p === "darwin" ? "open" : p === "win32" ? "cmd" : "xdg-open";
|
|
833
|
+
const args = p === "win32" ? ["/c", "start", "", url] : [url];
|
|
834
|
+
try {
|
|
835
|
+
spawn4(cmd, args, { stdio: "ignore", detached: true }).unref();
|
|
836
|
+
} catch {
|
|
837
|
+
}
|
|
838
|
+
}
|
|
801
839
|
var DEFAULT_ALLOWED_ORIGINS = ["https://prompd.app", "https://www.prompd.app", "https://prmd.ai", "https://www.prmd.ai"];
|
|
802
840
|
async function main() {
|
|
803
841
|
const args = parseArgs(process.argv.slice(2));
|
|
842
|
+
if (args.roots.length === 0) args.roots = [process.cwd()];
|
|
843
|
+
args.roots = [...new Set(args.roots)];
|
|
844
|
+
for (const r of args.roots) {
|
|
845
|
+
let ok = false;
|
|
846
|
+
try {
|
|
847
|
+
ok = statSync(r).isDirectory();
|
|
848
|
+
} catch {
|
|
849
|
+
ok = false;
|
|
850
|
+
}
|
|
851
|
+
if (!ok) throw new Error(`--root is not a directory: ${r}`);
|
|
852
|
+
}
|
|
804
853
|
const token = await loadOrCreateToken();
|
|
805
854
|
if (args.tlsCert && !args.tlsKey || !args.tlsCert && args.tlsKey) {
|
|
806
855
|
throw new Error("TLS needs both --tls-cert and --tls-key");
|
|
@@ -831,12 +880,12 @@ async function main() {
|
|
|
831
880
|
port: args.port,
|
|
832
881
|
host: args.host,
|
|
833
882
|
token,
|
|
834
|
-
tools: [...nodeFsTools(args.
|
|
883
|
+
tools: [...nodeFsTools(args.roots), ...args.allowShell ? [nodeShellTool(args.roots)] : []],
|
|
835
884
|
allowedOrigins,
|
|
836
885
|
tls,
|
|
837
|
-
// Advertised in the handshake so the host can show the working
|
|
838
|
-
//
|
|
839
|
-
info: { root: args.
|
|
886
|
+
// Advertised in the handshake so the host can show the available working
|
|
887
|
+
// directories and let the user pick one (confined to the union of roots).
|
|
888
|
+
info: { root: args.roots[0], roots: args.roots, platform: process.platform },
|
|
840
889
|
log: slog,
|
|
841
890
|
allowLan: args.allowLan
|
|
842
891
|
});
|
|
@@ -855,7 +904,7 @@ async function main() {
|
|
|
855
904
|
process.stdout.write(
|
|
856
905
|
[
|
|
857
906
|
`corenel sidecar listening on ${scheme}://${handle.host}:${handle.port}`,
|
|
858
|
-
` root: ${args.
|
|
907
|
+
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
908
|
` tools: fs_read/write/list${args.allowShell ? " + shell_exec" : " (shell off; pass --allow-shell to enable)"}`,
|
|
860
909
|
` token: ${token} (also at ${tokenPath()})`,
|
|
861
910
|
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 +915,13 @@ async function main() {
|
|
|
866
915
|
""
|
|
867
916
|
].filter((l) => l !== null).join("\n")
|
|
868
917
|
);
|
|
918
|
+
if (args.open) {
|
|
919
|
+
const loopback = `${handle.secure ? "wss" : "ws"}://127.0.0.1:${handle.port}`;
|
|
920
|
+
const target = `${args.appUrl.replace(/\/$/, "")}/studio#sidecar=${encodeURIComponent(loopback)}&token=${encodeURIComponent(token)}`;
|
|
921
|
+
process.stdout.write(` opening ${args.appUrl} (auto-pairing)\u2026
|
|
922
|
+
`);
|
|
923
|
+
openBrowser(target);
|
|
924
|
+
}
|
|
869
925
|
const shutdown = () => {
|
|
870
926
|
const closeServer = () => handle.close().then(() => process.exit(0));
|
|
871
927
|
if (tunnel) void tunnel.stop().catch(() => {
|
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.2",
|
|
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
|
+
}
|