@vibelet/cli 1.2.158 → 1.2.160

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/vibelet.mjs CHANGED
@@ -11252,12 +11252,12 @@ module.exports = __WEBPACK_EXTERNAL_createRequire(import.meta.url)("zlib");
11252
11252
  // EXPORTS
11253
11253
  __nccwpck_require__.d(__webpack_exports__, {
11254
11254
  vD: () => (/* reexport */ clearTunnelState),
11255
- QQ: () => (/* reexport */ getAliveTunnel),
11255
+ M: () => (/* reexport */ getAliveManagedQuickTunnel),
11256
11256
  DH: () => (/* reexport */ startTunnel),
11257
- HM: () => (/* reexport */ stopTunnel)
11257
+ HW: () => (/* reexport */ stopManagedQuickTunnel)
11258
11258
  });
11259
11259
 
11260
- // UNUSED EXPORTS: isProcessAlive, loadTunnelState, readCloudflaredLog, saveTunnelState
11260
+ // UNUSED EXPORTS: getAliveTunnel, isProcessAlive, isQuickTunnelProcessCommand, loadTunnelState, readCloudflaredLog, saveTunnelState, stopTunnel
11261
11261
 
11262
11262
  // EXTERNAL MODULE: external "node:child_process"
11263
11263
  var external_node_child_process_ = __nccwpck_require__(1421);
@@ -11479,6 +11479,139 @@ function extractQuickTunnelUrl(logContent) {
11479
11479
  return match?.[0] ?? null;
11480
11480
  }
11481
11481
 
11482
+ function isQuickTunnelUrl(url) {
11483
+ if (typeof url !== 'string' || !url.trim()) return false;
11484
+ try {
11485
+ return new URL(url).hostname.toLowerCase().endsWith('.trycloudflare.com');
11486
+ } catch {
11487
+ return false;
11488
+ }
11489
+ }
11490
+
11491
+ function normalizeOriginUrl(url) {
11492
+ if (typeof url !== 'string' || !url.trim()) return null;
11493
+ try {
11494
+ const parsed = new URL(url);
11495
+ const hostname = parsed.hostname.toLowerCase();
11496
+ if (
11497
+ parsed.protocol !== 'http:' ||
11498
+ !['localhost', '127.0.0.1', '[::1]'].includes(hostname) ||
11499
+ !parsed.port ||
11500
+ parsed.pathname !== '/' ||
11501
+ parsed.username ||
11502
+ parsed.password ||
11503
+ parsed.search ||
11504
+ parsed.hash
11505
+ ) {
11506
+ return null;
11507
+ }
11508
+ return parsed.href;
11509
+ } catch {
11510
+ return null;
11511
+ }
11512
+ }
11513
+
11514
+ function extractUrlArgs(commandLine) {
11515
+ const matches = String(commandLine).matchAll(/(?:^|\s)--url(?:=|\s+)(?:"([^"]+)"|'([^']+)'|([^\s"']+))/giu);
11516
+ return [...matches].map((match) => match[1] || match[2] || match[3]).filter(Boolean);
11517
+ }
11518
+
11519
+ function isQuickTunnelProcessCommand(commandLine, { originUrl = '' } = {}) {
11520
+ if (typeof commandLine !== 'string' || !commandLine.trim()) return false;
11521
+
11522
+ const referencesCloudflared =
11523
+ /(?:^|[\\/\s"'=])cloudflared(?:\.exe)?(?=$|[\s"'])/iu.test(commandLine) ||
11524
+ /[\\/]cloudflared[\\/]lib[\\/]cloudflared\.js(?=$|[\s"'])/iu.test(commandLine) ||
11525
+ /(?:^|\s)--package(?:=|\s+)cloudflared(?=$|\s)/iu.test(commandLine);
11526
+ if (!referencesCloudflared) return false;
11527
+ if (!/(?:^|\s)tunnel(?=$|\s)/iu.test(commandLine)) return false;
11528
+ // Named Tunnels use `cloudflared tunnel ... run`; never treat one as a
11529
+ // Vibelet-managed Quick Tunnel even if an unrelated --url flag is present.
11530
+ if (/(?:^|\s)run(?=$|\s)/iu.test(commandLine)) return false;
11531
+
11532
+ const expectedOriginUrl = originUrl ? normalizeOriginUrl(originUrl) : null;
11533
+ if (originUrl && !expectedOriginUrl) return false;
11534
+ return extractUrlArgs(commandLine).some((candidate) => {
11535
+ const normalizedCandidate = normalizeOriginUrl(candidate);
11536
+ if (!normalizedCandidate) return false;
11537
+ return !expectedOriginUrl || normalizedCandidate === expectedOriginUrl;
11538
+ });
11539
+ }
11540
+
11541
+ function readProcessCommandLine(pid, { platform = process.platform, spawnSyncImpl = external_node_child_process_.spawnSync } = {}) {
11542
+ if (!Number.isInteger(pid) || pid <= 0) return '';
11543
+
11544
+ if (platform === 'win32') {
11545
+ const result = spawnSyncImpl(
11546
+ 'powershell.exe',
11547
+ [
11548
+ '-NoProfile',
11549
+ '-NonInteractive',
11550
+ '-Command',
11551
+ `(Get-CimInstance Win32_Process -Filter "ProcessId = ${pid}").CommandLine`,
11552
+ ],
11553
+ { encoding: 'utf8', timeout: 1_500, windowsHide: true },
11554
+ );
11555
+ return result.status === 0 ? String(result.stdout || '').trim() : '';
11556
+ }
11557
+
11558
+ const result = spawnSyncImpl('ps', ['-ww', '-p', String(pid), '-o', 'command='], {
11559
+ encoding: 'utf8',
11560
+ timeout: 1_500,
11561
+ windowsHide: true,
11562
+ });
11563
+ return result.status === 0 ? String(result.stdout || '').trim() : '';
11564
+ }
11565
+
11566
+ function extractCloudflaredMetricsBaseUrl(logContent) {
11567
+ if (typeof logContent !== 'string' || logContent.length === 0) {
11568
+ return null;
11569
+ }
11570
+ const match = logContent.match(
11571
+ /Starting metrics server on (?:https?:\/\/)?(127\.0\.0\.1|localhost|\[::1\]):(\d+)\/metrics\b/iu,
11572
+ );
11573
+ if (!match) return null;
11574
+ return `http://${match[1]}:${match[2]}`;
11575
+ }
11576
+
11577
+ async function probeCloudflaredReadiness(logContent, { fetchFn = globalThis.fetch, timeoutMs = 1000 } = {}) {
11578
+ const metricsBaseUrl = extractCloudflaredMetricsBaseUrl(logContent);
11579
+ if (!metricsBaseUrl || typeof fetchFn !== 'function') return false;
11580
+
11581
+ const controller = new AbortController();
11582
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
11583
+ try {
11584
+ const response = await fetchFn(`${metricsBaseUrl}/ready`, { signal: controller.signal });
11585
+ return response.ok;
11586
+ } catch {
11587
+ return false;
11588
+ } finally {
11589
+ clearTimeout(timeout);
11590
+ }
11591
+ }
11592
+
11593
+ async function probeQuickTunnelPublicHealth(state, { fetchFn = globalThis.fetch, timeoutMs = 1_500 } = {}) {
11594
+ if (!isQuickTunnelUrl(state?.url) || typeof fetchFn !== 'function') return false;
11595
+
11596
+ const controller = new AbortController();
11597
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
11598
+ try {
11599
+ const healthUrl = new URL('/health', state.url);
11600
+ healthUrl.searchParams.set('_vibelet_reuse_probe', String(Date.now()));
11601
+ const response = await fetchFn(healthUrl, {
11602
+ headers: { 'cache-control': 'no-cache' },
11603
+ signal: controller.signal,
11604
+ });
11605
+ if (!response.ok) return false;
11606
+ const payload = await response.json().catch(() => null);
11607
+ return payload?.status === 'ok';
11608
+ } catch {
11609
+ return false;
11610
+ } finally {
11611
+ clearTimeout(timeout);
11612
+ }
11613
+ }
11614
+
11482
11615
  function getNpmFallbackCommand(platform = process.platform) {
11483
11616
  return platform === 'win32' ? 'npx.cmd' : 'npx';
11484
11617
  }
@@ -11683,18 +11816,22 @@ function formatCloudflaredFailureMessage({ launchSpec, logContent = '', logPath,
11683
11816
 
11684
11817
  if (phase === 'timeout') {
11685
11818
  const summary = summarizeCloudflaredLog(logContent);
11819
+ const waitTarget = extractQuickTunnelUrl(logContent) ? 'tunnel readiness' : 'tunnel URL';
11686
11820
  if (summary) {
11687
- return `Timed out waiting for tunnel URL (${Math.round((launchSpec?.urlTimeoutMs ?? 30_000) / 1000)}s)${launchSuffix}.\nRecent stderr:\n${summary}`;
11821
+ return `Timed out waiting for ${waitTarget} (${Math.round((launchSpec?.urlTimeoutMs ?? 30_000) / 1000)}s)${launchSuffix}.\nRecent stderr:\n${summary}`;
11688
11822
  }
11689
- return `Timed out waiting for tunnel URL (${Math.round((launchSpec?.urlTimeoutMs ?? 30_000) / 1000)}s)${launchSuffix}. Check network/proxy settings and ${logPath}.`;
11823
+ return `Timed out waiting for ${waitTarget} (${Math.round((launchSpec?.urlTimeoutMs ?? 30_000) / 1000)}s)${launchSuffix}. Check network/proxy settings and ${logPath}.`;
11690
11824
  }
11691
11825
 
11692
11826
  const summary = summarizeCloudflaredLog(logContent);
11827
+ const exitTarget = extractQuickTunnelUrl(logContent)
11828
+ ? 'before the tunnel became ready'
11829
+ : 'before producing a tunnel URL';
11693
11830
  if (summary) {
11694
- return `cloudflared exited before producing a tunnel URL${launchSuffix}.\nRecent stderr:\n${summary}`;
11831
+ return `cloudflared exited ${exitTarget}${launchSuffix}.\nRecent stderr:\n${summary}`;
11695
11832
  }
11696
11833
 
11697
- return `cloudflared exited before producing a tunnel URL${launchSuffix}. Check ${logPath}.`;
11834
+ return `cloudflared exited ${exitTarget}${launchSuffix}. Check ${logPath}.`;
11698
11835
  }
11699
11836
 
11700
11837
  function isProcessAlive(pid) {
@@ -11714,9 +11851,15 @@ function loadTunnelState({ tunnelStatePath }) {
11714
11851
  }
11715
11852
  }
11716
11853
 
11717
- function saveTunnelState({ tunnelStatePath, pid, url }) {
11854
+ function saveTunnelState({ tunnelStatePath, pid, url, managerId, originUrl }) {
11718
11855
  (0,external_node_fs_.mkdirSync)((0,external_node_path_.dirname)(tunnelStatePath), { recursive: true });
11719
- (0,external_node_fs_.writeFileSync)(tunnelStatePath, `${JSON.stringify({ pid, url }, null, 2)}\n`, 'utf8');
11856
+ const state = {
11857
+ pid,
11858
+ url,
11859
+ ...(managerId ? { managerId } : {}),
11860
+ ...(originUrl ? { originUrl } : {}),
11861
+ };
11862
+ (0,external_node_fs_.writeFileSync)(tunnelStatePath, `${JSON.stringify(state, null, 2)}\n`, 'utf8');
11720
11863
  }
11721
11864
 
11722
11865
  function clearTunnelState({ tunnelStatePath }) {
@@ -11735,6 +11878,147 @@ function stopTunnel({ tunnelStatePath, isAlive = isProcessAlive, killProcess = p
11735
11878
  clearTunnelState({ tunnelStatePath });
11736
11879
  }
11737
11880
 
11881
+ function waitForProcessExit(pid, { isAlive = isProcessAlive, timeoutMs = 5_000, pollIntervalMs = 50 } = {}) {
11882
+ const deadline = Date.now() + timeoutMs;
11883
+ const waitBuffer = new Int32Array(new SharedArrayBuffer(4));
11884
+ while (Date.now() < deadline) {
11885
+ if (!isAlive(pid)) return true;
11886
+ Atomics.wait(waitBuffer, 0, 0, Math.min(pollIntervalMs, Math.max(1, deadline - Date.now())));
11887
+ }
11888
+ return !isAlive(pid);
11889
+ }
11890
+
11891
+ function tunnelStateIdentityMatches(left, right) {
11892
+ if (!left || !right) return false;
11893
+ return (
11894
+ left.pid === right.pid &&
11895
+ left.url === right.url &&
11896
+ left.managerId === right.managerId &&
11897
+ normalizeOriginUrl(left.originUrl) === normalizeOriginUrl(right.originUrl)
11898
+ );
11899
+ }
11900
+
11901
+ /**
11902
+ * Stop a tracked Quick Tunnel only after both its persisted state and live
11903
+ * command line identify it as the same Vibelet-managed process. An unverifiable
11904
+ * live PID is deliberately left untouched so a Named Tunnel (or a reused PID)
11905
+ * can never be terminated from stale tunnel state.
11906
+ */
11907
+ function stopManagedQuickTunnel({
11908
+ tunnelStatePath,
11909
+ managerId,
11910
+ isAlive = isProcessAlive,
11911
+ getProcessCommandLine = readProcessCommandLine,
11912
+ killProcess = process.kill,
11913
+ waitForExit = waitForProcessExit,
11914
+ }) {
11915
+ const state = loadTunnelState({ tunnelStatePath });
11916
+ if (!state) return { status: 'absent', state: null };
11917
+ if (
11918
+ !Number.isInteger(state.pid) ||
11919
+ state.pid <= 0 ||
11920
+ !isQuickTunnelUrl(state.url) ||
11921
+ (state.managerId && state.managerId !== managerId)
11922
+ ) {
11923
+ return { status: 'not-managed-quick', state };
11924
+ }
11925
+
11926
+ if (!isAlive(state.pid)) {
11927
+ clearTunnelState({ tunnelStatePath });
11928
+ return { status: 'stale-state-cleared', state };
11929
+ }
11930
+
11931
+ // Legacy state did not record an owner or the exact origin. Even when its
11932
+ // PID currently belongs to a Quick Tunnel, that is not enough to prove it
11933
+ // is the same process (the PID may have been reused). Leave live legacy
11934
+ // state untouched and require a newly written, fully identified state file
11935
+ // before allowing any signal.
11936
+ if (!state.managerId || !normalizeOriginUrl(state.originUrl)) {
11937
+ return { status: 'identity-unverified', state };
11938
+ }
11939
+
11940
+ let commandLine = '';
11941
+ try {
11942
+ commandLine = getProcessCommandLine(state.pid);
11943
+ } catch {
11944
+ // Process inspection is best-effort. Never kill when identity cannot be
11945
+ // proven.
11946
+ }
11947
+ if (!isQuickTunnelProcessCommand(commandLine, { originUrl: state.originUrl || '' })) {
11948
+ return { status: 'identity-unverified', state };
11949
+ }
11950
+
11951
+ try {
11952
+ killProcess(state.pid, 'SIGTERM');
11953
+ } catch {
11954
+ if (isAlive(state.pid)) {
11955
+ return { status: 'stop-failed', state };
11956
+ }
11957
+ }
11958
+
11959
+ let exited = false;
11960
+ try {
11961
+ exited = waitForExit(state.pid, { isAlive });
11962
+ } catch {
11963
+ // A failed exit check can never justify clearing process state.
11964
+ }
11965
+ if (!exited) {
11966
+ return { status: 'stop-failed', state };
11967
+ }
11968
+
11969
+ const latestState = loadTunnelState({ tunnelStatePath });
11970
+ if (latestState && !tunnelStateIdentityMatches(latestState, state)) {
11971
+ // Another manager invocation replaced the state while the old process was
11972
+ // draining. Never erase the new process identity.
11973
+ return { status: 'stop-failed', state };
11974
+ }
11975
+ clearTunnelState({ tunnelStatePath });
11976
+ return { status: 'stopped', state };
11977
+ }
11978
+
11979
+ /**
11980
+ * Return a reusable Quick Tunnel only when its persisted owner/origin, live
11981
+ * command line, and public health endpoint all identify the same working
11982
+ * process. PID liveness alone is never sufficient because PIDs can be reused
11983
+ * by a Named Tunnel or an unrelated process.
11984
+ */
11985
+ async function getAliveManagedQuickTunnel({
11986
+ tunnelStatePath,
11987
+ managerId,
11988
+ isAlive = isProcessAlive,
11989
+ getProcessCommandLine = readProcessCommandLine,
11990
+ readinessProbe = probeQuickTunnelPublicHealth,
11991
+ }) {
11992
+ const state = loadTunnelState({ tunnelStatePath });
11993
+ if (
11994
+ !state ||
11995
+ !Number.isInteger(state.pid) ||
11996
+ state.pid <= 0 ||
11997
+ !isQuickTunnelUrl(state.url) ||
11998
+ state.managerId !== managerId ||
11999
+ !normalizeOriginUrl(state.originUrl) ||
12000
+ !isAlive(state.pid)
12001
+ ) {
12002
+ return null;
12003
+ }
12004
+
12005
+ let commandLine = '';
12006
+ try {
12007
+ commandLine = getProcessCommandLine(state.pid);
12008
+ } catch {
12009
+ return null;
12010
+ }
12011
+ if (!isQuickTunnelProcessCommand(commandLine, { originUrl: state.originUrl })) {
12012
+ return null;
12013
+ }
12014
+
12015
+ try {
12016
+ return (await readinessProbe(state)) ? state : null;
12017
+ } catch {
12018
+ return null;
12019
+ }
12020
+ }
12021
+
11738
12022
  function getAliveTunnel({ tunnelStatePath, isAlive = isProcessAlive }) {
11739
12023
  const state = loadTunnelState({ tunnelStatePath });
11740
12024
  if (state?.pid && state?.url && isAlive(state.pid)) {
@@ -11759,6 +12043,8 @@ async function startTunnel({
11759
12043
  updateStatePath,
11760
12044
  resolveLaunchSpec = resolvePreferredCloudflaredLaunchSpec,
11761
12045
  resolveFallbackLaunchSpec = resolveCloudflaredLaunchSpec,
12046
+ readinessProbe = probeCloudflaredReadiness,
12047
+ managerId,
11762
12048
  }) {
11763
12049
  if (!['auto', 'quic', 'http2'].includes(protocol)) {
11764
12050
  throw new Error(`Unsupported cloudflared protocol: ${protocol}`);
@@ -11800,10 +12086,11 @@ async function startTunnel({
11800
12086
  const stdoutLogFd = (0,external_node_fs_.openSync)(logPath, 'a');
11801
12087
  const stderrLogFd = (0,external_node_fs_.openSync)(logPath, 'a');
11802
12088
  let child;
12089
+ const originUrl = `http://localhost:${port}`;
11803
12090
  try {
11804
12091
  child = (0,external_node_child_process_.spawn)(
11805
12092
  launchSpec.command,
11806
- [...launchSpec.args, 'tunnel', '--protocol', effectiveProtocol, '--url', `http://localhost:${port}`],
12093
+ [...launchSpec.args, 'tunnel', '--no-autoupdate', '--protocol', effectiveProtocol, '--url', originUrl],
11807
12094
  {
11808
12095
  detached: true,
11809
12096
  stdio: ['ignore', stdoutLogFd, stderrLogFd],
@@ -11851,6 +12138,7 @@ async function startTunnel({
11851
12138
  let url = null;
11852
12139
  let settled = false;
11853
12140
  let childExited = false;
12141
+ let readinessProbeInFlight = false;
11854
12142
  let poll;
11855
12143
  let timeout;
11856
12144
 
@@ -11865,35 +12153,43 @@ async function startTunnel({
11865
12153
  }
11866
12154
 
11867
12155
  timeout = setTimeout(() => {
11868
- if (!url) {
11869
- try {
11870
- process.kill(pid, 'SIGTERM');
11871
- } catch {
11872
- // ignore
11873
- }
11874
- settle(
11875
- rejectStart,
11876
- new Error(
11877
- formatCloudflaredFailureMessage({
11878
- launchSpec,
11879
- logContent: readCloudflaredLog(logPath),
11880
- logPath,
11881
- phase: 'timeout',
11882
- }),
11883
- ),
11884
- );
12156
+ try {
12157
+ process.kill(pid, 'SIGTERM');
12158
+ } catch {
12159
+ // ignore
11885
12160
  }
12161
+ settle(
12162
+ rejectStart,
12163
+ new Error(
12164
+ formatCloudflaredFailureMessage({
12165
+ launchSpec,
12166
+ logContent: readCloudflaredLog(logPath),
12167
+ logPath,
12168
+ phase: 'timeout',
12169
+ }),
12170
+ ),
12171
+ );
11886
12172
  }, launchSpec.urlTimeoutMs);
11887
12173
 
11888
- poll = setInterval(() => {
12174
+ poll = setInterval(async () => {
12175
+ if (readinessProbeInFlight || settled) return;
11889
12176
  try {
11890
12177
  const content = (0,external_node_fs_.readFileSync)(logPath, 'utf8');
11891
12178
  const tunnelUrl = extractQuickTunnelUrl(content);
11892
12179
  if (tunnelUrl) {
11893
12180
  url = tunnelUrl;
11894
- saveTunnelState({ tunnelStatePath, pid, url });
11895
- settle(resolveStart, { pid, url });
11896
- return;
12181
+ readinessProbeInFlight = true;
12182
+ let ready = false;
12183
+ try {
12184
+ ready = await readinessProbe(content);
12185
+ } finally {
12186
+ readinessProbeInFlight = false;
12187
+ }
12188
+ if (ready && !settled) {
12189
+ saveTunnelState({ tunnelStatePath, pid, url, managerId, originUrl: managerId ? originUrl : undefined });
12190
+ settle(resolveStart, { pid, url });
12191
+ return;
12192
+ }
11897
12193
  }
11898
12194
  if (childExited) {
11899
12195
  settle(
@@ -11915,15 +12211,8 @@ async function startTunnel({
11915
12211
 
11916
12212
  child.once('exit', () => {
11917
12213
  childExited = true;
11918
- if (!url) {
12214
+ if (!settled) {
11919
12215
  const content = readCloudflaredLog(logPath);
11920
- const tunnelUrl = extractQuickTunnelUrl(content);
11921
- if (tunnelUrl) {
11922
- url = tunnelUrl;
11923
- saveTunnelState({ tunnelStatePath, pid, url });
11924
- settle(resolveStart, { pid, url });
11925
- return;
11926
- }
11927
12216
  settle(
11928
12217
  rejectStart,
11929
12218
  new Error(
@@ -13535,6 +13824,174 @@ function parseAccessTargetArg(argv, fail, env = process.env) {
13535
13824
  }
13536
13825
 
13537
13826
 
13827
+ /***/ }),
13828
+
13829
+ /***/ 3869:
13830
+ /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __nccwpck_require__) => {
13831
+
13832
+ /* harmony export */ __nccwpck_require__.d(__webpack_exports__, {
13833
+ /* harmony export */ BT: () => (/* binding */ tryUpdateRunningDaemonRelay),
13834
+ /* harmony export */ T7: () => (/* binding */ createRelayConfigStore),
13835
+ /* harmony export */ kS: () => (/* binding */ prepareManagedQuickTunnel),
13836
+ /* harmony export */ zj: () => (/* binding */ retireTrackedQuickTunnel)
13837
+ /* harmony export */ });
13838
+ /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __nccwpck_require__(3024);
13839
+ /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1__ = __nccwpck_require__(6760);
13840
+ /* harmony import */ var _cloudflared_tunnel_manager_mjs__WEBPACK_IMPORTED_MODULE_2__ = __nccwpck_require__(2755);
13841
+
13842
+
13843
+
13844
+
13845
+ const TUNNEL_MANAGER_ID = 'vibelet-cli';
13846
+
13847
+ function createRelayConfigStore(relayConfigPath) {
13848
+ return {
13849
+ load() {
13850
+ try {
13851
+ const data = JSON.parse((0,node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync)(relayConfigPath, 'utf8'));
13852
+ return data.relayUrl || '';
13853
+ } catch {
13854
+ return '';
13855
+ }
13856
+ },
13857
+ save(relayUrl) {
13858
+ (0,node_fs__WEBPACK_IMPORTED_MODULE_0__.mkdirSync)((0,node_path__WEBPACK_IMPORTED_MODULE_1__.dirname)(relayConfigPath), { recursive: true });
13859
+ (0,node_fs__WEBPACK_IMPORTED_MODULE_0__.writeFileSync)(relayConfigPath, `${JSON.stringify({ relayUrl }, null, 2)}\n`, 'utf8');
13860
+ },
13861
+ clear() {
13862
+ (0,node_fs__WEBPACK_IMPORTED_MODULE_0__.rmSync)(relayConfigPath, { force: true });
13863
+ },
13864
+ };
13865
+ }
13866
+
13867
+ function retireTrackedQuickTunnel({
13868
+ tunnelStatePath,
13869
+ isAlive,
13870
+ required = false,
13871
+ fail,
13872
+ writeStdout = (message) => process.stdout.write(message),
13873
+ writeStderr = (message) => process.stderr.write(message),
13874
+ }) {
13875
+ const retirement = (0,_cloudflared_tunnel_manager_mjs__WEBPACK_IMPORTED_MODULE_2__/* .stopManagedQuickTunnel */ .HW)({
13876
+ tunnelStatePath,
13877
+ isAlive,
13878
+ managerId: TUNNEL_MANAGER_ID,
13879
+ });
13880
+ if (retirement.status === 'stopped') {
13881
+ writeStdout(`Stopped obsolete managed Quick Tunnel: ${retirement.state.url} (pid ${retirement.state.pid}).\n`);
13882
+ } else if (retirement.status === 'stale-state-cleared') {
13883
+ writeStdout('Cleared stale managed Quick Tunnel state.\n');
13884
+ } else if (
13885
+ retirement.status === 'identity-unverified' ||
13886
+ retirement.status === 'stop-failed' ||
13887
+ retirement.status === 'not-managed-quick'
13888
+ ) {
13889
+ const stateSummary = retirement.state
13890
+ ? `${retirement.state.url || 'unknown URL'} (pid ${retirement.state.pid || 'unknown'})`
13891
+ : 'unknown tracked process';
13892
+ const message =
13893
+ retirement.status === 'stop-failed'
13894
+ ? `Tracked Quick Tunnel ${stateSummary} did not exit after a verified stop request; its state was preserved.`
13895
+ : `Tracked tunnel ${stateSummary} was left running because its Vibelet Quick Tunnel identity could not be safely verified.`;
13896
+ if (required) {
13897
+ fail(
13898
+ 'Refusing to replace an unverified tracked tunnel.',
13899
+ `${message}\nInspect the tracked process and tunnel.json before retrying --force.`,
13900
+ );
13901
+ } else {
13902
+ writeStderr(`${message}\n`);
13903
+ }
13904
+ }
13905
+ return retirement;
13906
+ }
13907
+
13908
+ async function prepareManagedQuickTunnel({
13909
+ force,
13910
+ port,
13911
+ logDir,
13912
+ tunnelStatePath,
13913
+ protocol,
13914
+ updateStatePath,
13915
+ isAlive,
13916
+ fail,
13917
+ writeStdout = (message) => process.stdout.write(message),
13918
+ writeStderr = (message) => process.stderr.write(message),
13919
+ }) {
13920
+ const existing = force
13921
+ ? null
13922
+ : await (0,_cloudflared_tunnel_manager_mjs__WEBPACK_IMPORTED_MODULE_2__/* .getAliveManagedQuickTunnel */ .M)({
13923
+ tunnelStatePath,
13924
+ isAlive,
13925
+ managerId: TUNNEL_MANAGER_ID,
13926
+ });
13927
+ if (existing) {
13928
+ writeStdout(`Reusing tunnel: ${existing.url} (pid ${existing.pid})\n`);
13929
+ return { failed: false, relayUrl: existing.url };
13930
+ }
13931
+
13932
+ // A non-ready tracked Quick Tunnel must be retired with the same strict
13933
+ // identity proof as --force before its state can be replaced.
13934
+ retireTrackedQuickTunnel({
13935
+ tunnelStatePath,
13936
+ isAlive,
13937
+ required: true,
13938
+ fail,
13939
+ writeStdout,
13940
+ writeStderr,
13941
+ });
13942
+ writeStdout('Starting Cloudflare Tunnel...\n');
13943
+ try {
13944
+ const tunnel = await (0,_cloudflared_tunnel_manager_mjs__WEBPACK_IMPORTED_MODULE_2__/* .startTunnel */ .DH)({
13945
+ port,
13946
+ logDir,
13947
+ tunnelStatePath,
13948
+ protocol,
13949
+ updateStatePath,
13950
+ managerId: TUNNEL_MANAGER_ID,
13951
+ });
13952
+ writeStdout(`Tunnel ready: ${tunnel.url}\n`);
13953
+ return { failed: false, relayUrl: tunnel.url };
13954
+ } catch (error) {
13955
+ (0,_cloudflared_tunnel_manager_mjs__WEBPACK_IMPORTED_MODULE_2__/* .clearTunnelState */ .vD)({ tunnelStatePath });
13956
+ const message = error instanceof Error ? error.message : String(error);
13957
+ writeStderr(`${message}\n`);
13958
+ writeStderr(
13959
+ 'Continuing with LAN/local pairing. Use `npx vibelet --access=remote --force` after fixing Cloudflare Tunnel, or pass `--access=https://<url>` for a custom tunnel.\n',
13960
+ );
13961
+ return { failed: true, relayUrl: '' };
13962
+ }
13963
+ }
13964
+
13965
+ async function tryUpdateRunningDaemonRelay({
13966
+ port,
13967
+ relayUrl,
13968
+ probeHealth,
13969
+ fetchFn = globalThis.fetch,
13970
+ writeStderr = (message) => process.stderr.write(message),
13971
+ }) {
13972
+ try {
13973
+ const response = await fetchFn(`http://127.0.0.1:${port}/connection/relay`, {
13974
+ method: 'POST',
13975
+ headers: { 'Content-Type': 'application/json' },
13976
+ body: JSON.stringify({ relayUrl }),
13977
+ signal: AbortSignal.timeout(2_000),
13978
+ });
13979
+ if (!response.ok) {
13980
+ if (response.status !== 404) {
13981
+ const payload = await response.text().catch(() => '');
13982
+ writeStderr(
13983
+ `Running daemon declined live relay refresh (${response.status})${payload ? `: ${payload.trim()}` : ''}.\n`,
13984
+ );
13985
+ }
13986
+ return null;
13987
+ }
13988
+ return await probeHealth(1_500);
13989
+ } catch {
13990
+ return null;
13991
+ }
13992
+ }
13993
+
13994
+
13538
13995
  /***/ }),
13539
13996
 
13540
13997
  /***/ 1010:
@@ -13612,6 +14069,7 @@ function describeLaunchctlResult(result) {
13612
14069
  /* harmony export */ SF: () => (/* binding */ selectWebUiConnectionTarget),
13613
14070
  /* harmony export */ YP: () => (/* binding */ createCompactPairingPayload),
13614
14071
  /* harmony export */ dE: () => (/* binding */ findUnsafeCleartextHosts),
14072
+ /* harmony export */ mp: () => (/* binding */ applyRelayUrlToPairingPayload),
13615
14073
  /* harmony export */ uR: () => (/* binding */ formatConnectionSummary)
13616
14074
  /* harmony export */ });
13617
14075
  /* unused harmony exports normalizeHostValue, isIpv4Host, isTailscaleHost, isSharedIpv4Host, isLocalNetworkHost, isLoopbackHost, isPlainLocalHostname, isTrustedCleartextHost, parseCommaSeparatedHosts, isQuickTunnelHost, buildLegacyConnection, formatConnectionSourceLabel, buildConnectionHttpUrl, isPreferredWebUiHost, encodePairingPayloadForUrlFragment */
@@ -13779,6 +14237,65 @@ function normalizePairingConnections(pairingPayload) {
13779
14237
  ]);
13780
14238
  }
13781
14239
 
14240
+ /**
14241
+ * Overlay a freshly selected relay on a pairing payload without requiring the
14242
+ * daemon process that issued the nonce to restart. This keeps active sessions
14243
+ * alive while still propagating a rotated Quick Tunnel URL through the QR/code.
14244
+ * Stable direct paths are retained as fallbacks; obsolete relay paths are
14245
+ * removed.
14246
+ */
14247
+ function applyRelayUrlToPairingPayload(pairingPayload, relayUrl) {
14248
+ const connections = normalizePairingConnections(pairingPayload);
14249
+ const directConnections = connections.filter((target) => target.kind !== 'relay');
14250
+ const normalizedRelayUrl = typeof relayUrl === 'string' ? relayUrl.trim() : '';
14251
+
14252
+ if (!normalizedRelayUrl) {
14253
+ const [preferredDirect, ...fallbackDirect] = directConnections;
14254
+ if (!preferredDirect) {
14255
+ return { ...pairingPayload, connections: [] };
14256
+ }
14257
+ return {
14258
+ ...pairingPayload,
14259
+ canonicalHost: preferredDirect.host,
14260
+ port: preferredDirect.port,
14261
+ fallbackHosts: fallbackDirect
14262
+ .filter((target) => target.port === preferredDirect.port)
14263
+ .map((target) => target.host),
14264
+ connections: directConnections,
14265
+ };
14266
+ }
14267
+
14268
+ let parsed;
14269
+ try {
14270
+ parsed = new URL(normalizedRelayUrl);
14271
+ } catch {
14272
+ return pairingPayload;
14273
+ }
14274
+ const secure = parsed.protocol === 'https:';
14275
+ const relayPort = parsed.port ? Number(parsed.port) : secure ? 443 : 80;
14276
+ if (!parsed.hostname || !Number.isInteger(relayPort) || relayPort <= 0 || relayPort > 65_535) {
14277
+ return pairingPayload;
14278
+ }
14279
+ const relayHost = normalizeHostValue(parsed.hostname);
14280
+ const quickTunnel = isQuickTunnelHost(relayHost);
14281
+ const relayConnection = {
14282
+ kind: 'relay',
14283
+ host: relayHost,
14284
+ port: relayPort,
14285
+ ...(secure ? { secure: true } : {}),
14286
+ source: quickTunnel ? 'quick_tunnel' : 'configured_relay',
14287
+ stability: quickTunnel ? 'ephemeral' : 'stable',
14288
+ };
14289
+
14290
+ return {
14291
+ ...pairingPayload,
14292
+ canonicalHost: relayHost,
14293
+ port: relayPort,
14294
+ fallbackHosts: undefined,
14295
+ connections: [relayConnection, ...directConnections],
14296
+ };
14297
+ }
14298
+
13782
14299
  function createCompactPairingPayload(pairingPayload) {
13783
14300
  const connections = normalizePairingConnections(pairingPayload);
13784
14301
  const compactPayload = {
@@ -13880,10 +14397,13 @@ function buildWebUiUrl(target, pairingPayload) {
13880
14397
 
13881
14398
  /* harmony export */ __nccwpck_require__.d(__webpack_exports__, {
13882
14399
  /* harmony export */ Ne: () => (/* binding */ shouldRefuseRuntimeDowngrade),
14400
+ /* harmony export */ Pg: () => (/* binding */ shouldRetireManagedQuickTunnel),
13883
14401
  /* harmony export */ ZN: () => (/* binding */ shouldReuseHealthyDaemon),
14402
+ /* harmony export */ gm: () => (/* binding */ shouldManageQuickTunnel),
13884
14403
  /* harmony export */ iX: () => (/* binding */ doesHealthSupportTerminalControl),
13885
14404
  /* harmony export */ nW: () => (/* binding */ doesHealthMatchRequestedConnectionConfig)
13886
14405
  /* harmony export */ });
14406
+ /* unused harmony export isQuickTunnelRelayUrl */
13887
14407
  function compareSemverTriple(a, b) {
13888
14408
  const pa = String(a)
13889
14409
  .split('.')
@@ -13913,10 +14433,15 @@ function shouldRefuseRuntimeDowngrade({ cliVersion, runtimeVersion, runtimeEntry
13913
14433
  return compareSemverTriple(cliVersion, runtimeVersion) < 0;
13914
14434
  }
13915
14435
 
13916
- function shouldReuseHealthyDaemon({ command, daemonHealthy, hasExplicitConfigOverrides }) {
14436
+ function shouldReuseHealthyDaemon({
14437
+ command,
14438
+ daemonHealthy,
14439
+ hasExplicitConfigOverrides,
14440
+ canApplyConnectionConfigWithoutRestart = false,
14441
+ }) {
13917
14442
  if (!daemonHealthy) return false;
13918
14443
  if (command !== 'default' && command !== 'start') return false;
13919
- return !hasExplicitConfigOverrides;
14444
+ return !hasExplicitConfigOverrides || canApplyConnectionConfigWithoutRestart;
13920
14445
  }
13921
14446
 
13922
14447
  function doesHealthSupportTerminalControl(health) {
@@ -13947,6 +14472,41 @@ function isQuickTunnelHost(host) {
13947
14472
  return normalizeHostValue(host.replace(/^https?:\/\//, '').replace(/\/.*$/, '')).endsWith('.trycloudflare.com');
13948
14473
  }
13949
14474
 
14475
+ function isQuickTunnelRelayUrl(relayUrl) {
14476
+ if (typeof relayUrl !== 'string' || !relayUrl.trim()) return false;
14477
+ try {
14478
+ return normalizeHostValue(new URL(relayUrl).hostname).endsWith('.trycloudflare.com');
14479
+ } catch {
14480
+ return isQuickTunnelHost(relayUrl);
14481
+ }
14482
+ }
14483
+
14484
+ /**
14485
+ * A saved stable relay is an explicit connection choice and must survive an
14486
+ * ordinary start/restart. Quick Tunnel URLs remain managed because their
14487
+ * process and hostname are ephemeral, while --force explicitly requests a new
14488
+ * managed tunnel.
14489
+ */
14490
+ function shouldManageQuickTunnel({
14491
+ startCommand,
14492
+ localMode = false,
14493
+ relayArg = null,
14494
+ hostArg = null,
14495
+ fallbackHostsArg = null,
14496
+ savedRelayUrl = '',
14497
+ force = false,
14498
+ }) {
14499
+ if (!startCommand || localMode || relayArg !== null || hostArg || fallbackHostsArg) {
14500
+ return false;
14501
+ }
14502
+ if (force) return true;
14503
+ return !savedRelayUrl || isQuickTunnelRelayUrl(savedRelayUrl);
14504
+ }
14505
+
14506
+ function shouldRetireManagedQuickTunnel({ startCommand, manageQuickTunnel }) {
14507
+ return Boolean(startCommand && !manageQuickTunnel);
14508
+ }
14509
+
13950
14510
  function hasRemoteAdvertisementInLocalMode(health) {
13951
14511
  const hosts = [
13952
14512
  typeof health.canonicalHost === 'string' ? health.canonicalHost : '',
@@ -14058,14 +14618,16 @@ __nccwpck_require__.a(__webpack_module__, async (__webpack_handle_async_dependen
14058
14618
  /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_4__ = __nccwpck_require__(6760);
14059
14619
  /* harmony import */ var node_url__WEBPACK_IMPORTED_MODULE_5__ = __nccwpck_require__(3136);
14060
14620
  /* harmony import */ var qrcode__WEBPACK_IMPORTED_MODULE_6__ = __nccwpck_require__(7514);
14061
- /* harmony import */ var _vibelet_cli_args_mjs__WEBPACK_IMPORTED_MODULE_14__ = __nccwpck_require__(6927);
14621
+ /* harmony import */ var _vibelet_cli_args_mjs__WEBPACK_IMPORTED_MODULE_15__ = __nccwpck_require__(6927);
14062
14622
  /* harmony import */ var _cloudflared_tunnel_manager_mjs__WEBPACK_IMPORTED_MODULE_7__ = __nccwpck_require__(2755);
14063
- /* harmony import */ var _vibelet_launchd_mjs__WEBPACK_IMPORTED_MODULE_11__ = __nccwpck_require__(1010);
14064
- /* harmony import */ var _linux_systemd_mjs__WEBPACK_IMPORTED_MODULE_8__ = __nccwpck_require__(4217);
14065
- /* harmony import */ var _vibelet_stop_logic_mjs__WEBPACK_IMPORTED_MODULE_12__ = __nccwpck_require__(6274);
14066
- /* harmony import */ var _vibelet_runtime_policy_mjs__WEBPACK_IMPORTED_MODULE_10__ = __nccwpck_require__(4);
14067
- /* harmony import */ var _terminal_wrapper_mjs__WEBPACK_IMPORTED_MODULE_9__ = __nccwpck_require__(1427);
14068
- /* harmony import */ var _vibelet_pairing_connections_mjs__WEBPACK_IMPORTED_MODULE_13__ = __nccwpck_require__(7721);
14623
+ /* harmony import */ var _vibelet_cloudflare_runtime_mjs__WEBPACK_IMPORTED_MODULE_8__ = __nccwpck_require__(3869);
14624
+ /* harmony import */ var _vibelet_launchd_mjs__WEBPACK_IMPORTED_MODULE_12__ = __nccwpck_require__(1010);
14625
+ /* harmony import */ var _linux_systemd_mjs__WEBPACK_IMPORTED_MODULE_9__ = __nccwpck_require__(4217);
14626
+ /* harmony import */ var _vibelet_stop_logic_mjs__WEBPACK_IMPORTED_MODULE_13__ = __nccwpck_require__(6274);
14627
+ /* harmony import */ var _vibelet_runtime_policy_mjs__WEBPACK_IMPORTED_MODULE_11__ = __nccwpck_require__(4);
14628
+ /* harmony import */ var _terminal_wrapper_mjs__WEBPACK_IMPORTED_MODULE_10__ = __nccwpck_require__(1427);
14629
+ /* harmony import */ var _vibelet_pairing_connections_mjs__WEBPACK_IMPORTED_MODULE_14__ = __nccwpck_require__(7721);
14630
+
14069
14631
 
14070
14632
 
14071
14633
 
@@ -14097,11 +14659,11 @@ const runtimeDir = (0,node_path__WEBPACK_IMPORTED_MODULE_4__.join)(vibeletDir, '
14097
14659
  const runtimeCurrentDir = (0,node_path__WEBPACK_IMPORTED_MODULE_4__.join)(runtimeDir, 'current');
14098
14660
  const runtimeMetadataPath = (0,node_path__WEBPACK_IMPORTED_MODULE_4__.join)(runtimeCurrentDir, 'runtime.json');
14099
14661
  const runtimeDaemonEntryPath = (0,node_path__WEBPACK_IMPORTED_MODULE_4__.join)(runtimeCurrentDir, 'dist', 'index.cjs');
14100
- const runtimeTerminalControlTokenPath = _terminal_wrapper_mjs__WEBPACK_IMPORTED_MODULE_9__/* .terminalControlTokenPath */ .gJ;
14662
+ const runtimeTerminalControlTokenPath = _terminal_wrapper_mjs__WEBPACK_IMPORTED_MODULE_10__/* .terminalControlTokenPath */ .gJ;
14101
14663
  const stdoutLogPath = (0,node_path__WEBPACK_IMPORTED_MODULE_4__.join)(logDir, 'daemon.stdout.log');
14102
14664
  const stderrLogPath = (0,node_path__WEBPACK_IMPORTED_MODULE_4__.join)(logDir, 'daemon.stderr.log');
14103
14665
  const pidFilePath = (0,node_path__WEBPACK_IMPORTED_MODULE_4__.join)(vibeletDir, 'daemon.pid');
14104
- const relayConfigPath = (0,node_path__WEBPACK_IMPORTED_MODULE_4__.join)(vibeletDir, 'relay.json');
14666
+ const relayConfig = (0,_vibelet_cloudflare_runtime_mjs__WEBPACK_IMPORTED_MODULE_8__/* .createRelayConfigStore */ .T7)((0,node_path__WEBPACK_IMPORTED_MODULE_4__.join)(vibeletDir, 'relay.json'));
14105
14667
  const tunnelStatePath = (0,node_path__WEBPACK_IMPORTED_MODULE_4__.join)(vibeletDir, 'tunnel.json');
14106
14668
  const cloudflaredUpdateStatePath = (0,node_path__WEBPACK_IMPORTED_MODULE_4__.join)(vibeletDir, 'cloudflared-update.json');
14107
14669
  const updateCheckPath = (0,node_path__WEBPACK_IMPORTED_MODULE_4__.join)(vibeletDir, 'update-check.json');
@@ -14254,7 +14816,7 @@ function ensureRuntimeInstalled() {
14254
14816
  const runtimeMetadata = readRuntimeMetadata();
14255
14817
 
14256
14818
  if (
14257
- (0,_vibelet_runtime_policy_mjs__WEBPACK_IMPORTED_MODULE_10__/* .shouldRefuseRuntimeDowngrade */ .Ne)({
14819
+ (0,_vibelet_runtime_policy_mjs__WEBPACK_IMPORTED_MODULE_11__/* .shouldRefuseRuntimeDowngrade */ .Ne)({
14258
14820
  cliVersion: packageJson.version,
14259
14821
  runtimeVersion: runtimeMetadata?.version,
14260
14822
  runtimeEntryExists: (0,node_fs__WEBPACK_IMPORTED_MODULE_1__.existsSync)(runtimeDaemonEntryPath),
@@ -14458,7 +15020,7 @@ function createDarwinBackend() {
14458
15020
  }
14459
15021
 
14460
15022
  function bootoutService() {
14461
- return (0,_vibelet_launchd_mjs__WEBPACK_IMPORTED_MODULE_11__/* .bootoutLaunchdService */ .B)({
15023
+ return (0,_vibelet_launchd_mjs__WEBPACK_IMPORTED_MODULE_12__/* .bootoutLaunchdService */ .B)({
14462
15024
  launchctl,
14463
15025
  launchDomain,
14464
15026
  label,
@@ -14547,7 +15109,7 @@ ${envSection}
14547
15109
  if (result.status !== 0) {
14548
15110
  fail(
14549
15111
  'Failed to bootstrap vibelet launch agent.',
14550
- [(0,_vibelet_launchd_mjs__WEBPACK_IMPORTED_MODULE_11__/* .describeLaunchctlResult */ .N)(bootoutResult.result), (0,_vibelet_launchd_mjs__WEBPACK_IMPORTED_MODULE_11__/* .describeLaunchctlResult */ .N)(result)].filter(Boolean).join('\n'),
15112
+ [(0,_vibelet_launchd_mjs__WEBPACK_IMPORTED_MODULE_12__/* .describeLaunchctlResult */ .N)(bootoutResult.result), (0,_vibelet_launchd_mjs__WEBPACK_IMPORTED_MODULE_12__/* .describeLaunchctlResult */ .N)(result)].filter(Boolean).join('\n'),
14551
15113
  );
14552
15114
  }
14553
15115
  }
@@ -14563,7 +15125,7 @@ ${envSection}
14563
15125
  stop() {
14564
15126
  const bootoutResult = bootoutService();
14565
15127
  if (!bootoutResult.ok) {
14566
- fail('Failed to unload vibelet launch agent.', (0,_vibelet_launchd_mjs__WEBPACK_IMPORTED_MODULE_11__/* .describeLaunchctlResult */ .N)(bootoutResult.result));
15128
+ fail('Failed to unload vibelet launch agent.', (0,_vibelet_launchd_mjs__WEBPACK_IMPORTED_MODULE_12__/* .describeLaunchctlResult */ .N)(bootoutResult.result));
14567
15129
  }
14568
15130
  // bootout returns before launchd actually finishes unloading. Poll
14569
15131
  // until the service is gone so a follow-up install() doesn't see a
@@ -14594,10 +15156,10 @@ function createLinuxBackend() {
14594
15156
  return [result?.stderr, result?.stdout].filter(Boolean).join('\n').trim();
14595
15157
  }
14596
15158
 
14597
- let useSystemd = (0,_linux_systemd_mjs__WEBPACK_IMPORTED_MODULE_8__/* .canUseSystemdUserManager */ .X)();
15159
+ let useSystemd = (0,_linux_systemd_mjs__WEBPACK_IMPORTED_MODULE_9__/* .canUseSystemdUserManager */ .X)();
14598
15160
 
14599
15161
  function demoteToDetachedIfSystemdUnavailable(result) {
14600
- if (!(0,_linux_systemd_mjs__WEBPACK_IMPORTED_MODULE_8__/* .isSystemdUserManagerUnavailable */ .a)(resultOutput(result))) {
15162
+ if (!(0,_linux_systemd_mjs__WEBPACK_IMPORTED_MODULE_9__/* .isSystemdUserManagerUnavailable */ .a)(resultOutput(result))) {
14601
15163
  return false;
14602
15164
  }
14603
15165
  useSystemd = false;
@@ -14896,9 +15458,18 @@ function isDaemonStartCommand(command) {
14896
15458
  return command === 'default' || command === 'start' || command === 'restart' || command === 'reset';
14897
15459
  }
14898
15460
 
15461
+ function retireTrackedQuickTunnel({ required = false } = {}) {
15462
+ return (0,_vibelet_cloudflare_runtime_mjs__WEBPACK_IMPORTED_MODULE_8__/* .retireTrackedQuickTunnel */ .zj)({
15463
+ tunnelStatePath,
15464
+ isAlive: isProcessAlive,
15465
+ required,
15466
+ fail,
15467
+ });
15468
+ }
15469
+
14899
15470
  async function stopRunningDaemon(backend) {
14900
15471
  try {
14901
- await (0,_vibelet_stop_logic_mjs__WEBPACK_IMPORTED_MODULE_12__/* .stopDaemonWithHooks */ .v)({
15472
+ await (0,_vibelet_stop_logic_mjs__WEBPACK_IMPORTED_MODULE_13__/* .stopDaemonWithHooks */ .v)({
14902
15473
  requestShutdown,
14903
15474
  backendStop: () => backend.stop(),
14904
15475
  waitForDaemonExit,
@@ -14911,7 +15482,7 @@ async function stopRunningDaemon(backend) {
14911
15482
  async function ensureDaemonForTerminalControl(backend) {
14912
15483
  const healthyDaemon = await probeHealth(1_500);
14913
15484
  const tokenReady = (0,node_fs__WEBPACK_IMPORTED_MODULE_1__.existsSync)(runtimeTerminalControlTokenPath);
14914
- const terminalControlReady = healthyDaemon && tokenReady && (0,_vibelet_runtime_policy_mjs__WEBPACK_IMPORTED_MODULE_10__/* .doesHealthSupportTerminalControl */ .iX)(healthyDaemon);
15485
+ const terminalControlReady = healthyDaemon && tokenReady && (0,_vibelet_runtime_policy_mjs__WEBPACK_IMPORTED_MODULE_11__/* .doesHealthSupportTerminalControl */ .iX)(healthyDaemon);
14915
15486
  if (terminalControlReady) {
14916
15487
  return healthyDaemon;
14917
15488
  }
@@ -14919,7 +15490,7 @@ async function ensureDaemonForTerminalControl(backend) {
14919
15490
  if (healthyDaemon && !terminalControlReady) {
14920
15491
  const activeSessions = typeof healthyDaemon.activeSessions === 'number' ? healthyDaemon.activeSessions : 0;
14921
15492
  if (activeSessions > 0) {
14922
- const reason = (0,_vibelet_runtime_policy_mjs__WEBPACK_IMPORTED_MODULE_10__/* .doesHealthSupportTerminalControl */ .iX)(healthyDaemon)
15493
+ const reason = (0,_vibelet_runtime_policy_mjs__WEBPACK_IMPORTED_MODULE_11__/* .doesHealthSupportTerminalControl */ .iX)(healthyDaemon)
14923
15494
  ? 'terminal-control token is missing'
14924
15495
  : 'the running daemon does not advertise terminal-control support';
14925
15496
  process.stderr.write(
@@ -14938,7 +15509,7 @@ async function ensureDaemonForTerminalControl(backend) {
14938
15509
  }
14939
15510
 
14940
15511
  function validateExplicitCleartextHosts({ hostArg, fallbackHostsArg }) {
14941
- const unsafeHosts = (0,_vibelet_pairing_connections_mjs__WEBPACK_IMPORTED_MODULE_13__/* .findUnsafeCleartextHosts */ .dE)({ hostArg, fallbackHostsArg });
15512
+ const unsafeHosts = (0,_vibelet_pairing_connections_mjs__WEBPACK_IMPORTED_MODULE_14__/* .findUnsafeCleartextHosts */ .dE)({ hostArg, fallbackHostsArg });
14942
15513
  if (unsafeHosts.length === 0) {
14943
15514
  return;
14944
15515
  }
@@ -14948,8 +15519,8 @@ function validateExplicitCleartextHosts({ hostArg, fallbackHostsArg }) {
14948
15519
  }
14949
15520
 
14950
15521
  async function printPairingQr(pairingPayload) {
14951
- const payload = JSON.stringify((0,_vibelet_pairing_connections_mjs__WEBPACK_IMPORTED_MODULE_13__/* .createCompactPairingPayload */ .YP)(pairingPayload));
14952
- const deepLink = (0,_vibelet_pairing_connections_mjs__WEBPACK_IMPORTED_MODULE_13__/* .buildPairingDeepLink */ .N7)(payload);
15522
+ const payload = JSON.stringify((0,_vibelet_pairing_connections_mjs__WEBPACK_IMPORTED_MODULE_14__/* .createCompactPairingPayload */ .YP)(pairingPayload));
15523
+ const deepLink = (0,_vibelet_pairing_connections_mjs__WEBPACK_IMPORTED_MODULE_14__/* .buildPairingDeepLink */ .N7)(payload);
14953
15524
  (0,node_fs__WEBPACK_IMPORTED_MODULE_1__.mkdirSync)(vibeletDir, { recursive: true });
14954
15525
  await qrcode__WEBPACK_IMPORTED_MODULE_6__.toFile(pairingQrPngPath, payload, {
14955
15526
  type: 'png',
@@ -14972,12 +15543,16 @@ async function printPairingQr(pairingPayload) {
14972
15543
 
14973
15544
  // ─── Commands ───────────────────────────────────────────────────────────────────
14974
15545
 
14975
- async function printPairingSummary(existingHealth = null) {
15546
+ async function printPairingSummary(existingHealth = null, { relayUrlOverride = null } = {}) {
14976
15547
  const health = existingHealth ?? (await waitForHealth());
14977
- const pairingPayload = await postJson('/pair/open');
14978
- const connections = (0,_vibelet_pairing_connections_mjs__WEBPACK_IMPORTED_MODULE_13__/* .normalizePairingConnections */ .No)(pairingPayload);
15548
+ const daemonPairingPayload = await postJson('/pair/open');
15549
+ const pairingPayload =
15550
+ relayUrlOverride === null
15551
+ ? daemonPairingPayload
15552
+ : (0,_vibelet_pairing_connections_mjs__WEBPACK_IMPORTED_MODULE_14__/* .applyRelayUrlToPairingPayload */ .mp)(daemonPairingPayload, relayUrlOverride);
15553
+ const connections = (0,_vibelet_pairing_connections_mjs__WEBPACK_IMPORTED_MODULE_14__/* .normalizePairingConnections */ .No)(pairingPayload);
14979
15554
  const [preferredConnection, ...otherConnections] = connections;
14980
- const webUiConnection = (0,_vibelet_pairing_connections_mjs__WEBPACK_IMPORTED_MODULE_13__/* .selectWebUiConnectionTarget */ .SF)(connections);
15555
+ const webUiConnection = (0,_vibelet_pairing_connections_mjs__WEBPACK_IMPORTED_MODULE_14__/* .selectWebUiConnectionTarget */ .SF)(connections);
14981
15556
 
14982
15557
  process.stdout.write(`Vibelet daemon is ready.\n\n`);
14983
15558
  process.stdout.write(`Device: ${health.displayName}\n`);
@@ -14985,16 +15560,16 @@ async function printPairingSummary(existingHealth = null) {
14985
15560
  process.stdout.write(`Host: ${pairingPayload.canonicalHost}\n`);
14986
15561
  process.stdout.write(`Port: ${pairingPayload.port}\n`);
14987
15562
  if (preferredConnection) {
14988
- process.stdout.write(`Preferred path: ${(0,_vibelet_pairing_connections_mjs__WEBPACK_IMPORTED_MODULE_13__/* .formatConnectionSummary */ .uR)(preferredConnection)}\n`);
15563
+ process.stdout.write(`Preferred path: ${(0,_vibelet_pairing_connections_mjs__WEBPACK_IMPORTED_MODULE_14__/* .formatConnectionSummary */ .uR)(preferredConnection)}\n`);
14989
15564
  }
14990
15565
  if (otherConnections.length > 0) {
14991
15566
  process.stdout.write(`Other paths:\n`);
14992
15567
  otherConnections.forEach((target) => {
14993
- process.stdout.write(` - ${(0,_vibelet_pairing_connections_mjs__WEBPACK_IMPORTED_MODULE_13__/* .formatConnectionSummary */ .uR)(target)}\n`);
15568
+ process.stdout.write(` - ${(0,_vibelet_pairing_connections_mjs__WEBPACK_IMPORTED_MODULE_14__/* .formatConnectionSummary */ .uR)(target)}\n`);
14994
15569
  });
14995
15570
  }
14996
15571
  if (webUiConnection) {
14997
- process.stdout.write(`Web UI: ${(0,_vibelet_pairing_connections_mjs__WEBPACK_IMPORTED_MODULE_13__/* .buildWebUiUrl */ .Gv)(webUiConnection, (0,_vibelet_pairing_connections_mjs__WEBPACK_IMPORTED_MODULE_13__/* .createCompactPairingPayload */ .YP)(pairingPayload))}\n`);
15572
+ process.stdout.write(`Web UI: ${(0,_vibelet_pairing_connections_mjs__WEBPACK_IMPORTED_MODULE_14__/* .buildWebUiUrl */ .Gv)(webUiConnection, (0,_vibelet_pairing_connections_mjs__WEBPACK_IMPORTED_MODULE_14__/* .createCompactPairingPayload */ .YP)(pairingPayload))}\n`);
14998
15573
  }
14999
15574
  process.stdout.write(`Paired devices: ${health.pairedDevices}\n`);
15000
15575
  await printPairingQr(pairingPayload);
@@ -15051,7 +15626,7 @@ function printHelp() {
15051
15626
  }
15052
15627
 
15053
15628
  function parseNamedArg(name, errorHint) {
15054
- return (0,_vibelet_cli_args_mjs__WEBPACK_IMPORTED_MODULE_14__/* .parseNamedArg */ .nE)(process.argv, name, errorHint, fail);
15629
+ return (0,_vibelet_cli_args_mjs__WEBPACK_IMPORTED_MODULE_15__/* .parseNamedArg */ .nE)(process.argv, name, errorHint, fail);
15055
15630
  }
15056
15631
 
15057
15632
  function parseRelayArg() {
@@ -15109,28 +15684,10 @@ function parseCommandArg(argv) {
15109
15684
  return 'default';
15110
15685
  }
15111
15686
 
15112
- function loadRelayConfig() {
15113
- try {
15114
- const data = JSON.parse((0,node_fs__WEBPACK_IMPORTED_MODULE_1__.readFileSync)(relayConfigPath, 'utf8'));
15115
- return data.relayUrl || '';
15116
- } catch {
15117
- return '';
15118
- }
15119
- }
15120
-
15121
- function saveRelayConfig(relayUrl) {
15122
- (0,node_fs__WEBPACK_IMPORTED_MODULE_1__.mkdirSync)(vibeletDir, { recursive: true });
15123
- (0,node_fs__WEBPACK_IMPORTED_MODULE_1__.writeFileSync)(relayConfigPath, JSON.stringify({ relayUrl }, null, 2) + '\n', 'utf8');
15124
- }
15125
-
15126
- function clearRelayConfig() {
15127
- (0,node_fs__WEBPACK_IMPORTED_MODULE_1__.rmSync)(relayConfigPath, { force: true });
15128
- }
15129
-
15130
15687
  // ─── Tunnel management ──────────────────────────────────────────────────────────
15131
15688
 
15132
- function getTunnelManagerOptions() {
15133
- return { tunnelStatePath, isAlive: isProcessAlive };
15689
+ async function tryUpdateRunningDaemonRelay(relayUrl) {
15690
+ return await (0,_vibelet_cloudflare_runtime_mjs__WEBPACK_IMPORTED_MODULE_8__/* .tryUpdateRunningDaemonRelay */ .BT)({ port, relayUrl, probeHealth });
15134
15691
  }
15135
15692
 
15136
15693
  async function main() {
@@ -15164,10 +15721,10 @@ async function main() {
15164
15721
  return;
15165
15722
  }
15166
15723
 
15167
- if ((0,_terminal_wrapper_mjs__WEBPACK_IMPORTED_MODULE_9__/* .isTerminalAgentCommand */ .$c)(command)) {
15724
+ if ((0,_terminal_wrapper_mjs__WEBPACK_IMPORTED_MODULE_10__/* .isTerminalAgentCommand */ .$c)(command)) {
15168
15725
  const backend = resolveBackend();
15169
15726
  await ensureDaemonForTerminalControl(backend);
15170
- process.exitCode = await (0,_terminal_wrapper_mjs__WEBPACK_IMPORTED_MODULE_9__/* .runManagedNativeTerminal */ .WA)({
15727
+ process.exitCode = await (0,_terminal_wrapper_mjs__WEBPACK_IMPORTED_MODULE_10__/* .runManagedNativeTerminal */ .WA)({
15171
15728
  agent: command,
15172
15729
  args: process.argv.slice(3),
15173
15730
  port,
@@ -15176,26 +15733,26 @@ async function main() {
15176
15733
  return;
15177
15734
  }
15178
15735
 
15179
- const accessTarget = (0,_vibelet_cli_args_mjs__WEBPACK_IMPORTED_MODULE_14__/* .parseAccessTargetArg */ .tY)(process.argv, fail);
15180
- if (accessTarget && (0,_vibelet_cli_args_mjs__WEBPACK_IMPORTED_MODULE_14__/* .hasLegacyAccessArgs */ .NT)(process.argv, process.env)) {
15736
+ const accessTarget = (0,_vibelet_cli_args_mjs__WEBPACK_IMPORTED_MODULE_15__/* .parseAccessTargetArg */ .tY)(process.argv, fail);
15737
+ if (accessTarget && (0,_vibelet_cli_args_mjs__WEBPACK_IMPORTED_MODULE_15__/* .hasLegacyAccessArgs */ .NT)(process.argv, process.env)) {
15181
15738
  fail(
15182
15739
  'Do not combine --access with legacy access flags.',
15183
15740
  'Use one form, for example `--access=local`, `--access=remote`, `--access=https://...`, or `--access=<host>`.',
15184
15741
  );
15185
15742
  }
15186
15743
  if (!accessTarget) {
15187
- (0,_vibelet_cli_args_mjs__WEBPACK_IMPORTED_MODULE_14__/* .consumeFlag */ .PC)(process.argv, 'remote') ||
15188
- (0,_vibelet_cli_args_mjs__WEBPACK_IMPORTED_MODULE_14__/* .consumeFlag */ .PC)(process.argv, 'tunnel') ||
15189
- (0,_vibelet_cli_args_mjs__WEBPACK_IMPORTED_MODULE_14__/* .readNpmConfigFlag */ .AW)('remote') ||
15190
- (0,_vibelet_cli_args_mjs__WEBPACK_IMPORTED_MODULE_14__/* .readNpmConfigFlag */ .AW)('tunnel');
15744
+ (0,_vibelet_cli_args_mjs__WEBPACK_IMPORTED_MODULE_15__/* .consumeFlag */ .PC)(process.argv, 'remote') ||
15745
+ (0,_vibelet_cli_args_mjs__WEBPACK_IMPORTED_MODULE_15__/* .consumeFlag */ .PC)(process.argv, 'tunnel') ||
15746
+ (0,_vibelet_cli_args_mjs__WEBPACK_IMPORTED_MODULE_15__/* .readNpmConfigFlag */ .AW)('remote') ||
15747
+ (0,_vibelet_cli_args_mjs__WEBPACK_IMPORTED_MODULE_15__/* .readNpmConfigFlag */ .AW)('tunnel');
15191
15748
  }
15192
15749
  const localFlag =
15193
15750
  accessTarget?.kind === 'local' ||
15194
15751
  (!accessTarget &&
15195
- ((0,_vibelet_cli_args_mjs__WEBPACK_IMPORTED_MODULE_14__/* .consumeFlag */ .PC)(process.argv, 'local') ||
15196
- (0,_vibelet_cli_args_mjs__WEBPACK_IMPORTED_MODULE_14__/* .readNpmConfigFlag */ .AW)('local') ||
15752
+ ((0,_vibelet_cli_args_mjs__WEBPACK_IMPORTED_MODULE_15__/* .consumeFlag */ .PC)(process.argv, 'local') ||
15753
+ (0,_vibelet_cli_args_mjs__WEBPACK_IMPORTED_MODULE_15__/* .readNpmConfigFlag */ .AW)('local') ||
15197
15754
  isTruthyEnvFlag(process.env.VIBELET_LOCAL_ONLY)));
15198
- const forceFlag = (0,_vibelet_cli_args_mjs__WEBPACK_IMPORTED_MODULE_14__/* .consumeFlag */ .PC)(process.argv, 'force') || (0,_vibelet_cli_args_mjs__WEBPACK_IMPORTED_MODULE_14__/* .readNpmConfigFlag */ .AW)('force');
15755
+ const forceFlag = (0,_vibelet_cli_args_mjs__WEBPACK_IMPORTED_MODULE_15__/* .consumeFlag */ .PC)(process.argv, 'force') || (0,_vibelet_cli_args_mjs__WEBPACK_IMPORTED_MODULE_15__/* .readNpmConfigFlag */ .AW)('force');
15199
15756
  const relayArg =
15200
15757
  accessTarget?.kind === 'relay'
15201
15758
  ? accessTarget.relayUrl
@@ -15219,14 +15776,32 @@ async function main() {
15219
15776
  let managedTunnelFailed = false;
15220
15777
  command = parseCommandArg(process.argv);
15221
15778
  const startCommand = isDaemonStartCommand(command);
15779
+ const savedRelayUrl = relayConfig.load();
15222
15780
  // --remote/--tunnel remain accepted for compatibility, but startup commands
15223
15781
  // now default to managed remote access unless another connection target wins.
15224
- const shouldManageTunnel = startCommand && !localFlag && relayArg === null && !hostArg && !fallbackHostsArg;
15782
+ const shouldManageTunnel = (0,_vibelet_runtime_policy_mjs__WEBPACK_IMPORTED_MODULE_11__/* .shouldManageQuickTunnel */ .gm)({
15783
+ startCommand,
15784
+ localMode: localFlag,
15785
+ relayArg,
15786
+ hostArg,
15787
+ fallbackHostsArg,
15788
+ savedRelayUrl,
15789
+ force: forceFlag,
15790
+ });
15225
15791
  const cloudflaredProtocol = process.env.VIBELET_CLOUDFLARED_PROTOCOL?.trim() || 'http2';
15226
15792
  if (!['auto', 'quic', 'http2'].includes(cloudflaredProtocol)) {
15227
15793
  fail(`Unsupported VIBELET_CLOUDFLARED_PROTOCOL: ${cloudflaredProtocol}. Use auto, quic, or http2.`);
15228
15794
  }
15229
15795
 
15796
+ if (
15797
+ (0,_vibelet_runtime_policy_mjs__WEBPACK_IMPORTED_MODULE_11__/* .shouldRetireManagedQuickTunnel */ .Pg)({
15798
+ startCommand,
15799
+ manageQuickTunnel: shouldManageTunnel,
15800
+ })
15801
+ ) {
15802
+ retireTrackedQuickTunnel();
15803
+ }
15804
+
15230
15805
  if (localFlag) {
15231
15806
  process.env.VIBELET_LOCAL_ONLY = '1';
15232
15807
  } else {
@@ -15234,47 +15809,35 @@ async function main() {
15234
15809
  }
15235
15810
 
15236
15811
  if (shouldManageTunnel) {
15237
- const existing = forceFlag ? null : (0,_cloudflared_tunnel_manager_mjs__WEBPACK_IMPORTED_MODULE_7__/* .getAliveTunnel */ .QQ)(getTunnelManagerOptions());
15238
- if (existing) {
15239
- process.stdout.write(`Reusing tunnel: ${existing.url} (pid ${existing.pid})\n`);
15240
- saveRelayConfig(existing.url);
15241
- } else {
15242
- if (forceFlag) (0,_cloudflared_tunnel_manager_mjs__WEBPACK_IMPORTED_MODULE_7__/* .stopTunnel */ .HM)(getTunnelManagerOptions());
15243
- process.stdout.write('Starting Cloudflare Tunnel...\n');
15244
- try {
15245
- const tunnel = await (0,_cloudflared_tunnel_manager_mjs__WEBPACK_IMPORTED_MODULE_7__/* .startTunnel */ .DH)({
15246
- port,
15247
- logDir,
15248
- tunnelStatePath,
15249
- protocol: cloudflaredProtocol,
15250
- updateStatePath: cloudflaredUpdateStatePath,
15251
- });
15252
- process.stdout.write(`Tunnel ready: ${tunnel.url}\n`);
15253
- saveRelayConfig(tunnel.url);
15254
- } catch (err) {
15255
- managedTunnelFailed = true;
15256
- (0,_cloudflared_tunnel_manager_mjs__WEBPACK_IMPORTED_MODULE_7__/* .clearTunnelState */ .vD)({ tunnelStatePath });
15257
- clearRelayConfig();
15258
- const message = err instanceof Error ? err.message : String(err);
15259
- process.stderr.write(`${message}\n`);
15260
- process.stderr.write(
15261
- 'Continuing with LAN/local pairing. Use `npx vibelet --access=remote --force` after fixing Cloudflare Tunnel, or pass `--access=https://<url>` for a custom tunnel.\n',
15262
- );
15263
- }
15812
+ const managedTunnel = await (0,_vibelet_cloudflare_runtime_mjs__WEBPACK_IMPORTED_MODULE_8__/* .prepareManagedQuickTunnel */ .kS)({
15813
+ force: forceFlag,
15814
+ port,
15815
+ logDir,
15816
+ tunnelStatePath,
15817
+ protocol: cloudflaredProtocol,
15818
+ updateStatePath: cloudflaredUpdateStatePath,
15819
+ isAlive: isProcessAlive,
15820
+ fail,
15821
+ });
15822
+ managedTunnelFailed = managedTunnel.failed;
15823
+ if (managedTunnel.relayUrl) {
15824
+ relayConfig.save(managedTunnel.relayUrl);
15825
+ } else if (managedTunnel.failed) {
15826
+ relayConfig.clear();
15264
15827
  }
15265
15828
  }
15266
15829
 
15267
15830
  // --access= clears saved relay; --access=https://... saves it; omitted uses saved value.
15268
15831
  if (relayArg !== null) {
15269
15832
  if (relayArg) {
15270
- saveRelayConfig(relayArg);
15833
+ relayConfig.save(relayArg);
15271
15834
  } else {
15272
- clearRelayConfig();
15835
+ relayConfig.clear();
15273
15836
  }
15274
15837
  }
15275
15838
  const shouldIgnoreSavedRelay =
15276
15839
  relayArg === null && (localFlag || Boolean(hostArg) || Boolean(fallbackHostsArg) || managedTunnelFailed);
15277
- const relayUrl = relayArg !== null ? relayArg : shouldIgnoreSavedRelay ? '' : loadRelayConfig();
15840
+ const relayUrl = relayArg !== null ? relayArg : shouldIgnoreSavedRelay ? '' : relayConfig.load();
15278
15841
  if (relayUrl) {
15279
15842
  process.env.VIBELET_RELAY_URL = relayUrl;
15280
15843
  } else {
@@ -15298,12 +15861,10 @@ async function main() {
15298
15861
  // Always try graceful HTTP shutdown first — gives the daemon time to
15299
15862
  // close sessions and flush logs before the service manager kills it.
15300
15863
  await stopRunningDaemon(backend);
15301
- // Also stop tunnel if running
15302
- const tunnelState = (0,_cloudflared_tunnel_manager_mjs__WEBPACK_IMPORTED_MODULE_7__/* .getAliveTunnel */ .QQ)(getTunnelManagerOptions());
15303
- if (tunnelState) {
15304
- (0,_cloudflared_tunnel_manager_mjs__WEBPACK_IMPORTED_MODULE_7__/* .stopTunnel */ .HM)(getTunnelManagerOptions());
15305
- process.stdout.write('Tunnel stopped.\n');
15306
- }
15864
+ // Stop only a tunnel whose persisted owner, origin, and live command all
15865
+ // prove it is the Vibelet-managed Quick Tunnel. Named/external tunnels are
15866
+ // deliberately left alone.
15867
+ retireTrackedQuickTunnel();
15307
15868
  process.stdout.write('Daemon stopped.\n');
15308
15869
  return;
15309
15870
  }
@@ -15311,11 +15872,15 @@ async function main() {
15311
15872
  if (command === 'status') {
15312
15873
  process.stdout.write(`Service (${backend.name}): ${backend.statusLabel()}\n`);
15313
15874
  process.stdout.write(`Runtime: ${(0,node_fs__WEBPACK_IMPORTED_MODULE_1__.existsSync)(runtimeDaemonEntryPath) ? runtimeDaemonEntryPath : 'not installed'}\n`);
15314
- const tunnelState = (0,_cloudflared_tunnel_manager_mjs__WEBPACK_IMPORTED_MODULE_7__/* .getAliveTunnel */ .QQ)(getTunnelManagerOptions());
15875
+ const tunnelState = await (0,_cloudflared_tunnel_manager_mjs__WEBPACK_IMPORTED_MODULE_7__/* .getAliveManagedQuickTunnel */ .M)({
15876
+ tunnelStatePath,
15877
+ isAlive: isProcessAlive,
15878
+ managerId: 'vibelet-cli',
15879
+ });
15315
15880
  if (tunnelState) {
15316
15881
  process.stdout.write(`Tunnel: ${tunnelState.url} (pid ${tunnelState.pid})\n`);
15317
15882
  }
15318
- const savedRelay = loadRelayConfig();
15883
+ const savedRelay = relayConfig.load();
15319
15884
  if (savedRelay) {
15320
15885
  process.stdout.write(`Relay: ${savedRelay}\n`);
15321
15886
  }
@@ -15364,14 +15929,14 @@ async function main() {
15364
15929
  if (command === 'reset') {
15365
15930
  const healthyDaemon = await probeHealth(1_500);
15366
15931
  const hasExplicitConfigOverrides =
15367
- !(0,_vibelet_runtime_policy_mjs__WEBPACK_IMPORTED_MODULE_10__/* .doesHealthMatchRequestedConnectionConfig */ .nW)({
15932
+ !(0,_vibelet_runtime_policy_mjs__WEBPACK_IMPORTED_MODULE_11__/* .doesHealthMatchRequestedConnectionConfig */ .nW)({
15368
15933
  health: healthyDaemon,
15369
15934
  relayUrl,
15370
15935
  canonicalHost: hostArg || '',
15371
15936
  fallbackHosts: fallbackHostsArg || '',
15372
15937
  localMode: localFlag,
15373
15938
  }) &&
15374
- (localFlag || Boolean(relayUrl) || Boolean(hostArg) || Boolean(fallbackHostsArg));
15939
+ (localFlag || relayArg !== null || Boolean(relayUrl) || Boolean(hostArg) || Boolean(fallbackHostsArg));
15375
15940
  const shouldReplaceDetachedDaemon =
15376
15941
  !healthyDaemon && !backend.handlesProcessLifecycle && backend.isServiceInstalled();
15377
15942
  if (shouldReplaceDetachedDaemon || (healthyDaemon && hasExplicitConfigOverrides)) {
@@ -15391,20 +15956,44 @@ async function main() {
15391
15956
  fail(`Unknown command: ${command}`);
15392
15957
  }
15393
15958
 
15394
- const healthyDaemon = await probeHealth(1_500);
15395
- const hasExplicitConfigOverrides =
15396
- !(0,_vibelet_runtime_policy_mjs__WEBPACK_IMPORTED_MODULE_10__/* .doesHealthMatchRequestedConnectionConfig */ .nW)({
15959
+ let healthyDaemon = await probeHealth(1_500);
15960
+ let hasExplicitConfigOverrides =
15961
+ !(0,_vibelet_runtime_policy_mjs__WEBPACK_IMPORTED_MODULE_11__/* .doesHealthMatchRequestedConnectionConfig */ .nW)({
15397
15962
  health: healthyDaemon,
15398
15963
  relayUrl,
15399
15964
  canonicalHost: hostArg || '',
15400
15965
  fallbackHosts: fallbackHostsArg || '',
15401
15966
  localMode: localFlag,
15402
15967
  }) &&
15403
- (localFlag || Boolean(relayUrl) || Boolean(hostArg) || Boolean(fallbackHostsArg));
15404
- const existingHealth = (0,_vibelet_runtime_policy_mjs__WEBPACK_IMPORTED_MODULE_10__/* .shouldReuseHealthyDaemon */ .ZN)({
15968
+ (localFlag ||
15969
+ shouldManageTunnel ||
15970
+ relayArg !== null ||
15971
+ Boolean(relayUrl) ||
15972
+ Boolean(hostArg) ||
15973
+ Boolean(fallbackHostsArg));
15974
+ const relayOverrideRequested =
15975
+ !localFlag && !hostArg && !fallbackHostsArg && (shouldManageTunnel || relayArg !== null || Boolean(savedRelayUrl));
15976
+ const canApplyConnectionConfigWithoutRestart = Boolean(
15977
+ healthyDaemon && relayOverrideRequested && hasExplicitConfigOverrides,
15978
+ );
15979
+ if (healthyDaemon && hasExplicitConfigOverrides && canApplyConnectionConfigWithoutRestart) {
15980
+ const refreshedHealth = await tryUpdateRunningDaemonRelay(relayUrl);
15981
+ if (refreshedHealth) {
15982
+ healthyDaemon = refreshedHealth;
15983
+ hasExplicitConfigOverrides = !(0,_vibelet_runtime_policy_mjs__WEBPACK_IMPORTED_MODULE_11__/* .doesHealthMatchRequestedConnectionConfig */ .nW)({
15984
+ health: healthyDaemon,
15985
+ relayUrl,
15986
+ canonicalHost: '',
15987
+ fallbackHosts: '',
15988
+ localMode: false,
15989
+ });
15990
+ }
15991
+ }
15992
+ const existingHealth = (0,_vibelet_runtime_policy_mjs__WEBPACK_IMPORTED_MODULE_11__/* .shouldReuseHealthyDaemon */ .ZN)({
15405
15993
  command,
15406
15994
  daemonHealthy: Boolean(healthyDaemon),
15407
15995
  hasExplicitConfigOverrides,
15996
+ canApplyConnectionConfigWithoutRestart,
15408
15997
  })
15409
15998
  ? healthyDaemon
15410
15999
  : null;
@@ -15418,7 +16007,7 @@ async function main() {
15418
16007
  const runningDriver = normalizeDriver(existingHealth.claudeDriverMode);
15419
16008
  const driverMismatch = requestedDriver !== null && runningDriver !== requestedDriver;
15420
16009
  const needsRestart = Boolean(isOutdated) || driverMismatch;
15421
- if (needsRestart && activeSessions === 0) {
16010
+ if (needsRestart && activeSessions === 0 && !canApplyConnectionConfigWithoutRestart) {
15422
16011
  process.stdout.write(
15423
16012
  isOutdated
15424
16013
  ? `Daemon is running v${runningVersion}, CLI is v${packageJson.version}. Restarting daemon to upgrade...\n`
@@ -15433,29 +16022,47 @@ async function main() {
15433
16022
  return;
15434
16023
  }
15435
16024
  if (isOutdated) {
15436
- process.stdout.write(
15437
- `Daemon is running v${runningVersion}, CLI is v${packageJson.version}, but ${activeSessions} active session(s) would be interrupted.\n`,
15438
- );
15439
- process.stdout.write('Run `npx vibelet restart` once those sessions are idle to upgrade the daemon.\n');
16025
+ if (canApplyConnectionConfigWithoutRestart) {
16026
+ process.stdout.write(
16027
+ `Daemon is running v${runningVersion}, CLI is v${packageJson.version}; the upgrade was deferred so this relay change cannot restart the running daemon.\n`,
16028
+ );
16029
+ process.stdout.write('Run `npx vibelet restart` later to upgrade the daemon explicitly.\n');
16030
+ } else {
16031
+ process.stdout.write(
16032
+ `Daemon is running v${runningVersion}, CLI is v${packageJson.version}, but ${activeSessions} active session(s) would be interrupted.\n`,
16033
+ );
16034
+ process.stdout.write('Run `npx vibelet restart` once those sessions are idle to upgrade the daemon.\n');
16035
+ }
15440
16036
  } else if (driverMismatch) {
16037
+ if (canApplyConnectionConfigWithoutRestart) {
16038
+ process.stdout.write(
16039
+ `Daemon driver switch to "${requestedDriver}" was deferred so this relay change cannot restart the running daemon.\n`,
16040
+ );
16041
+ } else {
16042
+ process.stdout.write(
16043
+ `Daemon is using the "${runningDriver}" Claude driver; you requested "${requestedDriver}", but ${activeSessions} active session(s) would be interrupted.\n`,
16044
+ );
16045
+ }
15441
16046
  process.stdout.write(
15442
- `Daemon is using the "${runningDriver}" Claude driver; you requested "${requestedDriver}", but ${activeSessions} active session(s) would be interrupted.\n`,
15443
- );
15444
- process.stdout.write(
15445
- `Run \`npx vibelet restart --claude-driver ${requestedDriver}\` once those sessions are idle to switch.\n`,
16047
+ `Run \`npx vibelet restart --claude-driver ${requestedDriver}\` later to switch explicitly.\n`,
15446
16048
  );
15447
16049
  } else {
15448
16050
  process.stdout.write('Vibelet daemon is already running.\n');
15449
16051
  process.stdout.write('Reusing the current runtime so active sessions stay alive.\n');
15450
16052
  process.stdout.write('Run `npx vibelet restart` to force a full restart.\n\n');
15451
16053
  }
15452
- await printPairingSummary(existingHealth);
16054
+ await printPairingSummary(existingHealth, {
16055
+ relayUrlOverride: relayOverrideRequested ? relayUrl : null,
16056
+ });
15453
16057
  return;
15454
16058
  }
15455
16059
 
15456
16060
  const shouldReplaceDetachedDaemon =
15457
16061
  !healthyDaemon && !backend.handlesProcessLifecycle && backend.isServiceInstalled();
15458
- if (shouldReplaceDetachedDaemon || (healthyDaemon && hasExplicitConfigOverrides)) {
16062
+ if (
16063
+ shouldReplaceDetachedDaemon ||
16064
+ (healthyDaemon && hasExplicitConfigOverrides && !canApplyConnectionConfigWithoutRestart)
16065
+ ) {
15459
16066
  await stopRunningDaemon(backend);
15460
16067
  }
15461
16068