vibelet 1.0.9 → 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 +97 -8
- package/dist/index.cjs +50 -48
- package/package.json +1 -1
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,15 +301,25 @@ 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
|
-
|
|
308
|
+
nodeBin,
|
|
237
309
|
runtimeDaemonEntryPath,
|
|
238
310
|
].map((value) => ` <string>${value}</string>`).join('\n');
|
|
239
311
|
|
|
240
|
-
|
|
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;
|
|
321
|
+
if (process.env.CODEX_PATH) envVars.CODEX_PATH = process.env.CODEX_PATH;
|
|
322
|
+
if (process.env.CLAUDE_PATH) envVars.CLAUDE_PATH = process.env.CLAUDE_PATH;
|
|
244
323
|
const envSection = Object.keys(envVars).length > 0
|
|
245
324
|
? ` <key>EnvironmentVariables</key>
|
|
246
325
|
<dict>
|
|
@@ -313,8 +392,15 @@ ${envSection}
|
|
|
313
392
|
},
|
|
314
393
|
|
|
315
394
|
stop() {
|
|
316
|
-
if (this.isServiceInstalled())
|
|
317
|
-
|
|
395
|
+
if (!this.isServiceInstalled()) return;
|
|
396
|
+
launchctl(['bootout', `${launchDomain}/${label}`]);
|
|
397
|
+
// bootout returns before launchd actually finishes unloading. Poll
|
|
398
|
+
// until the service is gone so a follow-up install() doesn't see a
|
|
399
|
+
// stale registration, skip bootstrap, and leave the daemon down.
|
|
400
|
+
const deadline = Date.now() + 5000;
|
|
401
|
+
const wait = new Int32Array(new SharedArrayBuffer(4));
|
|
402
|
+
while (Date.now() < deadline && this.isServiceInstalled()) {
|
|
403
|
+
Atomics.wait(wait, 0, 0, 100);
|
|
318
404
|
}
|
|
319
405
|
},
|
|
320
406
|
|
|
@@ -338,17 +424,20 @@ function createLinuxBackend() {
|
|
|
338
424
|
}
|
|
339
425
|
|
|
340
426
|
function unitContents() {
|
|
427
|
+
const nodeBin = stabilizeServiceFile(process.execPath);
|
|
428
|
+
const servicePath = buildServicePath(dirname(nodeBin));
|
|
341
429
|
return `[Unit]
|
|
342
430
|
Description=Vibelet Daemon
|
|
343
431
|
After=network.target
|
|
344
432
|
|
|
345
433
|
[Service]
|
|
346
|
-
ExecStart=${
|
|
434
|
+
ExecStart=${nodeBin} ${runtimeDaemonEntryPath}
|
|
347
435
|
WorkingDirectory=${runtimeCurrentDir}
|
|
348
436
|
Restart=always
|
|
349
437
|
RestartSec=3
|
|
350
438
|
StandardOutput=append:${stdoutLogPath}
|
|
351
439
|
StandardError=append:${stderrLogPath}
|
|
440
|
+
Environment=PATH=${servicePath}
|
|
352
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}` : ''}
|
|
353
442
|
|
|
354
443
|
[Install]
|
|
@@ -547,7 +636,7 @@ async function waitForDaemonExit(timeoutMs) {
|
|
|
547
636
|
return false;
|
|
548
637
|
}
|
|
549
638
|
|
|
550
|
-
async function waitForHealth(timeoutMs =
|
|
639
|
+
async function waitForHealth(timeoutMs = 30_000) {
|
|
551
640
|
const health = await probeHealth(timeoutMs);
|
|
552
641
|
if (health) {
|
|
553
642
|
return health;
|