stim 1.8.0 → 1.10.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 (91) hide show
  1. package/README.md +23 -155
  2. package/dist/{android-DWssqHQ8.mjs → android-787HhP5w.mjs} +168 -39
  3. package/dist/{android-BRgBYzU0.mjs → android-DBkeHwpm.mjs} +250 -200
  4. package/dist/{android-DeYdjjBP.mjs → android-DPov-fv2.mjs} +2 -2
  5. package/dist/{android-cas-SWIjzeuL.mjs → android-cas-CITmIeWY.mjs} +22 -14
  6. package/dist/android-cas-compiler.mjs +2 -2
  7. package/dist/app-install-CrJ6t_bv.mjs +819 -0
  8. package/dist/budget-BP4jZZL4.mjs +620 -0
  9. package/dist/build-progress-BXx-ZBe4.mjs +512 -0
  10. package/dist/{cache-manifest-CYJi8-Hx.mjs → cache-manifest-Pubfp8wV.mjs} +4 -4
  11. package/dist/cache-manifest.mjs +1 -1
  12. package/dist/cli.mjs +22 -19
  13. package/dist/collector-run.d.mts +7 -1
  14. package/dist/collector-run.mjs +10 -9
  15. package/dist/{command-output-B3ycpsYv.mjs → command-output-D5z2_bcF.mjs} +11 -2
  16. package/dist/{config-B7iyKWkL.mjs → config-BEq0Qw_a.mjs} +134 -121
  17. package/dist/{dependency-state-D0M7oKA7.mjs → dependency-state-r9UesA9a.mjs} +2 -2
  18. package/dist/{deps-oCXVNEqQ.mjs → deps-DP4eWqea.mjs} +8 -5
  19. package/dist/{detached-entry-Bpo6ZY_I.mjs → detached-entry-DJHoca49.mjs} +1 -1
  20. package/dist/{device-BUoZyMoR.mjs → dev-client-92U593KT.mjs} +1090 -660
  21. package/dist/{device-RdujjHLB.mjs → device-DhTZe7L4.mjs} +13 -20
  22. package/dist/{build-lock-CgtsB6TV.mjs → device-lease-CbEaIk7b.mjs} +481 -27
  23. package/dist/{device-pool-BxRwdufO.mjs → device-pool-Di8Q-9Ro.mjs} +7 -7
  24. package/dist/{device-remote-ByN8P6CB.mjs → device-remote-Dy1jGj3U.mjs} +645 -119
  25. package/dist/{doctor-BMt156qe.mjs → doctor-B-inAJmt.mjs} +137 -113
  26. package/dist/{error-diagnostics-D1Kaf-eT.mjs → error-diagnostics-xxWKNO3-.mjs} +22 -17
  27. package/dist/exec-DK8KaVHK.mjs +133 -0
  28. package/dist/{gc-v2YcoJvm.mjs → gc-ChPFZMRC.mjs} +836 -813
  29. package/dist/{guide-BTAlD1_y.mjs → guide-DwmaD_Wa.mjs} +953 -209
  30. package/dist/guide-status-CWvti2Lg.mjs +125 -0
  31. package/dist/idle-DpRLp5lj.mjs +157 -0
  32. package/dist/{build-slots-C-oAHRT1.mjs → in-use-CjUluqIX.mjs} +102 -8
  33. package/dist/{ios-BcCiI4TS.mjs → ios-B2Fpqvlb.mjs} +60 -71
  34. package/dist/{ios-BP04pY5f.mjs → ios-Dd-hYJBh.mjs} +241 -234
  35. package/dist/{ios-device-CFXA46ez.mjs → ios-device-BiOsSVzd.mjs} +3 -2
  36. package/dist/{ios-device-CauFJiYx.mjs → ios-device-CtRdEVjx.mjs} +149 -52
  37. package/dist/launch-verify-CQS_Kg-C.mjs +365 -0
  38. package/dist/{logs-CakdBTtm.mjs → logs-MARIy6m9.mjs} +20 -15
  39. package/dist/metro-BWLOJ_Wr.mjs +409 -0
  40. package/dist/{named-ports-DzMOlmaX.mjs → named-ports-DiTfSz6p.mjs} +4 -4
  41. package/dist/{ndjson-BZwI1NBv.mjs → ndjson-CDY9IkVC.mjs} +23 -43
  42. package/dist/{ndjson-DvHSsNU9.d.mts → ndjson-Du3j0AQ9.d.mts} +1 -0
  43. package/dist/{ownership-Cp52QAK1.mjs → ownership-D2xbLVoz.mjs} +2 -3
  44. package/dist/{teardown-BJqEzPo_.mjs → ownership-DZjdx8Gq.mjs} +664 -612
  45. package/dist/{ports-BIbJaKNW.mjs → ports-CLSSFOuz.mjs} +3 -3
  46. package/dist/{process-output-Dl5uKE03.mjs → process-output-Fpl8PYi8.mjs} +1 -1
  47. package/dist/{project-DDZWf_16.mjs → project-BXZzOABt.mjs} +8 -3
  48. package/dist/{reload-BdypPbjM.mjs → reload-DoETJ3rl.mjs} +11 -13
  49. package/dist/{server-bare-BQkyxa_b.mjs → server-bare-DVbb7kXH.mjs} +2 -2
  50. package/dist/{server-expo-BVAA1OqJ.mjs → server-expo-BvOCeakp.mjs} +8 -7
  51. package/dist/server-expo-DBVTdult.mjs +2 -0
  52. package/dist/{settings-BXXorVM2.mjs → settings-DOugPlPz.mjs} +59 -159
  53. package/dist/settings-EWYNwe72.mjs +378 -0
  54. package/dist/settings.schema.json +1064 -0
  55. package/dist/simslim-Ca3487H6.mjs +436 -0
  56. package/dist/{slot-launch-ZUUe0TQP.mjs → slot-launch-C5cg0fmk.mjs} +4 -4
  57. package/dist/spawn-claims-CUleC2cM.mjs +49 -0
  58. package/dist/{start-CmxtOKjU.mjs → start-F9gv5dcC.mjs} +159 -114
  59. package/dist/start-VoFsPSes.mjs +2 -0
  60. package/dist/{state-DZobzxLW.mjs → state-WGdIbi_d.mjs} +5 -3
  61. package/dist/{stats-BYcZ6n1h.mjs → stats-BAgJaLA4.mjs} +66 -5
  62. package/dist/{stats-CLg5RuDS.mjs → stats-DBYJ5bxL.mjs} +4 -4
  63. package/dist/status-DLCD-o3s.mjs +764 -0
  64. package/dist/status-Dr4fRkym.mjs +216 -0
  65. package/dist/{guide-status-Craq9z0t.mjs → stim-installations-CINXy_su.mjs} +3 -122
  66. package/dist/{stop-C8JW6kKp.mjs → stop-Dj4pfmyv.mjs} +111 -31
  67. package/dist/{stop-B1kOpYei.mjs → stop-ZvOeTmKb.mjs} +2 -2
  68. package/dist/supervisor-run.d.mts +11 -2
  69. package/dist/supervisor-run.mjs +228 -42
  70. package/dist/{workspace-process-lock-BYfZz_S-.mjs → workspace-process-lock-BXA96myD.mjs} +14 -9
  71. package/dist/workspaces-BTUyFfkv.mjs +1003 -0
  72. package/dist/worktree-4aZg2NnI.mjs +2 -0
  73. package/dist/{worktree-CFLA-6MX.mjs → worktree-BMS7GeqB.mjs} +523 -61
  74. package/dist/{worktree-FG_ZKKdb.mjs → worktree-BlizKDpZ.mjs} +200 -54
  75. package/dist/{xcode-B6YaWeg0.mjs → xcode-B2tBkf0m.mjs} +66 -57
  76. package/package.json +6 -6
  77. package/skill/SKILL.md +2 -0
  78. package/dist/app-install-BDbsSgCl.mjs +0 -1175
  79. package/dist/dev-client-BmT-ioFI.mjs +0 -797
  80. package/dist/device-lease-Ffz_M5t0.mjs +0 -359
  81. package/dist/exec-B29XNuhP.mjs +0 -77
  82. package/dist/logs-query-BGL-Pl12.mjs +0 -290
  83. package/dist/metro-C7bcJ0uo.mjs +0 -233
  84. package/dist/ownership-wolE-0Do.mjs +0 -470
  85. package/dist/public-http-probe-CyMDrQ3c.mjs +0 -61
  86. package/dist/reclaim-KJ-0uWEF.mjs +0 -361
  87. package/dist/server-expo-YDDG3eP7.mjs +0 -2
  88. package/dist/spawn-entry-nCE6m770.mjs +0 -14
  89. package/dist/status-DdkLki47.mjs +0 -426
  90. package/dist/workspace-state-BT5E3SA2.mjs +0 -146
  91. package/dist/{metro-store-CDDZHtPl.mjs → metro-store-DzeWkfqW.mjs} +1 -1
@@ -0,0 +1,764 @@
1
+ import { F as volumeRootFor, H as projectDeviceSlots, g as loadConfig$1, u as getConfigDir } from "./config-BEq0Qw_a.mjs";
2
+ import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
3
+ import { a as parseNdjsonLine } from "./ndjson-CDY9IkVC.mjs";
4
+ import { A as workspaceLogErrorIndex, a as processCwd, j as workspaceLogsDir, l as isMetroRunning, m as readWorkspaceState$1, n as listeningPids, o as processCwds, r as listeningPidsByPort, s as resolveProjectMetro } from "./metro-BWLOJ_Wr.mjs";
5
+ import { c as gitCommonDirOnDisk, p as linkedWorktreesOnDisk, s as gitCommonDir, y as repoRoot } from "./worktree-BlizKDpZ.mjs";
6
+ import { l as projectShortcut, o as findProjectRoot } from "./project-BXZzOABt.mjs";
7
+ import { l as readRemoteSession } from "./state-WGdIbi_d.mjs";
8
+ import { i as listLeaseFiles } from "./device-lease-CbEaIk7b.mjs";
9
+ import { a as buildStatusLine, c as createActivityReader, i as buildReport, o as parseActiveBuild, r as activeBuildState, t as ACTIVE_BUILD_KEY } from "./build-progress-BXx-ZBe4.mjs";
10
+ import { N as ownedAvdSerialResolver, c as androidToolPath } from "./android-787HhP5w.mjs";
11
+ import { b as readParked, h as POOL_SETTING_REMEDY, t as resolveSupervisorTarget, y as parkedMaxSetting } from "./ownership-DZjdx8Gq.mjs";
12
+ import { _ as parseSimctlList, u as listAllIosSimsAsync } from "./ios-B2Fpqvlb.mjs";
13
+ import { m as readEasSessionLedger$1, p as easMachineStateRoot$1 } from "./device-remote-Dy1jGj3U.mjs";
14
+ import { a as diskLine, c as parseDfFree, d as remoteDeviceState, f as tightVolumes, i as deviceLeaseStates, l as poolLine, n as capacity, o as environmentState, p as unprovisionedWorktrees, r as deviceLeaseLines, t as activityLabel, u as remoteDeviceLine } from "./status-Dr4fRkym.mjs";
15
+ import { a as statsProjectKey, r as readStats } from "./stats-BAgJaLA4.mjs";
16
+ import { i as readSupervisorState } from "./stop-Dj4pfmyv.mjs";
17
+ import { readdirSync, watch } from "node:fs";
18
+ import { closeSync as closeSync$1, existsSync as existsSync$1, fstatSync as fstatSync$1, openSync as openSync$1, readFileSync as readFileSync$1, readSync as readSync$1, renameSync as renameSync$1, rmSync as rmSync$1, writeFileSync as writeFileSync$1 } from "fs";
19
+ import { basename, dirname, join } from "path";
20
+ import { rotatedLogPath } from "@stim-cli/core";
21
+ import { ERROR_SOURCES, logFiles, queryLogs, readIdleStop, recordMatches } from "@stim-cli/core/state";
22
+ import { join as join$1 } from "node:path";
23
+ import { totalmem } from "os";
24
+ import chalk from "chalk";
25
+ //#region src/status-watch.ts
26
+ const WATCH_FALLBACK_MS = 3e4;
27
+ const WATCH_SIMCTL_INTERVAL_MS = 2e3;
28
+ const ADB_RESTART_MIN_MS = 1e3;
29
+ const ADB_RESTART_MAX_MS = 6e4;
30
+ const SIMCTL_TIMEOUT_MS = 1e4;
31
+ /**
32
+ * Coalesces change signals into refreshes: a burst of triggers runs `run` once
33
+ * after `debounceMs` of quiet, at most one run is in flight, and a trigger that
34
+ * arrives during a run causes exactly one more run after it.
35
+ */
36
+ function createRefreshScheduler({ debounceMs, run }) {
37
+ let timer = null;
38
+ let running = false;
39
+ let pending = false;
40
+ let stopped = false;
41
+ const fire = async () => {
42
+ timer = null;
43
+ running = true;
44
+ try {
45
+ await run();
46
+ } finally {
47
+ running = false;
48
+ if (pending) {
49
+ pending = false;
50
+ trigger();
51
+ }
52
+ }
53
+ };
54
+ function trigger(delayMs = debounceMs) {
55
+ if (stopped) return;
56
+ if (running) {
57
+ pending = true;
58
+ return;
59
+ }
60
+ if (timer) clearTimeout(timer);
61
+ timer = setTimeout(() => void fire(), delayMs);
62
+ }
63
+ return {
64
+ trigger,
65
+ stop() {
66
+ stopped = true;
67
+ if (timer) clearTimeout(timer);
68
+ timer = null;
69
+ }
70
+ };
71
+ }
72
+ /**
73
+ * Whether a change to `name` in a watched `$STIM_HOME` directory can change
74
+ * the status payload. A null name means the platform did not report one.
75
+ */
76
+ function changeAffectsStatus(dir, name) {
77
+ if (name === null) return true;
78
+ if (name.includes(".lock")) return false;
79
+ switch (dir) {
80
+ case "home": return name.startsWith("config.json") || name === "workspaces" || name === "device-locks";
81
+ case "workspace": return name.startsWith("state.json") || name === "logs";
82
+ case "eas": return name.startsWith("sessions.json");
83
+ default: return true;
84
+ }
85
+ }
86
+ /** A stable summary of every simulator's state, or null when the listing is unreadable. */
87
+ function simulatorSignature(simctlJson) {
88
+ try {
89
+ return parseSimctlList(simctlJson, { includeUnavailable: true }).map((sim) => `${sim.udid}:${sim.state}`).toSorted().join(",");
90
+ } catch {
91
+ return null;
92
+ }
93
+ }
94
+ /**
95
+ * Calls `onChange` when anything `stim status` reports may have changed:
96
+ * `$STIM_HOME` files, adb device arrivals and departures, and simulator
97
+ * state. simctl has no push API reachable from Node, so it is polled.
98
+ */
99
+ function watchStatusSources({ home, onChange, platform = process.platform, simctlIntervalMs = WATCH_SIMCTL_INTERVAL_MS }) {
100
+ let stopped = false;
101
+ const watchers = /* @__PURE__ */ new Map();
102
+ const desiredDirs = () => {
103
+ const dirs = /* @__PURE__ */ new Map([
104
+ [home, "home"],
105
+ [join$1(home, "workspaces"), "workspaces"],
106
+ [join$1(home, "device-locks"), "leases"],
107
+ [easMachineStateRoot$1(), "eas"]
108
+ ]);
109
+ let names = [];
110
+ try {
111
+ names = readdirSync(join$1(home, "workspaces"));
112
+ } catch {}
113
+ for (const name of names) {
114
+ const dir = join$1(home, "workspaces", name);
115
+ dirs.set(dir, "workspace");
116
+ dirs.set(join$1(dir, "logs"), "logs");
117
+ }
118
+ return dirs;
119
+ };
120
+ const reconcile = () => {
121
+ if (stopped) return;
122
+ const desired = desiredDirs();
123
+ for (const [dir, watcher] of watchers) if (!desired.has(dir)) {
124
+ watcher.close();
125
+ watchers.delete(dir);
126
+ }
127
+ for (const [dir, kind] of desired) {
128
+ if (watchers.has(dir)) continue;
129
+ try {
130
+ const watcher = watch(dir, (_event, name) => {
131
+ if (!changeAffectsStatus(kind, name === null ? null : String(name))) return;
132
+ if (kind === "home" || kind === "workspaces" || kind === "workspace") reconcile();
133
+ onChange();
134
+ });
135
+ watcher.on("error", () => {
136
+ watcher.close();
137
+ watchers.delete(dir);
138
+ });
139
+ watchers.set(dir, watcher);
140
+ } catch {}
141
+ }
142
+ };
143
+ const adb = trackAdbDevices(onChange);
144
+ let simctl = null;
145
+ let simTimer = null;
146
+ if (platform === "darwin") {
147
+ let last = null;
148
+ const check = () => {
149
+ if (simctl || stopped) return;
150
+ let out = "";
151
+ const child = getExecutor().spawn("xcrun", [
152
+ "simctl",
153
+ "list",
154
+ "devices",
155
+ "--json"
156
+ ], {
157
+ stdio: [
158
+ "ignore",
159
+ "pipe",
160
+ "ignore"
161
+ ],
162
+ timeout: SIMCTL_TIMEOUT_MS,
163
+ killSignal: "SIGKILL"
164
+ });
165
+ simctl = child;
166
+ child.stdout?.on("data", (chunk) => out += chunk.toString());
167
+ child.on("error", () => {
168
+ simctl = null;
169
+ });
170
+ child.on("close", () => {
171
+ simctl = null;
172
+ const signature = simulatorSignature(out);
173
+ if (signature === null || stopped) return;
174
+ if (last !== null && signature !== last) onChange();
175
+ last = signature;
176
+ });
177
+ };
178
+ check();
179
+ simTimer = setInterval(check, simctlIntervalMs);
180
+ }
181
+ reconcile();
182
+ return {
183
+ reconcile,
184
+ stop() {
185
+ stopped = true;
186
+ for (const watcher of watchers.values()) watcher.close();
187
+ watchers.clear();
188
+ if (simTimer) clearInterval(simTimer);
189
+ simctl?.kill("SIGKILL");
190
+ adb.stop();
191
+ }
192
+ };
193
+ }
194
+ /**
195
+ * Runs `adb track-devices` and calls `onChange` on every device list it
196
+ * reports. adb exits when its server restarts, so the tracker restarts with a
197
+ * doubling delay that resets once a run lasts a minute.
198
+ */
199
+ function trackAdbDevices(onChange) {
200
+ const exec = getExecutor();
201
+ const resolved = androidToolPath("adb");
202
+ const adb = resolved === "adb" ? exec.findExecutable("adb") : resolved;
203
+ if (!adb) return { stop() {} };
204
+ let child = null;
205
+ let restart = null;
206
+ let delay = ADB_RESTART_MIN_MS;
207
+ let stopped = false;
208
+ const start = () => {
209
+ restart = null;
210
+ if (stopped) return;
211
+ const startedAt = Date.now();
212
+ const current = exec.spawn(adb, ["track-devices"], { stdio: [
213
+ "ignore",
214
+ "pipe",
215
+ "ignore"
216
+ ] });
217
+ child = current;
218
+ current.stdout?.on("data", () => onChange());
219
+ current.on("error", () => {});
220
+ current.on("close", () => {
221
+ if (child === current) child = null;
222
+ if (stopped) return;
223
+ if (Date.now() - startedAt >= ADB_RESTART_MAX_MS) delay = ADB_RESTART_MIN_MS;
224
+ restart = setTimeout(start, delay);
225
+ delay = Math.min(delay * 2, ADB_RESTART_MAX_MS);
226
+ });
227
+ };
228
+ start();
229
+ return { stop() {
230
+ stopped = true;
231
+ if (restart) clearTimeout(restart);
232
+ child?.kill("SIGTERM");
233
+ } };
234
+ }
235
+ //#endregion
236
+ //#region src/diagnostics/error-index.ts
237
+ const INDEX_VERSION = 1;
238
+ const HEAD_BYTES = 1024;
239
+ const TAIL_BYTES = 256;
240
+ const CHUNK_BYTES = 4194304;
241
+ const NEWLINE = 10;
242
+ const CANDIDATE_PATTERNS = [
243
+ "\"level\":\"error\"",
244
+ "\"level\":\"fatal\"",
245
+ "\"marker\":true"
246
+ ].map((p) => Buffer.from(p));
247
+ const ERROR_CRITERIA = {
248
+ errorsOnly: true,
249
+ sources: ERROR_SOURCES,
250
+ includeNativeCrashes: true
251
+ };
252
+ /**
253
+ * The number of records `queryLogs({ dir, errorsOnly: true })` returns, without parsing every
254
+ * line on every call. `indexFile` keeps, per log file generation, the markers and error records
255
+ * already read and the byte offset they were read through; a later call reads only what was
256
+ * appended since. The index is a cache: losing it or a concurrent writer only costs a rescan.
257
+ */
258
+ function countErrorsSinceMarker(dir, indexFile) {
259
+ const previous = readIndex(indexFile);
260
+ const files = {};
261
+ let changed = false;
262
+ for (const name of logFiles(dir)) {
263
+ const current = join(dir, name);
264
+ for (const path of [rotatedLogPath(current), current]) {
265
+ const result = summarize(path, previous.files);
266
+ if (!result) continue;
267
+ files[result.key] = result.summary;
268
+ if (result.changed) changed = true;
269
+ }
270
+ }
271
+ if (changed || Object.keys(files).length !== Object.keys(previous.files).length) writeIndex(indexFile, {
272
+ version: INDEX_VERSION,
273
+ files
274
+ });
275
+ const records = [];
276
+ for (const summary of Object.values(files)) records.push(...Object.values(summary.markers), ...summary.errors);
277
+ return queryLogs({
278
+ records,
279
+ errorsOnly: true
280
+ }).length;
281
+ }
282
+ function summarize(path, known) {
283
+ let fd;
284
+ try {
285
+ fd = openSync$1(path, "r");
286
+ } catch {
287
+ return null;
288
+ }
289
+ try {
290
+ const { dev, ino, size } = fstatSync$1(fd);
291
+ const key = `${dev}:${ino}`;
292
+ const cached = known[key];
293
+ const base = cached && stillExtends(fd, cached, size) ? cached : null;
294
+ const start = base ?? {
295
+ offset: 0,
296
+ head: "",
297
+ tail: "",
298
+ markers: {},
299
+ errors: []
300
+ };
301
+ if (size <= start.offset) return {
302
+ key,
303
+ summary: start,
304
+ changed: !base
305
+ };
306
+ const summary = readFrom(fd, start, size);
307
+ return {
308
+ key,
309
+ summary,
310
+ changed: !base || summary.offset !== start.offset
311
+ };
312
+ } catch {
313
+ return null;
314
+ } finally {
315
+ closeSync$1(fd);
316
+ }
317
+ }
318
+ function stillExtends(fd, summary, size) {
319
+ if (size < summary.offset) return false;
320
+ const head = Buffer.from(summary.head, "base64");
321
+ const tail = Buffer.from(summary.tail, "base64");
322
+ return readAt(fd, 0, head.length).equals(head) && readAt(fd, summary.offset - tail.length, tail.length).equals(tail);
323
+ }
324
+ function readAt(fd, position, length) {
325
+ const buf = Buffer.alloc(length);
326
+ const read = length > 0 ? readSync$1(fd, buf, 0, length, position) : 0;
327
+ return buf.subarray(0, read);
328
+ }
329
+ function readFrom(fd, start, size) {
330
+ const markers = { ...start.markers };
331
+ const errors = [...start.errors];
332
+ let offset = start.offset;
333
+ let chunk = CHUNK_BYTES;
334
+ while (offset < size) {
335
+ const buf = readAt(fd, offset, Math.min(chunk, size - offset));
336
+ if (buf.length === 0) break;
337
+ const end = buf.lastIndexOf(NEWLINE);
338
+ if (end === -1) {
339
+ if (offset + buf.length >= size) break;
340
+ chunk *= 2;
341
+ continue;
342
+ }
343
+ for (const record of candidateRecords(buf.subarray(0, end + 1))) collect(record, markers, errors);
344
+ offset += end + 1;
345
+ }
346
+ if (offset === start.offset) return start;
347
+ return {
348
+ offset,
349
+ head: start.offset >= HEAD_BYTES ? start.head : readAt(fd, 0, Math.min(HEAD_BYTES, offset)).toString("base64"),
350
+ tail: readAt(fd, Math.max(0, offset - TAIL_BYTES), Math.min(TAIL_BYTES, offset)).toString("base64"),
351
+ markers,
352
+ errors
353
+ };
354
+ }
355
+ function candidateRecords(region) {
356
+ const lineStarts = /* @__PURE__ */ new Set();
357
+ for (const pattern of CANDIDATE_PATTERNS) for (let hit = region.indexOf(pattern); hit !== -1; hit = region.indexOf(pattern, hit + pattern.length)) lineStarts.add(region.lastIndexOf(NEWLINE, hit) + 1);
358
+ const records = [];
359
+ for (const lineStart of [...lineStarts].toSorted((a, b) => a - b)) {
360
+ const record = parseNdjsonLine(region.toString("utf-8", lineStart, region.indexOf(NEWLINE, lineStart)));
361
+ if (record) records.push(record);
362
+ }
363
+ return records;
364
+ }
365
+ function collect(record, markers, errors) {
366
+ const ts = typeof record.ts === "number" && Number.isFinite(record.ts) ? record.ts : null;
367
+ if (record.marker === true && ts !== null) {
368
+ const metro = record.src === "metro";
369
+ const slot = record.slot ?? "default";
370
+ const key = metro ? "metro" : JSON.stringify(["launch", slot]);
371
+ if ((markers[key]?.ts ?? -Infinity) < ts) markers[key] = metro ? {
372
+ src: "metro",
373
+ marker: true,
374
+ ts
375
+ } : {
376
+ marker: true,
377
+ ts,
378
+ slot
379
+ };
380
+ }
381
+ if (recordMatches(record, ERROR_CRITERIA)) errors.push({
382
+ src: record.src,
383
+ level: record.level,
384
+ event: record.event,
385
+ ts: ts ?? void 0,
386
+ slot: record.slot
387
+ });
388
+ }
389
+ function readIndex(file) {
390
+ try {
391
+ const parsed = JSON.parse(readFileSync$1(file, "utf-8"));
392
+ if (parsed?.version === INDEX_VERSION && parsed.files && typeof parsed.files === "object") return parsed;
393
+ } catch {}
394
+ return {
395
+ version: INDEX_VERSION,
396
+ files: {}
397
+ };
398
+ }
399
+ function writeIndex(file, index) {
400
+ const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
401
+ try {
402
+ writeFileSync$1(tmp, JSON.stringify(index));
403
+ renameSync$1(tmp, file);
404
+ } catch {
405
+ rmSync$1(tmp, { force: true });
406
+ }
407
+ }
408
+ //#endregion
409
+ //#region src/commands/status.ts
410
+ function formatGb(mb) {
411
+ return `${(mb / 1024).toFixed(1)} GB`;
412
+ }
413
+ function statusCommand(program) {
414
+ program.command("status").description("Show every environment on this machine: devices, ports, what is actually running, and anything stuck.").option("--json", "print the state as JSON").option("--watch", "keep running and print the state again each time it changes").action(async (opts) => {
415
+ if (opts.watch) return watchStatus(Boolean(opts.json));
416
+ for (const line of await statusLines(Boolean(opts.json))) console.log(line);
417
+ });
418
+ }
419
+ async function statusLines(json) {
420
+ const out = [];
421
+ const cfg = loadConfig$1();
422
+ const projects = Object.entries(cfg?.projects || {});
423
+ const cwdRoot = findProjectRoot(process.cwd());
424
+ const simsRead = listAllIosSimsAsync();
425
+ simsRead.catch(() => {});
426
+ const ports = await portLookup(projects.flatMap(([, proj]) => proj.metroPort ? [proj.metroPort] : []));
427
+ const processes = Promise.all(projects.map(async ([path, proj]) => {
428
+ const metro = proj.metroPort ? await resolveOnPort(proj.metroPort, path, ports) : null;
429
+ return {
430
+ metro,
431
+ supervisor: await supervisorFacts(path, proj, metro, ports)
432
+ };
433
+ }));
434
+ processes.catch(() => {});
435
+ const androidRuntimeOf = androidRuntimeReader();
436
+ const devices = projects.map(([, proj]) => ({
437
+ androidRuntimes: Object.fromEntries(projectDeviceSlots(proj).slice(1).map(({ slot, platforms }) => [slot, platforms.android?.owned && platforms.android.avdName ? androidRuntimeOf(platforms.android.avdName) : null])),
438
+ androidRuntime: proj.platforms?.android?.owned && proj.platforms.android.avdName ? androidRuntimeOf(proj.platforms.android.avdName) : null
439
+ }));
440
+ const logs = projects.map(([path]) => logFacts(path));
441
+ const simsByUdid = {};
442
+ let simsAvailable = true;
443
+ let simctlError = null;
444
+ try {
445
+ for (const sim of await simsRead) simsByUdid[sim.udid] = sim;
446
+ } catch (e) {
447
+ simsAvailable = false;
448
+ simctlError = String(e?.message || e).split("\n")[0] ?? "";
449
+ }
450
+ const running = await processes;
451
+ const worktrees = linkedWorktrees([process.cwd(), ...projects.map(([path]) => path)]);
452
+ const history = readStats().record?.history;
453
+ const states = [];
454
+ const labelOnlyRoots = [];
455
+ const easLedger = readEasSessionLedger$1();
456
+ for (const [i, [path, proj]] of projects.entries()) {
457
+ const { metro, supervisor } = running[i];
458
+ states.push(environmentState({
459
+ ...proj,
460
+ __path: path
461
+ }, {
462
+ simsByUdid,
463
+ metro,
464
+ worktrees,
465
+ simsAvailable,
466
+ ...devices[i],
467
+ supervisor,
468
+ logs: logs[i],
469
+ remote: remoteDeviceState(readRemoteSession(path), easLedger, path),
470
+ idleStop: readIdleStop(readWorkspaceState$1(path))
471
+ }));
472
+ const state = states[states.length - 1];
473
+ if (state) state.build = workspaceBuild(path, history);
474
+ labelOnlyRoots.push(Boolean(proj.worktreeRoot && !proj.bundleId && !state?.metro && !state?.ios && !state?.android));
475
+ }
476
+ const readActivity = createActivityReader();
477
+ for (const state of states) for (const device of [{
478
+ slot: "default",
479
+ ios: state.ios,
480
+ android: state.android
481
+ }, ...state.slots ?? []]) {
482
+ const base = {
483
+ slot: device.slot,
484
+ workspace: state.path
485
+ };
486
+ if (device.ios?.state === "Booted") device.ios.activity = readActivity({
487
+ ...base,
488
+ platform: "ios",
489
+ id: device.ios.udid
490
+ });
491
+ if (device.android && !device.android.physical && device.android.serial) device.android.activity = readActivity({
492
+ ...base,
493
+ platform: "android",
494
+ id: device.android.serial
495
+ });
496
+ }
497
+ const leaseNow = Date.now();
498
+ const leases = deviceLeaseStates(listLeaseFiles(), {
499
+ root: cwdRoot,
500
+ now: leaseNow
501
+ });
502
+ const totalMemoryMb = Math.round(totalmem() / 1048576);
503
+ const cap = capacity(states, totalMemoryMb);
504
+ const orphanWorktrees = unprovisionedWorktrees(worktrees, projects.map(([p]) => p));
505
+ const pools = ["ios", "android"].map((platform) => {
506
+ const { max, error } = parkedMaxSetting(platform);
507
+ return {
508
+ error,
509
+ line: poolLine({
510
+ platform,
511
+ parked: readParked(platform).length,
512
+ max
513
+ })
514
+ };
515
+ });
516
+ if (json) {
517
+ out.push(JSON.stringify({
518
+ environments: states.map((state, i) => labelOnlyRoots[i] ? {
519
+ ...state,
520
+ labelOnly: true
521
+ } : state),
522
+ capacity: cap,
523
+ deviceLeases: leases,
524
+ unprovisionedWorktrees: orphanWorktrees,
525
+ simctlAvailable: simsAvailable
526
+ }));
527
+ return out;
528
+ }
529
+ if (projects.length === 0 && orphanWorktrees.length === 0) {
530
+ out.push(chalk.dim("No projects registered."));
531
+ for (const pool of pools) {
532
+ if (pool.error) out.push(chalk.yellow(`${pool.error} ${POOL_SETTING_REMEDY}`));
533
+ if (pool.line) out.push(chalk.dim(pool.line));
534
+ }
535
+ for (const line of deviceLeaseLines(leases, leaseNow)) out.push(line);
536
+ return out;
537
+ }
538
+ if (!simsAvailable) out.push(chalk.yellow(`simctl could not be read (${simctlError}), so no iOS sim below could be checked.`));
539
+ for (const [i, [path, proj]] of projects.entries()) {
540
+ const state = states[i];
541
+ if (!state) continue;
542
+ const shortcut = projectShortcut(path, proj);
543
+ const marker = path === cwdRoot ? chalk.bold.cyan(`* ${shortcut}`) : shortcut;
544
+ const idle = state.live ? "" : chalk.dim(" [idle]");
545
+ out.push(`\n${marker}${idle} ${chalk.dim(`(${path})`)}`);
546
+ out.push(labelOnlyRoots[i] ? chalk.dim(" worktree root (holds the label; the app registers its own entry)") : chalk.dim(` app: ${proj.bundleId ?? "?"} (${proj.isExpo ? "expo" : "bare"})`));
547
+ if (state.metro) {
548
+ const label = state.metro.running ? chalk.green(`running (pid ${state.metro.pid})`) : state.metro.idleStop ? chalk.dim(`stopped (idle) after ${state.metro.idleStop.idleMinutes}m; \`stim start\`, \`ios\` or \`android\` restarts it`) : chalk.dim("not running");
549
+ out.push(` metro: port ${state.metro.port} ${label}`);
550
+ }
551
+ if (state.supervisor) {
552
+ const health = state.supervisor.healthy ? chalk.green("healthy") : chalk.yellow("not answering");
553
+ const mode = state.supervisor.mode ? chalk.dim(` (${state.supervisor.mode})`) : "";
554
+ out.push(` supervisor: pid ${state.supervisor.pid}${mode} ${health}`);
555
+ }
556
+ if (state.build) {
557
+ const line = ` ${buildStatusLine(state.build, Date.now())}`;
558
+ out.push(state.build.state === "running" ? line : chalk.yellow(line));
559
+ }
560
+ if (state.logs) {
561
+ const n = state.logs.errorsSinceMarker;
562
+ const errs = n > 0 ? chalk.yellow(` (${n} error${n === 1 ? "" : "s"} since the last marker)`) : "";
563
+ out.push(chalk.dim(` logs: ${state.logs.dir}`) + errs);
564
+ }
565
+ for (const deviceState of [{
566
+ slot: "default",
567
+ ios: state.ios,
568
+ android: state.android
569
+ }, ...state.slots ?? []]) {
570
+ const slotLabel = deviceState.slot === "default" ? "" : ` [${deviceState.slot}]`;
571
+ if (deviceState.ios) {
572
+ const booted = deviceState.ios.state === "Booted" ? chalk.green("booted") : chalk.dim(deviceState.ios.state.toLowerCase());
573
+ const owned = deviceState.ios.owned ? chalk.dim(" (owned)") : "";
574
+ out.push(` ios${slotLabel}: ${chalk.cyan(deviceState.ios.name ?? deviceState.ios.udid)} ${booted}${owned}${activitySuffix(deviceState.ios.activity)}`);
575
+ }
576
+ if (deviceState.android) {
577
+ const kind = deviceState.android.physical ? chalk.dim("(physical)") : chalk.dim("(emulator)");
578
+ const observed = deviceState.android.state ? ` ${deviceState.android.state}${deviceState.android.serial ? ` (${deviceState.android.serial})` : ""}` : "";
579
+ out.push(` android${slotLabel}: ${chalk.cyan(deviceState.android.name)} ${kind}${observed}${deviceState.android.owned ? chalk.dim(" (owned)") : ""}${activitySuffix(deviceState.android.activity)}`);
580
+ }
581
+ }
582
+ for (const remote of state.remoteDevices ?? []) out.push(` ${remoteDeviceLine(remote)}`);
583
+ for (const w of state.warnings) out.push(chalk.yellow(` ! ${w}`));
584
+ }
585
+ for (const pool of pools) {
586
+ if (pool.error) out.push(chalk.yellow(`\n${pool.error} ${POOL_SETTING_REMEDY}`));
587
+ if (pool.line) out.push(chalk.dim(`\n${pool.line}`));
588
+ }
589
+ const leaseLines = deviceLeaseLines(leases, leaseNow);
590
+ if (leaseLines.length) {
591
+ out.push("");
592
+ for (const line of leaseLines) out.push(line);
593
+ }
594
+ if (orphanWorktrees.length) {
595
+ out.push(chalk.dim(`\nWorktrees with no environment (${orphanWorktrees.length}):`));
596
+ for (const w of orphanWorktrees) out.push(chalk.dim(` ${w.path}${w.branch ? ` [${w.branch}]` : ""}`));
597
+ }
598
+ out.push(chalk.dim(`\n${cap.liveCount} live environment(s), roughly ${formatGb(cap.committedMb)} of ${formatGb(cap.totalMemoryMb)} committed.`));
599
+ const volumes = readVolumes(cwdRoot || process.cwd());
600
+ const line = diskLine(volumes);
601
+ if (line) {
602
+ const tight = tightVolumes(volumes);
603
+ if (tight.length) {
604
+ const which = tight.map((v) => v.volume).join(" and ");
605
+ out.push(chalk.yellow(`${line} A single iOS build can exhaust ${which} -- run \`stim gc\` before starting another environment.`));
606
+ } else out.push(chalk.dim(line));
607
+ }
608
+ if (cap.overCapacity) out.push(chalk.yellow("Over comfortable capacity. A machine that swaps is slower than one working in sequence -- release one before starting another."));
609
+ return out;
610
+ }
611
+ async function watchStatus(json) {
612
+ let last = null;
613
+ let sources = null;
614
+ const scheduler = createRefreshScheduler({
615
+ debounceMs: 250,
616
+ run: async () => {
617
+ let text;
618
+ try {
619
+ text = (await statusLines(json)).join("\n");
620
+ } catch (error) {
621
+ console.error(chalk.red(String(error?.message || error)));
622
+ return;
623
+ } finally {
624
+ sources?.reconcile();
625
+ }
626
+ if (text === last) return;
627
+ last = text;
628
+ process.stdout.write(`${!json && process.stdout.isTTY ? "\x1B[2J\x1B[H" : ""}${text}\n`);
629
+ }
630
+ });
631
+ const fallback = setInterval(() => scheduler.trigger(), WATCH_FALLBACK_MS);
632
+ const finish = () => {
633
+ clearInterval(fallback);
634
+ scheduler.stop();
635
+ sources?.stop();
636
+ process.exit(0);
637
+ };
638
+ process.on("SIGINT", finish);
639
+ process.on("SIGTERM", finish);
640
+ process.stdout.on("error", finish);
641
+ sources = watchStatusSources({
642
+ home: getConfigDir(),
643
+ onChange: () => scheduler.trigger()
644
+ });
645
+ scheduler.trigger(0);
646
+ await new Promise(() => {});
647
+ }
648
+ function activitySuffix(activity) {
649
+ const label = activityLabel(activity, Date.now());
650
+ return label ? ` -- ${activity?.state === "driven" ? chalk.magenta(label) : chalk.dim(label)}` : "";
651
+ }
652
+ function linkedWorktrees(paths) {
653
+ return [...new Set(paths.flatMap((path) => gitCommonDirOnDisk(path) ?? []))].flatMap((common) => {
654
+ const repository = basename(common) === ".git" ? dirname(common) : common;
655
+ return linkedWorktreesOnDisk(common).map((entry) => Object.assign(entry, { repository }));
656
+ });
657
+ }
658
+ function workspaceBuild(path, history) {
659
+ const record = parseActiveBuild(readWorkspaceState$1(path)?.[ACTIVE_BUILD_KEY]);
660
+ if (!record) return null;
661
+ const projectKey = statsProjectKey({
662
+ root: path,
663
+ commonDir: gitCommonDir(path),
664
+ repoRoot: repoRoot(path)
665
+ });
666
+ return buildReport(record, {
667
+ state: activeBuildState(record.claim),
668
+ history: history?.[projectKey]
669
+ });
670
+ }
671
+ function androidRuntimeReader() {
672
+ const resolve = ownedAvdSerialResolver({ timeoutMs: 5e3 });
673
+ return (avdName) => readAndroidRuntime(() => resolve(avdName));
674
+ }
675
+ function readAndroidRuntime(resolveSerial) {
676
+ try {
677
+ const resolved = resolveSerial();
678
+ return {
679
+ serial: resolved.serial ?? null,
680
+ state: resolved.serial ? "detected" : resolved.missing ? "missing" : resolved.notRunning ? "not-detected" : "unknown"
681
+ };
682
+ } catch (error) {
683
+ return {
684
+ serial: null,
685
+ state: "unknown",
686
+ error: String(error?.message || error).split("\n")[0]
687
+ };
688
+ }
689
+ }
690
+ function readVolumes(projectPath) {
691
+ const roots = [.../* @__PURE__ */ new Set([
692
+ "/",
693
+ volumeRootFor(getConfigDir()),
694
+ volumeRootFor(projectPath)
695
+ ])];
696
+ const volumes = [];
697
+ for (const volume of roots) {
698
+ const quoted = `'${volume.replace(/'/g, "'\\''")}'`;
699
+ const disk = parseDfFree(getExecutor().runQuiet(`df -k ${quoted}`, { timeoutMs: 5e3 }));
700
+ if (disk) volumes.push({
701
+ volume,
702
+ disk
703
+ });
704
+ }
705
+ return volumes;
706
+ }
707
+ async function portLookup(metroPorts) {
708
+ const ports = [...new Set(metroPorts)];
709
+ const answering = new Map(await Promise.all(ports.map(async (port) => [port, await isMetroRunning(port)])));
710
+ const listeners = listeningPidsByPort(ports.filter((port) => answering.get(port)));
711
+ const cwds = listeners.then((byPort) => processCwds([...byPort.values()].flatMap((pids) => pids.slice(0, 1))));
712
+ return {
713
+ answering,
714
+ pidsOf: async (port) => (await listeners).get(port) ?? listeningPids(port),
715
+ cwdOf: async (pid) => {
716
+ const known = await cwds;
717
+ return known.has(pid) ? known.get(pid) ?? null : processCwd(pid);
718
+ }
719
+ };
720
+ }
721
+ async function resolveOnPort(port, path, lookup) {
722
+ return lookup.answering.get(port) ?? await isMetroRunning(port) ? resolveProjectMetro(port, path, lookup) : { missing: true };
723
+ }
724
+ async function supervisorFacts(path, proj, metroResolution, lookup) {
725
+ const state = readSupervisorState(path);
726
+ const record = proj?.supervisor ?? null;
727
+ const pid = state?.pid ?? record?.pid ?? null;
728
+ if (!pid) return null;
729
+ const port = state?.port ?? record?.port ?? null;
730
+ const alive = resolveSupervisorTarget({
731
+ state,
732
+ record,
733
+ reservedPort: proj?.metroPort
734
+ }).status === "ours";
735
+ let healthy = false;
736
+ if (alive && port) {
737
+ const resolution = port === proj?.metroPort && metroResolution ? metroResolution : await resolveOnPort(port, path, lookup);
738
+ healthy = Boolean(resolution?.metro);
739
+ }
740
+ return {
741
+ pid,
742
+ mode: state?.mode ?? record?.mode ?? null,
743
+ startedAt: state?.startedAt ?? record?.startedAt ?? null,
744
+ alive,
745
+ healthy
746
+ };
747
+ }
748
+ function logFacts(path) {
749
+ const dir = workspaceLogsDir(path);
750
+ if (!existsSync$1(dir)) return null;
751
+ try {
752
+ return {
753
+ dir,
754
+ errorsSinceMarker: countErrorsSinceMarker(dir, workspaceLogErrorIndex(path))
755
+ };
756
+ } catch {
757
+ return {
758
+ dir,
759
+ errorsSinceMarker: 0
760
+ };
761
+ }
762
+ }
763
+ //#endregion
764
+ export { statusCommand as default, readVolumes };