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,24 +1,26 @@
1
- import { O as process_identity_exports, T as upsertProject, p as getProject } from "./config-lvc7d3C2.mjs";
2
- import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
3
- import { i as levelRank } from "./ndjson-CDY9IkVC.mjs";
4
- import { E as supervisorLogFile, M as workspaceLogsDir, S as ensureWorkspaceStorage, a as pidExists, b as writeWorkspaceState, c as resolveProjectMetro, f as reserveMetroPort, g as recordWorkspaceUse, h as readWorkspaceState$1, l as signalProcessTree, m as clearWorkspaceStateKeys } from "./metro-CcEo8Fl8.mjs";
5
- import { s as gitCommonDir, y as repoRoot } from "./worktree-BVrHsYZG.mjs";
6
- import { a as findProjectRoot, d as readAppJson, n as appProjectProblem, r as detectIsExpo, t as NO_PROJECT_REFUSAL, u as readAppConfigText } from "./project-CWlvEaeN.mjs";
7
- import { n as windowsLauncherArgs } from "./detached-entry-DJHoca49.mjs";
8
- import { c as readMetroTunnel, i as clearManagedMetroTunnel } from "./state-DHqvsbqC.mjs";
9
- import { a as withWorkspaceProcessLock, o as workspaceProcessLockError } from "./named-ports-Blg8IxOh.mjs";
10
- import { _ as stepTimer, l as phaseLine } from "./command-output-DVcpw6eE.mjs";
11
- import { A as resolveCacheProviderConfig, B as detectProviders, C as ngrokUrlSetting, E as publicUrlSetting, I as tunnelModeSetting, L as unknownSettingKeys, O as remoteAndroidSetting, P as settingShapeErrors, V as planMetroReach, a as SETTING_SHAPE_REMEDY, b as metroIdleStopMinutesSetting, d as cacheProviderSettingError, j as resolveSettings, k as remoteIosSetting, x as metroTunnelSettingError, z as PUBLIC_METRO_ENV } from "./settings-D61iNOsB.mjs";
12
- import { _ as withManagedTunnelLock, f as startTunnelSequence, h as withManagedRemoteWorktreeLock, m as terminateChild, p as stopTunnel, u as resolveSupervisorTarget } from "./stop-cause-hjGBOZTG.mjs";
13
- import { d as readProjectConfig } from "./remote-cache-CcR6o6lI.mjs";
14
- import { I as stopOwnedMetroForReset, t as budgetGate } from "./budget-CGDkhvv_.mjs";
1
+ import { t as getExecutor } from "./exec-CaErVv7t.mjs";
2
+ import { E as upsertProject, p as getProject } from "./config-D0DbpZtM.mjs";
3
+ import { t as process_identity_exports } from "./process-identity-BmRm1qV0.mjs";
4
+ import { n as windowsLauncherArgs } from "./detached-entry-CiTEgMR1.mjs";
5
+ import { _ as stepTimer, l as phaseLine } from "./command-output-DnJMdEY-.mjs";
6
+ import { C as metroTunnelSettingError, G as detectProviders, H as unknownSettingKeys, K as planMetroReach, M as remoteIosSetting, N as resolveCacheProviderConfig, P as resolveSettings, R as settingShapeErrors, S as metroPortSetting, T as ngrokUrlSetting, V as tunnelModeSetting, W as PUBLIC_METRO_ENV, a as SETTING_SHAPE_REMEDY, d as cacheProviderSettingError, f as deviceIdleShutdownMinutesSetting, gt as repoRoot, it as gitCommonDir, j as remoteAndroidSetting, k as publicUrlSetting, x as metroIdleStopMinutesSetting } from "./settings-oTSkkNrM.mjs";
7
+ import { d as readAppConfigText, f as readAppJson, n as appProjectProblem, o as findProjectRoot$1, r as detectIsExpo, t as NO_PROJECT_REFUSAL } from "./project-Du91Ilvc.mjs";
8
+ import { l as resolveProjectMetro, o as pidExists, p as reserveMetroPort, u as signalProcessTree } from "./metro-BeK1ZPz1.mjs";
9
+ import { c as writeWorkspaceState, g as workspaceAgentDeviceDir, i as recordWorkspaceUse, m as supervisorLogFile, n as clearWorkspaceStateKeys, r as readWorkspaceState$1, u as ensureWorkspaceStorage, x as workspaceLogsDir$1 } from "./workspace-state-B8-aJ1_5.mjs";
10
+ import { c as stopOwnedMetroForReset, t as budgetGate } from "./budget-DjrURfQn.mjs";
11
+ import { c as readMetroTunnel, i as clearManagedMetroTunnel } from "./state-DcW6vOd_.mjs";
12
+ import { a as workspaceLinks, i as workspaceLinkLine } from "./stim-desktop-Cnz8CPnl.mjs";
13
+ import { n as workspaceProcessLockError, t as withWorkspaceProcessLock } from "./workspace-process-lock-BL7Q0HCO.mjs";
14
+ import { a as terminateChild, c as withManagedTunnelLock, i as stopTunnel, o as withManagedRemoteWorktreeLock, r as startTunnelSequence, t as resolveSupervisorTarget } from "./ownership-hCkPzGmr.mjs";
15
+ import { i as levelRank } from "./ndjson-PclOIwVY.mjs";
16
+ import { t as spawnEntry } from "./spawn-entry-jw19m8Nl.mjs";
17
+ import { d as readProjectConfig } from "./remote-cache-C8-V13XN.mjs";
15
18
  import { appendFileSync, mkdirSync, openSync, readFileSync } from "node:fs";
19
+ import { dirname } from "node:path";
20
+ import { EventEmitter } from "node:events";
16
21
  import { existsSync as existsSync$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1 } from "fs";
17
- import { join } from "path";
22
+ import { join as join$1 } from "path";
18
23
  import { IDLE_STOP_KEY, queryLogs } from "@stim-cli/core/state";
19
- import { basename as basename$1, dirname as dirname$1, join as join$1 } from "node:path";
20
- import { EventEmitter } from "node:events";
21
- import { fileURLToPath } from "node:url";
22
24
  import chalk from "chalk";
23
25
  import { CACHE_PROVIDER_ENV, cacheProviderEnv } from "@stim-cli/cache";
24
26
  //#region src/workspace/app-id.ts
@@ -60,7 +62,7 @@ function detectAndroidPackage(projectRoot) {
60
62
  return detectAppIds(projectRoot).androidPackage;
61
63
  }
62
64
  function detectBundleIdFromPbxproj(projectRoot) {
63
- const iosDir = join(projectRoot, "ios");
65
+ const iosDir = join$1(projectRoot, "ios");
64
66
  if (!existsSync$1(iosDir)) return null;
65
67
  let entries;
66
68
  try {
@@ -70,7 +72,7 @@ function detectBundleIdFromPbxproj(projectRoot) {
70
72
  }
71
73
  for (const entry of entries) {
72
74
  if (!entry.isDirectory() || !entry.name.endsWith(".xcodeproj")) continue;
73
- const pbx = join(iosDir, entry.name, "project.pbxproj");
75
+ const pbx = join$1(iosDir, entry.name, "project.pbxproj");
74
76
  if (!existsSync$1(pbx)) continue;
75
77
  let text;
76
78
  try {
@@ -93,7 +95,7 @@ function detectBundleIdFromPbxproj(projectRoot) {
93
95
  return null;
94
96
  }
95
97
  function detectAndroidPackageFromGradle(projectRoot) {
96
- const gradle = join(projectRoot, "android", "app", "build.gradle");
98
+ const gradle = join$1(projectRoot, "android", "app", "build.gradle");
97
99
  if (!existsSync$1(gradle)) return null;
98
100
  let text;
99
101
  try {
@@ -108,18 +110,6 @@ function detectAndroidPackageFromGradle(projectRoot) {
108
110
  return null;
109
111
  }
110
112
  //#endregion
111
- //#region src/spawn-entry.ts
112
- const DEV_ENTRIES = {
113
- "supervisor-run": "./supervisor/run.ts",
114
- "collector-run": "./collector/run.ts",
115
- "web-run": "./web/run.ts"
116
- };
117
- function spawnEntry(name) {
118
- const here = fileURLToPath(import.meta.url);
119
- if (basename$1(dirname$1(here)) === "src") return fileURLToPath(new URL(DEV_ENTRIES[name], import.meta.url));
120
- return join$1(dirname$1(here), `${name}.mjs`);
121
- }
122
- //#endregion
123
113
  //#region src/commands/start.ts
124
114
  const DEFAULT_WAIT_SECONDS = 60;
125
115
  const POLL_MS = 500;
@@ -154,12 +144,13 @@ function liveSupervisor({ state, project, port, isAlive = pidExists, inspectIden
154
144
  startedAt: target.startedAt ?? null
155
145
  } : null;
156
146
  }
157
- function startFacts({ port, supervisor, logsDir, alreadyRunning }) {
147
+ function startFacts({ root, port, supervisor, logsDir, alreadyRunning }) {
158
148
  return {
159
149
  port,
160
150
  supervisorPid: supervisor?.pid ?? null,
161
151
  mode: supervisor?.mode ?? null,
162
152
  logsDir,
153
+ ...root ? { agentDevice: { stateDir: workspaceAgentDeviceDir(root) } } : {},
163
154
  alreadyRunning: Boolean(alreadyRunning)
164
155
  };
165
156
  }
@@ -194,14 +185,14 @@ function recordedSupervisorProcess(pid) {
194
185
  });
195
186
  }
196
187
  const RECORDED_SUPERVISOR_WAIT_MS = 15e3;
197
- function providersOnPath() {
188
+ function providersOnPath(mode) {
198
189
  return detectProviders((bin) => {
199
190
  try {
200
191
  return Boolean(getExecutor().findExecutable(bin));
201
192
  } catch {
202
193
  return false;
203
194
  }
204
- });
195
+ }, mode);
205
196
  }
206
197
  const DEFAULT_START_DEPS = {
207
198
  providers: providersOnPath,
@@ -260,7 +251,7 @@ function registerStart(program, overrides = {}) {
260
251
  remedy: "Pass a whole number of seconds, e.g. --wait 90."
261
252
  });
262
253
  const waitSeconds = wait.seconds;
263
- const root = findProjectRoot(process.cwd());
254
+ const root = findProjectRoot$1(process.cwd());
264
255
  if (!root) return fail(NO_PROJECT_REFUSAL);
265
256
  const projectProblem = appProjectProblem(root);
266
257
  if (projectProblem) return fail({
@@ -313,7 +304,8 @@ function registerStart(program, overrides = {}) {
313
304
  out,
314
305
  facts: result.facts,
315
306
  waited: result.waited,
316
- reclaimed: result.reclaimed
307
+ reclaimed: result.reclaimed,
308
+ links: workspaceLinks(root)
317
309
  });
318
310
  });
319
311
  }
@@ -348,7 +340,7 @@ async function startDevServer({ root, settings, waitSeconds = DEFAULT_WAIT_SECON
348
340
  message: settingError,
349
341
  remedy: "Set metro.tunnel to \"ngrok\" and metro.ngrokUrl to an HTTPS URL, or remove metro.ngrokUrl."
350
342
  });
351
- const remote = remoteFlag || remoteIosSetting(settings) !== null || remoteAndroidSetting(settings) !== null;
343
+ const remote = remoteFlag || remoteIosSetting(settings)?.kind === "backend" || remoteAndroidSetting(settings) !== null;
352
344
  const tunnelMode = tunnelModeSetting(settings) ?? "auto";
353
345
  const publicUrl = publicUrlSetting(settings);
354
346
  const tunnel = wantsExpoOwnTunnel({
@@ -376,6 +368,11 @@ async function startDevServer({ root, settings, waitSeconds = DEFAULT_WAIT_SECON
376
368
  };
377
369
  if (resetCache) {
378
370
  await gateBudget();
371
+ const pin = metroPortSetting(root);
372
+ if (pin.port !== null || pin.error) {
373
+ const pinCheck = await resolveWorkspaceMetroPort(root, note, "start", false);
374
+ if (typeof pinCheck !== "number") return fail(pinCheck);
375
+ }
379
376
  try {
380
377
  await stopOwnedMetroForReset(root);
381
378
  } catch (error) {
@@ -388,9 +385,9 @@ async function startDevServer({ root, settings, waitSeconds = DEFAULT_WAIT_SECON
388
385
  }
389
386
  note(phaseLine("cache", "clearing this app's Metro transform and file-map caches; devices preserved"));
390
387
  }
391
- const logsDir = workspaceLogsDir(root);
388
+ const logsDir = workspaceLogsDir$1(root);
392
389
  const logFile = supervisorLogFile(root);
393
- const port = await resolvePort(root, note);
390
+ const port = await resolvePort(root, note, fail);
394
391
  let publicOrigin = remote ? publicUrl : null;
395
392
  let resolution = await resolveProjectMetro(port, root);
396
393
  let supervisor = liveSupervisor({
@@ -510,7 +507,9 @@ async function startDevServer({ root, settings, waitSeconds = DEFAULT_WAIT_SECON
510
507
  ...tunnel ? ["--tunnel"] : [],
511
508
  ...resetCache ? ["--reset-cache"] : [],
512
509
  "--idle-stop-minutes",
513
- String(metroIdleStopMinutesSetting(settings))
510
+ String(metroIdleStopMinutesSetting(settings)),
511
+ "--device-idle-minutes",
512
+ String(deviceIdleShutdownMinutesSetting(settings))
514
513
  ];
515
514
  const childEnv = {
516
515
  ...process.env,
@@ -540,7 +539,7 @@ async function startDevServer({ root, settings, waitSeconds = DEFAULT_WAIT_SECON
540
539
  return null;
541
540
  };
542
541
  if (remote && !tunnel && !publicUrl && tunnelMode !== "off") {
543
- const available = d.providers();
542
+ const available = d.providers(tunnelMode);
544
543
  const plan = planMetroReach({
545
544
  mode: tunnelMode,
546
545
  metroPort: port,
@@ -554,7 +553,7 @@ async function startDevServer({ root, settings, waitSeconds = DEFAULT_WAIT_SECON
554
553
  remedy: plan.remedy
555
554
  });
556
555
  if ("start" in plan) {
557
- const candidates = tunnelMode === "auto" ? available : [plan.start];
556
+ const candidates = tunnelMode === "auto" ? available.filter((provider) => provider !== "tailscale") : [plan.start];
558
557
  const expectedStableUrl = ngrokUrlSetting(settings);
559
558
  let acquisition;
560
559
  try {
@@ -722,6 +721,7 @@ async function startDevServer({ root, settings, waitSeconds = DEFAULT_WAIT_SECON
722
721
  if ("failed" in acquisition) return fail(acquisition.failed);
723
722
  publicOrigin = acquisition.origin;
724
723
  managedTunnel = acquisition.tunnel;
724
+ if (managedTunnel.record.provider === "tailscale") note(phaseLine("lan", `tailnet-only Metro: ${publicOrigin}`));
725
725
  resolution = await resolveProjectMetro(port, root);
726
726
  supervisor = liveSupervisor({
727
727
  state: readWorkspaceState$1(root),
@@ -786,6 +786,7 @@ async function startDevServer({ root, settings, waitSeconds = DEFAULT_WAIT_SECON
786
786
  if (managedTunnelExited()) return failExitedManagedTunnel();
787
787
  clearWorkspaceStateKeys(root, [IDLE_STOP_KEY]);
788
788
  return startFacts({
789
+ root,
789
790
  port,
790
791
  supervisor,
791
792
  logsDir,
@@ -838,6 +839,7 @@ async function startDevServer({ root, settings, waitSeconds = DEFAULT_WAIT_SECON
838
839
  requireExpoTunnel();
839
840
  if (managedTunnelExited()) return failExitedManagedTunnel();
840
841
  return startFacts({
842
+ root,
841
843
  port,
842
844
  supervisor,
843
845
  logsDir,
@@ -903,6 +905,7 @@ async function startDevServer({ root, settings, waitSeconds = DEFAULT_WAIT_SECON
903
905
  startedAt: null
904
906
  };
905
907
  return startFacts({
908
+ root,
906
909
  port,
907
910
  supervisor,
908
911
  logsDir,
@@ -911,7 +914,7 @@ async function startDevServer({ root, settings, waitSeconds = DEFAULT_WAIT_SECON
911
914
  };
912
915
  const startLocked = async () => {
913
916
  try {
914
- return await withWorkspaceProcessLock(dirname$1(workspaceLogsDir(root)), "metro-start", runStart, { external: true });
917
+ return await withWorkspaceProcessLock(dirname(workspaceLogsDir$1(root)), "metro-start", runStart, { external: true });
915
918
  } catch (error) {
916
919
  if (workspaceProcessLockError(error) !== "timeout") throw error;
917
920
  return fail({
@@ -956,24 +959,69 @@ async function startDevServer({ root, settings, waitSeconds = DEFAULT_WAIT_SECON
956
959
  };
957
960
  }
958
961
  }
959
- async function resolvePort(root, note) {
962
+ async function resolvePort(root, note, fail) {
963
+ const result = await resolveWorkspaceMetroPort(root, note, "start");
964
+ return typeof result === "number" ? result : fail(result);
965
+ }
966
+ async function resolveWorkspaceMetroPort(root, note, command, reserve = true) {
967
+ const setting = metroPortSetting(root);
968
+ if (setting.error) return {
969
+ code: "STIM_BAD_ARG",
970
+ message: setting.error,
971
+ remedy: SETTING_SHAPE_REMEDY
972
+ };
973
+ const pinned = setting.port;
960
974
  const project = getProject(root);
961
975
  const recorded = project?.metroPort;
962
- if (!recorded) return await reserveMetroPort(root);
963
- const supervisor = resolveSupervisorTarget({
964
- state: readWorkspaceState$1(root)?.supervisor,
965
- record: project.supervisor,
966
- reservedPort: recorded
967
- });
968
- if (supervisor.status !== "none" && supervisor.status !== "stale") return recorded;
969
- const held = await resolveProjectMetro(recorded, root);
970
- if (!held.notOurs) return recorded;
971
- const fresh = await reserveMetroPort(root);
972
- if (fresh !== recorded) {
973
- note(chalk.yellow(`Port ${recorded} is held by something else (${held.notOurs}).`));
974
- note(chalk.dim(`Reserved port ${fresh} for this project instead.`));
976
+ if (pinned === null && command === "web") return recorded ?? await reserveMetroPort(root);
977
+ if (recorded) {
978
+ const supervisor = resolveSupervisorTarget({
979
+ state: readWorkspaceState$1(root)?.supervisor,
980
+ record: project.supervisor,
981
+ reservedPort: recorded
982
+ });
983
+ if (supervisor.status !== "none" && supervisor.status !== "stale") {
984
+ if (pinned === null || recorded === pinned) return recorded;
985
+ if (supervisor.status === "unverified") return {
986
+ code: "STIM_BAD_ARG",
987
+ message: `Cannot move this workspace's Metro to metro.port ${pinned}: ${supervisor.reason}.`,
988
+ remedy: `Stop that supervisor with the tool that started it, then retry, or restore metro.port to ${recorded}. Stim leaves unverified processes alone.`
989
+ };
990
+ return {
991
+ code: "STIM_BAD_ARG",
992
+ message: `This workspace's dev server is running on port ${recorded}, and metro.port pins it to ${pinned}.`,
993
+ remedy: "Run `stim stop`, then retry, or unset or restore metro.port."
994
+ };
995
+ }
996
+ }
997
+ if (pinned !== null) {
998
+ const held = await resolveProjectMetro(pinned, root);
999
+ if (held.notOurs) return {
1000
+ code: "STIM_BAD_ARG",
1001
+ message: `Port ${pinned} is held by something else (${held.notOurs}), and metro.port pins this workspace's Metro to it.`,
1002
+ remedy: "Stop the process on that port, or set another metro.port."
1003
+ };
1004
+ if (recorded === pinned) return recorded;
1005
+ } else if (recorded) {
1006
+ const held = await resolveProjectMetro(recorded, root);
1007
+ if (!held.notOurs) return recorded;
1008
+ const fresh = await reserveMetroPort(root);
1009
+ if (fresh !== recorded) {
1010
+ note(chalk.yellow(`Port ${recorded} is held by something else (${held.notOurs}).`));
1011
+ note(chalk.dim(`Reserved port ${fresh} for this project instead.`));
1012
+ }
1013
+ return fresh;
1014
+ }
1015
+ if (!reserve && pinned !== null) return pinned;
1016
+ try {
1017
+ return await reserveMetroPort(root, void 0, void 0, pinned);
1018
+ } catch (error) {
1019
+ return {
1020
+ code: "STIM_BAD_ARG",
1021
+ message: error.message,
1022
+ remedy: "Give each workspace its own metro.port, in the workspace layer or the environment."
1023
+ };
975
1024
  }
976
- return fresh;
977
1025
  }
978
1026
  async function waitForMetro({ root, port, seconds, aborted = () => false, probe = resolveProjectMetro }) {
979
1027
  const deadline = Date.now() + seconds * 1e3;
@@ -1014,17 +1062,20 @@ function failureEvidence({ logFile, logsDir, sinceTs }) {
1014
1062
  if (recent.length > 0) lines.push(fellBack ? "Full records: `stim logs`" : "Full records: `stim logs --errors`");
1015
1063
  return lines;
1016
1064
  }
1017
- function report({ json, out, facts, waited, reclaimed }) {
1065
+ function report({ json, out, facts, waited, reclaimed, links }) {
1018
1066
  if (json) {
1019
- console.log(JSON.stringify(reclaimed.length ? {
1067
+ console.log(JSON.stringify({
1020
1068
  ...facts,
1021
- reclaimed
1022
- } : facts));
1069
+ ...links ? { links } : {},
1070
+ ...reclaimed.length ? { reclaimed } : {}
1071
+ }));
1023
1072
  return;
1024
1073
  }
1025
1074
  const who = facts.supervisorPid ? `supervisor pid ${facts.supervisorPid}${facts.mode ? ` (${facts.mode})` : ""}` : "started outside Stim";
1026
1075
  out(chalk.green(`OK: dev server on port ${facts.port}, ${who}${facts.alreadyRunning ? " (already running)" : ""} ${waited}`));
1027
1076
  out(chalk.dim(phaseLine("logs", facts.logsDir)));
1077
+ if (facts.agentDevice) out(chalk.dim(phaseLine("agent-device", `AGENT_DEVICE_STATE_DIR=${facts.agentDevice.stateDir}`)));
1078
+ if (links) console.error(chalk.dim(workspaceLinkLine(links)));
1028
1079
  }
1029
1080
  //#endregion
1030
- export { registerStart as a, startFacts as c, wantsExpoOwnTunnel as d, spawnEntry as f, detectBundleId as h, readLogTail as i, supervisorEntry as l, detectAppIds as m, liveSupervisor as n, startCommand as o, detectAndroidPackage as p, parseWait as r, startDevServer as s, failureEvidence as t, tailLines as u };
1081
+ export { registerStart as a, startDevServer as c, tailLines as d, wantsExpoOwnTunnel as f, detectBundleId as h, readLogTail as i, startFacts as l, detectAppIds as m, liveSupervisor as n, resolveWorkspaceMetroPort as o, detectAndroidPackage as p, parseWait as r, startCommand as s, failureEvidence as t, supervisorEntry as u };
@@ -0,0 +1,41 @@
1
+ import { t as process_identity_exports } from "./process-identity-BmRm1qV0.mjs";
2
+ import { a as updateWorkspaceState, r as readWorkspaceState$1, v as workspaceDir, x as workspaceLogsDir$1 } from "./workspace-state-B8-aJ1_5.mjs";
3
+ import { join } from "node:path";
4
+ import { readMacosRecord } from "@stim-cli/core/state";
5
+ //#region src/macos/state.ts
6
+ const macosDir = (root) => join(workspaceDir(root), "macos");
7
+ const macosLogFile = (root) => join(workspaceLogsDir$1(root), "macos.ndjson");
8
+ const macosRuntimeClaim = (root) => join(macosDir(root), "runtime.lock");
9
+ function macosProcess(pid) {
10
+ const processToken = (0, process_identity_exports.captureProcessToken)(pid);
11
+ const start = (0, process_identity_exports.processStartMicros)(pid);
12
+ if (!processToken || start.status !== "running") throw Object.assign(/* @__PURE__ */ new Error(`Cannot verify macOS process ${pid}; refusing unmanaged launch.`), { code: "STIM_CLAIM_UNAVAILABLE" });
13
+ return {
14
+ pid,
15
+ processToken,
16
+ startedAtMicros: start.startedAtMicros
17
+ };
18
+ }
19
+ function requiredMacosRecord(root) {
20
+ const record = readMacosRecord(root);
21
+ if (!record && readWorkspaceState$1(root)?.macos !== void 0) throw Object.assign(/* @__PURE__ */ new Error("The recorded macOS owner is malformed; repair its workspace state before running."), { code: "STIM_MACOS_OWNER_UNVERIFIED" });
22
+ return record;
23
+ }
24
+ function updateMacosRecord(root, owner, patch) {
25
+ let updated = false;
26
+ updateWorkspaceState(root, (state) => {
27
+ const record = readMacosRecord(root);
28
+ if (!record || !(0, process_identity_exports.sameProcessRecord)(record.supervisor, owner)) return state;
29
+ updated = true;
30
+ return {
31
+ ...state,
32
+ macos: {
33
+ ...record,
34
+ ...patch
35
+ }
36
+ };
37
+ });
38
+ return updated;
39
+ }
40
+ //#endregion
41
+ export { requiredMacosRecord as a, macosRuntimeClaim as i, macosLogFile as n, updateMacosRecord as o, macosProcess as r, macosDir as t };
@@ -0,0 +1,12 @@
1
+ import "@stim-cli/core/state";
2
+ import * as import__stim_cli_core_process_identity from "@stim-cli/core/process-identity";
3
+ //#endregion
4
+ //#region src/supervisor/state.d.ts
5
+ declare const MODE_BARE = "bare-inproc";
6
+ declare const MODE_EXPO = "expo-child";
7
+ declare function clearWorkspaceSupervisor(root: string, expected?: process_identity_d_exports.ProcessRecord | null): boolean;
8
+ declare function clearExpoMetroTunnel(root: string): void;
9
+ declare function writePidFile(root: string, pid: number): string;
10
+ declare function readPidFile(root: string): number | null;
11
+ //#endregion
12
+ export { readPidFile as a, clearWorkspaceSupervisor as i, MODE_EXPO as n, writePidFile as o, clearExpoMetroTunnel as r, MODE_BARE as t };
@@ -1,8 +1,10 @@
1
- import { A as ownership_claim_exports, O as process_identity_exports } from "./config-lvc7d3C2.mjs";
2
- import { M as workspaceLogsDir, _ as updateWorkspaceState, a as pidExists, b as writeWorkspaceState, h as readWorkspaceState$1, k as workspaceDir, p as clearWorkspaceStateKey } from "./metro-CcEo8Fl8.mjs";
3
- import { readlinkSync, rmSync } from "node:fs";
4
- import { WEB_VIEWPORTS } from "@stim-cli/core/state";
1
+ import { n as ownership_claim_exports, t as process_identity_exports } from "./process-identity-BmRm1qV0.mjs";
2
+ import { o as pidExists } from "./metro-BeK1ZPz1.mjs";
3
+ import { a as updateWorkspaceState, c as writeWorkspaceState, r as readWorkspaceState$1, t as clearWorkspaceStateKey, v as workspaceDir, x as workspaceLogsDir$1 } from "./workspace-state-B8-aJ1_5.mjs";
4
+ import { readFileSync, readlinkSync, rmSync } from "node:fs";
5
5
  import { join } from "node:path";
6
+ import { rotatedLogPath } from "@stim-cli/core";
7
+ import { WEB_VIEWPORTS } from "@stim-cli/core/state";
6
8
  import { hostname } from "node:os";
7
9
  //#region src/web/profile.ts
8
10
  const SINGLETON_FILES = [
@@ -56,13 +58,13 @@ function webClaimRoot(root) {
56
58
  return join(webDir(root), "supervisor.lock");
57
59
  }
58
60
  function webLogFile(root) {
59
- return join(workspaceLogsDir(root), "web.ndjson");
61
+ return join(workspaceLogsDir$1(root), "web.ndjson");
60
62
  }
61
63
  function webSupervisorLogFile(root) {
62
- return join(workspaceLogsDir(root), "web-supervisor.log");
64
+ return join(workspaceLogsDir$1(root), "web-supervisor.log");
63
65
  }
64
66
  function browserLogFile(root) {
65
- return join(workspaceLogsDir(root), "browser.log");
67
+ return join(workspaceLogsDir$1(root), "browser.log");
66
68
  }
67
69
  function processRecord(value) {
68
70
  const record = value;
@@ -144,5 +146,65 @@ function webFacts(record) {
144
146
  status: supervisor === "same" && chrome === "running" ? "running" : "orphaned"
145
147
  };
146
148
  }
149
+ const PAGE_FAILURES = /* @__PURE__ */ new Set(["web_document_failed", "web_page_crashed"]);
150
+ const NAVIGATION = Buffer.from("\"event\":\"web_navigation\"");
151
+ const PAGE_EVENT = "\"event\":\"web_";
152
+ /**
153
+ * The latest page load in the web log's `lines`: the newest `web_navigation` marker, whether a document failure, a
154
+ * crash or the load event followed it, and the newest in-app route change after it. Null before the first
155
+ * navigation.
156
+ */
157
+ function latestPageLoad(lines) {
158
+ let failure = null;
159
+ let loaded = false;
160
+ let route = null;
161
+ for (let i = lines.length - 1; i >= 0; i--) {
162
+ let record;
163
+ try {
164
+ record = JSON.parse(lines[i]);
165
+ } catch {
166
+ continue;
167
+ }
168
+ if (record.event === "web_navigation" && typeof record.url === "string") {
169
+ const moved = route !== null && route !== record.url ? { route } : {};
170
+ if (failure !== null) return {
171
+ url: record.url,
172
+ state: "failed",
173
+ error: failure,
174
+ ...moved
175
+ };
176
+ return {
177
+ url: record.url,
178
+ state: loaded ? "loaded" : "loading",
179
+ ...moved
180
+ };
181
+ }
182
+ if (typeof record.event !== "string") continue;
183
+ if (record.event === "web_route" && typeof record.url === "string") route ??= record.url;
184
+ if (PAGE_FAILURES.has(record.event)) failure = typeof record.msg === "string" ? record.msg : record.event;
185
+ else if (record.event === "web_page_loaded") loaded = true;
186
+ }
187
+ return null;
188
+ }
189
+ /** The page lines from the newest navigation on, in the web log or, right after a rotation, its previous file. */
190
+ function pageLines(file) {
191
+ for (const path of [file, rotatedLogPath(file)]) {
192
+ let bytes;
193
+ try {
194
+ bytes = readFileSync(path);
195
+ } catch {
196
+ continue;
197
+ }
198
+ const at = bytes.lastIndexOf(NAVIGATION);
199
+ if (at < 0) continue;
200
+ const start = bytes.lastIndexOf(10, at) + 1;
201
+ return bytes.subarray(start).toString("utf8").split("\n").filter((line) => line.includes(PAGE_EVENT));
202
+ }
203
+ return null;
204
+ }
205
+ function readWebPage(root) {
206
+ const lines = pageLines(webLogFile(root));
207
+ return lines ? latestPageLoad(lines) : null;
208
+ }
147
209
  //#endregion
148
- export { readWebRecord as a, webDir as c, webProfileDir as d, webSupervisorLogFile as f, removeSingletonFiles as g, liveProfileHolder as h, clearWebRecord as i, webFacts as l, chromeProcessState as m, browserLogFile as n, updateWebRecord as o, writeWebRecord as p, cdpEndpoint as r, webClaimRoot as s, CDP_PORT_LABEL as t, webLogFile as u };
210
+ export { removeSingletonFiles as _, readWebPage as a, webClaimRoot as c, webLogFile as d, webProfileDir as f, liveProfileHolder as g, chromeProcessState as h, clearWebRecord as i, webDir as l, writeWebRecord as m, browserLogFile as n, readWebRecord as o, webSupervisorLogFile as p, cdpEndpoint as r, updateWebRecord as s, CDP_PORT_LABEL as t, webFacts as u };
@@ -1,5 +1,6 @@
1
- import { O as process_identity_exports, V as parseDeviceSlotKey, z as deviceSlotKey } from "./config-lvc7d3C2.mjs";
2
- import { D as supervisorPidFile, P as workspaceStateFile, _ as updateWorkspaceState, h as readWorkspaceState, p as clearWorkspaceStateKey, v as withWorkspaceStateLock } from "./metro-CcEo8Fl8.mjs";
1
+ import { B as parseDeviceSlotKey, R as deviceSlotKey } from "./config-D0DbpZtM.mjs";
2
+ import { t as process_identity_exports } from "./process-identity-BmRm1qV0.mjs";
3
+ import { C as workspaceStateFile, a as updateWorkspaceState, h as supervisorPidFile, o as withWorkspaceStateLock, r as readWorkspaceState, t as clearWorkspaceStateKey } from "./workspace-state-B8-aJ1_5.mjs";
3
4
  import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
5
  import { dirname } from "node:path";
5
6
  //#region src/supervisor/state.ts
@@ -22,7 +23,7 @@ function readMetroTunnel(root) {
22
23
  const startedAt = record.startedAt;
23
24
  const processToken = record.processToken;
24
25
  const logFile = record.logFile;
25
- if (provider !== "ngrok" && provider !== "cloudflared" || typeof pid !== "number" || typeof port !== "number") return null;
26
+ if (provider !== "ngrok" && provider !== "cloudflared" && provider !== "tailscale" || typeof pid !== "number" || typeof port !== "number") return null;
26
27
  return {
27
28
  kind: "managed",
28
29
  provider,
@@ -88,11 +89,13 @@ function readRemoteSession(root) {
88
89
  const platform = record.platform;
89
90
  const startedAt = record.startedAt;
90
91
  const webPreviewUrl = record.webPreviewUrl;
92
+ const deviceType = record.deviceType;
91
93
  return {
92
94
  platform: platform === "ios" || platform === "android" ? platform : null,
93
95
  sessionId: id,
94
96
  startedAt: typeof startedAt === "string" ? startedAt : null,
95
- webPreviewUrl: typeof webPreviewUrl === "string" && webPreviewUrl.length > 0 ? webPreviewUrl : null
97
+ webPreviewUrl: typeof webPreviewUrl === "string" && webPreviewUrl.length > 0 ? webPreviewUrl : null,
98
+ deviceType: typeof deviceType === "string" && deviceType.length > 0 ? deviceType : null
96
99
  };
97
100
  }
98
101
  function readRemoteSessionId(root) {
@@ -0,0 +1,77 @@
1
+ import { j as formatBytes } from "./config-D0DbpZtM.mjs";
2
+ import { o as formatLongDuration } from "./command-output-DnJMdEY-.mjs";
3
+ import { gt as repoRoot, it as gitCommonDir } from "./settings-oTSkkNrM.mjs";
4
+ import { o as findProjectRoot$1 } from "./project-Du91Ilvc.mjs";
5
+ import { i as getSwiftpmCacheUsage, n as swiftpmCacheLines, r as getAgentDeviceUsage, t as agentDeviceLines } from "./agent-device-usage-output-Dpih1Tfa.mjs";
6
+ import { archivedUsage, readArchives, readStatsReport, statsProjectKey } from "@stim-cli/core/state";
7
+ import chalk from "chalk";
8
+ //#region src/commands/stats.ts
9
+ const PLATFORMS = ["ios", "android"];
10
+ const PLATFORM_WIDTH = 9;
11
+ const RECENT_PLACEMENTS = 5;
12
+ function statsCommand(program) {
13
+ program.command("stats").description("Show how many `ios` and `android` runs this project and this machine have recorded, how many hit the build cache, and an estimate of the time that cache saved.").option("--json", "print the aggregates as JSON").action(async (opts) => {
14
+ const agentDevice = await getAgentDeviceUsage();
15
+ const swiftpmCache = await getSwiftpmCacheUsage();
16
+ const root = findProjectRoot$1(process.cwd());
17
+ const key = root ? statsProjectKey({
18
+ root,
19
+ commonDir: gitCommonDir(root),
20
+ repoRoot: repoRoot(root)
21
+ }) : null;
22
+ const { report, note } = readStatsReport(key, Date.now());
23
+ report.agentDevice = agentDevice;
24
+ report.swiftpmCache = swiftpmCache;
25
+ if (note) console.error(chalk.dim(note));
26
+ const { machine, project, offload } = report;
27
+ if (opts.json) {
28
+ console.log(JSON.stringify(report));
29
+ return;
30
+ }
31
+ const lines = [];
32
+ if (project) lines.push(`project ${project.key}`, ...sectionLines(project));
33
+ lines.push("machine", ...sectionLines(machine));
34
+ const usage = archivedUsage(readArchives());
35
+ lines.push("archive", ` archived workspaces: ${usage.count}, ${formatBytes(usage.bytes)}`);
36
+ if (offload.placements.length || Object.keys(offload.machines).length) lines.push(...placementLines(offload));
37
+ const agentLines = agentDeviceLines(report.agentDevice);
38
+ if (agentLines.length) lines.push(...agentLines, chalk.dim(" Stim never trims or deletes the shared runner builds, sessions, logs and other state or the hosted driver dir; a workspace's own agent-device dir goes only with its workspace."));
39
+ lines.push(...swiftpmCacheLines(report.swiftpmCache));
40
+ for (const line of lines) console.log(line);
41
+ });
42
+ }
43
+ function sectionLines(scope) {
44
+ const lines = PLATFORMS.filter((platform) => scope[platform]).map((platform) => bucketLine(platform, scope[platform]));
45
+ return lines.length ? lines : [chalk.dim(" no runs recorded")];
46
+ }
47
+ function placementLines(offload) {
48
+ const { here, offloaded, fellBack } = offload.today;
49
+ const lines = ["build placement", ` ${"today".padEnd(PLATFORM_WIDTH)}${here} here, ${offloaded} offloaded, ${fellBack} fell back`];
50
+ for (const [name, { today, total }] of Object.entries(offload.machines)) {
51
+ const saved = total.savedMs === 0 ? "" : `, ${total.savedMs > 0 ? "saved" : "lost"} ~${formatLongDuration(Math.abs(total.savedMs))} (estimated)`;
52
+ lines.push(` ${name}: today ${today.offloaded} offloaded, ${today.fallbacks} fell back; total ${total.offloaded} offloaded (${average(total.offloadedMs, total.offloaded)} avg)${saved}, ${total.fallbacks} fell back`);
53
+ }
54
+ for (const placement of offload.placements.slice(0, RECENT_PLACEMENTS)) {
55
+ const where = placement.decision === "here" ? "here" : `${placement.decision} ${placement.machine ?? ""}`.trim();
56
+ lines.push(chalk.dim(` ${placement.at.slice(0, 16).replace("T", " ")}Z ${placement.platform} ${where}: ${placement.reason}`));
57
+ }
58
+ return lines;
59
+ }
60
+ function bucketLine(platform, bucket) {
61
+ const finished = bucket.runs - bucket.failed;
62
+ const cells = [
63
+ `${bucket.runs} runs${bucket.failed > 0 ? ` (${bucket.failed} failed)` : ""}`,
64
+ `${bucket.hits} hits (${finished > 0 ? `${Math.round(bucket.hits / finished * 100)}%` : "-"})`,
65
+ `cold run ${average(bucket.coldRunMs, bucket.coldRuns)} avg`,
66
+ `hit run ${average(bucket.hitRunMs, bucket.hitRuns)} avg`,
67
+ ...bucket.offloadedRuns ? [`offloaded run ${average(bucket.offloadedRunMs ?? 0, bucket.offloadedRuns)} avg (${bucket.offloadedRuns}, last on ${bucket.lastOffloadHost ?? "?"})`] : [],
68
+ `saved ~${formatLongDuration(bucket.timeSavedMs)} (estimated)`,
69
+ `since ${bucket.firstRunAt.slice(0, 10)}`
70
+ ];
71
+ return ` ${platform.padEnd(PLATFORM_WIDTH)}${cells.join(" ")}`;
72
+ }
73
+ function average(totalMs, runs) {
74
+ return runs > 0 ? formatLongDuration(Math.round(totalMs / runs)) : "-";
75
+ }
76
+ //#endregion
77
+ export { statsCommand as default };