@boxes-dev/dvb 0.2.44 → 0.2.46

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/bin/dvb.cjs CHANGED
@@ -423,7 +423,7 @@ var init_repo = __esm({
423
423
  });
424
424
 
425
425
  // ../core/src/sprites.ts
426
- var import_node_crypto, import_node_util, import_ws, SpritesApiError, logger, USE_INTERNAL_SERVICE_API, INTERNAL_SERVICE_SIGNAL_TIMEOUT_SECONDS, toWebSocketErrorInfo, toWsUrl, shellQuote, requestJson, listSprites, requestNdjson, writeFile, normalizeServiceRecord, readFile, openExecSocket, attachExecSocket, openProxySocket, listExecSessions, listServices, internalServicesRequest, createCheckpointInternal, listServicesInternal, getServiceInternal, createServiceInternal, startServiceInternal, stopServiceInternal, execCommand, createSpritesClient;
426
+ var import_node_crypto, import_node_util, import_ws, SpritesApiError, logger, USE_INTERNAL_SERVICE_API, INTERNAL_SERVICE_SIGNAL_TIMEOUT_SECONDS, toWebSocketErrorInfo, toWsUrl, shellQuote, DEFAULT_SPRITES_HTTP_TIMEOUT_MS_IDEMPOTENT, DEFAULT_SPRITES_HTTP_TIMEOUT_MS_NON_IDEMPOTENT, DEFAULT_SPRITES_HTTP_MAX_ATTEMPTS, SPRITES_HTTP_RETRYABLE_STATUSES, delay, isIdempotentMethod, computeRetryDelayMs, isAbortError, getErrorCode, isRetryableFetchError, shouldRetrySpritesRequest, withSpritesRetry, requestJson, listSprites, requestNdjson, writeFile, normalizeServiceRecord, readFile, openExecSocket, attachExecSocket, openProxySocket, listExecSessions, listServices, internalServicesRequest, createCheckpointInternal, listServicesInternal, getServiceInternal, createServiceInternal, startServiceInternal, stopServiceInternal, execCommand, createSpritesClient;
427
427
  var init_sprites = __esm({
428
428
  "../core/src/sprites.ts"() {
429
429
  "use strict";
@@ -491,39 +491,126 @@ var init_sprites = __esm({
491
491
  return new URL(path26, `${protocol}//${base.host}`);
492
492
  };
493
493
  shellQuote = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
494
- requestJson = async (apiBaseUrl, token, method, path26, body) => {
495
- const requestId = (0, import_node_crypto.randomUUID)();
496
- const url = new URL(path26, apiBaseUrl);
497
- const headers = {
498
- authorization: `Bearer ${token}`,
499
- "x-request-id": requestId
500
- };
501
- if (body) {
502
- headers["content-type"] = "application/json";
503
- }
504
- const init = { method, headers };
505
- if (body) {
506
- init.body = JSON.stringify(body);
494
+ DEFAULT_SPRITES_HTTP_TIMEOUT_MS_IDEMPOTENT = 5e3;
495
+ DEFAULT_SPRITES_HTTP_TIMEOUT_MS_NON_IDEMPOTENT = 2e4;
496
+ DEFAULT_SPRITES_HTTP_MAX_ATTEMPTS = 5;
497
+ SPRITES_HTTP_RETRYABLE_STATUSES = /* @__PURE__ */ new Set([408, 409, 425, 429, 500, 502, 503, 504]);
498
+ delay = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
499
+ isIdempotentMethod = (method) => {
500
+ const normalized = method.toUpperCase();
501
+ return normalized === "GET" || normalized === "HEAD" || normalized === "PUT" || normalized === "DELETE" || normalized === "OPTIONS";
502
+ };
503
+ computeRetryDelayMs = (attempt) => {
504
+ const base = 250;
505
+ const cap = 2500;
506
+ const exp = Math.min(cap, base * 2 ** Math.max(0, attempt - 1));
507
+ const jitter = Math.floor(Math.random() * 120);
508
+ return exp + jitter;
509
+ };
510
+ isAbortError = (error) => error instanceof Error && (error.name === "AbortError" || error.message.includes("aborted"));
511
+ getErrorCode = (error) => {
512
+ if (!error || typeof error !== "object") return void 0;
513
+ const record = error;
514
+ const code2 = record.code;
515
+ return typeof code2 === "string" || typeof code2 === "number" ? String(code2) : void 0;
516
+ };
517
+ isRetryableFetchError = (error) => {
518
+ if (isAbortError(error)) return true;
519
+ if (error instanceof TypeError) return true;
520
+ const code2 = getErrorCode(error);
521
+ return code2 === "ECONNRESET" || code2 === "ETIMEDOUT" || code2 === "EAI_AGAIN" || code2 === "ENOTFOUND" || code2 === "ECONNREFUSED";
522
+ };
523
+ shouldRetrySpritesRequest = (method, error) => {
524
+ if (!isIdempotentMethod(method)) return false;
525
+ if (error instanceof SpritesApiError) {
526
+ return SPRITES_HTTP_RETRYABLE_STATUSES.has(error.status);
527
+ }
528
+ return isRetryableFetchError(error);
529
+ };
530
+ withSpritesRetry = async (options) => {
531
+ const opId = (0, import_node_crypto.randomUUID)();
532
+ const timeoutMs = options.timeoutMs ?? (isIdempotentMethod(options.method) ? DEFAULT_SPRITES_HTTP_TIMEOUT_MS_IDEMPOTENT : DEFAULT_SPRITES_HTTP_TIMEOUT_MS_NON_IDEMPOTENT);
533
+ const maxAttemptsRaw = options.maxAttempts ?? DEFAULT_SPRITES_HTTP_MAX_ATTEMPTS;
534
+ const maxAttempts = isIdempotentMethod(options.method) ? Math.max(1, maxAttemptsRaw) : 1;
535
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
536
+ const requestId = (0, import_node_crypto.randomUUID)();
537
+ const controller = new AbortController();
538
+ const timeoutHandle = typeof timeoutMs === "number" && timeoutMs > 0 ? setTimeout(() => controller.abort(), timeoutMs) : null;
539
+ try {
540
+ logger.info("sprites_request", {
541
+ opId,
542
+ attempt,
543
+ requestId,
544
+ method: options.method,
545
+ path: options.path,
546
+ timeoutMs
547
+ });
548
+ return await options.run(controller.signal, requestId);
549
+ } catch (error) {
550
+ const retryable = shouldRetrySpritesRequest(options.method, error);
551
+ const remaining = maxAttempts - attempt;
552
+ if (!retryable || remaining <= 0) {
553
+ throw error;
554
+ }
555
+ const delayMs = computeRetryDelayMs(attempt);
556
+ logger.warn("sprites_http_retry", {
557
+ opId,
558
+ attempt,
559
+ requestId,
560
+ method: options.method,
561
+ path: options.path,
562
+ timeoutMs,
563
+ delayMs,
564
+ remainingAttempts: remaining,
565
+ errorName: error instanceof Error ? error.name : void 0,
566
+ errorMessage: error instanceof Error ? error.message : String(error),
567
+ errorCode: getErrorCode(error),
568
+ ...error instanceof SpritesApiError ? { status: error.status } : {}
569
+ });
570
+ await delay(delayMs);
571
+ } finally {
572
+ if (timeoutHandle) clearTimeout(timeoutHandle);
573
+ }
507
574
  }
508
- logger.info("sprites_request", { requestId, method, path: path26 });
509
- const response = await fetch(url, init);
510
- const text = await response.text();
511
- logger.info("sprites_response", {
512
- requestId,
575
+ throw new Error(`Sprites request failed: ${options.method} ${options.path}`);
576
+ };
577
+ requestJson = async (apiBaseUrl, token, method, path26, body) => {
578
+ return await withSpritesRetry({
513
579
  method,
514
580
  path: path26,
515
- status: response.status
581
+ run: async (signal, requestId) => {
582
+ const url = new URL(path26, apiBaseUrl);
583
+ const headers = {
584
+ authorization: `Bearer ${token}`,
585
+ "x-request-id": requestId
586
+ };
587
+ if (body) {
588
+ headers["content-type"] = "application/json";
589
+ }
590
+ const init = { method, headers, signal };
591
+ if (body) {
592
+ init.body = JSON.stringify(body);
593
+ }
594
+ const response = await fetch(url, init);
595
+ const text = await response.text();
596
+ logger.info("sprites_response", {
597
+ requestId,
598
+ method,
599
+ path: path26,
600
+ status: response.status
601
+ });
602
+ if (!response.ok) {
603
+ const snippet = text.trim().slice(0, 200);
604
+ throw new SpritesApiError(response.status, method, path26, snippet);
605
+ }
606
+ if (!text) return null;
607
+ try {
608
+ return JSON.parse(text);
609
+ } catch {
610
+ return text;
611
+ }
612
+ }
516
613
  });
517
- if (!response.ok) {
518
- const snippet = text.trim().slice(0, 200);
519
- throw new SpritesApiError(response.status, method, path26, snippet);
520
- }
521
- if (!text) return null;
522
- try {
523
- return JSON.parse(text);
524
- } catch {
525
- return text;
526
- }
527
614
  };
528
615
  listSprites = async (apiBaseUrl, token, options) => {
529
616
  const params = new URLSearchParams();
@@ -564,88 +651,99 @@ var init_sprites = __esm({
564
651
  return response;
565
652
  };
566
653
  requestNdjson = async (apiBaseUrl, token, method, path26, body) => {
567
- const requestId = (0, import_node_crypto.randomUUID)();
568
- const url = new URL(path26, apiBaseUrl);
569
- const headers = {
570
- authorization: `Bearer ${token}`,
571
- "x-request-id": requestId
572
- };
573
- if (body) {
574
- headers["content-type"] = "application/json";
575
- }
576
- const init = { method, headers };
577
- if (body) {
578
- init.body = JSON.stringify(body);
579
- }
580
- logger.info("sprites_request", { requestId, method, path: path26 });
581
- const response = await fetch(url, init);
582
- logger.info("sprites_response", {
583
- requestId,
654
+ return await withSpritesRetry({
584
655
  method,
585
656
  path: path26,
586
- status: response.status
587
- });
588
- const text = await response.text();
589
- if (!response.ok) {
590
- const snippet = text.trim().slice(0, 200);
591
- throw new SpritesApiError(response.status, method, path26, snippet);
592
- }
593
- const lines = text.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
594
- const events = [];
595
- for (const line of lines) {
596
- try {
597
- const parsed = JSON.parse(line);
598
- if (Array.isArray(parsed)) {
599
- for (const entry of parsed) {
600
- if (entry && typeof entry === "object") {
601
- events.push(entry);
602
- } else {
603
- events.push({ value: entry });
657
+ run: async (signal, requestId) => {
658
+ const url = new URL(path26, apiBaseUrl);
659
+ const headers = {
660
+ authorization: `Bearer ${token}`,
661
+ "x-request-id": requestId
662
+ };
663
+ if (body) {
664
+ headers["content-type"] = "application/json";
665
+ }
666
+ const init = { method, headers, signal };
667
+ if (body) {
668
+ init.body = JSON.stringify(body);
669
+ }
670
+ const response = await fetch(url, init);
671
+ logger.info("sprites_response", {
672
+ requestId,
673
+ method,
674
+ path: path26,
675
+ status: response.status
676
+ });
677
+ const text = await response.text();
678
+ if (!response.ok) {
679
+ const snippet = text.trim().slice(0, 200);
680
+ throw new SpritesApiError(response.status, method, path26, snippet);
681
+ }
682
+ const lines = text.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
683
+ const events = [];
684
+ for (const line of lines) {
685
+ try {
686
+ const parsed = JSON.parse(line);
687
+ if (Array.isArray(parsed)) {
688
+ for (const entry of parsed) {
689
+ if (entry && typeof entry === "object") {
690
+ events.push(entry);
691
+ } else {
692
+ events.push({ value: entry });
693
+ }
694
+ }
695
+ continue;
604
696
  }
697
+ if (parsed && typeof parsed === "object") {
698
+ events.push(parsed);
699
+ continue;
700
+ }
701
+ events.push({ value: parsed });
702
+ } catch {
703
+ events.push({ raw: line });
605
704
  }
606
- continue;
607
705
  }
608
- if (parsed && typeof parsed === "object") {
609
- events.push(parsed);
610
- continue;
611
- }
612
- events.push({ value: parsed });
613
- } catch {
614
- events.push({ raw: line });
706
+ return events;
615
707
  }
616
- }
617
- return events;
708
+ });
618
709
  };
619
710
  writeFile = async (apiBaseUrl, token, name, remotePath, data) => {
620
- const requestId = (0, import_node_crypto.randomUUID)();
621
711
  const path26 = `/v1/sprites/${name}/fs/write`;
622
712
  const url = new URL(path26, apiBaseUrl);
623
713
  url.searchParams.set("path", remotePath);
624
714
  url.searchParams.set("mkdir", "true");
625
- logger.info("sprites_fs_write", {
626
- requestId,
627
- path: path26,
628
- name,
629
- remotePath,
630
- size: data.length
631
- });
632
- const response = await fetch(url, {
715
+ await withSpritesRetry({
633
716
  method: "PUT",
634
- headers: {
635
- authorization: `Bearer ${token}`,
636
- "content-type": "application/octet-stream",
637
- "x-request-id": requestId
638
- },
639
- body: data
640
- });
641
- logger.info("sprites_fs_write_response", {
642
- requestId,
643
717
  path: path26,
644
- status: response.status
718
+ run: async (signal, requestId) => {
719
+ logger.info("sprites_fs_write", {
720
+ requestId,
721
+ path: path26,
722
+ name,
723
+ remotePath,
724
+ size: data.length
725
+ });
726
+ const response = await fetch(url, {
727
+ method: "PUT",
728
+ headers: {
729
+ authorization: `Bearer ${token}`,
730
+ "content-type": "application/octet-stream",
731
+ "x-request-id": requestId
732
+ },
733
+ body: data,
734
+ signal
735
+ });
736
+ logger.info("sprites_fs_write_response", {
737
+ requestId,
738
+ path: path26,
739
+ status: response.status
740
+ });
741
+ if (!response.ok) {
742
+ const snippet = (await response.text()).trim().slice(0, 200);
743
+ throw new SpritesApiError(response.status, "PUT", path26, snippet);
744
+ }
745
+ }
645
746
  });
646
- if (!response.ok) {
647
- throw new Error(`Sprites FS write failed: ${response.status}`);
648
- }
649
747
  };
650
748
  normalizeServiceRecord = (entry) => {
651
749
  const rawName = entry.name;
@@ -676,38 +774,44 @@ var init_sprites = __esm({
676
774
  return service;
677
775
  };
678
776
  readFile = async (apiBaseUrl, token, name, options) => {
679
- const requestId = (0, import_node_crypto.randomUUID)();
680
777
  const path26 = `/v1/sprites/${name}/fs/read`;
681
778
  const url = new URL(path26, apiBaseUrl);
682
779
  url.searchParams.set("path", options.path);
683
780
  if (options.workingDir) {
684
781
  url.searchParams.set("workingDir", options.workingDir);
685
782
  }
686
- logger.info("sprites_fs_read", {
687
- requestId,
688
- path: path26,
689
- name,
690
- remotePath: options.path,
691
- workingDir: options.workingDir
692
- });
693
- const response = await fetch(url, {
783
+ return await withSpritesRetry({
694
784
  method: "GET",
695
- headers: {
696
- authorization: `Bearer ${token}`,
697
- "x-request-id": requestId
698
- }
699
- });
700
- logger.info("sprites_fs_read_response", {
701
- requestId,
702
785
  path: path26,
703
- status: response.status
786
+ run: async (signal, requestId) => {
787
+ logger.info("sprites_fs_read", {
788
+ requestId,
789
+ path: path26,
790
+ name,
791
+ remotePath: options.path,
792
+ workingDir: options.workingDir
793
+ });
794
+ const response = await fetch(url, {
795
+ method: "GET",
796
+ headers: {
797
+ authorization: `Bearer ${token}`,
798
+ "x-request-id": requestId
799
+ },
800
+ signal
801
+ });
802
+ logger.info("sprites_fs_read_response", {
803
+ requestId,
804
+ path: path26,
805
+ status: response.status
806
+ });
807
+ if (!response.ok) {
808
+ const snippet = (await response.text()).trim().slice(0, 200);
809
+ throw new SpritesApiError(response.status, "GET", path26, snippet);
810
+ }
811
+ const buffer = await response.arrayBuffer();
812
+ return new Uint8Array(buffer);
813
+ }
704
814
  });
705
- if (!response.ok) {
706
- const snippet = (await response.text()).trim().slice(0, 200);
707
- throw new SpritesApiError(response.status, "GET", path26, snippet);
708
- }
709
- const buffer = await response.arrayBuffer();
710
- return new Uint8Array(buffer);
711
815
  };
712
816
  openExecSocket = (apiBaseUrl, token, name, options) => {
713
817
  const url = toWsUrl(apiBaseUrl, `/v1/sprites/${name}/exec`);
@@ -931,7 +1035,7 @@ var init_sprites = __esm({
931
1035
  await internalServicesRequest(apiBaseUrl, token, name, command);
932
1036
  return [];
933
1037
  };
934
- execCommand = async (apiBaseUrl, token, name, cmd, requestId) => {
1038
+ execCommand = async (apiBaseUrl, token, name, cmd, requestId, options) => {
935
1039
  const path26 = `/v1/sprites/${name}/exec`;
936
1040
  const url = toWsUrl(apiBaseUrl, path26);
937
1041
  cmd.forEach((part) => url.searchParams.append("cmd", part));
@@ -942,11 +1046,28 @@ var init_sprites = __esm({
942
1046
  const stderr = [];
943
1047
  let exitCode = null;
944
1048
  let resolved = false;
1049
+ const timeoutMs = options?.timeoutMs;
1050
+ const handshakeTimeoutMs = options?.handshakeTimeoutMs ?? (typeof timeoutMs === "number" ? Math.min(timeoutMs, 6e4) : void 0);
1051
+ const timeoutHandle = typeof timeoutMs === "number" && timeoutMs > 0 ? setTimeout(() => {
1052
+ if (resolved) return;
1053
+ resolved = true;
1054
+ logger.warn("sprites_exec_timeout", {
1055
+ requestId,
1056
+ path: path26,
1057
+ timeoutMs
1058
+ });
1059
+ try {
1060
+ ws.terminate();
1061
+ } catch {
1062
+ }
1063
+ reject(new Error(`Exec websocket timed out after ${timeoutMs}ms`));
1064
+ }, timeoutMs) : null;
945
1065
  const ws = new import_ws.default(url.toString(), {
946
1066
  headers: {
947
1067
  Authorization: `Bearer ${token}`,
948
1068
  "x-request-id": requestId
949
- }
1069
+ },
1070
+ ...typeof handshakeTimeoutMs === "number" && handshakeTimeoutMs > 0 ? { handshakeTimeout: handshakeTimeoutMs } : {}
950
1071
  });
951
1072
  const readResponseSnippet = async (response, limit = 800) => new Promise((resolveBody) => {
952
1073
  let body = "";
@@ -972,6 +1093,8 @@ var init_sprites = __esm({
972
1093
  bodySnippet: snippet ? snippet.slice(0, 800) : void 0
973
1094
  });
974
1095
  if (!resolved) {
1096
+ if (timeoutHandle) clearTimeout(timeoutHandle);
1097
+ resolved = true;
975
1098
  const suffix = snippet ? ` (${snippet.slice(0, 800)})` : "";
976
1099
  reject(new Error(`Exec websocket error: Unexpected server response: ${status}${suffix}`));
977
1100
  }
@@ -980,6 +1103,7 @@ var init_sprites = __esm({
980
1103
  const finish = (code2) => {
981
1104
  if (resolved) return;
982
1105
  resolved = true;
1106
+ if (timeoutHandle) clearTimeout(timeoutHandle);
983
1107
  logger.info("sprites_exec_response", { requestId, path: path26, status: code2 });
984
1108
  resolve({
985
1109
  exitCode: code2,
@@ -1037,6 +1161,8 @@ var init_sprites = __esm({
1037
1161
  errorStack: errorInfo.stack
1038
1162
  });
1039
1163
  if (!resolved) {
1164
+ if (timeoutHandle) clearTimeout(timeoutHandle);
1165
+ resolved = true;
1040
1166
  reject(new Error(`Exec websocket error: ${errorMessage}`));
1041
1167
  }
1042
1168
  };
@@ -1091,7 +1217,7 @@ var init_sprites = __esm({
1091
1217
  await writeFile(apiBaseUrl, token, name, remotePath, data);
1092
1218
  },
1093
1219
  readFile: async (name, options) => readFile(apiBaseUrl, token, name, options),
1094
- exec: async (name, cmd) => {
1220
+ exec: async (name, cmd, options) => {
1095
1221
  const requestId = (0, import_node_crypto.randomUUID)();
1096
1222
  logger.info("sprites_exec", {
1097
1223
  requestId,
@@ -1099,7 +1225,7 @@ var init_sprites = __esm({
1099
1225
  cmd,
1100
1226
  path: `/v1/sprites/${name}/exec`
1101
1227
  });
1102
- return execCommand(apiBaseUrl, token, name, cmd, requestId);
1228
+ return execCommand(apiBaseUrl, token, name, cmd, requestId, options);
1103
1229
  },
1104
1230
  listExecSessions: async (name) => listExecSessions(apiBaseUrl, token, name),
1105
1231
  listServices: async (name) => USE_INTERNAL_SERVICE_API ? listServicesInternal(apiBaseUrl, token, name) : listServices(apiBaseUrl, token, name),
@@ -4726,27 +4852,27 @@ var init_authentication_manager = __esm({
4726
4852
  );
4727
4853
  return;
4728
4854
  }
4729
- let delay = Math.min(
4855
+ let delay3 = Math.min(
4730
4856
  MAXIMUM_REFRESH_DELAY,
4731
4857
  (tokenValiditySeconds - this.refreshTokenLeewaySeconds) * 1e3
4732
4858
  );
4733
- if (delay <= 0) {
4859
+ if (delay3 <= 0) {
4734
4860
  this.logger.warn(
4735
4861
  `Refetching auth token immediately, configured leeway ${this.refreshTokenLeewaySeconds}s is larger than the token's lifetime ${tokenValiditySeconds}s`
4736
4862
  );
4737
- delay = 0;
4863
+ delay3 = 0;
4738
4864
  }
4739
4865
  const refetchTokenTimeoutId = setTimeout(() => {
4740
4866
  this._logVerbose("running scheduled token refetch");
4741
4867
  void this.refetchToken();
4742
- }, delay);
4868
+ }, delay3);
4743
4869
  this.setAuthState({
4744
4870
  state: "waitingForScheduledRefetch",
4745
4871
  refetchTokenTimeoutId,
4746
4872
  config: this.authState.config
4747
4873
  });
4748
4874
  this._logVerbose(
4749
- `scheduled preemptive auth token refetching in ${delay}ms`
4875
+ `scheduled preemptive auth token refetching in ${delay3}ms`
4750
4876
  );
4751
4877
  }
4752
4878
  // Protects against simultaneous calls to `setConfig`
@@ -19741,8 +19867,8 @@ var init_connect = __esm({
19741
19867
  pendingPortLogs.set(event.port, token2);
19742
19868
  const delays = [30, 60, 120, 200];
19743
19869
  void (async () => {
19744
- for (const delay of delays) {
19745
- await new Promise((resolve) => setTimeout(resolve, delay));
19870
+ for (const delay3 of delays) {
19871
+ await new Promise((resolve) => setTimeout(resolve, delay3));
19746
19872
  if (pendingPortLogs.get(event.port) !== token2) {
19747
19873
  return;
19748
19874
  }
@@ -22317,7 +22443,7 @@ var init_weztermMux = __esm({
22317
22443
  });
22318
22444
 
22319
22445
  // src/devbox/commands/init/remote.ts
22320
- var import_node_crypto10, DAEMON_DIR, DAEMON_TARBALL, DAEMON_BUNDLE_DIR, DAEMON_ENTRY, DAEMON_WRAPPER, DAEMON_SERVICE_NAME, DAEMON_CONFIG_FILE, DEFAULT_DAEMON_BASE_URL, DEFAULT_HEARTBEAT_MS, logger8, truncateTail, shellQuote3, expandHome2, execWithLog, writeRemoteFile, bootstrapDevbox, buildWeztermMuxConfig, buildWeztermMuxRunner, patchBashrc, writeRemoteCodexConfig, stageRemoteSetupArtifacts, resolveDaemonUrl, fetchDaemonBinary, buildDaemonConfig, isSameArgs, ensureSpriteDaemonService, installWeztermMux, ensureWeztermMuxService, hasWeztermMuxBinary, isWeztermMuxHealthy, installSpriteDaemon;
22446
+ var import_node_crypto10, DAEMON_DIR, DAEMON_TARBALL, DAEMON_BUNDLE_DIR, DAEMON_ENTRY, DAEMON_WRAPPER, DAEMON_SERVICE_NAME, DAEMON_CONFIG_FILE, DEFAULT_DAEMON_BASE_URL, DEFAULT_HEARTBEAT_MS, BOOTSTRAP_EXEC_TIMEOUT_MS, BOOTSTRAP_EXEC_RETRY_DELAY_MS, logger8, truncateTail, shellQuote3, expandHome2, execWithLog, isExecTimeoutError, delay2, writeRemoteFile, bootstrapDevbox, buildWeztermMuxConfig, buildWeztermMuxRunner, patchBashrc, writeRemoteCodexConfig, stageRemoteSetupArtifacts, resolveDaemonUrl, fetchDaemonBinary, buildDaemonConfig, isSameArgs, ensureSpriteDaemonService, installWeztermMux, ensureWeztermMuxService, hasWeztermMuxBinary, isWeztermMuxHealthy, installSpriteDaemon;
22321
22447
  var init_remote = __esm({
22322
22448
  "src/devbox/commands/init/remote.ts"() {
22323
22449
  "use strict";
@@ -22335,6 +22461,8 @@ var init_remote = __esm({
22335
22461
  DAEMON_CONFIG_FILE = `${DAEMON_DIR}/config.json`;
22336
22462
  DEFAULT_DAEMON_BASE_URL = process.env.SPRITE_DAEMON_BASE_URL?.trim() || "https://deploy-dev.boxes.dev";
22337
22463
  DEFAULT_HEARTBEAT_MS = 3e4;
22464
+ BOOTSTRAP_EXEC_TIMEOUT_MS = 9e4;
22465
+ BOOTSTRAP_EXEC_RETRY_DELAY_MS = 1e4;
22338
22466
  logger8 = createLogger("devbox-init");
22339
22467
  truncateTail = (value, maxChars) => {
22340
22468
  if (value.length <= maxChars) return value;
@@ -22348,7 +22476,7 @@ var init_remote = __esm({
22348
22476
  }
22349
22477
  return value;
22350
22478
  };
22351
- execWithLog = async (client, canonical, script, stage) => {
22479
+ execWithLog = async (client, canonical, script, stage, options) => {
22352
22480
  const requestId = (0, import_node_crypto10.randomUUID)();
22353
22481
  const startedAt2 = Date.now();
22354
22482
  logger8.info("sprites_request", {
@@ -22358,7 +22486,11 @@ var init_remote = __esm({
22358
22486
  box: canonical,
22359
22487
  stage
22360
22488
  });
22361
- const result = await client.exec(canonical, ["/bin/bash", "-lc", script]);
22489
+ const result = await client.exec(
22490
+ canonical,
22491
+ ["/bin/bash", "-lc", script],
22492
+ options
22493
+ );
22362
22494
  const durationMs = Date.now() - startedAt2;
22363
22495
  logger8.info("sprites_response", {
22364
22496
  requestId,
@@ -22378,6 +22510,8 @@ var init_remote = __esm({
22378
22510
  });
22379
22511
  return result;
22380
22512
  };
22513
+ isExecTimeoutError = (error) => error instanceof Error && typeof error.message === "string" && error.message.startsWith("Exec websocket timed out after ");
22514
+ delay2 = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
22381
22515
  writeRemoteFile = async (client, canonical, path26, content, stage) => {
22382
22516
  const requestId = (0, import_node_crypto10.randomUUID)();
22383
22517
  logger8.info("sprites_request", {
@@ -22409,12 +22543,28 @@ var init_remote = __esm({
22409
22543
  " sudo -n chown -R sprite:sprite /home/sprite/.devbox || true",
22410
22544
  "fi"
22411
22545
  ].join("\n");
22412
- const permsResult = await execWithLog(
22546
+ let permsResult = await execWithLog(
22413
22547
  client,
22414
22548
  canonical,
22415
22549
  permissionsScript,
22416
- "bootstrap-permissions"
22417
- );
22550
+ "bootstrap-permissions",
22551
+ { timeoutMs: BOOTSTRAP_EXEC_TIMEOUT_MS }
22552
+ ).catch(async (error) => {
22553
+ if (!isExecTimeoutError(error)) throw error;
22554
+ logger8.warn("sprites_exec_retry_after_timeout", {
22555
+ stage: "bootstrap-permissions",
22556
+ timeoutMs: BOOTSTRAP_EXEC_TIMEOUT_MS,
22557
+ delayMs: BOOTSTRAP_EXEC_RETRY_DELAY_MS
22558
+ });
22559
+ await delay2(BOOTSTRAP_EXEC_RETRY_DELAY_MS);
22560
+ return await execWithLog(
22561
+ client,
22562
+ canonical,
22563
+ permissionsScript,
22564
+ "bootstrap-permissions",
22565
+ { timeoutMs: BOOTSTRAP_EXEC_TIMEOUT_MS }
22566
+ );
22567
+ });
22418
22568
  if (permsResult.exitCode !== 0) {
22419
22569
  const details = permsResult.stderr || permsResult.stdout || "";
22420
22570
  throw new Error(
@@ -22434,7 +22584,8 @@ var init_remote = __esm({
22434
22584
  client,
22435
22585
  canonical,
22436
22586
  ipv4PreferenceScript,
22437
- "bootstrap-ipv4-preference"
22587
+ "bootstrap-ipv4-preference",
22588
+ { timeoutMs: BOOTSTRAP_EXEC_TIMEOUT_MS }
22438
22589
  );
22439
22590
  if (ipv4Result.exitCode !== 0) {
22440
22591
  const details = ipv4Result.stderr || ipv4Result.stdout || "";
@@ -23079,30 +23230,34 @@ chmod 755 ${WEZTERM_MUX_RUNNER_PATH}`,
23079
23230
  DAEMON_CONFIG_FILE,
23080
23231
  Buffer.from(buildDaemonConfig(convexUrl, heartbeatToken))
23081
23232
  );
23082
- const result = await client.exec(spriteName, [
23083
- "/bin/bash",
23084
- "--noprofile",
23085
- "--norc",
23086
- "-e",
23087
- "-u",
23088
- "-o",
23089
- "pipefail",
23090
- "-c",
23233
+ const result = await client.exec(
23234
+ spriteName,
23091
23235
  [
23092
- `mkdir -p ${DAEMON_DIR}`,
23093
- `rm -rf ${DAEMON_BUNDLE_DIR}`,
23094
- `mkdir -p ${DAEMON_BUNDLE_DIR}`,
23095
- `tar -xzf ${DAEMON_TARBALL} -C ${DAEMON_BUNDLE_DIR}`,
23096
- `chmod 755 ${DAEMON_ENTRY} || true`,
23097
- `chmod 755 ${DAEMON_BUNDLE_DIR}/bin/* || true`,
23236
+ "/bin/bash",
23237
+ "--noprofile",
23238
+ "--norc",
23239
+ "-e",
23240
+ "-u",
23241
+ "-o",
23242
+ "pipefail",
23243
+ "-c",
23098
23244
  [
23099
- `${DAEMON_ENTRY} daemonctl install --json --timeout-ms 20000`,
23100
- `--tarball ${DAEMON_TARBALL}`,
23101
- `--release-sha ${release.sha256}`,
23102
- `--config-path ${DAEMON_CONFIG_FILE}`
23103
- ].join(" ")
23104
- ].join("\n")
23105
- ]);
23245
+ `mkdir -p ${DAEMON_DIR}`,
23246
+ `rm -rf ${DAEMON_BUNDLE_DIR}`,
23247
+ `mkdir -p ${DAEMON_BUNDLE_DIR}`,
23248
+ `tar -xzf ${DAEMON_TARBALL} -C ${DAEMON_BUNDLE_DIR}`,
23249
+ `chmod 755 ${DAEMON_ENTRY} || true`,
23250
+ `chmod 755 ${DAEMON_BUNDLE_DIR}/bin/* || true`,
23251
+ [
23252
+ `${DAEMON_ENTRY} daemonctl install --json --timeout-ms 20000`,
23253
+ `--tarball ${DAEMON_TARBALL}`,
23254
+ `--release-sha ${release.sha256}`,
23255
+ `--config-path ${DAEMON_CONFIG_FILE}`
23256
+ ].join(" ")
23257
+ ].join("\n")
23258
+ ],
23259
+ { timeoutMs: 12e4 }
23260
+ );
23106
23261
  if (result.exitCode !== 0) {
23107
23262
  throw new Error(result.stderr || result.stdout || "Daemon install failed.");
23108
23263
  }
@@ -23892,17 +24047,26 @@ var init_template = __esm({
23892
24047
  });
23893
24048
 
23894
24049
  // src/devbox/commands/init/codex/prompts.ts
23895
- var import_promises23, import_node_path17, resolvePromptsDir, readPromptTemplate, loadPrompt, loadLocalEnvSecretsScanPrompt, loadLocalExternalScanPrompt, loadLocalExtraArtifactsScanPrompt, loadLocalServicesScanPrompt;
24050
+ var import_node_fs5, import_promises23, import_node_path17, resolvePromptsDir, readPromptTemplate, loadPrompt, loadLocalEnvSecretsScanPrompt, loadLocalExternalScanPrompt, loadLocalExtraArtifactsScanPrompt, loadLocalServicesScanPrompt;
23896
24051
  var init_prompts = __esm({
23897
24052
  "src/devbox/commands/init/codex/prompts.ts"() {
23898
24053
  "use strict";
24054
+ import_node_fs5 = __toESM(require("node:fs"), 1);
23899
24055
  import_promises23 = __toESM(require("node:fs/promises"), 1);
23900
24056
  import_node_path17 = __toESM(require("node:path"), 1);
23901
24057
  init_template();
23902
24058
  resolvePromptsDir = () => {
23903
- const binPath = process.argv[1];
23904
- if (binPath) {
23905
- return import_node_path17.default.resolve(import_node_path17.default.dirname(binPath), "..", "prompts");
24059
+ const argv1 = typeof process.argv[1] === "string" ? process.argv[1] : "";
24060
+ if (argv1) {
24061
+ try {
24062
+ const real = import_node_fs5.default.realpathSync(argv1);
24063
+ return import_node_path17.default.resolve(import_node_path17.default.dirname(real), "..", "prompts");
24064
+ } catch {
24065
+ }
24066
+ try {
24067
+ return import_node_path17.default.resolve(import_node_path17.default.dirname(argv1), "..", "prompts");
24068
+ } catch {
24069
+ }
23906
24070
  }
23907
24071
  return import_node_path17.default.resolve(process.cwd(), "prompts");
23908
24072
  };
@@ -24424,21 +24588,25 @@ fi`
24424
24588
  });
24425
24589
 
24426
24590
  // src/devbox/commands/init/codex/plan.ts
24427
- var import_node_path18, import_promises24, resolveSchemaPath, writeSetupEnvSecretsSchema, writeSetupExternalSchema, writeSetupExtraArtifactsSchema, writeServicesSchema, isObject4, isServicesSelection, isSetupPlan, isSetupEnvSecretsPlan, isSetupExternalPlan, isSetupExtraArtifactsPlan, isServicesPlan, readSetupPlan, readSetupEnvSecretsPlan, readSetupExternalPlan, readSetupExtraArtifactsPlan, readServicesPlan, writeSetupPlan, mergeSetupScans;
24591
+ var import_node_path18, import_node_fs6, import_promises24, resolveSchemaPath, writeSetupEnvSecretsSchema, writeSetupExternalSchema, writeSetupExtraArtifactsSchema, writeServicesSchema, isObject4, isServicesSelection, isSetupPlan, isSetupEnvSecretsPlan, isSetupExternalPlan, isSetupExtraArtifactsPlan, isServicesPlan, readSetupPlan, readSetupEnvSecretsPlan, readSetupExternalPlan, readSetupExtraArtifactsPlan, readServicesPlan, writeSetupPlan, mergeSetupScans;
24428
24592
  var init_plan = __esm({
24429
24593
  "src/devbox/commands/init/codex/plan.ts"() {
24430
24594
  "use strict";
24431
24595
  import_node_path18 = __toESM(require("node:path"), 1);
24596
+ import_node_fs6 = __toESM(require("node:fs"), 1);
24432
24597
  import_promises24 = __toESM(require("node:fs/promises"), 1);
24433
24598
  resolveSchemaPath = (fileName) => {
24434
- const binPath = process.argv[1];
24435
- if (binPath) {
24436
- return import_node_path18.default.resolve(
24437
- import_node_path18.default.dirname(binPath),
24438
- "..",
24439
- "codex",
24440
- fileName
24441
- );
24599
+ const argv1 = typeof process.argv[1] === "string" ? process.argv[1] : "";
24600
+ if (argv1) {
24601
+ try {
24602
+ const real = import_node_fs6.default.realpathSync(argv1);
24603
+ return import_node_path18.default.resolve(import_node_path18.default.dirname(real), "..", "codex", fileName);
24604
+ } catch {
24605
+ }
24606
+ try {
24607
+ return import_node_path18.default.resolve(import_node_path18.default.dirname(argv1), "..", "codex", fileName);
24608
+ } catch {
24609
+ }
24442
24610
  }
24443
24611
  return import_node_path18.default.resolve(process.cwd(), "codex", fileName);
24444
24612
  };
@@ -24728,12 +24896,12 @@ ${bottom}
24728
24896
  });
24729
24897
 
24730
24898
  // src/devbox/commands/init/codex/local.ts
24731
- var import_node_child_process9, import_node_fs5, import_promises25, import_node_path19, stripAnsi3, extractBoldText2, runCodexExec, runLocalSetupEnvSecretsScan, runLocalSetupExternalScan, runLocalSetupExtraArtifactsScan, runLocalServicesScan, toPosixPath, toRepoRelativePath, countSecretVars, buildEnvFileHint, buildExternalDependencyLabel, promptForPlanApproval, promptForServicesApproval;
24899
+ var import_node_child_process9, import_node_fs7, import_promises25, import_node_path19, stripAnsi3, extractBoldText2, runCodexExec, runLocalSetupEnvSecretsScan, runLocalSetupExternalScan, runLocalSetupExtraArtifactsScan, runLocalServicesScan, toPosixPath, toRepoRelativePath, countSecretVars, buildEnvFileHint, buildExternalDependencyLabel, promptForPlanApproval, promptForServicesApproval;
24732
24900
  var init_local = __esm({
24733
24901
  "src/devbox/commands/init/codex/local.ts"() {
24734
24902
  "use strict";
24735
24903
  import_node_child_process9 = require("node:child_process");
24736
- import_node_fs5 = require("node:fs");
24904
+ import_node_fs7 = require("node:fs");
24737
24905
  import_promises25 = __toESM(require("node:fs/promises"), 1);
24738
24906
  import_node_path19 = __toESM(require("node:path"), 1);
24739
24907
  init_dist2();
@@ -24752,11 +24920,11 @@ var init_local = __esm({
24752
24920
  try {
24753
24921
  if (stdoutLogPath) {
24754
24922
  await import_promises25.default.mkdir(import_node_path19.default.dirname(stdoutLogPath), { recursive: true });
24755
- stdoutStream = (0, import_node_fs5.createWriteStream)(stdoutLogPath, { flags: "a" });
24923
+ stdoutStream = (0, import_node_fs7.createWriteStream)(stdoutLogPath, { flags: "a" });
24756
24924
  }
24757
24925
  if (stderrLogPath) {
24758
24926
  await import_promises25.default.mkdir(import_node_path19.default.dirname(stderrLogPath), { recursive: true });
24759
- stderrStream = (0, import_node_fs5.createWriteStream)(stderrLogPath, { flags: "a" });
24927
+ stderrStream = (0, import_node_fs7.createWriteStream)(stderrLogPath, { flags: "a" });
24760
24928
  }
24761
24929
  } catch {
24762
24930
  stdoutStream = null;
@@ -32199,7 +32367,7 @@ var init_cli = __esm({
32199
32367
  });
32200
32368
 
32201
32369
  // src/bin/dvb.ts
32202
- var import_node_fs6 = __toESM(require("node:fs"), 1);
32370
+ var import_node_fs8 = __toESM(require("node:fs"), 1);
32203
32371
  var import_node_os11 = __toESM(require("node:os"), 1);
32204
32372
  var import_node_path26 = __toESM(require("node:path"), 1);
32205
32373
  init_src();
@@ -32257,8 +32425,8 @@ run().catch((error) => {
32257
32425
  if (homeDir) {
32258
32426
  try {
32259
32427
  const logDir = import_node_path26.default.join(resolveDevboxDir(homeDir), "wezterm");
32260
- import_node_fs6.default.mkdirSync(logDir, { recursive: true });
32261
- import_node_fs6.default.appendFileSync(
32428
+ import_node_fs8.default.mkdirSync(logDir, { recursive: true });
32429
+ import_node_fs8.default.appendFileSync(
32262
32430
  import_node_path26.default.join(logDir, "proxy.log"),
32263
32431
  `${(/* @__PURE__ */ new Date()).toISOString()} ${message}
32264
32432
  `