stim 1.14.0 → 1.16.0

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.
Files changed (152) hide show
  1. package/README.md +2 -0
  2. package/dist/activity-BxwdD81S.mjs +609 -0
  3. package/dist/agent-device-usage-output-Dpih1Tfa.mjs +411 -0
  4. package/dist/{android-d6rGq1pA.mjs → android-B_NumVyQ.mjs} +169 -81
  5. package/dist/{android-LbPFRWVL.mjs → android-Ift4kVvt.mjs} +517 -413
  6. package/dist/android-cas-8U4DPqeR.mjs +167 -0
  7. package/dist/android-cas-compiler.mjs +2 -2
  8. package/dist/{android-XpvCrV7q.mjs → android-r2rBL8kV.mjs} +2 -2
  9. package/dist/{app-install-BpgSgkJj.mjs → app-install-CKWdbXIX.mjs} +128 -151
  10. package/dist/attempt-Bk2ouQVL.mjs +39 -0
  11. package/dist/budget-DjrURfQn.mjs +2700 -0
  12. package/dist/{build-plan-Dcr-3ie0.mjs → build-plan-DjeSb2Xl.mjs} +192 -653
  13. package/dist/build-slots-rUk77xqh.mjs +132 -0
  14. package/dist/{cache-manifest-C68QQ0ua.mjs → cache-manifest-DnUHBJaU.mjs} +2 -2
  15. package/dist/cache-manifest.mjs +1 -1
  16. package/dist/{chrome-BPCiC3ZX.mjs → chrome-BrcVWQyD.mjs} +1 -1
  17. package/dist/cli-BkYZ_6hy.mjs +51 -0
  18. package/dist/cli.mjs +6 -42
  19. package/dist/client-BTY686R6.mjs +1368 -0
  20. package/dist/collector-run.mjs +9 -195
  21. package/dist/{command-output-DVcpw6eE.mjs → command-output-DnJMdEY-.mjs} +1 -1
  22. package/dist/{config-lvc7d3C2.mjs → config-D0DbpZtM.mjs} +28 -29
  23. package/dist/{created-devices-CllH_8eb.mjs → created-devices-D5owjN4C.mjs} +2 -2
  24. package/dist/{dependency-state-DsT7aKeo.mjs → dependency-state-B6cMXZab.mjs} +2 -2
  25. package/dist/{deps-D9IcKqXS.mjs → deps-urbmfXig.mjs} +3 -3
  26. package/dist/{detached-entry-DJHoca49.mjs → detached-entry-CiTEgMR1.mjs} +1 -1
  27. package/dist/dev-client-CroAIjjj.mjs +43 -0
  28. package/dist/{device-CjuuAJjQ.mjs → device-DZQjF7H5.mjs} +8 -7
  29. package/dist/device-capacity-BHwxNBlu.mjs +136 -0
  30. package/dist/device-host-worker.d.mts +1 -0
  31. package/dist/device-host-worker.mjs +964 -0
  32. package/dist/device-ios-BadmeL2V.mjs +381 -0
  33. package/dist/{device-lease-5c3WrVRm.mjs → device-lease-D-hLqx1b.mjs} +43 -36
  34. package/dist/{device-pool-BiyDyPP-.mjs → device-lease-run-CqjShqWW.mjs} +6 -126
  35. package/dist/device-pool-CUaEWMMm.mjs +125 -0
  36. package/dist/{device-remote-D87zEzaq.mjs → device-remote-D5WVWqOq.mjs} +69 -163
  37. package/dist/{doctor-x9pnU9fq.mjs → doctor-Cimf-gGB.mjs} +241 -103
  38. package/dist/{error-diagnostics-C5HzcwCW.mjs → error-diagnostics-B9L4Ae0x.mjs} +15 -18
  39. package/dist/{exec-DK8KaVHK.mjs → exec-CaErVv7t.mjs} +5 -1
  40. package/dist/{gc-ko6mUAYD.mjs → gc-Bqy4bjEu.mjs} +599 -108
  41. package/dist/{android-cas-iZK3DOCr.mjs → gradle-BsO1MdsH.mjs} +8 -294
  42. package/dist/{guide-6R0VNFqI.mjs → guide-B_uyKTxW.mjs} +2511 -239
  43. package/dist/{guide-status-BZrFPIA_.mjs → guide-status-CjEZMAD4.mjs} +3 -3
  44. package/dist/hosted-client-a-WmqE2L.mjs +197 -0
  45. package/dist/hosted-ios-CIWelIqf.mjs +220 -0
  46. package/dist/hosted-logs-D5g041Q3.mjs +187 -0
  47. package/dist/hosted-macos-BSZyIb3y.mjs +135 -0
  48. package/dist/idle-DYVymG92.mjs +1099 -0
  49. package/dist/idle-shutdown-CTsDnAde.mjs +122 -0
  50. package/dist/in-use-DvBw0Kxo.mjs +130 -0
  51. package/dist/{ios-BwDPrnYQ.mjs → ios-BaInn3sW.mjs} +201 -10
  52. package/dist/ios-Cr1h5gnO.mjs +189 -0
  53. package/dist/{ios-P1f5nm2n.mjs → ios-aq3QoGI3.mjs} +1159 -633
  54. package/dist/{ios-device-BiOsSVzd.mjs → ios-device-Ee7sDXfp.mjs} +1 -1
  55. package/dist/{ios-device-DGglU3E_.mjs → ios-device-FfCN_C8p.mjs} +12 -7
  56. package/dist/ios-state-DS5UpYTH.mjs +48 -0
  57. package/dist/{launch-verify-Deg4tB3Z.mjs → launch-verify-2DBRizjo.mjs} +5 -5
  58. package/dist/logs-CHEVK8zD.mjs +371 -0
  59. package/dist/machines-BIBUdhpj.mjs +265 -0
  60. package/dist/macos-CSYEAFmF.mjs +473 -0
  61. package/dist/macos-run.d.mts +5 -0
  62. package/dist/macos-run.mjs +2 -0
  63. package/dist/maintenance-run.d.mts +4 -0
  64. package/dist/maintenance-run.mjs +254 -0
  65. package/dist/{metro-CcEo8Fl8.mjs → metro-BeK1ZPz1.mjs} +20 -120
  66. package/dist/metro-gateway-BJ-rxQAB.mjs +84 -0
  67. package/dist/{named-ports-Blg8IxOh.mjs → named-ports-Bk_YXEmE.mjs} +6 -59
  68. package/dist/native-run-NaBrRFtQ.mjs +147 -0
  69. package/dist/{native-runtime-DjBHRg5m.mjs → native-runtime-DBzRnEKS.mjs} +9 -5
  70. package/dist/{ndjson-CDY9IkVC.mjs → ndjson-PclOIwVY.mjs} +2 -2
  71. package/dist/offload-worker.d.mts +122 -0
  72. package/dist/offload-worker.mjs +606 -0
  73. package/dist/{ownership-DyYDF5ww.mjs → ownership-DiA16AsN.mjs} +3 -2
  74. package/dist/ownership-hCkPzGmr.mjs +571 -0
  75. package/dist/{page-BHUzTDFt.mjs → page-QF_Ke10s.mjs} +7 -2
  76. package/dist/{ports-BguS83ex.mjs → ports-R2k4Knz3.mjs} +3 -3
  77. package/dist/prebuild-D2fsca_Z.mjs +213 -0
  78. package/dist/preview-DhVbvPAZ.mjs +318 -0
  79. package/dist/process-identity-BmRm1qV0.mjs +12 -0
  80. package/dist/{project-CWlvEaeN.mjs → project-Du91Ilvc.mjs} +74 -19
  81. package/dist/pull-request-pxnDCQzn.mjs +322 -0
  82. package/dist/pull-requests.d.mts +120 -0
  83. package/dist/pull-requests.mjs +2 -0
  84. package/dist/recordings-BIXOzVnr.mjs +37 -0
  85. package/dist/{reload-BiniwsSZ.mjs → reload-rM9jV1fz.mjs} +109 -24
  86. package/dist/{remote-cache-CcR6o6lI.mjs → remote-cache-C8-V13XN.mjs} +5 -5
  87. package/dist/run-f4vI6EnK.mjs +119 -0
  88. package/dist/{server-bare-DGfgyJ5J.mjs → server-bare-CpPGmnmO.mjs} +33 -15
  89. package/dist/server-expo-CVqO2c35.mjs +2 -0
  90. package/dist/{server-expo-BVuwQ65v.mjs → server-expo-MC0rHlOC.mjs} +8 -12
  91. package/dist/settings-BBpMcDbD.mjs +25 -0
  92. package/dist/{settings-B-fsC-Om.mjs → settings-ZkDzkzvB.mjs} +22 -10
  93. package/dist/settings-oTSkkNrM.mjs +1439 -0
  94. package/dist/settings.schema.json +845 -97
  95. package/dist/{simslim-DrDN4WuV.mjs → simslim-B8-7AoTI.mjs} +8 -132
  96. package/dist/{slot-launch-D5t8EiMf.mjs → slot-launch-B8VrHxn1.mjs} +11 -8
  97. package/dist/{spawn-claims-Drw_o-Fb.mjs → spawn-claims-D0XfSe8u.mjs} +1 -1
  98. package/dist/spawn-entry-jw19m8Nl.mjs +17 -0
  99. package/dist/stage-CzaiwFsI.mjs +143 -0
  100. package/dist/start-CX8Ig-U2.mjs +2 -0
  101. package/dist/{start-s4RXp1wy.mjs → start-ClDfwXa4.mjs} +116 -65
  102. package/dist/state-3DwSQIBK.mjs +41 -0
  103. package/dist/state-B4PQFXMW.d.mts +12 -0
  104. package/dist/{state-Dk9RVXI7.mjs → state-C1jB7Q8q.mjs} +70 -8
  105. package/dist/{state-DHqvsbqC.mjs → state-DcW6vOd_.mjs} +7 -4
  106. package/dist/stats-CEZ_i5je.mjs +77 -0
  107. package/dist/{status-BgAuxESg.mjs → status-BuvBFeII.mjs} +118 -20
  108. package/dist/{status-BsPAnfOI.mjs → status-Cs7VLg9D.mjs} +542 -283
  109. package/dist/status-DlKvVJBE.mjs +49 -0
  110. package/dist/stdout-reader-C8yjre1x.mjs +23 -0
  111. package/dist/stim-desktop-Cnz8CPnl.mjs +63 -0
  112. package/dist/stim-footprint.swift +32 -0
  113. package/dist/{stim-installations-CINXy_su.mjs → stim-installations-QMEI32v1.mjs} +1 -1
  114. package/dist/stop-B0Yxtmiy.mjs +48 -0
  115. package/dist/{stop-BzreOWJp.mjs → stop-Bffozsac.mjs} +83 -16
  116. package/dist/{stop-BWIF_6BK.mjs → stop-Dt_dM16A.mjs} +2 -2
  117. package/dist/stop-cause-CidxIqup.mjs +97 -0
  118. package/dist/supervisor-run.d.mts +43 -11
  119. package/dist/supervisor-run.mjs +363 -24
  120. package/dist/support-CAP8qDUF.mjs +316 -0
  121. package/dist/support-D4aYJniO.mjs +201 -0
  122. package/dist/teardown-CL6vDs2Y.mjs +727 -0
  123. package/dist/{xcode-Cp5KvK9x.mjs → toolchain-DkUflduf.mjs} +364 -14
  124. package/dist/trigger-Bemu8ytw.mjs +65 -0
  125. package/dist/trigger-CXts6qt1.mjs +2 -0
  126. package/dist/warm-progress-CpGv3Wb6.mjs +732 -0
  127. package/dist/{web-BsQs1WlO.mjs → web-DWUaMp8P.mjs} +37 -21
  128. package/dist/web-run.mjs +310 -13
  129. package/dist/workspace-process-lock-BL7Q0HCO.mjs +57 -0
  130. package/dist/workspace-state-B8-aJ1_5.mjs +371 -0
  131. package/dist/workspaces-BV4pdHFz.mjs +306 -0
  132. package/dist/{worktree-UrQWXSeb.mjs → worktree-K-xj9Db0.mjs} +625 -256
  133. package/dist/worktree-jnG8Iph5.mjs +2 -0
  134. package/package.json +10 -5
  135. package/shim/bundle-response.cjs +28 -12
  136. package/dist/activity-CYVDcrt3.mjs +0 -356
  137. package/dist/budget-CGDkhvv_.mjs +0 -1745
  138. package/dist/build-progress-DxKpwnaS.mjs +0 -247
  139. package/dist/idle-DXtx3RbX.mjs +0 -191
  140. package/dist/in-use-DlqBDKqY.mjs +0 -249
  141. package/dist/logs-Civ4gbBH.mjs +0 -486
  142. package/dist/merge-state-ytFQCeh4.mjs +0 -262
  143. package/dist/server-expo-DIHDhvGg.mjs +0 -2
  144. package/dist/settings-D61iNOsB.mjs +0 -629
  145. package/dist/start-Cetfvy8l.mjs +0 -2
  146. package/dist/stats-CFpFGlJV.mjs +0 -62
  147. package/dist/stats-CX1Z8Tca.mjs +0 -368
  148. package/dist/stim-desktop-CXt8-Z3J.mjs +0 -40
  149. package/dist/stop-cause-hjGBOZTG.mjs +0 -1231
  150. package/dist/worktree-BVrHsYZG.mjs +0 -752
  151. package/dist/worktree-CHWWvm5v.mjs +0 -2
  152. package/dist/{metro-store-DzeWkfqW.mjs → metro-store-CDDZHtPl.mjs} +1 -1
@@ -1,31 +1,36 @@
1
- import { _ as loadConfig$1, s as configCorruptRepair, u as getConcurrencyLimits } from "./config-lvc7d3C2.mjs";
2
- import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
3
- import { C as sharedBuildCache, O as workspaceDerivedData } from "./metro-CcEo8Fl8.mjs";
4
- import { D as makeTemporaryDirectory, E as filesystemDevice, a as dirtyFingerprintFiles, h as locallyKnownUpstream, k as temporaryRoot, m as listWorktrees, s as gitCommonDir, x as resolveSourceCheckout, y as repoRoot } from "./worktree-BVrHsYZG.mjs";
5
- import { a as findProjectRoot, n as appProjectProblem, r as detectIsExpo, s as isPackageResolvable } from "./project-CWlvEaeN.mjs";
6
- import { d as quotedPath, f as refuseNoProject, l as phaseLine, u as plural } from "./command-output-DVcpw6eE.mjs";
7
- import { C as diffFingerprintSources, D as fingerprintProject, h as listBuildLocks } from "./device-lease-5c3WrVRm.mjs";
8
- import { D as listAdbDevices, E as hostSystemImageArch, a as androidHome, ct as parkedMaxSetting, rt as POOL_SETTING_REMEDY } from "./android-d6rGq1pA.mjs";
9
- import { F as settingsLayers, G as resolveOptimizations, L as unknownSettingKeys, N as settingOrigin, O as remoteAndroidSetting, P as settingShapeErrors, R as webSettings, T as projectOptimizations, U as compilerCacheFallbackMessage, a as SETTING_SHAPE_REMEDY, i as SETTINGS_WITH_RESOLVE_TIME_FALLBACK, j as resolveSettings, k as remoteIosSetting, n as PATH_SETTINGS, v as iosSimSlimProfileSetting, y as mergeSettingsLayers } from "./settings-D61iNOsB.mjs";
10
- import { O as hostMemoryPressureAdvice, k as readHostMemoryPressure, l as listAllIosSims } from "./ios-BwDPrnYQ.mjs";
11
- import { o as listBuildSlots } from "./in-use-DlqBDKqY.mjs";
12
- import { a as findChrome, t as CHROME_INSTALL_REMEDY } from "./chrome-BPCiC3ZX.mjs";
13
- import { f as resolveEasCliBin, i as checkEasAuth$1, l as ownerFromConfig, u as providerFromConfig } from "./remote-cache-CcR6o6lI.mjs";
14
- import { D as MIN_EAS_CLI_SIMULATOR_VERSION, O as easCliSupport, k as easCliUpgradeRemedy, s as readInstalledEasCliVersion } from "./device-remote-D87zEzaq.mjs";
15
- import { n as inspectStimVersions, t as compareStimVersions } from "./stim-installations-CINXy_su.mjs";
16
- import { a as liveOwnedDeviceCount, d as MIN_EAS_CLI_BUILD_DOWNLOAD_VERSION, n as simslimIsOnPath } from "./simslim-DrDN4WuV.mjs";
17
- import { n as budgetLine, r as inspectBudget } from "./budget-CGDkhvv_.mjs";
18
- import { _ as androidPathRoomRemedy, a as apkOutputsDir, f as projectCmakeLauncher, g as androidPathRoomMessage, h as androidPathRoom, i as androidSdkRefusal, r as resolveAndroidCompilerCache, t as readAndroidCasToolchain } from "./android-cas-iZK3DOCr.mjs";
19
- import { h as podInstallCommand, i as ccacheEnabled, o as detectXcodeMajor } from "./xcode-Cp5KvK9x.mjs";
20
- import { n as recordDoctorRun } from "./guide-status-BZrFPIA_.mjs";
21
- import { n as hasInstalledDependencies, r as installedNpmTreeIsValid, t as dependencyState } from "./dependency-state-DsT7aKeo.mjs";
1
+ import { t as getExecutor } from "./exec-CaErVv7t.mjs";
2
+ import { _ as loadConfig$1, p as getProject, s as configCorruptRepair, u as getConcurrencyLimits } from "./config-D0DbpZtM.mjs";
3
+ import { d as quotedPath, f as refuseNoProject, l as phaseLine, u as plural } from "./command-output-DnJMdEY-.mjs";
4
+ import { B as settingsLayers, Ct as filesystemDevice, Et as temporaryRoot, H as unknownSettingKeys, I as settingOrigin, M as remoteIosSetting, O as projectOptimizations, P as resolveSettings, R as settingShapeErrors, U as webSettings, Y as compilerCacheFallbackMessage, Z as resolveOptimizations, a as SETTING_SHAPE_REMEDY, b as mergeSettingsLayers, dt as listWorktrees, ft as locallyKnownUpstream, gt as repoRoot, i as SETTINGS_WITH_RESOLVE_TIME_FALLBACK, it as gitCommonDir, j as remoteAndroidSetting, n as PATH_SETTINGS, nt as dirtyFingerprintFiles, vt as resolveSourceCheckout, wt as makeTemporaryDirectory, y as iosSimSlimProfileSetting } from "./settings-oTSkkNrM.mjs";
5
+ import { c as isPackageResolvable, n as appProjectProblem, o as findProjectRoot$1, r as detectIsExpo } from "./project-Du91Ilvc.mjs";
6
+ import { _ as workspaceDerivedData, d as sharedBuildCache } from "./workspace-state-B8-aJ1_5.mjs";
7
+ import { i as inspectBudget, n as budgetLine, z as inspectWatchmanMemory } from "./budget-DjrURfQn.mjs";
8
+ import { C as diffFingerprintSources, D as fingerprintProject, h as listBuildLocks } from "./device-lease-D-hLqx1b.mjs";
9
+ import { n as listBuildSlots } from "./build-slots-rUk77xqh.mjs";
10
+ import { D as hostSystemImageArch, O as listAdbDevices, ft as parkedMaxSetting, o as androidHome, ot as POOL_SETTING_REMEDY } from "./android-B_NumVyQ.mjs";
11
+ import { A as hostMemoryPressureAdvice, c as iosRuntimeMatches, j as readHostMemoryPressure, p as listIosRuntimes, u as listAllIosSims, y as pickDefaultIosCreation } from "./ios-BaInn3sW.mjs";
12
+ import { C as podInstallCommand, D as projectCmakeLauncher, S as bundlerPin, f as ccacheEnabled, i as macosToolchain, m as detectXcodeMajor, n as androidToolchain, r as iosToolchain, t as androidRequirements } from "./toolchain-DkUflduf.mjs";
13
+ import { b as realIo, h as inspectBuildMachines, o as offloadCheck, u as simulatorRuntime } from "./client-BTY686R6.mjs";
14
+ import { n as inspectDeviceHostMachines } from "./machines-BIBUdhpj.mjs";
15
+ import { a as findChrome, t as CHROME_INSTALL_REMEDY } from "./chrome-BrcVWQyD.mjs";
16
+ import { i as liveOwnedDeviceCount } from "./device-capacity-BHwxNBlu.mjs";
17
+ import { d as androidPathRoomRemedy, l as androidPathRoom, n as apkOutputsDir, t as androidSdkRefusal, u as androidPathRoomMessage } from "./gradle-BsO1MdsH.mjs";
18
+ import { h as resolveRuntime, p as resolveDeviceType } from "./support-D4aYJniO.mjs";
19
+ import { f as resolveEasCliBin, i as checkEasAuth$1, l as ownerFromConfig, u as providerFromConfig } from "./remote-cache-C8-V13XN.mjs";
20
+ import { _ as easCliSupport, g as MIN_EAS_CLI_SIMULATOR_VERSION, s as readInstalledEasCliVersion, v as easCliUpgradeRemedy } from "./device-remote-D5WVWqOq.mjs";
21
+ import { n as inspectStimVersions, t as compareStimVersions } from "./stim-installations-QMEI32v1.mjs";
22
+ import { n as simslimIsOnPath, r as MIN_EAS_CLI_BUILD_DOWNLOAD_VERSION } from "./simslim-B8-7AoTI.mjs";
23
+ import { r as resolveAndroidCompilerCache, t as readAndroidCasToolchain } from "./android-cas-8U4DPqeR.mjs";
24
+ import { r as maintenanceStatus, t as maintenanceLine } from "./status-DlKvVJBE.mjs";
25
+ import { n as recordDoctorRun } from "./guide-status-CjEZMAD4.mjs";
26
+ import { n as hasInstalledDependencies, r as installedNpmTreeIsValid, t as dependencyState } from "./dependency-state-B6cMXZab.mjs";
22
27
  import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync, rmSync } from "node:fs";
23
- import { InvalidArgumentError } from "commander";
28
+ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
24
29
  import { existsSync as existsSync$1, lstatSync as lstatSync$1, mkdirSync as mkdirSync$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, realpathSync as realpathSync$1, renameSync as renameSync$1, rmSync as rmSync$1, writeFileSync as writeFileSync$1 } from "fs";
25
- import { dirname, isAbsolute, join, relative, resolve, sep } from "path";
26
- import { readJsonObject, settingDefinition } from "@stim-cli/core/state";
27
- import { basename as basename$1, dirname as dirname$1, isAbsolute as isAbsolute$1, join as join$1, relative as relative$1, resolve as resolve$1, sep as sep$1 } from "node:path";
28
30
  import { homedir } from "os";
31
+ import { dirname as dirname$1, isAbsolute as isAbsolute$1, join as join$1, relative as relative$1, resolve as resolve$1, sep as sep$1 } from "path";
32
+ import { InvalidArgumentError } from "commander";
33
+ import { isJsonObject, maintenanceNdjsonFile, readHostedAppMetadata, readHostedSessions, readJsonObject, readStats, settingDefinition } from "@stim-cli/core/state";
29
34
  import chalk from "chalk";
30
35
  import * as expoFingerprint from "@expo/fingerprint";
31
36
  //#region src/diagnostics/sandbox.ts
@@ -48,8 +53,8 @@ function detectHarness(env = process.env) {
48
53
  function expandHome(value, home) {
49
54
  const v = value.trim();
50
55
  if (v === "~" || v === "$HOME") return home;
51
- if (v.startsWith("~/")) return join(home, v.slice(2));
52
- if (v.startsWith("$HOME/")) return join(home, v.slice(6));
56
+ if (v.startsWith("~/")) return join$1(home, v.slice(2));
57
+ if (v.startsWith("$HOME/")) return join$1(home, v.slice(6));
53
58
  return v;
54
59
  }
55
60
  /**
@@ -59,7 +64,7 @@ function expandHome(value, home) {
59
64
  */
60
65
  function sandboxBlocksStimHome(stimHome, home = homedir()) {
61
66
  const dir = expandHome(stimHome, home);
62
- const probe = join(dir, `.probe-${process.pid}-${Math.random().toString(36).slice(2)}`);
67
+ const probe = join$1(dir, `.probe-${process.pid}-${Math.random().toString(36).slice(2)}`);
63
68
  try {
64
69
  mkdirSync$1(dir, { recursive: true });
65
70
  writeFileSync$1(probe, "");
@@ -72,14 +77,14 @@ function sandboxBlocksStimHome(stimHome, home = homedir()) {
72
77
  }
73
78
  /** Where a per-user, gitignored Claude Code settings file belongs for this project. */
74
79
  function claudeLocalSettingsPath(root) {
75
- return join(root, ".claude", "settings.local.json");
80
+ return join$1(root, ".claude", "settings.local.json");
76
81
  }
77
82
  /** Every settings file that may already carry the allowance, most local first. */
78
83
  function allowanceSearchPaths(settingsRoot, home = homedir()) {
79
84
  return [
80
85
  claudeLocalSettingsPath(settingsRoot),
81
- join(settingsRoot, ".claude", "settings.json"),
82
- join(home, ".claude", "settings.json")
86
+ join$1(settingsRoot, ".claude", "settings.json"),
87
+ join$1(home, ".claude", "settings.json")
83
88
  ];
84
89
  }
85
90
  function isPlainObject(value) {
@@ -119,7 +124,7 @@ function nested(source, ...keys) {
119
124
  function covers(entry, target, home) {
120
125
  const posix = (value) => {
121
126
  const expanded = expandHome(value, home);
122
- return sep === "/" ? expanded : expanded.replaceAll(sep, "/");
127
+ return sep$1 === "/" ? expanded : expanded.replaceAll(sep$1, "/");
123
128
  };
124
129
  const e = posix(entry).replace(/\/+\*+$/, "").replace(/\/+$/, "");
125
130
  const t = posix(target).replace(/\/+$/, "");
@@ -191,7 +196,7 @@ function applyClaudeAllowance(path, allowance) {
191
196
  status: "refused",
192
197
  reason: `${path} holds a "${blocker}" that is not an object, so Stim cannot merge into it without discarding that value.`
193
198
  };
194
- mkdirSync$1(dirname(path), { recursive: true });
199
+ mkdirSync$1(dirname$1(path), { recursive: true });
195
200
  const tmp = `${path}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
196
201
  writeFileSync$1(tmp, `${JSON.stringify(withAllowance(existing, allowance), null, 2)}\n`);
197
202
  try {
@@ -252,14 +257,14 @@ function checkStorageLayout(projectRoot, { platform, host = process.platform, de
252
257
  const source = resolveSourceCheckout(target);
253
258
  check("Worktree copy", ["path" in source ? source.path : target, target], "Keep the source checkout and linked worktree on the same volume to share file blocks when warming.");
254
259
  const cache = sharedBuildCache();
255
- check("Cached app/APK staging", [cache, stagingRoot(join$1(cache, "artifact.app"))], temporaryFix);
260
+ check("Cached app/APK staging", [cache, stagingRoot(join(cache, "artifact.app"))], temporaryFix);
256
261
  const cacheFix = "Place STIM_BUILD_CACHE / machine caches.buildCache on the build-output volume, or accept the full-copy cost of keeping the cache on a separate volume.";
257
- if ((platform === "ios" || platform !== "android" && existsSync(join$1(projectRoot, "ios"))) && host === "darwin") {
258
- const output = join$1(workspaceDerivedData(projectRoot), "Build", "Products");
262
+ if ((platform === "ios" || platform !== "android" && existsSync(join(projectRoot, "ios"))) && host === "darwin") {
263
+ const output = join(workspaceDerivedData(projectRoot), "Build", "Products");
259
264
  check("iOS build-cache storage", [output, cache], cacheFix);
260
- check("iOS device app staging", [output, stagingRoot(join$1(output, "artifact.app"))], temporaryFix);
265
+ check("iOS device app staging", [output, stagingRoot(join(output, "artifact.app"))], temporaryFix);
261
266
  }
262
- if (platform === "android" || platform !== "ios" && existsSync(join$1(projectRoot, "android"))) check("Android build-cache storage", [apkOutputsDir(projectRoot), cache], cacheFix);
267
+ if (platform === "android" || platform !== "ios" && existsSync(join(projectRoot, "android"))) check("Android build-cache storage", [apkOutputsDir(projectRoot), cache], cacheFix);
263
268
  } catch (error) {
264
269
  findings.push({
265
270
  level: "cost",
@@ -343,23 +348,23 @@ function parseIosDebugArchitectures(output) {
343
348
  };
344
349
  }
345
350
  function inspectIosDebugArchitectures(projectRoot) {
346
- const iosRoot = join$1(projectRoot, "ios");
351
+ const iosRoot = join(projectRoot, "ios");
347
352
  if (!existsSync(iosRoot)) return [];
348
353
  const findings = [];
349
354
  const unknown = [];
350
355
  let projects;
351
356
  try {
352
- projects = readdirSync(iosRoot).filter((name) => name.endsWith(".xcodeproj")).toSorted().map((name) => join$1(iosRoot, name));
357
+ projects = readdirSync(iosRoot).filter((name) => name.endsWith(".xcodeproj")).toSorted().map((name) => join(iosRoot, name));
353
358
  } catch {
354
359
  projects = [];
355
360
  }
356
361
  if (!projects.length) unknown.push("app project");
357
- const pods = join$1(iosRoot, "Pods", "Pods.xcodeproj");
362
+ const pods = join(iosRoot, "Pods", "Pods.xcodeproj");
358
363
  if (existsSync(pods)) projects.push(pods);
359
- else if (existsSync(join$1(iosRoot, "Podfile"))) unknown.push("Pods project (not generated)");
364
+ else if (existsSync(join(iosRoot, "Podfile"))) unknown.push("Pods project (not generated)");
360
365
  const deadline = Date.now() + 6e4;
361
366
  for (const project of projects) {
362
- const label = relative$1(projectRoot, project);
367
+ const label = relative(projectRoot, project);
363
368
  const timeoutMs = Math.min(3e4, deadline - Date.now());
364
369
  if (timeoutMs <= 0) {
365
370
  unknown.push(label);
@@ -421,13 +426,13 @@ function children(path) {
421
426
  }
422
427
  }
423
428
  function moduleRoots(root) {
424
- const modules = [join$1(root, "android", "app")];
425
- const dependencies = join$1(root, "node_modules");
429
+ const modules = [join(root, "android", "app")];
430
+ const dependencies = join(root, "node_modules");
426
431
  for (const name of children(dependencies)) {
427
432
  if (name.startsWith(".")) continue;
428
- const entry = join$1(dependencies, name);
429
- const packages = name.startsWith("@") ? children(entry).map((child) => join$1(entry, child)) : [entry];
430
- modules.push(...packages.map((pkg) => join$1(pkg, "android")));
433
+ const entry = join(dependencies, name);
434
+ const packages = name.startsWith("@") ? children(entry).map((child) => join(entry, child)) : [entry];
435
+ modules.push(...packages.map((pkg) => join(pkg, "android")));
431
436
  }
432
437
  return modules;
433
438
  }
@@ -435,7 +440,7 @@ function cacheFiles(base, depth = 0) {
435
440
  const files = [];
436
441
  for (const name of children(base)) {
437
442
  if (depth === 0 && /^stim-[a-f0-9]{16}$/.test(name)) continue;
438
- const path = join$1(base, name);
443
+ const path = join(base, name);
439
444
  const entry = lstatSync(path);
440
445
  if (entry.isSymbolicLink()) continue;
441
446
  if (name === "CMakeCache.txt" && entry.isFile()) files.push(path);
@@ -444,8 +449,8 @@ function cacheFiles(base, depth = 0) {
444
449
  return files;
445
450
  }
446
451
  function readCxxLauncherStates(root) {
447
- return moduleRoots(root).flatMap((module) => cacheFiles(join$1(module, ".cxx")).map((path) => ({
448
- path: relative$1(root, path),
452
+ return moduleRoots(root).flatMap((module) => cacheFiles(join(module, ".cxx")).map((path) => ({
453
+ path: relative(root, path),
449
454
  launcher: parseCmakeCacheLauncher(readFileSync(path, "utf8"))
450
455
  })));
451
456
  }
@@ -455,13 +460,13 @@ function declaredLauncher(module) {
455
460
  "build.gradle.kts",
456
461
  "CMakeLists.txt"
457
462
  ].some((name) => {
458
- const path = join$1(module, name);
463
+ const path = join(module, name);
459
464
  return existsSync(path) && Boolean(projectCmakeLauncher(readFileSync(path, "utf8")));
460
465
  });
461
466
  }
462
467
  function contained(root, path) {
463
- const rel = relative$1(root, path);
464
- return rel !== "" && rel !== ".." && !rel.startsWith(`..${sep$1}`) && !isAbsolute$1(rel);
468
+ const rel = relative(root, path);
469
+ return rel !== "" && rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
465
470
  }
466
471
  function selectedCompilerCache(root) {
467
472
  try {
@@ -485,24 +490,24 @@ function repairCxxLauncherState(root) {
485
490
  };
486
491
  if (selectedCompilerCache(root) !== "ccache") return result;
487
492
  if (!getExecutor().findExecutable("ccache")) return result;
488
- const appOverrides = declaredLauncher(join$1(root, "android", "app"));
493
+ const appOverrides = declaredLauncher(join(root, "android", "app"));
489
494
  const targets = readCxxLauncherStates(root).filter(({ launcher }) => {
490
495
  if (!launcher) return true;
491
496
  const command = launcher.split(";")[0]?.trim();
492
- return Boolean(command && isAbsolute$1(command) && basename$1(command) === "ccache" && !existsSync(command));
497
+ return Boolean(command && isAbsolute(command) && basename(command) === "ccache" && !existsSync(command));
493
498
  });
494
499
  for (const state of targets) {
495
- const target = dirname$1(join$1(root, state.path));
496
- const label = relative$1(root, target);
500
+ const target = dirname(join(root, state.path));
501
+ const label = relative(root, target);
497
502
  try {
498
503
  let cxx = target;
499
- while (contained(root, cxx) && basename$1(cxx) !== ".cxx") cxx = dirname$1(cxx);
500
- if (basename$1(cxx) !== ".cxx" || target === cxx) throw new Error("unrecognized generated CMake layout");
501
- if (appOverrides || declaredLauncher(dirname$1(cxx))) throw new Error("project configures its own compiler launcher");
504
+ while (contained(root, cxx) && basename(cxx) !== ".cxx") cxx = dirname(cxx);
505
+ if (basename(cxx) !== ".cxx" || target === cxx) throw new Error("unrecognized generated CMake layout");
506
+ if (appOverrides || declaredLauncher(dirname(cxx))) throw new Error("project configures its own compiler launcher");
502
507
  const canonicalTarget = realpathSync(target);
503
508
  if (!contained(canonicalRoot, canonicalTarget)) throw new Error("generated directory resolves outside this checkout");
504
- if (realpathSync(cxx) !== join$1(realpathSync(dirname$1(cxx)), ".cxx")) throw new Error(".cxx is a symbolic link");
505
- const gitPath = relative$1(canonicalRoot, canonicalTarget);
509
+ if (realpathSync(cxx) !== join(realpathSync(dirname(cxx)), ".cxx")) throw new Error(".cxx is a symbolic link");
510
+ const gitPath = relative(canonicalRoot, canonicalTarget);
506
511
  if (getExecutor().runFile("git", [
507
512
  "ls-files",
508
513
  "-z",
@@ -643,7 +648,7 @@ function compilerCacheFinding({ fallback, optimizations, file }) {
643
648
  function missingPath(value, projectRoot, exists, trim = false) {
644
649
  const opened = trim ? value.trim() : value;
645
650
  if (opened.trim() === "") return null;
646
- const path = isAbsolute$1(opened) ? opened : resolve$1(projectRoot, opened);
651
+ const path = isAbsolute(opened) ? opened : resolve(projectRoot, opened);
647
652
  if (exists(path)) return null;
648
653
  return opened === opened.trim() ? path : JSON.stringify(path);
649
654
  }
@@ -657,15 +662,42 @@ function finding(level, title, detail, fix) {
657
662
  fix
658
663
  };
659
664
  }
665
+ function checkOffloadCandidate(placements, machines, peers, now) {
666
+ if (machines.length || !peers.some((peer) => isJsonObject(peer) && peer.OS === "macOS" && peer.Online === true)) return null;
667
+ const cold = placements.filter((placement) => {
668
+ const age = now - Date.parse(placement.at);
669
+ return placement.decision === "here" && !placement.failed && (placement.buildMs ?? 0) > 0 && age >= 0 && age <= 6048e5;
670
+ });
671
+ if (cold.length < 3) return null;
672
+ const average = cold.reduce((total, placement) => total + placement.buildMs, 0) / cold.length;
673
+ if (average <= 18e4) return null;
674
+ return {
675
+ code: "offload-candidate",
676
+ ...finding("note", "Builds could run on another Mac", `Cold builds averaged ~${Math.round(average / 6e4)} min over ${cold.length} builds this week.`, "In Stim Desktop, open Settings > Build machines > Add.")
677
+ };
678
+ }
679
+ function readOffloadCandidate(tailnetStatus, now, host) {
680
+ if (host !== "darwin") return null;
681
+ try {
682
+ const entries = loadConfig$1()?.offload?.machines;
683
+ const machines = Array.isArray(entries) ? entries.filter((entry) => typeof entry === "string") : [];
684
+ const placements = readStats().record?.placements ?? [];
685
+ if (machines.length || placements.length < 3) return null;
686
+ const status = tailnetStatus();
687
+ return checkOffloadCandidate(placements, machines, isJsonObject(status) && isJsonObject(status.Peer) ? Object.values(status.Peer) : [], now);
688
+ } catch {
689
+ return null;
690
+ }
691
+ }
660
692
  function mainCheckoutProjectRoot(projectRoot) {
661
693
  const currentRepoRoot = repoRoot(projectRoot);
662
694
  if (!currentRepoRoot) return projectRoot;
663
695
  try {
664
696
  const source = resolveSourceCheckout(currentRepoRoot);
665
697
  if ("refusal" in source) return projectRoot;
666
- const projectRel = relative(currentRepoRoot, realpathSync$1(projectRoot));
698
+ const projectRel = relative$1(currentRepoRoot, realpathSync$1(projectRoot));
667
699
  if (projectRel.startsWith("..")) return projectRoot;
668
- return resolve(source.path, projectRel);
700
+ return resolve$1(source.path, projectRel);
669
701
  } catch {
670
702
  return projectRoot;
671
703
  }
@@ -684,11 +716,11 @@ function brokenPodLinks(podsRoot) {
684
716
  }
685
717
  }
686
718
  function hasIosWarmOutput(root) {
687
- if (existsSync$1(join(root, "ios", "build"))) return true;
719
+ if (existsSync$1(join$1(root, "ios", "build"))) return true;
688
720
  const derivedData = workspaceDerivedData(root);
689
- if (existsSync$1(join(derivedData, "Build", "Products"))) return true;
721
+ if (existsSync$1(join$1(derivedData, "Build", "Products"))) return true;
690
722
  try {
691
- return readdirSync$1(derivedData).some((entry) => /^scheme-[a-f0-9]{64}$/.test(entry) && existsSync$1(join(derivedData, entry, "Build", "Products")));
723
+ return readdirSync$1(derivedData).some((entry) => /^scheme-[a-f0-9]{64}$/.test(entry) && existsSync$1(join$1(derivedData, entry, "Build", "Products")));
692
724
  } catch {
693
725
  return false;
694
726
  }
@@ -717,8 +749,8 @@ function mainOperation(root) {
717
749
  "--git-dir"
718
750
  ])?.trim();
719
751
  if (!gitDir) return null;
720
- if (existsSync$1(join(gitDir, "rebase-merge")) || existsSync$1(join(gitDir, "rebase-apply"))) return "rebase";
721
- return existsSync$1(join(gitDir, "MERGE_HEAD")) ? "merge" : null;
752
+ if (existsSync$1(join$1(gitDir, "rebase-merge")) || existsSync$1(join$1(gitDir, "rebase-apply"))) return "rebase";
753
+ return existsSync$1(join$1(gitDir, "MERGE_HEAD")) ? "merge" : null;
722
754
  }
723
755
  function dirtyTrackedPaths(root) {
724
756
  return (getExecutor().runFileQuiet("git", [
@@ -782,9 +814,9 @@ function checkMainCheckout(projectRoot, { npmTreeValid, brokenPods = void 0, ups
782
814
  if ((npmTreeValid === void 0 ? installedNpmTreeIsValid(dependencies.root) : npmTreeValid) === false) findings.push(finding("cost", "The source checkout dependency tree is stale", `npm reports that ${dependencies.root}/node_modules does not match the project dependency graph. Copying it makes each worktree start from the same invalid state.`, `Run \`${installCommand}\` before creating native worktrees, or let \`stim worktree warm --refresh\` run it from a linked worktree.`));
783
815
  }
784
816
  }
785
- const podfileLock = join(mainRoot, "ios", "Podfile.lock");
786
- const podManifest = join(mainRoot, "ios", "Pods", "Manifest.lock");
787
- const podsRoot = join(mainRoot, "ios", "Pods");
817
+ const podfileLock = join$1(mainRoot, "ios", "Podfile.lock");
818
+ const podManifest = join$1(mainRoot, "ios", "Pods", "Manifest.lock");
819
+ const podsRoot = join$1(mainRoot, "ios", "Pods");
788
820
  if (localIos && existsSync$1(podfileLock)) {
789
821
  let podsState = null;
790
822
  if (!existsSync$1(podManifest)) podsState = "missing";
@@ -794,18 +826,18 @@ function checkMainCheckout(projectRoot, { npmTreeValid, brokenPods = void 0, ups
794
826
  podsState = "stale";
795
827
  }
796
828
  if (podsState) {
797
- const iosRoot = join(mainRoot, "ios");
829
+ const iosRoot = join$1(mainRoot, "ios");
798
830
  const podCommand = `cd ${quotedPath(iosRoot)} && ${podInstallCommand(mainRoot)}`;
799
831
  findings.push(finding("cost", `The source checkout CocoaPods state is ${podsState}`, `ios/Pods cannot be reused safely because its Manifest.lock ${podsState === "missing" ? "is absent" : "does not match ios/Podfile.lock"}.`, `Run \`${podCommand}\` before creating native worktrees, or let \`stim worktree warm --refresh\` run it from a linked worktree.`));
800
832
  }
801
833
  const broken = brokenPods === void 0 && existsSync$1(podsRoot) ? brokenPodLinks(podsRoot) : brokenPods || [];
802
834
  if (broken.length) {
803
- const iosRoot = join(mainRoot, "ios");
835
+ const iosRoot = join$1(mainRoot, "ios");
804
836
  const podCommand = `cd ${quotedPath(iosRoot)} && ${podInstallCommand(mainRoot, "--clean-install")}`;
805
837
  findings.push(finding("cost", "The source checkout CocoaPods state has broken links", `${broken.length} symlink${broken.length === 1 ? "" : "s"} under ios/Pods point to missing files. Worktrees copy these broken links and can fail during compilation. First: ${broken[0]}.`, `Run \`${podCommand}\` before creating native worktrees.`));
806
838
  }
807
839
  }
808
- const coldPlatforms = [localIos && existsSync$1(join(mainRoot, "ios")) && !hasIosWarmOutput(mainRoot) ? "iOS" : null, platform !== "ios" && existsSync$1(join(mainRoot, "android")) && !existsSync$1(join(mainRoot, "android", "build")) && !existsSync$1(join(mainRoot, "android", "app", "build")) ? "Android" : null].filter((coldPlatform) => coldPlatform !== null);
840
+ const coldPlatforms = [localIos && existsSync$1(join$1(mainRoot, "ios")) && !hasIosWarmOutput(mainRoot) ? "iOS" : null, platform !== "ios" && existsSync$1(join$1(mainRoot, "android")) && !existsSync$1(join$1(mainRoot, "android", "build")) && !existsSync$1(join$1(mainRoot, "android", "app", "build")) ? "Android" : null].filter((coldPlatform) => coldPlatform !== null);
809
841
  if (coldPlatforms.length) findings.push(finding("note", `The source checkout has no ${coldPlatforms.join(" or ")} warm build output`, "The shared Stim artifact or compilation cache can still be warm. Building the source checkout once gives later native worktrees the strongest warm starting point.", `When more native worktrees are expected, run \`stim start\`, \`stim ${coldPlatforms[0] === "iOS" ? "ios" : "android"}\`, and \`stim stop\` from ${mainRoot}.`));
810
842
  if (linkedWorktrees === void 0 ? hasLinkedWorktree(projectRoot) : linkedWorktrees) findings.push(...seedFindings(mainRoot, upstream === void 0 ? locallyKnownUpstream(mainRoot) : upstream));
811
843
  return findings;
@@ -861,7 +893,7 @@ function checkCcacheInstalled(onPath) {
861
893
  }
862
894
  function launcherPath(launcher) {
863
895
  const command = launcher?.split(";")[0]?.trim();
864
- return command && isAbsolute(command) ? command : null;
896
+ return command && isAbsolute$1(command) ? command : null;
865
897
  }
866
898
  function checkCxxCompilerLauncher({ states, ccacheOnPath, launcherExists = existsSync$1 }) {
867
899
  const missing = states.map((state) => ({
@@ -933,7 +965,7 @@ function checkEasBuildDownload({ easJson = false, remoteEas = false, easCliResol
933
965
  return finding("cost", easCli.version ? `eas-cli ${easCli.version} cannot download EAS builds for --eas-profile` : "The eas-cli version could not be read for --eas-profile", `\`stim ios --eas-profile\` and \`stim android --eas-profile\` download a finished EAS development build with \`eas build:download --build-id\`, which needs eas-cli ${MIN_EAS_CLI_BUILD_DOWNLOAD_VERSION} or later. Older versions refuse with STIM_EAS_UNAVAILABLE before any download.`, `${easCliUpgradeRemedy(MIN_EAS_CLI_BUILD_DOWNLOAD_VERSION)}.`);
934
966
  }
935
967
  function projectEasBuildDownloadFinding(projectRoot, remoteBackends, lookupEasCli) {
936
- const easJson = existsSync$1(join(projectRoot, "eas.json"));
968
+ const easJson = existsSync$1(join$1(projectRoot, "eas.json"));
937
969
  return checkEasBuildDownload({
938
970
  easJson,
939
971
  remoteEas: remoteBackends.includes("eas"),
@@ -953,7 +985,7 @@ function checkIosHost(platform, host) {
953
985
  }
954
986
  function checkXcodeEnvLineEndings(projectRoot, platform, host) {
955
987
  if (platform === "android" || host !== "win32") return null;
956
- const xcodeEnv = join(projectRoot, "ios", ".xcode.env");
988
+ const xcodeEnv = join$1(projectRoot, "ios", ".xcode.env");
957
989
  if (!existsSync$1(xcodeEnv)) return null;
958
990
  let content;
959
991
  try {
@@ -964,10 +996,14 @@ function checkXcodeEnvLineEndings(projectRoot, platform, host) {
964
996
  if (!content.includes("\r")) return null;
965
997
  return finding("cost", "ios/.xcode.env has CRLF line endings", "An EAS build uploads the working-tree bytes of ios/.xcode.env, and Xcode sources that file with sh on the build worker, where each carriage return becomes part of the line and the build fails with `: command not found`. On Windows the usual cause is core.autocrlf=true rewriting the file at checkout.", "Run `git config core.autocrlf input` then `git checkout -- ios/.xcode.env`, or add `ios/.xcode.env text eol=lf` to .gitattributes and check the file out again.");
966
998
  }
967
- function runDoctor(projectRoot, { readFile = readFileSync$1, xcodeMajor = null, easAuth = checkEasAuth$1, concurrency = getConcurrencyLimits, liveDevices = null, activeBuilds = null, remoteEnv = process.env, lookupAgentDevice = null, lookupEasCli = null, lookupSimSlim = null, memoryPressure = readHostMemoryPressure, lookupCcache = null, lookupChrome, platform, host = process.platform } = {}) {
999
+ function configuredRemoteBackends(projectSettings, platform) {
1000
+ const iosTarget = remoteIosSetting(projectSettings);
1001
+ return [.../* @__PURE__ */ new Set([...platform !== "android" ? [iosTarget?.kind === "backend" ? iosTarget.backend : null] : [], ...platform !== "ios" ? [remoteAndroidSetting(projectSettings)] : []])].filter((backend) => backend !== null);
1002
+ }
1003
+ function runDoctor(projectRoot, { readFile = readFileSync$1, xcodeMajor = null, easAuth = checkEasAuth$1, concurrency = getConcurrencyLimits, liveDevices = null, activeBuilds = null, remoteEnv = process.env, lookupAgentDevice = null, lookupEasCli = null, lookupSimSlim = null, memoryPressure = readHostMemoryPressure, lookupCcache = null, lookupChrome, tailnetStatus = realIo.status, now = Date.now, platform, host = process.platform } = {}) {
968
1004
  const localIos = platform !== "android" && host === "darwin";
969
1005
  const read = (rel) => {
970
- const p = join(projectRoot, rel);
1006
+ const p = join$1(projectRoot, rel);
971
1007
  if (!existsSync$1(p)) return null;
972
1008
  try {
973
1009
  return readFile(p, "utf-8");
@@ -983,15 +1019,15 @@ function runDoctor(projectRoot, { readFile = readFileSync$1, xcodeMajor = null,
983
1019
  });
984
1020
  if (machineSettings.corrupt) return [machineSettings.corrupt];
985
1021
  const projectSettings = machineSettings.settings;
986
- const pkg = readJsonObject(join(projectRoot, "package.json"));
987
- const appConfig = readJsonObject(join(projectRoot, "app.json"));
1022
+ const pkg = readJsonObject(join$1(projectRoot, "package.json"));
1023
+ const appConfig = readJsonObject(join$1(projectRoot, "app.json"));
988
1024
  const dynamicConfig = appConfig ? null : [
989
1025
  "app.config.ts",
990
1026
  "app.config.js",
991
1027
  "app.config.mjs"
992
- ].find((f) => existsSync$1(join(projectRoot, f))) || null;
993
- const podfileProperties = readJsonObject(join(projectRoot, "ios", "Podfile.properties.json"));
994
- const podfile = read(join("ios", "Podfile"));
1028
+ ].find((f) => existsSync$1(join$1(projectRoot, f))) || null;
1029
+ const podfileProperties = readJsonObject(join$1(projectRoot, "ios", "Podfile.properties.json"));
1030
+ const podfile = read(join$1("ios", "Podfile"));
995
1031
  const metroConfig = read("metro.config.js") ?? read("metro.config.cjs");
996
1032
  const isExpo = detectIsExpo(projectRoot);
997
1033
  const expoRange = (pkg?.dependencies)?.expo || "";
@@ -1040,7 +1076,7 @@ function runDoctor(projectRoot, { readFile = readFileSync$1, xcodeMajor = null,
1040
1076
  profileError: simslimProfileError,
1041
1077
  onPath: simslimProfile ? lookupSimSlim ? lookupSimSlim() : simslimIsOnPath() : false
1042
1078
  });
1043
- const remoteBackends = [.../* @__PURE__ */ new Set([...platform !== "android" ? [remoteIosSetting(projectSettings)] : [], ...platform !== "ios" ? [remoteAndroidSetting(projectSettings)] : []])].filter((backend) => backend !== null);
1079
+ const remoteBackends = configuredRemoteBackends(projectSettings, platform);
1044
1080
  const daemonInEnv = Boolean(remoteEnv.AGENT_DEVICE_DAEMON_BASE_URL?.trim() && remoteEnv.AGENT_DEVICE_DAEMON_AUTH_TOKEN?.trim());
1045
1081
  const agentDeviceOnPath = remoteBackends.length ? lookupAgentDevice ? lookupAgentDevice() : agentDeviceIsOnPath() : false;
1046
1082
  const easCliResolvable = remoteBackends.includes("eas") ? lookupEasCli ? lookupEasCli() : Boolean(resolveEasCliBin(projectRoot)) : false;
@@ -1080,6 +1116,7 @@ function runDoctor(projectRoot, { readFile = readFileSync$1, xcodeMajor = null,
1080
1116
  easFinding,
1081
1117
  easBuildDownloadFinding,
1082
1118
  concurrencyFinding,
1119
+ readOffloadCandidate(tailnetStatus, now(), host),
1083
1120
  memoryAdvice ? finding("cost", "Host memory pressure can stall the iOS simulator", memoryAdvice, "Free host memory, then retry. Run `stim guide lifecycle simslim` for the optional memory reduction setup.") : null,
1084
1121
  simslimFinding,
1085
1122
  ...remoteFindings,
@@ -1105,13 +1142,13 @@ function checkAndroidPathRoom(projectRoot, platform, host, hostArch = process.ar
1105
1142
  };
1106
1143
  }
1107
1144
  function checkAndroidSdk(projectRoot, platform) {
1108
- const androidDir = join(projectRoot, "android");
1145
+ const androidDir = join$1(projectRoot, "android");
1109
1146
  if (platform === "ios" || platform !== "android" && !existsSync$1(androidDir)) return null;
1110
1147
  const sdkPath = androidHome();
1111
1148
  const refusal = androidSdkRefusal({
1112
1149
  sdkPath,
1113
1150
  sdkExists: existsSync$1(sdkPath),
1114
- hasLocalProperties: existsSync$1(join(androidDir, "local.properties"))
1151
+ hasLocalProperties: existsSync$1(join$1(androidDir, "local.properties"))
1115
1152
  });
1116
1153
  if (!refusal) return null;
1117
1154
  return {
@@ -1120,7 +1157,7 @@ function checkAndroidSdk(projectRoot, platform) {
1120
1157
  };
1121
1158
  }
1122
1159
  function androidCcacheFindings(projectRoot, platform, lookupCcache) {
1123
- if (platform !== "android" && !existsSync$1(join(projectRoot, "android"))) return [];
1160
+ if (platform !== "android" && !existsSync$1(join$1(projectRoot, "android"))) return [];
1124
1161
  const onPath = lookupCcache ? lookupCcache() : ccacheIsOnPath();
1125
1162
  let cxx;
1126
1163
  try {
@@ -1183,7 +1220,7 @@ function checkFingerprintParity({ projectHash, worktreeHash, changed = [], dirty
1183
1220
  }
1184
1221
  function gitMetadataAt(path) {
1185
1222
  try {
1186
- const git = lstatSync$1(join(path, ".git"));
1223
+ const git = lstatSync$1(join$1(path, ".git"));
1187
1224
  return git.isFile() || git.isDirectory();
1188
1225
  } catch {
1189
1226
  return false;
@@ -1198,14 +1235,14 @@ function listDirectory(dir) {
1198
1235
  }
1199
1236
  function hasLinkedPackageWithGit(projectRoot) {
1200
1237
  const root = repoRoot(projectRoot) ?? projectRoot;
1201
- for (let dir = projectRoot;; dir = dirname(dir)) {
1202
- if (linkedPackageWithGitIn(join(dir, "node_modules"))) return true;
1203
- if (dir === root || dirname(dir) === dir || relative(root, dirname(dir)).startsWith("..")) return false;
1238
+ for (let dir = projectRoot;; dir = dirname$1(dir)) {
1239
+ if (linkedPackageWithGitIn(join$1(dir, "node_modules"))) return true;
1240
+ if (dir === root || dirname$1(dir) === dir || relative$1(root, dirname$1(dir)).startsWith("..")) return false;
1204
1241
  }
1205
1242
  }
1206
1243
  function linkedPackageWithGitIn(nodeModules) {
1207
- return listDirectory(nodeModules).filter((entry) => !entry.startsWith(".")).flatMap((entry) => entry.startsWith("@") ? listDirectory(join(nodeModules, entry)).map((child) => join(entry, child)) : [entry]).some((name) => {
1208
- const root = join(nodeModules, name);
1244
+ return listDirectory(nodeModules).filter((entry) => !entry.startsWith(".")).flatMap((entry) => entry.startsWith("@") ? listDirectory(join$1(nodeModules, entry)).map((child) => join$1(entry, child)) : [entry]).some((name) => {
1245
+ const root = join$1(nodeModules, name);
1209
1246
  try {
1210
1247
  return lstatSync$1(root).isSymbolicLink() && gitMetadataAt(root);
1211
1248
  } catch {
@@ -1257,14 +1294,14 @@ async function detectFingerprintParity(projectRoot, { createFingerprint = expoFi
1257
1294
  "--git-dir"
1258
1295
  ], { timeoutMs: 1e4 }) == null) return null;
1259
1296
  if (hasInstalledDependencies(projectRoot)) return null;
1260
- const platform = selectedPlatform ?? (existsSync$1(join(projectRoot, "ios")) ? "ios" : existsSync$1(join(projectRoot, "android")) ? "android" : void 0);
1297
+ const platform = selectedPlatform ?? (existsSync$1(join$1(projectRoot, "ios")) ? "ios" : existsSync$1(join$1(projectRoot, "android")) ? "android" : void 0);
1261
1298
  let base;
1262
1299
  try {
1263
1300
  base = makeTemporaryDirectory(projectRoot, "stim-parity-");
1264
1301
  } catch {
1265
1302
  return null;
1266
1303
  }
1267
- const worktree = join(base, "head");
1304
+ const worktree = join$1(base, "head");
1268
1305
  if (exec.runFileQuiet("git", [
1269
1306
  "-C",
1270
1307
  projectRoot,
@@ -1327,11 +1364,65 @@ async function detectFingerprintParity(projectRoot, { createFingerprint = expoFi
1327
1364
  }
1328
1365
  }
1329
1366
  //#endregion
1367
+ //#region src/device-host/agent-driver.ts
1368
+ function hostedAgentDriverFinding(driver, apps) {
1369
+ if (apps === 0 || driver !== void 0 && driver !== "none") return null;
1370
+ return {
1371
+ code: "hosted-agent-driver",
1372
+ level: "note",
1373
+ title: `${apps === 1 ? "A hosted app runs" : `${apps} hosted apps run`} with no agent driver`,
1374
+ detail: "The client's coding agent cannot drive these apps because `hosting.agentDriver` is none, so stim-server starts no driver. agent-device confines macOS control to one macos-app lease and iOS control to one simulator through its daemon policy.",
1375
+ fix: "stim settings set hosting.agentDriver agent-device, with agent-device 0.21.20 or later"
1376
+ };
1377
+ }
1378
+ function runningHostedApps() {
1379
+ try {
1380
+ return readHostedSessions().filter((session) => {
1381
+ if (!["macos", "ios"].includes(session.platform) || session.state !== "ready" || !session.appAttempt) return false;
1382
+ try {
1383
+ return readHostedAppMetadata(session.id, session.appAttempt).state === "installed";
1384
+ } catch {
1385
+ return false;
1386
+ }
1387
+ }).length;
1388
+ } catch {
1389
+ return 0;
1390
+ }
1391
+ }
1392
+ /** Notes installed hosted macOS and iOS apps on this Mac while `hosting.agentDriver` starts no driver. */
1393
+ function inspectHostedAgentDriver() {
1394
+ const hosting = loadConfig$1()?.hosting;
1395
+ return hostedAgentDriverFinding(isJsonObject(hosting) ? hosting.agentDriver : void 0, runningHostedApps());
1396
+ }
1397
+ //#endregion
1330
1398
  //#region src/commands/doctor.ts
1331
1399
  function parseDoctorPlatform(value) {
1332
1400
  if (value === "ios" || value === "android") return value;
1333
1401
  throw new InvalidArgumentError("expected one of: ios, android");
1334
1402
  }
1403
+ /**
1404
+ * The simulator runtime `stim ios` would build for here: the one `ios.runtime` names, else the workspace's own
1405
+ * simulator's, else the one it would create a simulator on.
1406
+ */
1407
+ function iosTargetRuntime(root) {
1408
+ try {
1409
+ const settings = resolveSettings({
1410
+ projectPath: root,
1411
+ gitCommonDir: gitCommonDir(root),
1412
+ repoRoot: repoRoot(root) ?? root
1413
+ });
1414
+ const runtimes = listIosRuntimes();
1415
+ const runtime = resolveRuntime(null, settings);
1416
+ if (runtime) return runtimes.find((each) => iosRuntimeMatches(each, runtime))?.identifier ?? null;
1417
+ const udid = getProject(root)?.platforms?.ios?.deviceUdid;
1418
+ const own = typeof udid === "string" ? simulatorRuntime(udid) : null;
1419
+ if (own) return own;
1420
+ const deviceType = resolveDeviceType(null, settings) ?? void 0;
1421
+ return pickDefaultIosCreation([], runtimes, { deviceType })?.runtimeId ?? null;
1422
+ } catch {
1423
+ return null;
1424
+ }
1425
+ }
1335
1426
  function doctorTarget(platform) {
1336
1427
  if (platform === "ios") return "iOS";
1337
1428
  if (platform === "android") return "Android";
@@ -1434,8 +1525,8 @@ function applySandboxFix(root, env = process.env) {
1434
1525
  console.error(chalk.dim("Added writes to Stim's state directory, the simulator XPC service, and local port binding. Claude Code reads project settings from the directory a session starts in, so this file only counts for sessions rooted there. Restart the session for it to take effect."));
1435
1526
  }
1436
1527
  function doctorCommand(program, version, inspectVersions = inspectStimVersions, host = process.platform) {
1437
- program.command("doctor").description("Inspect the source checkout and report project state that can make native worktrees slow or invalid. The checkout is left untouched unless --fix is passed; --platform filters native findings. Each run in a React Native or Expo app is recorded in Stim state so guide can say when doctor is due.").option("--json", "print the findings as JSON").option("--platform <platform>", "report shared findings plus only this native platform: ios or android", parseDoctorPlatform).option("--fix", "repair the sandbox allowance when the report names it, and stale Android .cxx configurations in this checkout. Stop native builds first. Generated CMake output must be ignored and untracked; custom launcher settings and source files are preserved.").action(async (opts) => {
1438
- const root = findProjectRoot(process.cwd());
1528
+ program.command("doctor").description("Inspect the source checkout and report project state that can make native worktrees slow or invalid. The checkout is left untouched unless --fix is passed; --platform filters native findings. Each run in a React Native or Expo app is recorded in Stim state so guide can say when doctor is due.").option("--json", "print the findings as JSON").option("--platform <platform>", "report shared findings plus only this native platform: ios or android", parseDoctorPlatform).option("--fix", "repair the sandbox allowance when the report names it, and stale Android .cxx configurations in this checkout; ask each machine in offload.machines for build access. Stop native builds first. Generated CMake output must be ignored and untracked; custom launcher settings and source files are preserved.").action(async (opts) => {
1529
+ const root = findProjectRoot$1(process.cwd());
1439
1530
  if (!root) {
1440
1531
  refuseNoProject({ json: Boolean(opts.json) });
1441
1532
  return;
@@ -1470,19 +1561,62 @@ function doctorCommand(program, version, inspectVersions = inspectStimVersions,
1470
1561
  if (shadowed) findings.push(shadowed);
1471
1562
  const budget = await inspectBudget(root);
1472
1563
  findings.push(...budget.findings);
1564
+ const watchman = await inspectWatchmanMemory();
1565
+ if (watchman) findings.push(watchman);
1566
+ const checksIos = opts.platform !== "android" && host === "darwin";
1567
+ const checksAndroid = opts.platform === "android" || opts.platform === void 0 && (existsSync(join(root, "android")) || detectIsExpo(root));
1568
+ const checksMacos = host === "darwin" && opts.platform === void 0 && existsSync(join(root, "Package.swift"));
1569
+ const offloadTargets = () => [
1570
+ ...checksIos ? [{
1571
+ platform: "ios",
1572
+ local: iosToolchain(),
1573
+ runtime: iosTargetRuntime(root),
1574
+ cocoapodsPinned: bundlerPin(root) !== null
1575
+ }] : [],
1576
+ ...checksAndroid ? [{
1577
+ platform: "android",
1578
+ local: androidToolchain(),
1579
+ requires: androidRequirements(root)
1580
+ }] : [],
1581
+ ...checksMacos ? [{
1582
+ platform: "macos",
1583
+ local: macosToolchain()
1584
+ }] : []
1585
+ ];
1586
+ const buildMachines = await inspectBuildMachines({
1587
+ fix: opts.fix === true,
1588
+ check: checksIos || checksAndroid || checksMacos ? offloadCheck(root, offloadTargets) : null
1589
+ });
1590
+ findings.push(...buildMachines.findings);
1591
+ const deviceHosts = await inspectDeviceHostMachines({ fix: opts.fix === true });
1592
+ findings.push(...deviceHosts.findings);
1593
+ const agentDriver = inspectHostedAgentDriver();
1594
+ if (agentDriver) findings.push(agentDriver);
1595
+ const maintenance = maintenanceStatus();
1596
+ const maintenanceLines = [
1597
+ phaseLine("maintenance.mode", maintenance.mode),
1598
+ phaseLine("maintenance log", maintenanceNdjsonFile()),
1599
+ phaseLine("last pass", maintenanceLine(maintenance, false) ?? "no pass has run yet")
1600
+ ];
1473
1601
  if (opts.json) {
1474
1602
  console.log(JSON.stringify({
1475
1603
  project: root,
1476
1604
  platform: opts.platform ?? null,
1477
1605
  stim,
1478
1606
  budget: budget.report,
1607
+ maintenance: {
1608
+ ...maintenance,
1609
+ logPath: maintenanceNdjsonFile()
1610
+ },
1611
+ buildMachines: buildMachines.machines,
1612
+ deviceHosts: deviceHosts.machines,
1479
1613
  findings
1480
1614
  }));
1481
1615
  recordDoctorRun(root, opts.platform, version);
1482
1616
  return;
1483
1617
  }
1484
1618
  if (findings.length === 0) {
1485
- const lines = doctorSuccessLines(opts.platform, stim, budget.report);
1619
+ const lines = [...doctorSuccessLines(opts.platform, stim, budget.report), ...maintenanceLines];
1486
1620
  for (const [index, line] of lines.entries()) if (index === 1) console.log(chalk.green(line));
1487
1621
  else if (line && !line.startsWith(" ")) console.log(chalk.bold(line));
1488
1622
  else console.log(chalk.dim(line));
@@ -1491,7 +1625,11 @@ function doctorCommand(program, version, inspectVersions = inspectStimVersions,
1491
1625
  }
1492
1626
  const ordered = findings.toSorted((a, b) => a.level === b.level ? 0 : a.level === "cost" ? -1 : 1);
1493
1627
  console.log(chalk.bold(`Doctor (${doctorTarget(opts.platform)})`));
1494
- for (const line of [...stimVersionLines(stim), ...budgetLines(budget.report)]) console.log(chalk.dim(line));
1628
+ for (const line of [
1629
+ ...stimVersionLines(stim),
1630
+ ...budgetLines(budget.report),
1631
+ ...maintenanceLines
1632
+ ]) console.log(chalk.dim(line));
1495
1633
  for (const f of ordered) {
1496
1634
  const tag = f.level === "cost" ? chalk.yellow("costs time") : chalk.dim("note");
1497
1635
  console.log(`\n${tag} ${chalk.bold(f.title)}`);