vibelet 1.0.10 → 1.0.11

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/bin/vibelet.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { spawn, spawnSync } from 'node:child_process';
4
- import { cpSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, openSync, writeSync } from 'node:fs';
4
+ import { cpSync, existsSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, writeFileSync, openSync, writeSync } from 'node:fs';
5
5
  import { homedir } from 'node:os';
6
- import { dirname, join, resolve } from 'node:path';
6
+ import { basename, delimiter, dirname, join, resolve } from 'node:path';
7
7
  import { fileURLToPath } from 'node:url';
8
8
  import QRCode from 'qrcode';
9
9
  import { extractQuickTunnelUrl } from './cloudflared-quick-tunnel.mjs';
@@ -218,6 +218,75 @@ function isProcessAlive(pid) {
218
218
  }
219
219
  }
220
220
 
221
+ // ─── Service PATH helpers ──────────────────────────────────────────────────────
222
+
223
+ // Transient shims like fnm_multishells/<pid>/bin disappear or get GC'd after the
224
+ // shell that created them exits. Long-lived services (launchd, systemd) that
225
+ // inherit these paths end up spawning wrapper scripts whose `exec node` fails
226
+ // with "node: not found". Stabilize to the underlying stable dir/file.
227
+ function isTransientNodePath(p) {
228
+ return typeof p === 'string' && p.includes('fnm_multishells');
229
+ }
230
+
231
+ function stabilizeServiceFile(p) {
232
+ if (!p || !isTransientNodePath(p)) return p;
233
+ try {
234
+ const stable = join(realpathSync(dirname(p)), basename(p));
235
+ if (existsSync(stable) && !isTransientNodePath(stable)) return stable;
236
+ } catch { /* broken symlink */ }
237
+ return p;
238
+ }
239
+
240
+ function stabilizeServiceDir(dir) {
241
+ if (!dir || !isTransientNodePath(dir)) return dir;
242
+ try {
243
+ const real = realpathSync(dir);
244
+ if (real && !isTransientNodePath(real)) return real;
245
+ } catch { /* broken symlink */ }
246
+ return dir;
247
+ }
248
+
249
+ // Build a PATH the daemon can rely on under launchd/systemd, where no login
250
+ // shell ran and no rc files loaded. Puts the node bin dir first so wrapper
251
+ // scripts spawned by the daemon (codex, claude) can resolve `node` via PATH,
252
+ // then falls back to common system and user-tool locations.
253
+ function buildServicePath(nodeBinDir) {
254
+ const home = homedir();
255
+ const pnpmHome = process.env.PNPM_HOME
256
+ || (process.platform === 'win32' ? join(home, 'pnpm') : join(home, 'Library', 'pnpm'));
257
+ const baseline = process.platform === 'win32'
258
+ ? [
259
+ nodeBinDir,
260
+ join(home, '.npm-global', 'bin'),
261
+ join(home, '.local', 'bin'),
262
+ join(home, '.claude', 'bin'),
263
+ pnpmHome,
264
+ ]
265
+ : [
266
+ nodeBinDir,
267
+ '/usr/local/bin',
268
+ '/opt/homebrew/bin',
269
+ '/usr/bin',
270
+ '/bin',
271
+ '/usr/sbin',
272
+ '/sbin',
273
+ join(home, '.npm-global', 'bin'),
274
+ join(home, '.local', 'bin'),
275
+ join(home, '.claude', 'bin'),
276
+ pnpmHome,
277
+ ];
278
+ const current = (process.env.PATH || '')
279
+ .split(delimiter)
280
+ .filter(Boolean)
281
+ .map(stabilizeServiceDir);
282
+ const out = [];
283
+ const seen = new Set();
284
+ for (const p of [...baseline, ...current]) {
285
+ if (p && !seen.has(p)) { out.push(p); seen.add(p); }
286
+ }
287
+ return out.join(delimiter);
288
+ }
289
+
221
290
  // ─── Platform service backends ──────────────────────────────────────────────────
222
291
 
223
292
  function createDarwinBackend() {
@@ -232,12 +301,20 @@ function createDarwinBackend() {
232
301
  }
233
302
 
234
303
  function plistContents() {
304
+ // Prefer a stable node path — fnm_multishells shims disappear when the
305
+ // installing shell exits, leaving launchd unable to respawn the daemon.
306
+ const nodeBin = stabilizeServiceFile(process.execPath);
235
307
  const programArgs = [
236
- process.execPath,
308
+ nodeBin,
237
309
  runtimeDaemonEntryPath,
238
310
  ].map((value) => ` <string>${value}</string>`).join('\n');
239
311
 
240
- const envVars = { VIBE_PORT: String(port) };
312
+ // launchd starts the daemon with a bare PATH; inject a rich one so child
313
+ // CLIs like codex (a sh wrapper that does `exec node`) can find node.
314
+ const envVars = {
315
+ PATH: buildServicePath(dirname(nodeBin)),
316
+ VIBE_PORT: String(port),
317
+ };
241
318
  if (process.env.VIBELET_RELAY_URL) envVars.VIBELET_RELAY_URL = process.env.VIBELET_RELAY_URL;
242
319
  if (process.env.VIBELET_CANONICAL_HOST) envVars.VIBELET_CANONICAL_HOST = process.env.VIBELET_CANONICAL_HOST;
243
320
  if (process.env.VIBELET_FALLBACK_HOSTS) envVars.VIBELET_FALLBACK_HOSTS = process.env.VIBELET_FALLBACK_HOSTS;
@@ -347,17 +424,20 @@ function createLinuxBackend() {
347
424
  }
348
425
 
349
426
  function unitContents() {
427
+ const nodeBin = stabilizeServiceFile(process.execPath);
428
+ const servicePath = buildServicePath(dirname(nodeBin));
350
429
  return `[Unit]
351
430
  Description=Vibelet Daemon
352
431
  After=network.target
353
432
 
354
433
  [Service]
355
- ExecStart=${process.execPath} ${runtimeDaemonEntryPath}
434
+ ExecStart=${nodeBin} ${runtimeDaemonEntryPath}
356
435
  WorkingDirectory=${runtimeCurrentDir}
357
436
  Restart=always
358
437
  RestartSec=3
359
438
  StandardOutput=append:${stdoutLogPath}
360
439
  StandardError=append:${stderrLogPath}
440
+ Environment=PATH=${servicePath}
361
441
  Environment=VIBE_PORT=${port}${process.env.VIBELET_RELAY_URL ? `\nEnvironment=VIBELET_RELAY_URL=${process.env.VIBELET_RELAY_URL}` : ''}${process.env.VIBELET_CANONICAL_HOST ? `\nEnvironment=VIBELET_CANONICAL_HOST=${process.env.VIBELET_CANONICAL_HOST}` : ''}${process.env.VIBELET_FALLBACK_HOSTS ? `\nEnvironment=VIBELET_FALLBACK_HOSTS=${process.env.VIBELET_FALLBACK_HOSTS}` : ''}
362
442
 
363
443
  [Install]
@@ -556,7 +636,7 @@ async function waitForDaemonExit(timeoutMs) {
556
636
  return false;
557
637
  }
558
638
 
559
- async function waitForHealth(timeoutMs = 10_000) {
639
+ async function waitForHealth(timeoutMs = 30_000) {
560
640
  const health = await probeHealth(timeoutMs);
561
641
  if (health) {
562
642
  return health;