@corenel/sidecar 0.1.2 → 0.1.4
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 +2 -2
- package/dist/cli.js +42 -9
- package/package.json +48 -41
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ match the site you're connecting from (the app fills it in for you).
|
|
|
25
25
|
| `--allow-origin <url>` | _(none)_ | Browser origin allowed to connect. Required for a web page. Repeatable. |
|
|
26
26
|
| `--port <n>` | `4858` | Port to listen on. |
|
|
27
27
|
| `--root <dir>` | cwd | Root the file tools are confined to. |
|
|
28
|
-
| `--allow-shell` | off | Enable the `
|
|
28
|
+
| `--allow-shell` | off | Enable the `sidecar_shell_exec` tool (RCE on your machine — opt-in). |
|
|
29
29
|
| `--host <addr>` | `127.0.0.1` | Bind address. Non-loopback is reachable off-box; keep the token secret. |
|
|
30
30
|
| `--tls-cert <f>` `--tls-key <f>` | _(none)_ | Serve `wss://` (needed for a remote sidecar from an https page). |
|
|
31
31
|
|
|
@@ -35,6 +35,6 @@ match the site you're connecting from (the app fills it in for you).
|
|
|
35
35
|
allowed Origin.
|
|
36
36
|
- The HTTP proxy (for local LLMs) only reaches **loopback** hosts — it can't be used as
|
|
37
37
|
an SSRF pivot to internal services.
|
|
38
|
-
- `
|
|
38
|
+
- `sidecar_shell_exec` is off unless you pass `--allow-shell`.
|
|
39
39
|
|
|
40
40
|
Licensed under Elastic-2.0.
|
package/dist/cli.js
CHANGED
|
@@ -10,6 +10,13 @@ import { spawn as spawn4 } from "node:child_process";
|
|
|
10
10
|
import { WebSocketServer } from "ws";
|
|
11
11
|
import { createServer as createHttpsServer } from "node:https";
|
|
12
12
|
|
|
13
|
+
// ../protocol/stateDir.ts
|
|
14
|
+
var DEFAULT_STATE_DIR = ".corenel";
|
|
15
|
+
var dirName = DEFAULT_STATE_DIR;
|
|
16
|
+
function stateDirName() {
|
|
17
|
+
return dirName;
|
|
18
|
+
}
|
|
19
|
+
|
|
13
20
|
// ../protocol/sidecar.ts
|
|
14
21
|
var SIDECAR_PROTOCOL = 1;
|
|
15
22
|
function isLoopbackHost(host) {
|
|
@@ -39,6 +46,10 @@ var SidecarServer = class {
|
|
|
39
46
|
authed = false;
|
|
40
47
|
hostSeq = 0;
|
|
41
48
|
hostPending = /* @__PURE__ */ new Map();
|
|
49
|
+
/** The connected host's dot-dir name, from `hello.stateDir` (informational —
|
|
50
|
+
* doesn't change any sidecar behavior yet; reserved for future workspace-state
|
|
51
|
+
* features that need to know the host's dir name). */
|
|
52
|
+
hostStateDir = ".corenel";
|
|
42
53
|
constructor(opts) {
|
|
43
54
|
this.opts = opts;
|
|
44
55
|
this.ch = opts.channel;
|
|
@@ -88,7 +99,8 @@ var SidecarServer = class {
|
|
|
88
99
|
return;
|
|
89
100
|
}
|
|
90
101
|
this.authed = true;
|
|
91
|
-
this.
|
|
102
|
+
this.hostStateDir = m.stateDir ?? ".corenel";
|
|
103
|
+
this.opts.log?.(`paired \u2014 ${this.tools.size} ${this.tools.size === 1 ? "tool" : "tools"} advertised (host dir: ${this.hostStateDir})`);
|
|
92
104
|
const tools = [...this.tools.values()].map(({ run: _run, ...decl }) => decl);
|
|
93
105
|
this.ch.send({ t: "welcome", tools, features: ["http-proxy"], ...this.opts.info ? { info: this.opts.info } : {} });
|
|
94
106
|
}
|
|
@@ -279,6 +291,7 @@ function startSidecar(opts) {
|
|
|
279
291
|
wss2.on("connection", onConnection);
|
|
280
292
|
return new Promise((resolveHandle, reject) => {
|
|
281
293
|
httpsServer.on("error", reject);
|
|
294
|
+
wss2.on("error", reject);
|
|
282
295
|
httpsServer.listen(opts.port, host, () => {
|
|
283
296
|
const addr = httpsServer.address();
|
|
284
297
|
const boundPort = typeof addr === "object" && addr ? addr.port : opts.port;
|
|
@@ -348,7 +361,7 @@ var MAX_READ_BYTES = 16 * 1024 * 1024;
|
|
|
348
361
|
function nodeFsTools(roots) {
|
|
349
362
|
return [
|
|
350
363
|
{
|
|
351
|
-
name: "
|
|
364
|
+
name: "sidecar_fs_read_file",
|
|
352
365
|
description: "Read a UTF-8 text file from the sidecar machine, relative to a pre-approved root.",
|
|
353
366
|
parameters: {
|
|
354
367
|
type: "object",
|
|
@@ -367,7 +380,7 @@ function nodeFsTools(roots) {
|
|
|
367
380
|
}
|
|
368
381
|
},
|
|
369
382
|
{
|
|
370
|
-
name: "
|
|
383
|
+
name: "sidecar_fs_write_file",
|
|
371
384
|
description: "Write a UTF-8 text file on the sidecar machine, relative to a pre-approved root.",
|
|
372
385
|
parameters: {
|
|
373
386
|
type: "object",
|
|
@@ -389,7 +402,7 @@ function nodeFsTools(roots) {
|
|
|
389
402
|
}
|
|
390
403
|
},
|
|
391
404
|
{
|
|
392
|
-
name: "
|
|
405
|
+
name: "sidecar_fs_read_bytes",
|
|
393
406
|
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
407
|
parameters: {
|
|
395
408
|
type: "object",
|
|
@@ -408,7 +421,7 @@ function nodeFsTools(roots) {
|
|
|
408
421
|
}
|
|
409
422
|
},
|
|
410
423
|
{
|
|
411
|
-
name: "
|
|
424
|
+
name: "sidecar_fs_list_dir",
|
|
412
425
|
description: "List the entries of a directory on the sidecar machine, relative to a pre-approved root.",
|
|
413
426
|
parameters: {
|
|
414
427
|
type: "object",
|
|
@@ -429,7 +442,7 @@ var MAX_TIMEOUT_MS = 3e5;
|
|
|
429
442
|
var MAX_OUTPUT = 1e5;
|
|
430
443
|
function nodeShellTool(roots) {
|
|
431
444
|
return {
|
|
432
|
-
name: "
|
|
445
|
+
name: "sidecar_shell_exec",
|
|
433
446
|
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.",
|
|
434
447
|
parameters: {
|
|
435
448
|
type: "object",
|
|
@@ -496,7 +509,7 @@ import { randomBytes } from "node:crypto";
|
|
|
496
509
|
import { homedir } from "node:os";
|
|
497
510
|
import { join } from "node:path";
|
|
498
511
|
function corenelDir() {
|
|
499
|
-
return join(homedir(),
|
|
512
|
+
return join(homedir(), stateDirName());
|
|
500
513
|
}
|
|
501
514
|
function tokenPath() {
|
|
502
515
|
return join(corenelDir(), "daemon.token");
|
|
@@ -823,9 +836,27 @@ function parseArgs(argv) {
|
|
|
823
836
|
else if (a === "--tunnel-name") out.tunnelName = argv[++i];
|
|
824
837
|
else if (a === "--open") out.open = true;
|
|
825
838
|
else if (a === "--app-url") out.appUrl = argv[++i];
|
|
839
|
+
else if (a === "--sidecar" || a === "start" || a === "--") {
|
|
840
|
+
} else throw new Error(`unknown argument: ${a}
|
|
841
|
+
valid flags: ${KNOWN_FLAGS.join(" ")}`);
|
|
826
842
|
}
|
|
827
843
|
return out;
|
|
828
844
|
}
|
|
845
|
+
var KNOWN_FLAGS = [
|
|
846
|
+
"--port",
|
|
847
|
+
"--host",
|
|
848
|
+
"--root",
|
|
849
|
+
"--allow-origin",
|
|
850
|
+
"--allow-shell",
|
|
851
|
+
"--allow-lan",
|
|
852
|
+
"--tls-cert",
|
|
853
|
+
"--tls-key",
|
|
854
|
+
"--self-signed",
|
|
855
|
+
"--tunnel",
|
|
856
|
+
"--tunnel-name",
|
|
857
|
+
"--open",
|
|
858
|
+
"--app-url"
|
|
859
|
+
];
|
|
829
860
|
var isIp2 = (s) => /^\d{1,3}(?:\.\d{1,3}){3}$/.test(s);
|
|
830
861
|
function openBrowser(url) {
|
|
831
862
|
const p = process.platform;
|
|
@@ -905,7 +936,7 @@ async function main() {
|
|
|
905
936
|
[
|
|
906
937
|
`corenel sidecar listening on ${scheme}://${handle.host}:${handle.port}`,
|
|
907
938
|
args.roots.length === 1 ? ` root: ${args.roots[0]}` : ` roots: ${args.roots.join("\n ")} (${args.roots.length} approved; pick the active one in the app)`,
|
|
908
|
-
` tools:
|
|
939
|
+
` tools: sidecar_fs_read/write/list${args.allowShell ? " + sidecar_shell_exec" : " (shell off; pass --allow-shell to enable)"}`,
|
|
909
940
|
` token: ${token} (also at ${tokenPath()})`,
|
|
910
941
|
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(" ")}`,
|
|
911
942
|
` origins: ${allowedOrigins.join(", ")}${args.allowLan ? " + any private-LAN origin (--allow-lan)" : args.allowOrigins.length ? "" : " (canonical defaults; pass --allow-origin to add dev/self-hosted)"}`,
|
|
@@ -932,7 +963,9 @@ async function main() {
|
|
|
932
963
|
process.on("SIGTERM", shutdown);
|
|
933
964
|
}
|
|
934
965
|
main().catch((e) => {
|
|
935
|
-
|
|
966
|
+
const code = e?.code;
|
|
967
|
+
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);
|
|
968
|
+
process.stderr.write(`corenel-sidecar: ${msg}
|
|
936
969
|
`);
|
|
937
970
|
process.exit(1);
|
|
938
971
|
});
|
package/package.json
CHANGED
|
@@ -1,41 +1,48 @@
|
|
|
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": [
|
|
8
|
-
|
|
9
|
-
"corenel
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
],
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
},
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"
|
|
27
|
-
},
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@corenel/sidecar",
|
|
3
|
+
"version": "0.1.4",
|
|
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": [
|
|
8
|
+
"prompd",
|
|
9
|
+
"corenel",
|
|
10
|
+
"sidecar",
|
|
11
|
+
"ollama",
|
|
12
|
+
"local-llm",
|
|
13
|
+
"cors"
|
|
14
|
+
],
|
|
15
|
+
"bin": {
|
|
16
|
+
"corenel-sidecar": "dist/cli.js"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18"
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "node build.mjs",
|
|
30
|
+
"prepublishOnly": "node build.mjs",
|
|
31
|
+
"start": "tsx src/cli.ts",
|
|
32
|
+
"typecheck": "tsc --noEmit",
|
|
33
|
+
"verify": "tsx src/verify.ts"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"selfsigned": "^2.4.1",
|
|
37
|
+
"ws": "^8.18.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@corenel/protocol": "workspace:*",
|
|
41
|
+
"@corenel/harness": "workspace:*",
|
|
42
|
+
"@types/node": "^24.13.1",
|
|
43
|
+
"@types/ws": "^8.5.13",
|
|
44
|
+
"esbuild": "^0.21.5",
|
|
45
|
+
"tsx": "^4.19.2",
|
|
46
|
+
"typescript": "^5.9.3"
|
|
47
|
+
}
|
|
48
|
+
}
|