stim 1.2.0 → 1.3.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 (59) hide show
  1. package/README.md +30 -0
  2. package/dist/android-BqVIJoG7.mjs +1368 -0
  3. package/dist/{android-Bcmev2s2.mjs → android-CqcW1hrP.mjs} +148 -56
  4. package/dist/{android-cas-D6HPZ6UQ.mjs → android-cas-BR5YJBYv.mjs} +5 -5
  5. package/dist/{app-install-NFxUahnH.mjs → app-install-DIp9riNx.mjs} +6 -6
  6. package/dist/{build-slots-D0b6sJyo.mjs → build-slots-C3-R4XQr.mjs} +2 -2
  7. package/dist/{cache-manifest-Cu4_Znw9.mjs → cache-manifest-CXR-Y8pH.mjs} +1 -1
  8. package/dist/cache-manifest.mjs +1 -1
  9. package/dist/cli.mjs +13 -13
  10. package/dist/collector-run.d.mts +3 -1
  11. package/dist/collector-run.mjs +33 -8
  12. package/dist/{config-CMtIaTk9.mjs → config-CcefMY2Q.mjs} +93 -12
  13. package/dist/{deps-BLBsTNDS.mjs → deps-Spz8ozgo.mjs} +1 -1
  14. package/dist/{dev-client-RpvXxCXV.mjs → dev-client-BjisHDB5.mjs} +186 -9
  15. package/dist/{device-CoCPmazS.mjs → device-BkiW-gkk.mjs} +19 -11
  16. package/dist/{device-lease-Dpik41RY.mjs → device-lease-BJYybF-2.mjs} +107 -83
  17. package/dist/{device-pool-B5pEVa3t.mjs → device-pool-CPbLGfrB.mjs} +18 -12
  18. package/dist/{device-remote-E81KAdXf.mjs → device-remote-AkYCKS8U.mjs} +7 -7
  19. package/dist/{doctor-CqPeMsSm.mjs → doctor-Bmv86UUW.mjs} +11 -12
  20. package/dist/{doctor-D_CX7ib5.mjs → doctor-fAMXC4vl.mjs} +4 -4
  21. package/dist/{error-diagnostics-Dw8Gn3yw.mjs → error-diagnostics-DHC31rXA.mjs} +62 -52
  22. package/dist/{gc-B7KkDinF.mjs → gc-GGu-8ULN.mjs} +66 -57
  23. package/dist/{guide-BS2Xc24o.mjs → guide-92CPA21s.mjs} +196 -13
  24. package/dist/{ios-B7KLgIp5.mjs → ios-BSXFeO5H.mjs} +183 -54
  25. package/dist/{ios-D1h1Di39.mjs → ios-Deh5js5V.mjs} +78 -40
  26. package/dist/{ios-device-xNt0Lg6v.mjs → ios-device-CWOM_S44.mjs} +1 -1
  27. package/dist/{logs-DcX9AvRF.mjs → logs-DPNF4C9s.mjs} +9 -6
  28. package/dist/{logs-query-oqn47Ffx.mjs → logs-query-R-Cqc6Qv.mjs} +22 -5
  29. package/dist/{metro-COslRK_F.mjs → metro-CORqXyTL.mjs} +2 -2
  30. package/dist/{ndjson-DcAtEx_K.mjs → ndjson-BZwI1NBv.mjs} +5 -2
  31. package/dist/{ownership-CGPart6T.mjs → ownership-8O073GIC.mjs} +3 -3
  32. package/dist/{ownership-CYOyqxUo.mjs → ownership-BpCKiRlQ.mjs} +3 -3
  33. package/dist/{ownership-claim-7zRxxTfZ.mjs → ownership-claim-Bf2CAfw3.mjs} +4 -3
  34. package/dist/{project-BBTVABUz.mjs → project-fKmuIaMI.mjs} +2 -2
  35. package/dist/{reclaim-C5Jmuahx.mjs → reclaim-iW9Cad7k.mjs} +80 -74
  36. package/dist/{reload-Z8nKifAk.mjs → reload-DazHDSFA.mjs} +32 -24
  37. package/dist/{server-bare-DEk37Mz2.mjs → server-bare-DEzPZLvf.mjs} +2 -2
  38. package/dist/server-expo-44em5Pmg.mjs +2 -0
  39. package/dist/{server-expo-C3ktAwUG.mjs → server-expo-DizT4OMk.mjs} +4 -4
  40. package/dist/{settings-DwU1_U_q.mjs → settings-DEJf6_kJ.mjs} +2 -2
  41. package/dist/slot-launch-cci0xvmC.mjs +19 -0
  42. package/dist/{start-BrY4EuIg.mjs → start-mObjQH2W.mjs} +11 -11
  43. package/dist/{state-CZ-ER-8d.mjs → state-B3XA2wp2.mjs} +9 -9
  44. package/dist/{stats-BCWEVLrM.mjs → stats-BR-kwj0B.mjs} +1 -1
  45. package/dist/{stats-BsF15Bti.mjs → stats-CaaQr4yN.mjs} +3 -3
  46. package/dist/{status-DFI1Hfpg.mjs → status-BxQNqu9t.mjs} +55 -24
  47. package/dist/{stop-B7DR4awK.mjs → stop-CrHGcjrV.mjs} +2 -2
  48. package/dist/{stop-Btg770vV.mjs → stop-DimGXEKY.mjs} +93 -42
  49. package/dist/supervisor-run.d.mts +1 -2
  50. package/dist/supervisor-run.mjs +5 -5
  51. package/dist/{remote-cache-DDmJ1Roq.mjs → teardown-ClFp1Aax.mjs} +887 -888
  52. package/dist/{workspace-process-lock-BKfdys1q.mjs → workspace-process-lock-3NcQnZ0X.mjs} +1 -1
  53. package/dist/{worktree-BPTbI-LN.mjs → worktree-DxTgN1CJ.mjs} +12 -12
  54. package/dist/{worktree-C79xx_kM.mjs → worktree-Eq6TLH8N.mjs} +1 -1
  55. package/dist/{xcode-DeKCribl.mjs → xcode-D7sAp-Zy.mjs} +82 -56
  56. package/package.json +5 -5
  57. package/dist/android-K63_0UfF.mjs +0 -727
  58. package/dist/build-lock-BsVoLDaq.mjs +0 -645
  59. package/dist/server-expo-Drd7ksHu.mjs +0 -2
@@ -1,20 +1,19 @@
1
1
  import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
- import { A as directorySize, M as isOnMountedVolume, N as listMountedVolumes, P as volumeRootFor, g as loadConfig, i as clearDevice, j as formatBytes, u as getConfigDir, v as removeProject } from "./config-CMtIaTk9.mjs";
3
- import { i as registeredCaches } from "./cache-manifest-Cu4_Znw9.mjs";
4
- import { T as workspaceDir, k as workspaceStateFile } from "./state-CZ-ER-8d.mjs";
5
- import { g as repoRoot, s as gitCommonDir } from "./worktree-C79xx_kM.mjs";
6
- import { a as findProjectRoot, i as detectIsExpo } from "./project-BBTVABUz.mjs";
7
- import { S as readParked, _ as teardownParkedIosSim, b as dropParked, d as resolveEasCliBin, g as teardownParkedAvd, h as teardownOwnedIosSim, m as teardownOwnedAvd, v as POOL_SETTING_REMEDY, x as parkedMaxSetting } from "./remote-cache-DDmJ1Roq.mjs";
8
- import { A as resolveSettings, N as settingShapeErrors, a as SETTING_SHAPE_REMEDY } from "./settings-DwU1_U_q.mjs";
2
+ import { A as directorySize, M as isOnMountedVolume, N as listMountedVolumes, P as volumeRootFor, g as loadConfig, i as clearDevice, j as formatBytes, u as getConfigDir, v as removeProject, z as projectDeviceSlots } from "./config-CcefMY2Q.mjs";
3
+ import { i as registeredCaches } from "./cache-manifest-CXR-Y8pH.mjs";
4
+ import { T as workspaceDir, k as workspaceStateFile } from "./state-B3XA2wp2.mjs";
5
+ import { g as repoRoot, s as gitCommonDir } from "./worktree-Eq6TLH8N.mjs";
6
+ import { a as findProjectRoot, i as detectIsExpo } from "./project-fKmuIaMI.mjs";
9
7
  import { m as shortUdid, o as formatLongDuration, u as plural } from "./command-output-8wHMEde0.mjs";
10
- import { d as listIosDeviceTypes, g as parseRuntimeVersion, u as listAllIosSims } from "./ios-D1h1Di39.mjs";
11
- import { r as listBuildLocks } from "./build-lock-BsVoLDaq.mjs";
12
- import { d as readClaimSet, o as clearFreeClaimSet } from "./ownership-claim-7zRxxTfZ.mjs";
13
- import { i as listLeaseFiles, r as leaseIsExpired, u as removeExpiredLease } from "./device-lease-Dpik41RY.mjs";
14
- import { n as listBuildSlots } from "./build-slots-D0b6sJyo.mjs";
15
- import { S as listOrphanedAvdDirectories, T as ownedAvdDirectory, b as listAvds } from "./android-K63_0UfF.mjs";
16
- import { C as verifyStoppedSession, S as stopSessionArgs, _ as inspectSessionForTeardown, d as withEasProjectLock, f as easMachineStateRoot, g as getSessionArgs, h as findOrphanedOwnedSessions, m as removeEasSessionClaim, p as readEasSessionLedger, u as withRemoteSessionLock, v as isDefinitiveMissingSessionError, x as parseSessionListPage, y as listOwnedSessionsArgs } from "./device-remote-E81KAdXf.mjs";
17
- import { n as reclaimProject, t as describeDereferenced } from "./reclaim-C5Jmuahx.mjs";
8
+ import { d as readClaimSet, o as clearFreeClaimSet } from "./ownership-claim-Bf2CAfw3.mjs";
9
+ import { B as listBuildLocks, S as listOrphanedAvdDirectories, T as ownedAvdDirectory, b as listAvds } from "./android-BqVIJoG7.mjs";
10
+ import { A as resolveSettings, N as settingShapeErrors, a as SETTING_SHAPE_REMEDY } from "./settings-DEJf6_kJ.mjs";
11
+ import { a as POOL_SETTING_REMEDY, c as parkedMaxSetting, i as teardownParkedIosSim, l as readParked, n as teardownOwnedIosSim, r as teardownParkedAvd, s as dropParked, t as teardownOwnedAvd, x as resolveEasCliBin } from "./teardown-ClFp1Aax.mjs";
12
+ import { d as listIosDeviceTypes, g as parseRuntimeVersion, u as listAllIosSims } from "./ios-Deh5js5V.mjs";
13
+ import { i as listLeaseFiles, r as leaseIsExpired, u as removeExpiredLease } from "./device-lease-BJYybF-2.mjs";
14
+ import { n as listBuildSlots } from "./build-slots-C3-R4XQr.mjs";
15
+ import { C as verifyStoppedSession, S as stopSessionArgs, _ as inspectSessionForTeardown, d as withEasProjectLock, f as easMachineStateRoot, g as getSessionArgs, h as findOrphanedOwnedSessions, m as removeEasSessionClaim, p as readEasSessionLedger, u as withRemoteSessionLock, v as isDefinitiveMissingSessionError, x as parseSessionListPage, y as listOwnedSessionsArgs } from "./device-remote-AkYCKS8U.mjs";
16
+ import { n as reclaimProject, t as describeDereferenced } from "./reclaim-iW9Cad7k.mjs";
18
17
  import { InvalidArgumentError } from "commander";
19
18
  import { existsSync, readFileSync, readdirSync, realpathSync, rmSync, statSync } from "fs";
20
19
  import { homedir, tmpdir } from "os";
@@ -413,16 +412,18 @@ function findOrphanedDevices({ sims = [], avds = [], config, isMounted, deadProj
413
412
  for (const [path, proj] of Object.entries(config?.projects || {})) {
414
413
  if (dead.has(path)) continue;
415
414
  const mounted = isMounted ? isMounted(path) : true;
416
- const ios = proj?.platforms?.ios;
417
- if (ios?.deviceUdid) referenced.set(ios.deviceUdid, {
418
- path,
419
- mounted
420
- });
421
- const android = proj?.platforms?.android;
422
- if (android?.avdName) referenced.set(android.avdName, {
423
- path,
424
- mounted
425
- });
415
+ for (const { platforms } of projectDeviceSlots(proj)) {
416
+ const ios = platforms.ios;
417
+ if (ios?.deviceUdid) referenced.set(ios.deviceUdid, {
418
+ path,
419
+ mounted
420
+ });
421
+ const android = platforms.android;
422
+ if (android?.avdName) referenced.set(android.avdName, {
423
+ path,
424
+ mounted
425
+ });
426
+ }
426
427
  }
427
428
  for (const sim of readParked("ios", { config })) referenced.set(sim.udid, {
428
429
  path: "the simulator pool",
@@ -481,22 +482,26 @@ function findStaleProjectDevices({ config, sims = [], avds = [], olderThanDays,
481
482
  const touched = lastTouched(path);
482
483
  if (!Number.isFinite(touched) || touched >= cutoff) continue;
483
484
  const idleDays = Math.floor((now - touched) / DAY_MS);
484
- const ios = proj?.platforms?.ios;
485
- if (ios?.owned && ios.deviceUdid && liveSims.has(ios.deviceUdid)) stale.push({
486
- kind: "ios",
487
- id: ios.deviceUdid,
488
- name: liveSims.get(ios.deviceUdid),
489
- project: path,
490
- idleDays
491
- });
492
- const android = proj?.platforms?.android;
493
- if (android?.owned && android.avdName && liveAvds.has(android.avdName)) stale.push({
494
- kind: "android",
495
- id: android.avdName,
496
- name: android.avdName,
497
- project: path,
498
- idleDays
499
- });
485
+ for (const { slot, platforms } of projectDeviceSlots(proj)) {
486
+ const ios = platforms.ios;
487
+ if (ios?.owned && ios.deviceUdid && liveSims.has(ios.deviceUdid)) stale.push({
488
+ kind: "ios",
489
+ id: ios.deviceUdid,
490
+ name: liveSims.get(ios.deviceUdid),
491
+ project: path,
492
+ ...slot === "default" ? {} : { slot },
493
+ idleDays
494
+ });
495
+ const android = platforms.android;
496
+ if (android?.owned && android.avdName && liveAvds.has(android.avdName)) stale.push({
497
+ kind: "android",
498
+ id: android.avdName,
499
+ name: android.avdName,
500
+ project: path,
501
+ idleDays,
502
+ ...slot === "default" ? {} : { slot }
503
+ });
504
+ }
500
505
  }
501
506
  return stale;
502
507
  }
@@ -507,20 +512,24 @@ function findStaleDeviceRecords({ config, sims = [], avds = [], deadProjects = [
507
512
  const stale = [];
508
513
  for (const [path, proj] of Object.entries(config?.projects || {})) {
509
514
  if (dead.has(path)) continue;
510
- const ios = proj?.platforms?.ios;
511
- if (simsChecked && ios?.deviceUdid && !liveSims.has(ios.deviceUdid)) stale.push({
512
- kind: "ios",
513
- id: ios.deviceUdid,
514
- project: path,
515
- owned: Boolean(ios.owned)
516
- });
517
- const android = proj?.platforms?.android;
518
- if (avdsChecked && android?.avdName && !liveAvds.has(android.avdName)) stale.push({
519
- kind: "android",
520
- id: android.avdName,
521
- project: path,
522
- owned: Boolean(android.owned)
523
- });
515
+ for (const { slot, platforms } of projectDeviceSlots(proj)) {
516
+ const ios = platforms.ios;
517
+ if (simsChecked && ios?.deviceUdid && !liveSims.has(ios.deviceUdid)) stale.push({
518
+ kind: "ios",
519
+ id: ios.deviceUdid,
520
+ project: path,
521
+ ...slot === "default" ? {} : { slot },
522
+ owned: Boolean(ios.owned)
523
+ });
524
+ const android = platforms.android;
525
+ if (avdsChecked && android?.avdName && !liveAvds.has(android.avdName)) stale.push({
526
+ kind: "android",
527
+ id: android.avdName,
528
+ project: path,
529
+ ...slot === "default" ? {} : { slot },
530
+ owned: Boolean(android.owned)
531
+ });
532
+ }
524
533
  }
525
534
  return stale;
526
535
  }
@@ -704,12 +713,12 @@ function deleteProjectDevices(orphanedDevices, staleDevices, staleDeviceRecords)
704
713
  for (const d of staleDevices) {
705
714
  const status = reap(d);
706
715
  if (status === "torn-down" || status === "missing") {
707
- clearDevice(d.project, d.kind);
716
+ clearDevice(d.project, d.kind, d.slot);
708
717
  console.log(chalk.dim(` cleared the ${d.kind} record for ${d.project}`));
709
718
  }
710
719
  }
711
720
  for (const r of staleDeviceRecords) {
712
- clearDevice(r.project, r.kind);
721
+ clearDevice(r.project, r.kind, r.slot);
713
722
  console.log(chalk.green(`Cleared the ${r.kind} record for ${r.project} (${r.id} is not on this machine)`));
714
723
  }
715
724
  return deleteFailures;
@@ -1,4 +1,4 @@
1
- import { t as ANDROID_AVD_CONFIG_HELP } from "./settings-DwU1_U_q.mjs";
1
+ import { t as ANDROID_AVD_CONFIG_HELP } from "./settings-DEJf6_kJ.mjs";
2
2
  import chalk from "chalk";
3
3
  //#endregion
4
4
  //#region src/guide/index.ts
@@ -31,6 +31,15 @@ branch that is not the default one -- only once the repository has at least
31
31
  one linked worktree, so read it from inside the worktree. A single-checkout
32
32
  session is never told that its own branch is a problem.
33
33
 
34
+ MULTIPLE DEVICES
35
+
36
+ Use ios/android --slot <name> to retain multiple devices in one workspace,
37
+ including several of the same model. Reuse the same slot name on subsequent
38
+ runs. Read guide lifecycle options for the complete slot workflow. Use the
39
+ reported device ID for UI interaction. All slots share Metro; reload can reach
40
+ multiple devices, and a shared bundle request does not prove a slot launched.
41
+ Use stop --slot <name> for one slot, or plain stop for the whole workspace.
42
+
34
43
  NORMAL WORKFLOW
35
44
 
36
45
  Work in the current checkout by default. When the task needs another branch or
@@ -66,7 +75,9 @@ For iOS Debug architecture findings, review the project's overrides and imported
66
75
  Podfile helpers using guide lifecycle options. Doctor --fix does not change them.
67
76
  For parallel iOS work, review the recommended optional SimSlim setup in
68
77
  stim guide lifecycle simslim. If simulator process startup times out, check
69
- host memory pressure and free memory before retrying; do not restart other
78
+ host memory pressure and free memory before retrying. Boot progress reports
79
+ current and highest observed pressure; a timeout does not establish OOM.
80
+ Avoid repeated reboots under unchanged pressure; do not restart other
70
81
  workspaces' devices or close their apps without asking. That guide covers
71
82
  the recovery steps and profile tradeoffs.
72
83
  For a linked native library carrying Git metadata, add the printed .git entries to
@@ -88,6 +99,16 @@ them. It preserves source, custom launcher settings, and the shared ccache.
88
99
  stim start
89
100
  stim ios # or: stim android
90
101
 
102
+ For a project using EAS development builds, read stim guide lifecycle eas to
103
+ select a profile from eas.json for the requested target, then run
104
+ stim ios --eas-profile <name> or stim android --eas-profile <name>.
105
+ Ask the user if the profile choice is ambiguous. A miss stops with
106
+ STIM_EAS_BUILD_MISSING and an EAS build command. Run that command only when the
107
+ session authorizes the potentially billable build, then retry Stim. The
108
+ presence of eas.json does not select EAS or authorize building.
109
+ Physical --device targets are supported. Follow EAS device-registration and
110
+ rebuild remedies only when the session authorizes those account changes.
111
+
91
112
  Read stim guide lifecycle concurrency when a build waits on another workspace
92
113
  or a build call times out. A native build can outlive a shell timeout; if the
93
114
  tool call timed out, retry the same command and follow its printed remedy if
@@ -277,7 +298,13 @@ FULL TOPIC LIST
277
298
  },
278
299
  facts: {
279
300
  summary: "The --json payloads: `start`, `ios`, `android`, `reload`, `stop`, `status`, `doctor`, `device lock`/`unlock`, and the error contract",
280
- preamble: () => `FACTS CONTRACT
301
+ preamble: () => `SLOTS
302
+ Named ios/android runs add slot to their JSON facts. Default-run fields remain
303
+ compatible. status adds a slots array per environment with each named slot's
304
+ ios/android device facts; top-level ios/android still describe default.
305
+ Named collector, lease-holder, and launch keys use platform:slot internally.
306
+
307
+ FACTS CONTRACT
281
308
 
282
309
  \`start\`, \`ios\`, \`android\`, \`reload\`, \`stop\`, \`status\`, \`stats\`, \`doctor\`,
283
310
  and \`device lock\`/\`device unlock\` each print exactly ONE line of JSON on
@@ -341,6 +368,11 @@ line by design (see \`guide logs\`), not this single-payload contract.`,
341
368
  absent for automatic selection; not the app URL scheme
342
369
  cacheKey the shared-build-cache key derived from it (the
343
370
  configuration is part of it: -release-sim vs -debug-sim)
371
+ With --eas-profile, fingerprint is computed by EAS CLI using the selected
372
+ profile/environment, and cacheKey identifies the EAS project and build ID
373
+ separately from local native builds. cacheHit is "remote" for the EAS
374
+ source, including when EAS CLI reuses its own downloaded artifact cache.
375
+
344
376
  cacheHit WHICH LEVEL answered, not a boolean:
345
377
  "local" this machine's shared cache (free, instant)
346
378
  "remote" the project's own Expo buildCacheProvider (a
@@ -874,7 +906,13 @@ one dim note on STDERR reading \`No matching log records in <logs dir>\`
874
906
  (human mode only -- \`--json\` prints nothing at all, on either stream).
875
907
  The only exit-1 paths are a malformed query and no project.
876
908
 
909
+ Device collectors and build records carry their named slot. Use --slot phone
910
+ for that slot's timeline; its launch marker cannot hide a sibling's errors.
911
+ Untagged legacy records belong to default. Metro/client records are shared and
912
+ usually untagged: use an unfiltered workspace query to inspect those errors.
913
+
877
914
  FLAGS
915
+ --slot <name> only this slot's records (default includes untagged records)
878
916
  --source <s...> metro, client, device, build (one or more), or all. An
879
917
  unknown value is REJECTED rather than quietly matching
880
918
  nothing.
@@ -1138,6 +1176,24 @@ WHAT WRITES WHAT
1138
1176
  Every refusal from \`ios\` / \`android\` carries a stable CODE. Branch on the
1139
1177
  code, never on the message.`,
1140
1178
  sections: {
1179
+ STIM_EAS_BUILD_MISSING: {
1180
+ summary: "no completed EAS development build matches; build only with session authorization",
1181
+ body: () => `STIM_EAS_BUILD_MISSING
1182
+ No compatible build matches the selected EAS project, profile, platform and
1183
+ native fingerprint. No device was acquired and no local or cloud build was
1184
+ started. The remedy prints the exact npx eas-cli build command. Check session
1185
+ authorization for its potential cost before running it, then retry Stim.
1186
+ See stim guide lifecycle eas.`
1187
+ },
1188
+ STIM_EAS_UNAVAILABLE: {
1189
+ summary: "EAS lookup/download failed, or another run holds the artifact claim",
1190
+ body: () => `STIM_EAS_UNAVAILABLE
1191
+ EAS CLI is unavailable, a profile/fingerprint/list/download operation failed,
1192
+ the response could not be validated, or another run holds the artifact claim.
1193
+ Follow the printed remedy: inspect the named EAS command or retry once the
1194
+ holder finishes. This is not proof that a build is missing. No native build
1195
+ is started. See stim guide lifecycle eas.`
1196
+ },
1141
1197
  STIM_WORKSPACE_STATE: {
1142
1198
  summary: "$STIM_HOME/workspaces could not be prepared, or the digest directory belongs to another project",
1143
1199
  aliases: ["STIM_WORKSPACE_COLLISION"],
@@ -1464,7 +1520,10 @@ spending a build or a bundle, that the check can succeed.`,
1464
1520
  profile;
1465
1521
  - it is a development or ad hoc profile whose device list does not name
1466
1522
  this UDID. Register the UDID at developer.apple.com, regenerate the
1467
- profile, and build once from Xcode.`
1523
+ profile, and build once from Xcode.
1524
+ With --eas-profile, follow the EAS device:create and build commands in the
1525
+ refusal instead. Registration, signing changes and cloud builds need session
1526
+ authorization. See stim guide lifecycle eas.`
1468
1527
  },
1469
1528
  STIM_NO_SIGNING_IDENTITY: {
1470
1529
  summary: "no single keychain identity resolves; ios.signingIdentitySha1 for two certificates",
@@ -1642,8 +1701,8 @@ so a Debug run on one is wired to a LAN origin instead of localhost.`,
1642
1701
  and \`adb devices\`, then retry after any other run finishes. Keep the AVD and
1643
1702
  its process locks while its state is unverified.
1644
1703
  On iOS a slow first boot is waited out for up to ten minutes while the
1645
- simulator still reports Booting -- a long silent wait on a loaded machine
1646
- is patience, not a hang. The failure names the udid and the wait.
1704
+ simulator reports Booting or Booted but bootstatus has not completed.
1705
+ Booted alone does not end that wait. The failure names the udid and the wait.
1647
1706
  After boot, a process-spawn probe must finish within 30 seconds before
1648
1707
  installation. If it fails, the refusal includes observed host memory pressure
1649
1708
  when available. Free memory before retrying under pressure; see
@@ -2403,6 +2462,82 @@ TWO REPORTS, TWO QUESTIONS
2403
2462
  the machine, with a hit rate and an estimate of the time saved (see
2404
2463
  \`guide facts stats\`).`,
2405
2464
  sections: {
2465
+ eas: {
2466
+ summary: "download a matching EAS development build; explicit profile, costs, cache and miss remedies",
2467
+ body: () => `EAS DEVELOPMENT BUILDS
2468
+
2469
+ stim ios --eas-profile ios-simulator
2470
+ stim android --eas-profile development
2471
+ stim ios --eas-profile development-device --device <udid>
2472
+ stim android --eas-profile development --device <serial>
2473
+
2474
+ An eas.json file does not select EAS automatically. The flag names the profile
2475
+ and selects EAS Build as the artifact source. The profile must resolve to
2476
+ "developmentClient": true and "distribution": "internal". For iOS, set
2477
+ "ios.simulator": true for a simulator, or false (or omit it) for --device.
2478
+ An explicit ios.buildConfiguration must be Debug;
2479
+ android.gradleCommand must be a single :app:assemble<Variant>Debug task producing
2480
+ an APK. Install eas-cli and authenticate with eas login
2481
+ or EXPO_TOKEN. The Expo app must already be linked to the intended EAS project.
2482
+
2483
+ Use the profile the user names. Otherwise inspect eas.json, including extends
2484
+ and platform overrides, for the requested platform, simulator or physical
2485
+ device, and app variant or environment. Choose a profile only when those
2486
+ requirements identify one compatible development profile. If there is no
2487
+ compatible profile or the choice is ambiguous, ask the user. Profile names
2488
+ alone do not establish compatibility. Confirm the resolved settings with
2489
+ npx eas-cli config --platform <ios|android> --profile <name> --json --non-interactive.
2490
+ Pass the selected name to --eas-profile; the CLI does not infer it.
2491
+
2492
+ Stim delegates profile inheritance, environment resolution and fingerprinting
2493
+ to EAS CLI. fingerprint:generate uploads fingerprint metadata to EAS. It does
2494
+ not start a native build. EAS access is needed even when the artifact is
2495
+ already cached, because the current profile and fingerprint must be resolved.
2496
+
2497
+ Stim matches the EAS project, profile, native fingerprint, platform and
2498
+ simulator/internal distribution target against a completed build. It downloads
2499
+ an iOS .app or Android .apk with EAS CLI, then uses its existing installation,
2500
+ Metro connection, log capture and launch verification on the selected device.
2501
+ The flag also works with --remote; remote EAS Simulator sessions have their
2502
+ own costs, independent of this download path. Local --scheme, --configuration,
2503
+ --variant and --no-build-cache selectors
2504
+ cannot be combined with --eas-profile. Local configuration/variant defaults
2505
+ are ignored for this development-build run.
2506
+
2507
+ Physical devices use Stim's usual selection and lease rules. An iOS app must
2508
+ have a development-client URL scheme and a valid embedded provisioning profile
2509
+ that includes the target UDID. Stim installs the signed app without re-signing
2510
+ it. If the profile does not admit the device, the remedy points to
2511
+ npx eas-cli device:create and npx eas-cli build --platform ios --profile <name>.
2512
+ Registration, signing changes and cloud builds need session authorization.
2513
+ Run the EAS build interactively when its provisioning profile needs refreshing,
2514
+ then retry the same Stim command.
2515
+
2516
+ Start Metro with stim start as usual. Metro uses the local workspace's
2517
+ environment; arrange the appropriate local variables before starting it.
2518
+ EAS environment resolution for native fingerprinting does not configure Metro.
2519
+
2520
+ EAS CLI caches extracted artifacts by project and build ID in its own temporary
2521
+ cache. Stim calls build:download and uses the returned artifact without making
2522
+ another cache copy. Every run queries the latest matching build, so a rebuild
2523
+ with a refreshed provisioning profile is selected even if its native fingerprint
2524
+ is unchanged. Stim coordinates concurrent downloads by project and build ID.
2525
+ cacheHit: "remote" identifies the EAS source, including when EAS CLI reuses its
2526
+ disk cache. The fingerprint fact is the EAS native fingerprint. Stim's local
2527
+ buildCache and remoteBuildCache settings do not control EAS CLI's cache.
2528
+ EAS cache files are managed by EAS CLI and are outside Stim gc.
2529
+
2530
+ On STIM_EAS_BUILD_MISSING, Stim stops before acquiring a device and prints:
2531
+
2532
+ npx eas-cli build --platform ios --profile ios-simulator
2533
+
2534
+ Run that command only when the session authorizes the potentially billable
2535
+ cloud build. Once it completes, retry the same Stim command. No build-on-miss
2536
+ flag exists, and Stim never starts a cloud build or falls back to local
2537
+ compilation in this mode. Authentication, network, invalid output or download
2538
+ failures produce STIM_EAS_UNAVAILABLE, with the failed EAS command to inspect.
2539
+ If another run holds the artifact claim, wait for it to finish and retry.`
2540
+ },
2406
2541
  readiness: {
2407
2542
  summary: "implement optional pending/ready app logs, deadlines, errors, and platform isolation",
2408
2543
  body: () => `OPTIONAL APP READINESS
@@ -3081,13 +3216,13 @@ OPT-IN CONCURRENCY LIMITS (UNLIMITED BY DEFAULT)
3081
3216
  summary: "every flag per command, Android variants and flavors, the per-run simulator model, runtime and system image",
3082
3217
  body: () => `THE OPTION SURFACE, IN FULL
3083
3218
  start --json --wait <seconds> --remote --reset-cache
3084
- ios --json --no-metro-check --no-build-cache --scheme <name> --configuration <name> --device-type <name> --runtime <version> --device [udid] --wait <seconds> --no-wait --remote <proxy|eas>
3085
- android --json --no-metro-check --no-build-cache --variant <name> --system-image <id> --device [serial] --wait <seconds> --no-wait --remote <proxy|eas>
3219
+ ios --slot <name> --json --no-metro-check --no-build-cache --scheme <name> --configuration <name> --device-type <name> --runtime <version> --device [udid] --wait <seconds> --no-wait --remote <proxy|eas>
3220
+ android --slot <name> --json --no-metro-check --no-build-cache --variant <name> --system-image <id> --device [serial] --wait <seconds> --no-wait --remote <proxy|eas>
3086
3221
  reload [ios|android] --json
3087
- device lock <ios|android> [id] --for <duration> --wait <seconds> --json;
3088
- unlock [ios|android] --json
3089
- logs --source --level --since --grep --tail --follow --errors --json
3090
- stop --json
3222
+ device lock <ios|android> [id] --slot <name> --for <duration> --wait <seconds> --json;
3223
+ unlock [ios|android] --slot <name> --json
3224
+ logs --slot <name> --source --level --since --grep --tail --follow --errors --json
3225
+ stop --slot <name> --json
3091
3226
  status --json (already machine-wide)
3092
3227
  stats --json (this project and machine-wide)
3093
3228
  doctor --json --fix --platform <ios|android>
@@ -3095,6 +3230,38 @@ OPT-IN CONCURRENCY LIMITS (UNLIMITED BY DEFAULT)
3095
3230
  gc --delete --older-than <days> --cache <name|all>
3096
3231
  worktree warm --refresh; remove [path] --force
3097
3232
 
3233
+ DEVICE SLOTS
3234
+ Use --slot <name> on ios or android to keep any number of simulators,
3235
+ emulators, or physical devices in the same workspace. Names are reusable
3236
+ identities, not models: two slots can request identical models. A slot has
3237
+ one target per platform. Omitting the flag selects default and preserves
3238
+ the workspace's existing assignment.
3239
+
3240
+ stim ios --slot phone
3241
+ stim ios --slot tablet --device-type "iPad Pro 13-inch (M4)"
3242
+ stim ios --slot hardware --device <udid>
3243
+ stim android --slot second-phone
3244
+ stim logs --slot tablet --source device
3245
+ stim stop --slot tablet
3246
+
3247
+ All slots share this workspace's Metro server and build cache. Native CLI
3248
+ runs serialize workspace mutations; a waiting run can wait up to 30 minutes.
3249
+ Named slots support local devices; remote sessions use the default slot.
3250
+ Names use 1-64 letters, digits, underscores or hyphens, starting with a letter
3251
+ or digit. Reserved object-property names are refused.
3252
+
3253
+ stop --slot shuts down that slot's owned devices and releases its leases,
3254
+ retaining Metro and sibling slots. Plain stop handles the whole workspace.
3255
+ status reports named devices under slots and counts their memory. Device
3256
+ caps count every slot. Recycling uses the same model/runtime-matched pool
3257
+ for every slot, with one shared cap per platform and oldest-first eviction.
3258
+
3259
+ Metro cannot reliably identify a particular simulator's bundle request.
3260
+ When slots coexist, a shared bundle event is not proof that a particular
3261
+ slot launched: Debug launch can report unverified. Check the reported device
3262
+ directly. Release verification still checks its process. reload ios/android
3263
+ addresses matching Metro peers across slots; it is not a single-slot reload.
3264
+
3098
3265
  That is the whole surface today, and it is deliberately small. It can grow
3099
3266
  when a flag is genuinely the best answer -- but project-specific knowledge
3100
3267
  (release builds, variants, device targets) belongs in a script the repo owns,
@@ -3600,6 +3767,13 @@ HOST MEMORY PRESSURE AND STALLED SIMULATORS
3600
3767
  a final boot-state query can take 30 seconds. Opening the Simulator app after
3601
3768
  boot is best-effort and takes at most 5 seconds. A timeout is not proof of an
3602
3769
  app crash or OOM.
3770
+ During boot, Stim reports elapsed time, the simulator name, last boot output,
3771
+ current pressure and the highest observed pressure roughly every 15 seconds.
3772
+ Failure diagnostics retain the highest pressure and unavailable sample count;
3773
+ they do not infer the cause of a timeout. Monitoring stops when boot ends.
3774
+ Monitoring and timeout handling are best-effort: synchronous CLI work can
3775
+ delay them. Diagnostics report observation gaps over 30 seconds; pressure
3776
+ during a gap is unobserved, even when the surrounding readings are normal.
3603
3777
  Doctor and failure diagnostics report macOS memory pressure when available;
3604
3778
  a failed query remains unknown. Existing swap or low free RAM alone is not
3605
3779
  enough to diagnose pressure.
@@ -3614,7 +3788,16 @@ HOST MEMORY PRESSURE AND STALLED SIMULATORS
3614
3788
  },
3615
3789
  cleanup: {
3616
3790
  summary: "Where simulators come from, and how they get reclaimed",
3617
- preamble: () => `CLEANUP AND DISK
3791
+ preamble: () => `DEVICE SLOTS
3792
+
3793
+ Cleanup enumerates every slot. stop --slot <name> keeps the shared server and
3794
+ other slots; plain stop and worktree remove handle the whole workspace.
3795
+ An upgrade retains existing device assignments as the default slot. Do not
3796
+ wipe state to upgrade: it records ownership needed for safe teardown. Use the
3797
+ same slot-aware CLI for all commands while named assignments exist; older
3798
+ versions cannot reliably manage their assignments.
3799
+
3800
+ CLEANUP AND DISK
3618
3801
 
3619
3802
  WHAT RECLAIMS AN OWNED DEVICE
3620
3803
  stim worktree remove parks eligible owned simulators and emulators