@threadbase-sh/streamer 1.52.2 → 1.52.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 +8 -4
- package/dist/cli.cjs +22 -9
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +20 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +20 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ node dist/cli.cjs serve --verbose --local-no-auth
|
|
|
35
35
|
|
|
36
36
|
#### Server address
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
By default, the server listens on port `8766` on all interfaces (WebSocket path: `/ws`).
|
|
39
39
|
|
|
40
40
|
#### Automatic updates
|
|
41
41
|
|
|
@@ -43,7 +43,7 @@ npm and Homebrew installs can auto-update: [docs/guides/auto-update.md](docs/gui
|
|
|
43
43
|
|
|
44
44
|
## Remote Access
|
|
45
45
|
|
|
46
|
-
The server listens on port 8766 on all interfaces, so devices on the same LAN can already reach it. To let the mobile app reach it from *outside* your LAN, expose it via a tunnel — the fastest is a Cloudflare quick-tunnel (no account needed):
|
|
46
|
+
The server listens on port 8766 on all interfaces by default, so devices on the same LAN can already reach it. To restrict it to this machine, start it with `tb-streamer serve --host 127.0.0.1`. To let the mobile app reach it from *outside* your LAN, expose it via a tunnel — the fastest is a Cloudflare quick-tunnel (no account needed):
|
|
47
47
|
|
|
48
48
|
> **Before you do this, read [Security](#security--read-this-before-exposing-the-server).** A tunnel makes the server reachable from the public internet, and the API key is the only thing between a stranger and a shell on this machine.
|
|
49
49
|
|
|
@@ -72,11 +72,15 @@ npm run db:validate # check for missing/duplicate/orphaned project_id data
|
|
|
72
72
|
|
|
73
73
|
The key lives at `~/.threadbase/server.yaml` as plaintext (`chmod 0600`), which is the same posture as `~/.aws/credentials` or `~/.npmrc`. Rotate it with `POST /api/auth/rotate` if it leaks; re-pair your devices afterwards.
|
|
74
74
|
|
|
75
|
-
**The server listens on all network interfaces**, not just loopback
|
|
75
|
+
**The server listens on all network interfaces by default**, not just loopback. So every device on your LAN can already reach port 8766, and the API key is the only thing stopping them. On a home network that is usually fine; on café Wi-Fi, a co-working space, or a corporate VLAN it is not. To bind only to loopback, run:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
tb-streamer serve --host 127.0.0.1
|
|
79
|
+
```
|
|
76
80
|
|
|
77
81
|
**A tunnel extends that to the entire internet.** From that moment the key is the only thing between a stranger and your filesystem.
|
|
78
82
|
|
|
79
|
-
|
|
83
|
+
For stricter network policies, also use your firewall or a network namespace.
|
|
80
84
|
|
|
81
85
|
### There is no spend limit
|
|
82
86
|
|
package/dist/cli.cjs
CHANGED
|
@@ -152274,6 +152274,7 @@ var StreamerServer = class {
|
|
|
152274
152274
|
dbPool = null;
|
|
152275
152275
|
dbInstanceId = null;
|
|
152276
152276
|
disableDb = false;
|
|
152277
|
+
host;
|
|
152277
152278
|
// Skip the startup warm-up scan (test hook; see ServerConfig.skipStartupWarmup).
|
|
152278
152279
|
skipStartupWarmup;
|
|
152279
152280
|
autoResumeOnBoot;
|
|
@@ -152395,6 +152396,7 @@ var StreamerServer = class {
|
|
|
152395
152396
|
}
|
|
152396
152397
|
this.verbose = config2.verbose ?? false;
|
|
152397
152398
|
this.disableDb = config2.disableDb ?? false;
|
|
152399
|
+
this.host = config2.host;
|
|
152398
152400
|
this.skipStartupWarmup = config2.skipStartupWarmup ?? false;
|
|
152399
152401
|
this.autoResumeOnBoot = config2.autoResumeOnBoot ?? false;
|
|
152400
152402
|
this.scanProfiles = config2.scanProfiles;
|
|
@@ -153047,7 +153049,7 @@ var StreamerServer = class {
|
|
|
153047
153049
|
await runMigrations2(this.dbPool);
|
|
153048
153050
|
this.log.info("Database migrations applied", { event: "db.migrations_applied" });
|
|
153049
153051
|
}
|
|
153050
|
-
await this.bindWithRetry(port);
|
|
153052
|
+
await this.bindWithRetry(port, this.host);
|
|
153051
153053
|
if (!this.ptyManager.isRemote()) {
|
|
153052
153054
|
this.idleReaperTimer = setInterval(() => this.reapIdleSessions(), IDLE_REAP_SWEEP_MS);
|
|
153053
153055
|
this.idleReaperTimer.unref?.();
|
|
@@ -153056,7 +153058,8 @@ var StreamerServer = class {
|
|
|
153056
153058
|
{
|
|
153057
153059
|
this.log.info(`Streamer server listening on port ${port}`, {
|
|
153058
153060
|
port,
|
|
153059
|
-
event: "server.listening"
|
|
153061
|
+
event: "server.listening",
|
|
153062
|
+
...this.host !== void 0 && { host: this.host }
|
|
153060
153063
|
});
|
|
153061
153064
|
try {
|
|
153062
153065
|
this.runtimeStore = RuntimeStore.open(this.runtimeDbPath);
|
|
@@ -153270,15 +153273,15 @@ var StreamerServer = class {
|
|
|
153270
153273
|
// Bind the HTTP listener, retrying on a transient EADDRINUSE. See the call
|
|
153271
153274
|
// site in listen() for why the race exists (kickstart -k relaunch). Total
|
|
153272
153275
|
// worst case ≈ 6 × 500 ms = 3 s before the final attempt rethrows.
|
|
153273
|
-
async bindWithRetry(port, attempts = 6, delayMs = 500) {
|
|
153276
|
+
async bindWithRetry(port, host, attempts = 6, delayMs = 500) {
|
|
153274
153277
|
this.binding = true;
|
|
153275
153278
|
try {
|
|
153276
|
-
await this.bindWithRetryLoop(port, attempts, delayMs);
|
|
153279
|
+
await this.bindWithRetryLoop(port, host, attempts, delayMs);
|
|
153277
153280
|
} finally {
|
|
153278
153281
|
this.binding = false;
|
|
153279
153282
|
}
|
|
153280
153283
|
}
|
|
153281
|
-
async bindWithRetryLoop(port, attempts, delayMs) {
|
|
153284
|
+
async bindWithRetryLoop(port, host, attempts, delayMs) {
|
|
153282
153285
|
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
153283
153286
|
try {
|
|
153284
153287
|
await new Promise((resolve4, reject) => {
|
|
@@ -153292,7 +153295,11 @@ var StreamerServer = class {
|
|
|
153292
153295
|
};
|
|
153293
153296
|
this.httpServer.once("error", onError);
|
|
153294
153297
|
this.httpServer.once("listening", onListening);
|
|
153295
|
-
|
|
153298
|
+
if (host === void 0) {
|
|
153299
|
+
this.httpServer.listen(port);
|
|
153300
|
+
} else {
|
|
153301
|
+
this.httpServer.listen(port, host);
|
|
153302
|
+
}
|
|
153296
153303
|
});
|
|
153297
153304
|
return;
|
|
153298
153305
|
} catch (err) {
|
|
@@ -153300,13 +153307,18 @@ var StreamerServer = class {
|
|
|
153300
153307
|
if (e.code === "EADDRINUSE" && attempt === attempts) {
|
|
153301
153308
|
this.log.error(
|
|
153302
153309
|
`port ${port} still busy (EADDRINUSE) after ${attempts} attempts; giving up`,
|
|
153303
|
-
{
|
|
153310
|
+
{
|
|
153311
|
+
port,
|
|
153312
|
+
attempts,
|
|
153313
|
+
event: "server.bind_failed",
|
|
153314
|
+
...host !== void 0 && { host }
|
|
153315
|
+
}
|
|
153304
153316
|
);
|
|
153305
153317
|
}
|
|
153306
153318
|
if (e.code !== "EADDRINUSE" || attempt === attempts) throw err;
|
|
153307
153319
|
this.log.debug?.(
|
|
153308
153320
|
`port ${port} busy (EADDRINUSE), retry ${attempt}/${attempts - 1} in ${delayMs}ms`,
|
|
153309
|
-
{ port, attempt, event: "server.bind_retry" }
|
|
153321
|
+
{ port, attempt, event: "server.bind_retry", ...host !== void 0 && { host } }
|
|
153310
153322
|
);
|
|
153311
153323
|
await new Promise((r) => setTimeout(r, delayMs));
|
|
153312
153324
|
}
|
|
@@ -157841,7 +157853,7 @@ function registerProdCommands(program3) {
|
|
|
157841
157853
|
var log14 = getLogger("cli");
|
|
157842
157854
|
var program2 = new Command();
|
|
157843
157855
|
program2.name("threadbase-streamer").description("PTY session management, WebSocket streaming, and REST API server for Claude Code").version(getVersion());
|
|
157844
|
-
program2.command("serve").description("Start the streamer server").option("-p, --port <number>", "Port to listen on", "8766").option("--api-key <key>", "API key for authentication").option("--local-no-auth", "Skip auth for localhost requests", false).option("-v, --verbose", "Verbose output", false).option("--log-menubar-requests", "Log /healthz requests from the menubar app", false).option("--browse-root <path>", "Root directory for file browsing").option(
|
|
157856
|
+
program2.command("serve").description("Start the streamer server").option("-p, --port <number>", "Port to listen on", "8766").option("--host <address>", "Host/address to bind the server to (default: all interfaces)").option("--api-key <key>", "API key for authentication").option("--local-no-auth", "Skip auth for localhost requests", false).option("-v, --verbose", "Verbose output", false).option("--log-menubar-requests", "Log /healthz requests from the menubar app", false).option("--browse-root <path>", "Root directory for file browsing").option(
|
|
157845
157857
|
"--public-url <url>",
|
|
157846
157858
|
"Public URL clients should use to reach this server (https:// required, except localhost). Falls back to THREADBASE_PUBLIC_URL env or public_url: in ~/.threadbase/server.yaml."
|
|
157847
157859
|
).option(
|
|
@@ -158029,6 +158041,7 @@ program2.command("serve").description("Start the streamer server").option("-p, -
|
|
|
158029
158041
|
}
|
|
158030
158042
|
const server = new StreamerServer({
|
|
158031
158043
|
port: resolvedPort,
|
|
158044
|
+
host: opts.host,
|
|
158032
158045
|
apiKey,
|
|
158033
158046
|
apiKeySource: opts.apiKey ? "cli" : "config",
|
|
158034
158047
|
localNoAuth: opts.localNoAuth,
|