mindwire 0.1.2 → 0.1.5

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/dist/client.d.ts CHANGED
@@ -102,7 +102,7 @@ export declare class Mindwire {
102
102
  * call before the target has connected (nothing to reap yet).
103
103
  */
104
104
  close(): Promise<void>;
105
- /** `GET /healthz` — public liveness check. Resolves with the daemon's health payload when it is up. */
105
+ /** `GET /healthz` — authenticated liveness check. Resolves with the daemon's health payload when it is up. */
106
106
  health(): Promise<Health>;
107
107
  /**
108
108
  * `GET /stats` — the daemon **process's** resource snapshot (heap in use, memory reserved from the
package/dist/index.cjs CHANGED
@@ -469,7 +469,7 @@ var Run = class _Run {
469
469
  };
470
470
 
471
471
  // src/version.ts
472
- var SDK_VERSION = "0.1.2" ;
472
+ var SDK_VERSION = "0.1.5" ;
473
473
 
474
474
  // src/daemon-binary.ts
475
475
  function supported(platform, arch) {
@@ -580,17 +580,19 @@ async function spawnDaemon(opts) {
580
580
  const { spawn } = await import('child_process');
581
581
  const net = await import('net');
582
582
  const proc = serverRuntime().process;
583
+ const { randomBytes } = await import('crypto');
583
584
  const resolved = await resolveBinary(opts.bin);
584
585
  const bin = resolved.bin;
585
586
  const port = await freePort(net);
586
587
  const baseUrl = `http://127.0.0.1:${port}`;
588
+ const token = randomBytes(32).toString("hex");
587
589
  const child = spawn(bin, [], {
588
590
  env: {
589
591
  ...proc.env,
590
592
  ADDR: `127.0.0.1:${port}`,
591
593
  AGENT_CWD: opts.cwd ?? proc.cwd(),
592
594
  STATE_PATH: opts.statePath ?? ".mindwire-state.json",
593
- DAEMON_TOKEN: ""
595
+ DAEMON_TOKEN: token
594
596
  },
595
597
  stdio: "ignore"
596
598
  });
@@ -609,8 +611,8 @@ async function spawnDaemon(opts) {
609
611
  stop();
610
612
  proc.exit?.(130);
611
613
  });
612
- await waitHealthy(baseUrl, () => spawnError, resolved);
613
- return { baseUrl, stop };
614
+ await waitHealthy(baseUrl, token, () => spawnError, resolved);
615
+ return { baseUrl, token, stop };
614
616
  }
615
617
  async function resolveBinary(explicit) {
616
618
  const fs = await import('fs');
@@ -662,7 +664,7 @@ function freePort(net) {
662
664
  });
663
665
  });
664
666
  }
665
- async function waitHealthy(baseUrl, getError, resolved, timeoutMs = 15e3) {
667
+ async function waitHealthy(baseUrl, token, getError, resolved, timeoutMs = 15e3) {
666
668
  const deadline = Date.now() + timeoutMs;
667
669
  while (Date.now() < deadline) {
668
670
  const err = getError();
@@ -670,7 +672,7 @@ async function waitHealthy(baseUrl, getError, resolved, timeoutMs = 15e3) {
670
672
  throw new MindwireError(daemonStartError(resolved, err), { cause: err });
671
673
  }
672
674
  try {
673
- const res = await fetch(`${baseUrl}/healthz`);
675
+ const res = await fetch(`${baseUrl}/healthz`, { headers: { Authorization: `Bearer ${token}` } });
674
676
  if (res.ok) return;
675
677
  } catch {
676
678
  }
@@ -805,7 +807,7 @@ var Mindwire = class _Mindwire {
805
807
  if (h) await h.stop();
806
808
  }
807
809
  // ---- health & catalog ----------------------------------------------------
808
- /** `GET /healthz` — public liveness check. Resolves with the daemon's health payload when it is up. */
810
+ /** `GET /healthz` — authenticated liveness check. Resolves with the daemon's health payload when it is up. */
809
811
  health() {
810
812
  return this.http.request("GET", "/healthz");
811
813
  }
@@ -1307,11 +1309,12 @@ var STATE = `${DAEMON_DIR}/agent-state.json`;
1307
1309
  var LOG = `${DAEMON_DIR}/daemon.log`;
1308
1310
  async function ensureDaemon(host, cfg) {
1309
1311
  const emit2 = makeEmit(cfg);
1312
+ const token = cfg.token ?? (await import('crypto')).randomBytes(32).toString("hex");
1310
1313
  try {
1311
1314
  await waitHostReady(host);
1312
1315
  emit2({ phase: "connect", message: "runtime ready" });
1313
1316
  const desired = cfg.desiredVersion ?? SDK_VERSION;
1314
- const health = await probeHealth(host, cfg.port);
1317
+ const health = await probeHealth(host, cfg.port, token);
1315
1318
  if (health.reachable) {
1316
1319
  emit2({
1317
1320
  phase: "probe",
@@ -1319,31 +1322,32 @@ async function ensureDaemon(host, cfg) {
1319
1322
  version: health.version
1320
1323
  });
1321
1324
  const upToDate = health.version !== void 0 && health.version === desired;
1322
- if (upToDate || !cfg.autoUpdate) {
1325
+ if (!cfg.forceDeploy && (upToDate || !cfg.autoUpdate)) {
1323
1326
  emit2({
1324
1327
  phase: "skip",
1325
1328
  message: upToDate ? `daemon already at v${desired}` : "keeping the running daemon",
1326
1329
  version: health.version
1327
1330
  });
1328
- return;
1331
+ return token;
1329
1332
  }
1330
1333
  } else {
1331
1334
  emit2({ phase: "probe", message: "no daemon reachable; deploying" });
1332
1335
  }
1333
- await deploy(host, cfg, emit2);
1336
+ await deploy(host, cfg, emit2, token);
1337
+ return token;
1334
1338
  } catch (err) {
1335
1339
  emit2({ phase: "error", message: "ensure failed", error: err instanceof Error ? err.message : String(err) });
1336
1340
  throw err;
1337
1341
  }
1338
1342
  }
1339
- async function deploy(host, cfg, emit2) {
1343
+ async function deploy(host, cfg, emit2, token) {
1340
1344
  const arch = await probeArch(host);
1341
1345
  const desired = cfg.desiredVersion ?? SDK_VERSION;
1342
1346
  let acquire = "";
1343
1347
  if (cfg.daemonBin) {
1344
1348
  const binPath = await resolveLinuxDaemon(cfg.daemonBin, arch);
1345
1349
  const bytes = await readBytes(binPath);
1346
- emit2({ phase: "upload", message: `uploading daemon (${arch}, ${bytes.length} bytes)`, arch, bytes: bytes.length });
1350
+ emit2({ phase: "upload", message: `uploading daemon (${arch}, ${formatMiB(bytes.length)})`, arch, bytes: bytes.length });
1347
1351
  await host.putFile(BIN_NEW, bytes, { mode: "0755" });
1348
1352
  } else {
1349
1353
  if (!/^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$/.test(desired)) {
@@ -1386,9 +1390,9 @@ async function deploy(host, cfg, emit2) {
1386
1390
  `mv -f ${BIN_NEW} ${BIN}`,
1387
1391
  `chmod +x ${BIN}`,
1388
1392
  // Detach so the daemon survives this exec's shell exiting. ADDR=":<port>" binds 0.0.0.0.
1389
- `setsid nohup env ADDR=":${cfg.port}" AGENT_TYPE="${cfg.agent}" AGENT_CWD="${cfg.agentCwd}" STATE_PATH="${STATE}" DAEMON_TOKEN="" ${BIN} > ${LOG} 2>&1 < /dev/null &`,
1393
+ `setsid nohup env ADDR=":${cfg.port}" AGENT_TYPE="${cfg.agent}" AGENT_CWD="${cfg.agentCwd}" STATE_PATH="${STATE}" DAEMON_TOKEN=${shellQuote(token)} ${BIN} > ${LOG} 2>&1 < /dev/null &`,
1390
1394
  // Health-poll from inside the VM (loopback) and emit a marker — exit codes are unreliable here.
1391
- `for i in $(seq 1 60); do curl -fsS --max-time 2 http://127.0.0.1:${cfg.port}/healthz >/dev/null 2>&1 && { echo MINDWIRE_READY; exit 0; }; sleep 0.25; done`,
1395
+ `for i in $(seq 1 60); do curl -fsS --max-time 2 -H ${shellQuote(`Authorization: Bearer ${token}`)} http://127.0.0.1:${cfg.port}/healthz >/dev/null 2>&1 && { echo MINDWIRE_READY; exit 0; }; sleep 0.25; done`,
1392
1396
  "echo MINDWIRE_FAIL",
1393
1397
  `tail -n 40 ${LOG} 2>/dev/null || true`
1394
1398
  ].join("\n");
@@ -1402,8 +1406,8 @@ async function deploy(host, cfg, emit2) {
1402
1406
  }
1403
1407
  emit2({ phase: "ready", message: "daemon deployed and healthy" });
1404
1408
  }
1405
- async function probeHealth(host, port) {
1406
- const script = `out=$(curl -fsS --max-time 3 http://127.0.0.1:${port}/healthz 2>/dev/null) && printf '<<MW_H>>%s<<MW_H>>' "$out" || printf '<<MW_H>><<MW_H>>'`;
1409
+ async function probeHealth(host, port, token) {
1410
+ const script = `out=$(curl -fsS --max-time 3 -H ${shellQuote(`Authorization: Bearer ${token}`)} http://127.0.0.1:${port}/healthz 2>/dev/null) && printf '<<MW_H>>%s<<MW_H>>' "$out" || printf '<<MW_H>><<MW_H>>'`;
1407
1411
  const res = await host.exec(["bash", "-lc", script], { timeoutSeconds: 15 });
1408
1412
  const body = ((res.stdout ?? "").match(/<<MW_H>>([\s\S]*?)<<MW_H>>/)?.[1] ?? "").trim();
1409
1413
  if (!body) return { reachable: false };
@@ -1439,10 +1443,11 @@ async function waitHostReady(host, timeoutMs = 6e4) {
1439
1443
  async function resolveLinuxDaemon(explicit, arch) {
1440
1444
  const fs = await import('fs');
1441
1445
  if (explicit) {
1442
- if (!fs.existsSync(explicit)) {
1443
- throw new MindwireError(`mindwire: sandbox daemonBin not found at ${explicit}`);
1446
+ const resolved = explicit.replaceAll("{arch}", arch);
1447
+ if (!fs.existsSync(resolved)) {
1448
+ throw new MindwireError(`mindwire: sandbox daemonBin not found at ${resolved}`);
1444
1449
  }
1445
- return explicit;
1450
+ return resolved;
1446
1451
  }
1447
1452
  return ensureDaemonBinary({ platform: "linux", arch: arch === "arm64" ? "arm64" : "x64" });
1448
1453
  }
@@ -1456,6 +1461,9 @@ function sleep(ms) {
1456
1461
  function shellQuote(value) {
1457
1462
  return `'${value.replace(/'/g, "'\\''")}'`;
1458
1463
  }
1464
+ function formatMiB(bytes) {
1465
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1466
+ }
1459
1467
 
1460
1468
  // src/target/container.ts
1461
1469
  async function provisionContainer(host, cfg, onLog) {
@@ -1469,7 +1477,7 @@ async function provisionContainer(host, cfg, onLog) {
1469
1477
  };
1470
1478
  await ensureDockerReady(host, cfg.install ?? "never", emit2);
1471
1479
  const { containerId, hostPort, created } = await runContainer(host, cfg, emit2);
1472
- await ensureDaemon(new ContainerHost(host, containerId), {
1480
+ const token = await ensureDaemon(new ContainerHost(host, containerId), {
1473
1481
  port: cfg.daemonPort,
1474
1482
  agent: cfg.agent,
1475
1483
  agentCwd: cfg.agentCwd,
@@ -1488,7 +1496,7 @@ async function provisionContainer(host, cfg, onLog) {
1488
1496
  } catch {
1489
1497
  }
1490
1498
  };
1491
- return { containerId, hostPort, stop };
1499
+ return { containerId, hostPort, token, stop };
1492
1500
  }
1493
1501
  var ContainerHost = class {
1494
1502
  constructor(base, containerId) {
@@ -1724,7 +1732,7 @@ async function connectSsh(opts, spec) {
1724
1732
  });
1725
1733
  }
1726
1734
  async function provisionSsh(client, makeTunnel, cfg) {
1727
- await ensureDaemon(new SshHost(client), {
1735
+ const token = await ensureDaemon(new SshHost(client), {
1728
1736
  port: cfg.daemonPort,
1729
1737
  agent: cfg.agent,
1730
1738
  agentCwd: cfg.agentCwd,
@@ -1747,7 +1755,7 @@ async function provisionSsh(client, makeTunnel, cfg) {
1747
1755
  }
1748
1756
  }
1749
1757
  };
1750
- return { id: cfg.id, baseUrl: tunnel.baseUrl, stop };
1758
+ return { id: cfg.id, baseUrl: tunnel.baseUrl, token, stop };
1751
1759
  }
1752
1760
  async function provisionSshContainer(client, makeTunnel, cfg) {
1753
1761
  const container = await provisionContainer(
@@ -1784,7 +1792,7 @@ async function provisionSshContainer(client, makeTunnel, cfg) {
1784
1792
  }
1785
1793
  }
1786
1794
  };
1787
- return { id: cfg.id, baseUrl: tunnel.baseUrl, stop };
1795
+ return { id: cfg.id, baseUrl: tunnel.baseUrl, token: container.token, stop };
1788
1796
  }
1789
1797
  async function openConnection(mod, opts) {
1790
1798
  const auth = await buildAuth(opts);
@@ -1996,7 +2004,7 @@ async function provisionDocker(docker2, config = {}, onLog) {
1996
2004
  `mindwire: the Docker container does not publish a host port for ${port}/tcp. ` + (created ? "This is unexpected for a container mindwire created." : "Attach to a container started with that port published (e.g. `-p 0:" + port + "`).")
1997
2005
  );
1998
2006
  }
1999
- await ensureDaemon(new DockerHost(container), {
2007
+ const token = await ensureDaemon(new DockerHost(container), {
2000
2008
  port,
2001
2009
  agent,
2002
2010
  agentCwd,
@@ -2015,7 +2023,7 @@ async function provisionDocker(docker2, config = {}, onLog) {
2015
2023
  } catch {
2016
2024
  }
2017
2025
  };
2018
- return { id: container.id, baseUrl: `http://127.0.0.1:${hostPort}`, stop };
2026
+ return { id: container.id, baseUrl: `http://127.0.0.1:${hostPort}`, token, stop };
2019
2027
  }
2020
2028
  async function ensureImage(docker2, image, policy, onLog) {
2021
2029
  const emit2 = (message, error) => {
@@ -2152,26 +2160,32 @@ async function provisionOblien(client, config = {}, onLog) {
2152
2160
  const ws = await client.workspaces.create(params);
2153
2161
  workspaceId = ws.id;
2154
2162
  }
2163
+ await config.onWorkspace?.(workspaceId);
2155
2164
  const handle = client.workspace(workspaceId);
2156
2165
  await handle.start().catch(() => {
2157
2166
  });
2158
2167
  let rt = await handle.runtime();
2159
- await ensureDaemon(new OblienHost(rt), {
2168
+ const token = await ensureDaemon(new OblienHost(rt), {
2160
2169
  port,
2161
2170
  agent,
2162
2171
  agentCwd,
2163
2172
  daemonBin: config.daemonBin,
2164
2173
  autoUpdate: config.autoUpdate,
2174
+ forceDeploy: config.forceDeploy,
2165
2175
  target: "oblien",
2166
- onLog
2176
+ onLog,
2177
+ token: config.daemonToken
2167
2178
  });
2168
2179
  const fetchImpl = async (url, init) => {
2169
2180
  const { pathname, search } = new URL(url);
2170
2181
  const path = pathname + search;
2171
- let res = await rt.proxy(port).fetch(path, init);
2182
+ const headers = new Headers(init?.headers);
2183
+ headers.set("X-Mindwire-Token", token);
2184
+ headers.delete("Authorization");
2185
+ let res = await rt.proxy(port).fetch(path, { ...init, headers });
2172
2186
  if (res.status === 401) {
2173
2187
  rt = await handle.runtime({ force: true });
2174
- res = await rt.proxy(port).fetch(path, init);
2188
+ res = await rt.proxy(port).fetch(path, { ...init, headers });
2175
2189
  }
2176
2190
  return res;
2177
2191
  };
@@ -2182,7 +2196,7 @@ async function provisionOblien(client, config = {}, onLog) {
2182
2196
  } catch {
2183
2197
  }
2184
2198
  };
2185
- return { id: workspaceId, workspaceId, baseUrl: SENTINEL_BASE, fetch: fetchImpl, stop };
2199
+ return { id: workspaceId, workspaceId, baseUrl: SENTINEL_BASE, token, fetch: fetchImpl, stop };
2186
2200
  }
2187
2201
  async function importOblien() {
2188
2202
  const spec = "oblien";