stim 1.14.0 → 1.15.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 (130) hide show
  1. package/README.md +2 -0
  2. package/dist/{activity-CYVDcrt3.mjs → activity-B2-kb7k0.mjs} +281 -41
  3. package/dist/{android-XpvCrV7q.mjs → android-CYmq0jNi.mjs} +2 -2
  4. package/dist/{android-LbPFRWVL.mjs → android-Cu_L4RpL.mjs} +502 -407
  5. package/dist/{android-d6rGq1pA.mjs → android-Db2eEMaf.mjs} +152 -65
  6. package/dist/android-cas-PnKfAdB2.mjs +167 -0
  7. package/dist/android-cas-compiler.mjs +2 -2
  8. package/dist/{app-install-BpgSgkJj.mjs → app-install-CAAplXAt.mjs} +95 -161
  9. package/dist/{budget-CGDkhvv_.mjs → budget-CMiqghig.mjs} +1038 -73
  10. package/dist/{build-plan-Dcr-3ie0.mjs → build-plan-aeGMFX7e.mjs} +193 -653
  11. package/dist/build-progress-C2Jv1AtA.mjs +872 -0
  12. package/dist/build-slots-Df1ApSuH.mjs +127 -0
  13. package/dist/{cache-manifest-C68QQ0ua.mjs → cache-manifest-DnUHBJaU.mjs} +2 -2
  14. package/dist/cache-manifest.mjs +1 -1
  15. package/dist/{chrome-BPCiC3ZX.mjs → chrome-BrcVWQyD.mjs} +1 -1
  16. package/dist/cli-0cX5s4NM.mjs +45 -0
  17. package/dist/cli.mjs +6 -42
  18. package/dist/client-C2IS36Qf.mjs +1333 -0
  19. package/dist/collector-run.mjs +8 -7
  20. package/dist/{command-output-DVcpw6eE.mjs → command-output-CliHHn7a.mjs} +1 -1
  21. package/dist/{config-lvc7d3C2.mjs → config-D0DbpZtM.mjs} +28 -29
  22. package/dist/{created-devices-CllH_8eb.mjs → created-devices-BwVb3liH.mjs} +2 -2
  23. package/dist/{dependency-state-DsT7aKeo.mjs → dependency-state-BmsjKZ2H.mjs} +2 -2
  24. package/dist/{deps-D9IcKqXS.mjs → deps-BFlKbOIp.mjs} +3 -3
  25. package/dist/{detached-entry-DJHoca49.mjs → detached-entry-CiTEgMR1.mjs} +1 -1
  26. package/dist/dev-client-CjzLoNs0.mjs +80 -0
  27. package/dist/{device-CjuuAJjQ.mjs → device-BZ1IyS5A.mjs} +7 -7
  28. package/dist/device-capacity-DMuKC7nx.mjs +136 -0
  29. package/dist/device-host-worker.d.mts +1 -0
  30. package/dist/device-host-worker.mjs +769 -0
  31. package/dist/device-ios-DMQBUPus.mjs +381 -0
  32. package/dist/{device-lease-5c3WrVRm.mjs → device-lease-W5Pi1q38.mjs} +43 -36
  33. package/dist/{device-pool-BiyDyPP-.mjs → device-pool-B2r38YjS.mjs} +4 -4
  34. package/dist/{device-remote-D87zEzaq.mjs → device-remote-9ulPsdhY.mjs} +68 -162
  35. package/dist/{doctor-x9pnU9fq.mjs → doctor-BcE5xsBn.mjs} +191 -99
  36. package/dist/{error-diagnostics-C5HzcwCW.mjs → error-diagnostics-Cb0P1WLS.mjs} +12 -11
  37. package/dist/{exec-DK8KaVHK.mjs → exec-CaErVv7t.mjs} +5 -1
  38. package/dist/{gc-ko6mUAYD.mjs → gc-BuL1Genr.mjs} +429 -101
  39. package/dist/{android-cas-iZK3DOCr.mjs → gradle-CmdOBq1d.mjs} +8 -294
  40. package/dist/{guide-6R0VNFqI.mjs → guide-cCSGRFd3.mjs} +1959 -222
  41. package/dist/{guide-status-BZrFPIA_.mjs → guide-status-CIoWK6mG.mjs} +3 -3
  42. package/dist/hosted-macos-DwowNU7W.mjs +447 -0
  43. package/dist/idle-shutdown-B5LPaNyY.mjs +122 -0
  44. package/dist/in-use-CndPMilN.mjs +110 -0
  45. package/dist/{ios-BwDPrnYQ.mjs → ios-3CtCRhwr.mjs} +201 -10
  46. package/dist/{ios-P1f5nm2n.mjs → ios-Chob8ny7.mjs} +512 -263
  47. package/dist/{ios-device-DGglU3E_.mjs → ios-device-DERmMmk-.mjs} +12 -7
  48. package/dist/{ios-device-BiOsSVzd.mjs → ios-device-Ee7sDXfp.mjs} +1 -1
  49. package/dist/{launch-verify-Deg4tB3Z.mjs → launch-verify-CLlofxRt.mjs} +4 -4
  50. package/dist/{logs-Civ4gbBH.mjs → logs-h9h6MUvE.mjs} +95 -13
  51. package/dist/machine-usage-D_YYMzfR.mjs +283 -0
  52. package/dist/machines-CYtOCqcj.mjs +259 -0
  53. package/dist/macos-BwtbUtTN.mjs +468 -0
  54. package/dist/macos-run.d.mts +5 -0
  55. package/dist/macos-run.mjs +2 -0
  56. package/dist/{merge-state-ytFQCeh4.mjs → merge-state-BnxLNQiM.mjs} +22 -12
  57. package/dist/{metro-CcEo8Fl8.mjs → metro-Di8EN2Tb.mjs} +20 -120
  58. package/dist/{named-ports-Blg8IxOh.mjs → named-ports-Dj_XBNaL.mjs} +6 -59
  59. package/dist/native-run-DFDoIRbY.mjs +147 -0
  60. package/dist/{native-runtime-DjBHRg5m.mjs → native-runtime-i6OkVg-w.mjs} +9 -5
  61. package/dist/offload-worker.d.mts +121 -0
  62. package/dist/offload-worker.mjs +601 -0
  63. package/dist/{ownership-DyYDF5ww.mjs → ownership-BkYxUtuj.mjs} +3 -2
  64. package/dist/{page-BHUzTDFt.mjs → page-QF_Ke10s.mjs} +7 -2
  65. package/dist/{ports-BguS83ex.mjs → ports-DddwttKM.mjs} +3 -3
  66. package/dist/prebuild-BvxDlD6C.mjs +213 -0
  67. package/dist/process-identity-BmRm1qV0.mjs +12 -0
  68. package/dist/{project-CWlvEaeN.mjs → project-CV3tDFHl.mjs} +9 -18
  69. package/dist/pull-request-pxnDCQzn.mjs +322 -0
  70. package/dist/pull-requests.d.mts +120 -0
  71. package/dist/pull-requests.mjs +2 -0
  72. package/dist/recordings-GEXp4iBT.mjs +37 -0
  73. package/dist/{reload-BiniwsSZ.mjs → reload-ChPiwvot.mjs} +70 -22
  74. package/dist/{remote-cache-CcR6o6lI.mjs → remote-cache-CO2elm_b.mjs} +4 -4
  75. package/dist/run-B-xqRrsg.mjs +119 -0
  76. package/dist/{server-bare-DGfgyJ5J.mjs → server-bare-X7PUgizL.mjs} +33 -15
  77. package/dist/{server-expo-BVuwQ65v.mjs → server-expo-DhtX6bom.mjs} +6 -10
  78. package/dist/server-expo-KJeD109I.mjs +2 -0
  79. package/dist/{settings-D61iNOsB.mjs → settings-CduVcoxj.mjs} +77 -26
  80. package/dist/{settings-B-fsC-Om.mjs → settings-DbL6uhH8.mjs} +16 -10
  81. package/dist/settings.schema.json +357 -2
  82. package/dist/{simslim-DrDN4WuV.mjs → simslim-Ciluxlq4.mjs} +8 -132
  83. package/dist/{slot-launch-D5t8EiMf.mjs → slot-launch-BY0BrOLh.mjs} +6 -6
  84. package/dist/{spawn-claims-Drw_o-Fb.mjs → spawn-claims-D0XfSe8u.mjs} +1 -1
  85. package/dist/spawn-entry-DncpJvWL.mjs +16 -0
  86. package/dist/stage-CzaiwFsI.mjs +143 -0
  87. package/dist/start-CPO4wRg7.mjs +2 -0
  88. package/dist/{start-s4RXp1wy.mjs → start-CUD9nasA.mjs} +108 -61
  89. package/dist/state-B4PQFXMW.d.mts +12 -0
  90. package/dist/state-BxCgeTQu.mjs +41 -0
  91. package/dist/{state-DHqvsbqC.mjs → state-CaeaHbDA.mjs} +7 -4
  92. package/dist/{state-Dk9RVXI7.mjs → state-DFm77wRn.mjs} +67 -5
  93. package/dist/stats-DBLLFmKd.mjs +66 -0
  94. package/dist/{status-BsPAnfOI.mjs → status-DYXxNOT0.mjs} +423 -268
  95. package/dist/{status-BgAuxESg.mjs → status-l84Gbvp2.mjs} +116 -20
  96. package/dist/stdout-reader-C8yjre1x.mjs +23 -0
  97. package/dist/stim-desktop-Cnz8CPnl.mjs +63 -0
  98. package/dist/stim-footprint.swift +32 -0
  99. package/dist/{stim-installations-CINXy_su.mjs → stim-installations-QMEI32v1.mjs} +1 -1
  100. package/dist/{stop-BzreOWJp.mjs → stop-Bad61z_2.mjs} +38 -12
  101. package/dist/stop-Bvf1MflS.mjs +48 -0
  102. package/dist/{stop-BWIF_6BK.mjs → stop-CceYh1yG.mjs} +2 -2
  103. package/dist/stop-cause-CZ_Mr-v9.mjs +665 -0
  104. package/dist/supervisor-run.d.mts +43 -11
  105. package/dist/supervisor-run.mjs +227 -19
  106. package/dist/support-CDQbDkL5.mjs +315 -0
  107. package/dist/support-WLOzVKUv.mjs +176 -0
  108. package/dist/teardown-BYuQafiA.mjs +718 -0
  109. package/dist/{xcode-Cp5KvK9x.mjs → toolchain-DmeYjXD9.mjs} +363 -13
  110. package/dist/warm-progress-DVHLN4uC.mjs +463 -0
  111. package/dist/{idle-DXtx3RbX.mjs → watchman-Bx0wceoF.mjs} +55 -8
  112. package/dist/{web-BsQs1WlO.mjs → web-k6w8-HnE.mjs} +37 -20
  113. package/dist/web-run.mjs +309 -12
  114. package/dist/workspace-process-lock-BL7Q0HCO.mjs +57 -0
  115. package/dist/workspace-state-CL2V2Ons.mjs +371 -0
  116. package/dist/worktree-BT_uULFx.mjs +2 -0
  117. package/dist/{worktree-BVrHsYZG.mjs → worktree-C0c5Udwa.mjs} +55 -54
  118. package/dist/{worktree-UrQWXSeb.mjs → worktree-qXKzADSC.mjs} +154 -252
  119. package/package.json +10 -5
  120. package/shim/bundle-response.cjs +28 -12
  121. package/dist/build-progress-DxKpwnaS.mjs +0 -247
  122. package/dist/in-use-DlqBDKqY.mjs +0 -249
  123. package/dist/server-expo-DIHDhvGg.mjs +0 -2
  124. package/dist/start-Cetfvy8l.mjs +0 -2
  125. package/dist/stats-CFpFGlJV.mjs +0 -62
  126. package/dist/stats-CX1Z8Tca.mjs +0 -368
  127. package/dist/stim-desktop-CXt8-Z3J.mjs +0 -40
  128. package/dist/stop-cause-hjGBOZTG.mjs +0 -1231
  129. package/dist/worktree-CHWWvm5v.mjs +0 -2
  130. package/dist/{metro-store-DzeWkfqW.mjs → metro-store-CDDZHtPl.mjs} +1 -1
@@ -1,6 +1,6 @@
1
- import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
2
- import { n as TOOL_ERROR_PREFIX } from "./ios-device-BiOsSVzd.mjs";
3
- import { i as INSTALL_ERROR } from "./app-install-BpgSgkJj.mjs";
1
+ import { t as getExecutor } from "./exec-CaErVv7t.mjs";
2
+ import { n as TOOL_ERROR_PREFIX } from "./ios-device-Ee7sDXfp.mjs";
3
+ import { i as INSTALL_ERROR } from "./app-install-CAAplXAt.mjs";
4
4
  import { mkdtempSync, readFileSync, rmSync } from "node:fs";
5
5
  import { join } from "node:path";
6
6
  import { tmpdir } from "node:os";
@@ -38,12 +38,17 @@ function parseDevicectlDevices(payload) {
38
38
  transportType: text(connection.transportType),
39
39
  platform: text(hardware.platform),
40
40
  reality: text(hardware.reality),
41
- tunnelState: text(connection.tunnelState)
41
+ tunnelState: text(connection.tunnelState),
42
+ model: text(hardware.marketingName)
42
43
  });
43
44
  }
44
45
  return out;
45
46
  }
46
47
  function listIosDevices({ exec = null } = {}) {
48
+ return readIosDevices({ exec }) ?? [];
49
+ }
50
+ /** devicectl's device list, or null when devicectl failed or did not answer within `timeoutMs`. */
51
+ function readIosDevices({ exec = null, timeoutMs = DEVICECTL_TIMEOUT_MS } = {}) {
47
52
  const executor = exec || getExecutor();
48
53
  const dir = mkdtempSync(join(tmpdir(), "stim-devicectl-"));
49
54
  const out = join(dir, "devices.json");
@@ -54,10 +59,10 @@ function listIosDevices({ exec = null } = {}) {
54
59
  "devices",
55
60
  "-j",
56
61
  out
57
- ], { timeoutMs: DEVICECTL_TIMEOUT_MS });
62
+ ], { timeoutMs });
58
63
  return parseDevicectlDevices(readFileSync(out, "utf-8"));
59
64
  } catch {
60
- return [];
65
+ return null;
61
66
  } finally {
62
67
  rmSync(dir, {
63
68
  recursive: true,
@@ -507,4 +512,4 @@ function localNetworkPending(records, { since = 0, pid = null, lanOrigin }) {
507
512
  return false;
508
513
  }
509
514
  //#endregion
510
- export { iosPoolCandidates as a, listIosDevices as c, verifyIosDeviceReleaseLaunch as d, iosDeviceProcess as i, localNetworkPending as l, installIosDeviceApp as n, iosPoolNoCandidatesRefusal as o, iosDeviceBounds as r, isWirelessIosDevice as s, awaitIosDeviceLaunch as t, resolveIosPhysicalDevice as u };
515
+ export { iosPoolCandidates as a, isWirelessIosDevice as c, readIosDevices as d, resolveIosPhysicalDevice as f, iosDeviceProcess as i, listIosDevices as l, installIosDeviceApp as n, iosPoolNoCandidatesRefusal as o, verifyIosDeviceReleaseLaunch as p, iosDeviceBounds as r, isReachable as s, awaitIosDeviceLaunch as t, localNetworkPending as u };
@@ -1,4 +1,4 @@
1
- import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
1
+ import { t as getExecutor } from "./exec-CaErVv7t.mjs";
2
2
  //#region src/collector/ios-device.ts
3
3
  const CONSOLE_ENV = { OS_ACTIVITY_DT_MODE: "enable" };
4
4
  const DEV_MENU_LAUNCH_ARGS = [
@@ -1,8 +1,8 @@
1
- import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
1
+ import { t as getExecutor } from "./exec-CaErVv7t.mjs";
2
2
  import { o as readNdjsonGenerations } from "./ndjson-CDY9IkVC.mjs";
3
- import { s as isAppLaunchError } from "./command-output-DVcpw6eE.mjs";
4
- import { t as DEV_MENU_LAUNCH_ARGS } from "./ios-device-BiOsSVzd.mjs";
5
- import { d as deviceShellArg, n as ANDROID_DISABLE_AUTO_LAUNCH_EXTRA, o as androidAppProcess } from "./app-install-BpgSgkJj.mjs";
3
+ import { s as isAppLaunchError } from "./command-output-CliHHn7a.mjs";
4
+ import { t as DEV_MENU_LAUNCH_ARGS } from "./ios-device-Ee7sDXfp.mjs";
5
+ import { f as deviceShellArg, n as ANDROID_DISABLE_AUTO_LAUNCH_EXTRA, s as androidAppProcess } from "./app-install-CAAplXAt.mjs";
6
6
  import { join } from "node:path";
7
7
  //#region src/engine/app-readiness.ts
8
8
  const APP_READINESS_TIMEOUT_MS = 3e4;
@@ -1,13 +1,16 @@
1
- import { H as parseDeviceSlotOption, O as process_identity_exports, U as projectDeviceSlots, _ as loadConfig$1, g as isPathPrefix } from "./config-lvc7d3C2.mjs";
1
+ import { H as projectDeviceSlots, V as parseDeviceSlotOption, _ as loadConfig$1, g as isPathPrefix } from "./config-D0DbpZtM.mjs";
2
+ import { t as process_identity_exports } from "./process-identity-BmRm1qV0.mjs";
2
3
  import { n as SOURCES, t as LEVELS } from "./ndjson-CDY9IkVC.mjs";
3
- import { M as workspaceLogsDir, h as readWorkspaceState$1 } from "./metro-CcEo8Fl8.mjs";
4
- import { i as findCommandWorkspace } from "./project-CWlvEaeN.mjs";
5
- import { f as refuseNoProject, v as launchErrorPreview } from "./command-output-DVcpw6eE.mjs";
6
- import { a as readAgentDeviceRecords, t as agentDeviceLiveness } from "./activity-CYVDcrt3.mjs";
7
- import { i as captureWorkspaceCrashes, t as errorDiagnostics } from "./error-diagnostics-C5HzcwCW.mjs";
4
+ import { C as workspaceLogsDir, r as readWorkspaceState$1 } from "./workspace-state-CL2V2Ons.mjs";
5
+ import { i as findCommandWorkspace } from "./project-CV3tDFHl.mjs";
6
+ import { o as readAgentDeviceRecords, t as agentDeviceLiveness } from "./activity-B2-kb7k0.mjs";
7
+ import { f as refuseNoProject, l as phaseLine, v as launchErrorPreview } from "./command-output-CliHHn7a.mjs";
8
+ import { a as pullHostedMacosLogs, t as connectHost } from "./hosted-macos-DwowNU7W.mjs";
9
+ import { i as captureWorkspaceCrashes, t as errorDiagnostics } from "./error-diagnostics-Cb0P1WLS.mjs";
10
+ import { t as watchStdoutReader } from "./stdout-reader-C8yjre1x.mjs";
8
11
  import { closeSync, fstatSync, openSync, readFileSync, readSync, readdirSync, realpathSync, statSync } from "node:fs";
9
- import { attachExpoErrorContext, buildCriteria, compileGrep, fileSizes, followLogs, logFiles, parseSince, queryLogs, readLogRecords, recordMatches, sortByTs } from "@stim-cli/core/state";
10
12
  import { join, relative, resolve, sep } from "node:path";
13
+ import { attachExpoErrorContext, buildCriteria, compileGrep, fileSizes, followLogs, logFiles, parseSince, queryLogs, readLogRecords, readMacosRecord, recordMatches, sortByTs } from "@stim-cli/core/state";
11
14
  import { homedir } from "node:os";
12
15
  import chalk from "chalk";
13
16
  //#region src/devices/agent-actions.ts
@@ -17,6 +20,8 @@ function object(value) {
17
20
  }
18
21
  function parseAgentEvents(lines) {
19
22
  const events = [];
23
+ const started = [];
24
+ const finished = [];
20
25
  let session = null;
21
26
  let unknownVersion = null;
22
27
  for (const line of lines) {
@@ -37,9 +42,14 @@ function parseAgentEvents(lines) {
37
42
  }
38
43
  if (typeof entry.session === "string") session ??= entry.session;
39
44
  const { kind, command, summary } = entry;
45
+ const requestId = typeof entry.requestId === "string" ? entry.requestId : null;
46
+ if (kind === "request.started" && requestId && Number.isFinite(ts)) started.push([requestId, ts]);
40
47
  if (kind !== "action.recorded" && kind !== "request.finished") continue;
41
48
  const failed = kind === "request.finished" && entry.status === "error";
42
- if (kind === "request.finished" && !failed) continue;
49
+ if (kind === "request.finished" && !failed) {
50
+ if (requestId) finished.push(requestId);
51
+ continue;
52
+ }
43
53
  if (!Number.isFinite(ts) || typeof entry.session !== "string" || typeof command !== "string") continue;
44
54
  if (typeof summary !== "string") continue;
45
55
  events.push({
@@ -49,11 +59,14 @@ function parseAgentEvents(lines) {
49
59
  command,
50
60
  summary,
51
61
  failed,
52
- details: object(entry.details)
62
+ details: object(entry.details),
63
+ requestId
53
64
  });
54
65
  }
55
66
  return {
56
67
  events,
68
+ started,
69
+ finished,
57
70
  session,
58
71
  unknownVersion
59
72
  };
@@ -103,9 +116,10 @@ function spanDevice(spans, ts) {
103
116
  for (const span of spans) if (span.from <= ts) device = span.deviceId;
104
117
  return device;
105
118
  }
106
- function agentRecord(event, deviceId, target) {
119
+ function agentRecord(event, deviceId, target, startedAt) {
107
120
  return {
108
121
  ts: event.ts,
122
+ ...startedAt !== void 0 && startedAt <= event.ts ? { startedAt } : {},
109
123
  src: "agent",
110
124
  level: event.failed ? "error" : "info",
111
125
  msg: event.summary,
@@ -221,7 +235,8 @@ function createAgentActionReader({ targets, sinceTs = -Infinity, home = homedir(
221
235
  closes: [],
222
236
  runner: null,
223
237
  session: null,
224
- unknownReported: false
238
+ unknownReported: false,
239
+ started: /* @__PURE__ */ new Map()
225
240
  };
226
241
  cursors.set(name, cursor);
227
242
  try {
@@ -234,8 +249,13 @@ function createAgentActionReader({ targets, sinceTs = -Infinity, home = homedir(
234
249
  cursor.offset = chunk.next;
235
250
  lines.push(...chunk.text.split("\n"));
236
251
  const parsed = parseAgentEvents(lines);
252
+ for (const [requestId, at] of parsed.started) cursor.started.set(requestId, at);
237
253
  cursor.session ??= parsed.session;
238
254
  const session = cursor.session;
255
+ const matched = new Map(parsed.events.flatMap((event) => event.requestId && cursor.started.has(event.requestId) ? [[event.requestId, cursor.started.get(event.requestId)]] : []));
256
+ const failed = parsed.events.flatMap((event) => event.failed && event.requestId ? [event.requestId] : []);
257
+ for (const requestId of [...parsed.finished, ...failed]) cursor.started.delete(requestId);
258
+ for (const requestId of [...cursor.started.keys()].slice(0, -64)) cursor.started.delete(requestId);
239
259
  if (!parsed.events.length && !parsed.unknownVersion) continue;
240
260
  const runnerPath = join(dir, "runner.log");
241
261
  let runnerSize = -1;
@@ -267,13 +287,69 @@ function createAgentActionReader({ targets, sinceTs = -Infinity, home = homedir(
267
287
  if (event.ts < sinceTs) continue;
268
288
  const deviceId = deviceAt(event.ts);
269
289
  const target = deviceId ? byId.get(deviceId) : void 0;
270
- if (target && deviceId) out.push(agentRecord(event, deviceId, target));
290
+ const startedAt = event.requestId ? matched.get(event.requestId) : void 0;
291
+ if (target && deviceId) out.push(agentRecord(event, deviceId, target, startedAt));
271
292
  }
272
293
  }
273
294
  return out.toSorted((a, b) => a.ts - b.ts);
274
295
  };
275
296
  }
276
297
  //#endregion
298
+ //#region src/device-host/hosted-logs-sync.ts
299
+ function warning(machine, error) {
300
+ const message = error.message.replace(/\.$/, "");
301
+ const hint = message.includes("refused device-host.logs.query") ? ` If ${machine} runs an older stim-server, update it.` : "";
302
+ return phaseLine("device", `Could not read the app's logs from ${machine}: ${message}. Showing the logs already copied here.${hint}`);
303
+ }
304
+ async function syncHostedMacosLogs(root, placement) {
305
+ let host;
306
+ try {
307
+ host = await connectHost(placement.machine);
308
+ await pullHostedMacosLogs(root, placement, host);
309
+ return true;
310
+ } catch (error) {
311
+ process.stderr.write(`${warning(placement.machine, error)}\n`);
312
+ return false;
313
+ } finally {
314
+ host?.connection.close();
315
+ }
316
+ }
317
+ function followHostedMacosLogs(root, { failing = false, intervalMs = 500, retryMs = 5e3 } = {}) {
318
+ let host;
319
+ let retryAt = 0;
320
+ let stopped = false;
321
+ let running = false;
322
+ const tick = async () => {
323
+ if (running || stopped || Date.now() < retryAt) return;
324
+ running = true;
325
+ try {
326
+ const placement = readMacosRecord(root)?.host;
327
+ if (!placement) {
328
+ host?.connection.close();
329
+ host = void 0;
330
+ return;
331
+ }
332
+ host ??= await connectHost(placement.machine);
333
+ await pullHostedMacosLogs(root, placement, host);
334
+ failing = false;
335
+ } catch (error) {
336
+ host?.connection.close();
337
+ host = void 0;
338
+ if (!failing) process.stderr.write(`${warning(readMacosRecord(root)?.host?.machine ?? "the host", error)}\n`);
339
+ failing = true;
340
+ retryAt = Date.now() + retryMs;
341
+ } finally {
342
+ running = false;
343
+ }
344
+ };
345
+ const timer = setInterval(() => void tick(), intervalMs);
346
+ return () => {
347
+ stopped = true;
348
+ clearInterval(timer);
349
+ host?.connection.close();
350
+ };
351
+ }
352
+ //#endregion
277
353
  //#region src/commands/logs.ts
278
354
  const LEVEL_WIDTH = 5;
279
355
  const SRC_WIDTH = 6;
@@ -359,7 +435,7 @@ const LEVEL_COLOURS = {
359
435
  fatal: chalk.bgRed
360
436
  };
361
437
  function logsCommand(program) {
362
- program.command("logs").description("Query this workspace's merged NDJSON log timeline (bundler, client, device, build, agent-device actions). Prints and exits; an existing timeline with nothing matching is a successful, empty result. Use --follow to stream.").option("--slot <name>", "Only records attributed to this device slot", parseDeviceSlotOption).option("--source <s...>", "Only these sources: metro, client, device, build, agent, or all").option("--level <l>", `Minimum level: ${LEVELS.join(", ")}`).option("--since <d>", "Only records newer than this, e.g. 30s, 5m, 2h").option("--grep <re>", "Only records whose message matches this regular expression").option("--tail <n>", "Only the last n matching records").option("--errors", "Errors and fatals since the last marker, from metro, client and build, plus confirmed native app-crash reports and stim web browser errors. Add --source device or --source all for general device errors.").option("--follow", "Keep streaming new records until interrupted").option("--json", "Emit the raw records, one per line (valid NDJSON; zero matches is zero bytes, exit 0)").action(async (opts) => {
438
+ program.command("logs").description("Query this workspace's merged NDJSON log timeline (bundler, client, device, build, agent actions). Prints and exits; an existing timeline with nothing matching is a successful, empty result. Use --follow to stream.").option("--slot <name>", "Only this device slot's records, plus the shared Metro and app client records once it has launched", parseDeviceSlotOption).option("--source <s...>", "Only these sources: metro, client, device, build, agent, or all").option("--level <l>", `Minimum level: ${LEVELS.join(", ")}`).option("--since <d>", "Only records newer than this, e.g. 30s, 5m, 2h").option("--grep <re>", "Only records whose message matches this regular expression").option("--tail <n>", "Only the last n matching records").option("--errors", "Errors and fatals since the last marker, from metro, client and build, plus confirmed native app-crash reports and stim web browser errors. Add --source device or --source all for general device errors.").option("--follow", "Keep streaming new records until interrupted").option("--json", "Emit the raw records, one per line (valid NDJSON; zero matches is zero bytes, exit 0)").action(async (opts) => {
363
439
  const root = findCommandWorkspace(process.cwd());
364
440
  if (!root) {
365
441
  refuseNoProject({ json: false });
@@ -393,6 +469,8 @@ function logsCommand(program) {
393
469
  if (compiled.error) fail(compiled.error);
394
470
  }
395
471
  requireLogsWorkspace(root, dir);
472
+ const hosted = readMacosRecord(root)?.host;
473
+ const hostedReachable = hosted ? await syncHostedMacosLogs(root, hosted) : true;
396
474
  const query = {
397
475
  slot: opts.slot,
398
476
  dir,
@@ -469,11 +547,15 @@ function logsCommand(program) {
469
547
  const agentTimer = readAgent ? setInterval(() => {
470
548
  for (const record of readAgent()) if (recordMatches(record, criteria)) emit(record);
471
549
  }, 500) : null;
550
+ const stopHosted = hosted ? followHostedMacosLogs(root, { failing: !hostedReachable }) : null;
472
551
  const finish = () => {
473
552
  stop();
553
+ stopHosted?.();
554
+ stopReader();
474
555
  if (agentTimer) clearInterval(agentTimer);
475
556
  process.exit(0);
476
557
  };
558
+ const stopReader = watchStdoutReader(finish);
477
559
  process.on("SIGINT", finish);
478
560
  process.on("SIGTERM", finish);
479
561
  });
@@ -0,0 +1,283 @@
1
+ import { t as getExecutor } from "./exec-CaErVv7t.mjs";
2
+ import { basename, dirname, join } from "node:path";
3
+ import { compiledHelper, configDir } from "@stim-cli/core";
4
+ import { MACHINE_OWNER_KINDS } from "@stim-cli/core/state";
5
+ import { fileURLToPath } from "node:url";
6
+ //#region src/footprint.ts
7
+ const SOURCE = "stim-footprint.swift";
8
+ const COMPILE_TIMEOUT_MS = 12e4;
9
+ const READ_TIMEOUT_MS = 5e3;
10
+ function helperSource() {
11
+ const here = fileURLToPath(import.meta.url);
12
+ if (basename(dirname(here)) === "src") return fileURLToPath(new URL(`../helper/${SOURCE}`, import.meta.url));
13
+ return join(dirname(here), SOURCE);
14
+ }
15
+ async function buildHelper() {
16
+ const exec = getExecutor();
17
+ if (!exec.runFileQuiet("xcode-select", ["-p"], { timeoutMs: READ_TIMEOUT_MS })) return null;
18
+ const source = helperSource();
19
+ try {
20
+ return await compiledHelper({
21
+ dir: join(configDir(), "helpers"),
22
+ name: "stim-footprint",
23
+ inputs: [source],
24
+ version: "",
25
+ compile: async (output) => {
26
+ await exec.runFileAsync("xcrun", [
27
+ "swiftc",
28
+ "-O",
29
+ "-swift-version",
30
+ "5",
31
+ "-o",
32
+ output,
33
+ source
34
+ ], { timeoutMs: COMPILE_TIMEOUT_MS });
35
+ }
36
+ });
37
+ } catch {
38
+ return null;
39
+ }
40
+ }
41
+ const RETRY_BACKOFF_INITIAL_MS = 1e4;
42
+ const RETRY_BACKOFF_MAX_MS = 6e5;
43
+ let built = null;
44
+ let failure = null;
45
+ /** The `stim-footprint` helper, compiled on first use. A success is kept for the process's life; a failure retries after a doubling backoff instead of being pinned forever. */
46
+ function footprintHelper() {
47
+ const home = configDir();
48
+ if (built?.home === home) return built.promise;
49
+ if (failure?.home === home && Date.now() < failure.nextRetryAt) return Promise.resolve(null);
50
+ const promise = buildHelper().then((helper) => {
51
+ if (helper === null) {
52
+ const backoffMs = failure?.home === home ? Math.min(failure.backoffMs * 2, RETRY_BACKOFF_MAX_MS) : RETRY_BACKOFF_INITIAL_MS;
53
+ failure = {
54
+ home,
55
+ nextRetryAt: Date.now() + backoffMs,
56
+ backoffMs
57
+ };
58
+ if (built?.promise === promise) built = null;
59
+ return null;
60
+ }
61
+ return helper;
62
+ });
63
+ built = {
64
+ home,
65
+ promise
66
+ };
67
+ return promise;
68
+ }
69
+ /** Parses the helper's `<pid> <bytes>` lines into footprint bytes by pid. */
70
+ function parseFootprints(output) {
71
+ const footprints = /* @__PURE__ */ new Map();
72
+ for (const line of output.split("\n")) {
73
+ const match = /^(\d+) (\d+)$/.exec(line.trim());
74
+ if (match) footprints.set(Number(match[1]), Number(match[2]));
75
+ }
76
+ return footprints;
77
+ }
78
+ /** Every readable process's physical footprint in bytes, or null when the helper is unavailable or fails. */
79
+ async function readFootprints() {
80
+ const helper = await footprintHelper();
81
+ const output = helper ? getExecutor().runFileQuiet(helper, [], { timeoutMs: READ_TIMEOUT_MS }) : null;
82
+ return output === null ? null : parseFootprints(output);
83
+ }
84
+ //#endregion
85
+ //#region src/machine-usage.ts
86
+ const SHARED = [
87
+ {
88
+ name: "CoreSimulator services",
89
+ pattern: /\/CoreSimulator\.framework\/|\/Simulator\.app\/Contents\/MacOS\//
90
+ },
91
+ {
92
+ name: "adb server",
93
+ pattern: /(^|\/)adb\s.*\bfork-server\b/
94
+ },
95
+ {
96
+ name: "Android emulator services",
97
+ pattern: /\/emulator\/netsimd(\s|$)/
98
+ },
99
+ {
100
+ name: "Gradle daemon",
101
+ pattern: /\borg\.gradle\.launcher\.daemon\.bootstrap\.GradleDaemon\b/
102
+ },
103
+ {
104
+ name: "Kotlin daemon",
105
+ pattern: /\bKotlinCompileDaemon\b/
106
+ },
107
+ {
108
+ name: "Watchman",
109
+ pattern: /(^|\/)watchman(\s|$)/
110
+ }
111
+ ];
112
+ const STIM_SERVER_COMMAND = /(^|[\s/])stim-server(\.mjs)?(\s|$)/;
113
+ const LAUNCHD_SIM = /^launchd_sim\s.*\/Devices\/([^/\s]+)\//;
114
+ const MAX_DEPTH = 64;
115
+ const EMULATOR = /(?:^|\/)(?:qemu-system-[^\s/]+|emulator)(?:\s.*)?\s(?:-avd\s+|@)(\S+)/;
116
+ function emulatorAvd(command) {
117
+ return EMULATOR.exec(command)?.[1] ?? null;
118
+ }
119
+ function workspaceOwner(kind, env, name, id) {
120
+ return {
121
+ kind,
122
+ name,
123
+ workspace: env.path,
124
+ id,
125
+ owned: true
126
+ };
127
+ }
128
+ function rank(owner) {
129
+ return MACHINE_OWNER_KINDS.indexOf(owner.kind);
130
+ }
131
+ /**
132
+ * Attributes every process in `processes` to at most one owner: the owner of its nearest ancestor (itself included)
133
+ * that is a root. Roots are each booted simulator's `launchd_sim`, each emulator launcher and qemu process, and each
134
+ * workspace's verified supervisor and Metro, running build and Chrome. Only a process under none of those falls back
135
+ * to stim-server or a machine-wide service in `SHARED`, so a `simctl` a workspace's log collector runs stays with the
136
+ * workspace. An emulator counts in the workspace that records its AVD whenever its process runs, including while adb
137
+ * does not list it. A process with no root ancestor is left out. `footprints` holds physical footprint bytes by pid,
138
+ * or is null when they could not be read, and the owners' `memoryMb` then sums resident sizes. A process missing from
139
+ * `footprints`, another user's or one that exited between the two reads, counts its resident size.
140
+ */
141
+ function attributeMachineUsage({ processes, footprints, environments, roots, simNames }) {
142
+ const owners = /* @__PURE__ */ new Map();
143
+ const rootOf = /* @__PURE__ */ new Map();
144
+ const fallbackOf = /* @__PURE__ */ new Map();
145
+ const claim = (pid, key, owner, into = rootOf) => {
146
+ if (!owners.has(key)) owners.set(key, owner);
147
+ into.set(pid, key);
148
+ };
149
+ const sims = /* @__PURE__ */ new Map();
150
+ const emulators = /* @__PURE__ */ new Map();
151
+ for (const env of environments) for (const device of [{
152
+ slot: "default",
153
+ ios: env.ios,
154
+ android: env.android
155
+ }, ...env.slots ?? []]) {
156
+ const slot = device.slot === "default" ? {} : { slot: device.slot };
157
+ if (device.ios?.state === "Booted") sims.set(device.ios.udid.toUpperCase(), {
158
+ kind: "simulator",
159
+ name: device.ios.name ?? device.ios.udid,
160
+ workspace: env.path,
161
+ ...slot,
162
+ id: device.ios.udid,
163
+ owned: device.ios.owned
164
+ });
165
+ if (device.android?.name && !device.android.physical) emulators.set(device.android.name, {
166
+ kind: "emulator",
167
+ name: device.android.name,
168
+ workspace: env.path,
169
+ ...slot,
170
+ id: device.android.name,
171
+ owned: device.android.owned
172
+ });
173
+ }
174
+ for (const p of processes) {
175
+ const shared = SHARED.find(({ pattern }) => pattern.test(p.command));
176
+ if (shared) {
177
+ const owner = {
178
+ kind: "shared",
179
+ name: shared.name,
180
+ workspace: null,
181
+ id: null,
182
+ owned: false
183
+ };
184
+ claim(p.pid, `shared:${shared.name}`, owner, fallbackOf);
185
+ continue;
186
+ }
187
+ if (STIM_SERVER_COMMAND.test(p.command)) {
188
+ claim(p.pid, "server", {
189
+ kind: "server",
190
+ name: "stim-server",
191
+ workspace: null,
192
+ id: null,
193
+ owned: false
194
+ }, fallbackOf);
195
+ continue;
196
+ }
197
+ const udid = LAUNCHD_SIM.exec(p.command)?.[1]?.toUpperCase();
198
+ if (udid) {
199
+ const owner = sims.get(udid) ?? {
200
+ kind: "simulator",
201
+ name: simNames[udid] ?? udid,
202
+ workspace: null,
203
+ id: udid,
204
+ owned: false
205
+ };
206
+ claim(p.pid, `simulator:${udid}`, owner);
207
+ continue;
208
+ }
209
+ const avd = emulatorAvd(p.command);
210
+ if (avd) {
211
+ const owner = emulators.get(avd) ?? {
212
+ kind: "emulator",
213
+ name: avd,
214
+ workspace: null,
215
+ id: avd,
216
+ owned: false
217
+ };
218
+ claim(p.pid, `emulator:${avd}`, owner);
219
+ }
220
+ }
221
+ const present = new Set(processes.map((p) => p.pid));
222
+ const byPath = new Map(environments.map((env) => [env.path, env]));
223
+ for (const root of roots) {
224
+ const env = byPath.get(root.path);
225
+ if (!env) continue;
226
+ const metroPids = [root.supervisorPid, env.metro?.running ? env.metro.pid : null];
227
+ for (const pid of metroPids) if (pid && present.has(pid)) {
228
+ const port = env.metro?.port ?? null;
229
+ claim(pid, `metro:${env.path}`, workspaceOwner("metro", env, port ? `Metro :${port}` : "Metro", port ? String(port) : null));
230
+ }
231
+ if (root.build && present.has(root.build.pid)) {
232
+ const label = root.build.platform === "ios" ? "iOS build" : "Android build";
233
+ claim(root.build.pid, `build:${env.path}`, workspaceOwner("build", env, label, root.build.platform));
234
+ }
235
+ for (const pid of root.macosPids ?? []) if (present.has(pid)) claim(pid, `macos:${env.path}`, workspaceOwner("macos", env, env.macos?.product ?? "macOS app", null));
236
+ for (const pid of root.browserPids) if (present.has(pid)) claim(pid, `browser:${env.path}`, workspaceOwner("browser", env, "Chrome", null));
237
+ }
238
+ const parentOf = new Map(processes.map((p) => [p.pid, p.ppid]));
239
+ const totals = /* @__PURE__ */ new Map();
240
+ for (const p of processes) {
241
+ let pid = p.pid;
242
+ let key;
243
+ let fallback;
244
+ for (let depth = 0; pid !== void 0 && pid > 1 && depth < MAX_DEPTH; depth++) {
245
+ key = rootOf.get(pid);
246
+ if (key) break;
247
+ fallback ??= fallbackOf.get(pid);
248
+ pid = parentOf.get(pid);
249
+ }
250
+ key ??= fallback;
251
+ if (!key) continue;
252
+ const total = totals.get(key) ?? {
253
+ rssKb: 0,
254
+ memoryKb: 0,
255
+ cpu: 0,
256
+ processes: 0
257
+ };
258
+ total.rssKb += p.rssKb;
259
+ const footprint = footprints?.get(p.pid);
260
+ total.memoryKb += footprint === void 0 ? p.rssKb : footprint / 1024;
261
+ total.cpu += p.cpuPercent;
262
+ total.processes += 1;
263
+ totals.set(key, total);
264
+ }
265
+ const list = [];
266
+ for (const [key, owner] of owners) {
267
+ const total = totals.get(key);
268
+ if (total) list.push({
269
+ ...owner,
270
+ cpuPercent: Math.round(total.cpu),
271
+ residentMb: Math.round(total.rssKb / 1024),
272
+ memoryMb: Math.round(total.memoryKb / 1024),
273
+ processes: total.processes
274
+ });
275
+ }
276
+ list.sort((x, y) => rank(x) - rank(y) || (x.workspace ?? "~").localeCompare(y.workspace ?? "~") || x.name.localeCompare(y.name));
277
+ return {
278
+ memorySource: footprints ? "footprint" : "rss",
279
+ owners: list
280
+ };
281
+ }
282
+ //#endregion
283
+ export { attributeMachineUsage as n, readFootprints as r, STIM_SERVER_COMMAND as t };