stim 1.15.0 → 1.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/dist/{activity-B2-kb7k0.mjs → activity-BxwdD81S.mjs} +29 -16
  2. package/dist/agent-device-usage-output-Dpih1Tfa.mjs +411 -0
  3. package/dist/{android-Db2eEMaf.mjs → android-B_NumVyQ.mjs} +21 -20
  4. package/dist/{android-Cu_L4RpL.mjs → android-Ift4kVvt.mjs} +56 -47
  5. package/dist/{android-cas-PnKfAdB2.mjs → android-cas-8U4DPqeR.mjs} +1 -1
  6. package/dist/{android-CYmq0jNi.mjs → android-r2rBL8kV.mjs} +1 -1
  7. package/dist/{app-install-CAAplXAt.mjs → app-install-CKWdbXIX.mjs} +48 -5
  8. package/dist/attempt-Bk2ouQVL.mjs +39 -0
  9. package/dist/{budget-CMiqghig.mjs → budget-DjrURfQn.mjs} +356 -366
  10. package/dist/{build-plan-aeGMFX7e.mjs → build-plan-DjeSb2Xl.mjs} +19 -20
  11. package/dist/{build-slots-Df1ApSuH.mjs → build-slots-rUk77xqh.mjs} +7 -2
  12. package/dist/{cli-0cX5s4NM.mjs → cli-BkYZ_6hy.mjs} +23 -17
  13. package/dist/cli.mjs +1 -1
  14. package/dist/{client-C2IS36Qf.mjs → client-BTY686R6.mjs} +57 -22
  15. package/dist/collector-run.mjs +5 -192
  16. package/dist/{command-output-CliHHn7a.mjs → command-output-DnJMdEY-.mjs} +1 -1
  17. package/dist/{created-devices-BwVb3liH.mjs → created-devices-D5owjN4C.mjs} +1 -1
  18. package/dist/{dependency-state-BmsjKZ2H.mjs → dependency-state-B6cMXZab.mjs} +1 -1
  19. package/dist/{deps-BFlKbOIp.mjs → deps-urbmfXig.mjs} +2 -2
  20. package/dist/{dev-client-CjzLoNs0.mjs → dev-client-CroAIjjj.mjs} +3 -40
  21. package/dist/{device-BZ1IyS5A.mjs → device-DZQjF7H5.mjs} +7 -6
  22. package/dist/{device-capacity-DMuKC7nx.mjs → device-capacity-BHwxNBlu.mjs} +2 -2
  23. package/dist/device-host-worker.mjs +223 -28
  24. package/dist/{device-ios-DMQBUPus.mjs → device-ios-BadmeL2V.mjs} +7 -7
  25. package/dist/{device-lease-W5Pi1q38.mjs → device-lease-D-hLqx1b.mjs} +2 -2
  26. package/dist/{device-pool-B2r38YjS.mjs → device-lease-run-CqjShqWW.mjs} +5 -125
  27. package/dist/device-pool-CUaEWMMm.mjs +125 -0
  28. package/dist/{device-remote-9ulPsdhY.mjs → device-remote-D5WVWqOq.mjs} +12 -12
  29. package/dist/{doctor-BcE5xsBn.mjs → doctor-Cimf-gGB.mjs} +80 -34
  30. package/dist/{error-diagnostics-Cb0P1WLS.mjs → error-diagnostics-B9L4Ae0x.mjs} +11 -15
  31. package/dist/{gc-BuL1Genr.mjs → gc-Bqy4bjEu.mjs} +194 -31
  32. package/dist/{gradle-CmdOBq1d.mjs → gradle-BsO1MdsH.mjs} +4 -4
  33. package/dist/{guide-cCSGRFd3.mjs → guide-B_uyKTxW.mjs} +588 -53
  34. package/dist/{guide-status-CIoWK6mG.mjs → guide-status-CjEZMAD4.mjs} +1 -1
  35. package/dist/hosted-client-a-WmqE2L.mjs +197 -0
  36. package/dist/hosted-ios-CIWelIqf.mjs +220 -0
  37. package/dist/hosted-logs-D5g041Q3.mjs +187 -0
  38. package/dist/hosted-macos-BSZyIb3y.mjs +135 -0
  39. package/dist/{build-progress-C2Jv1AtA.mjs → idle-DYVymG92.mjs} +233 -6
  40. package/dist/{idle-shutdown-B5LPaNyY.mjs → idle-shutdown-CTsDnAde.mjs} +6 -6
  41. package/dist/{in-use-CndPMilN.mjs → in-use-DvBw0Kxo.mjs} +29 -9
  42. package/dist/{ios-3CtCRhwr.mjs → ios-BaInn3sW.mjs} +2 -2
  43. package/dist/ios-Cr1h5gnO.mjs +189 -0
  44. package/dist/{ios-Chob8ny7.mjs → ios-aq3QoGI3.mjs} +729 -452
  45. package/dist/{ios-device-DERmMmk-.mjs → ios-device-FfCN_C8p.mjs} +1 -1
  46. package/dist/ios-state-DS5UpYTH.mjs +48 -0
  47. package/dist/{launch-verify-CLlofxRt.mjs → launch-verify-2DBRizjo.mjs} +3 -3
  48. package/dist/logs-CHEVK8zD.mjs +371 -0
  49. package/dist/{machines-CYtOCqcj.mjs → machines-BIBUdhpj.mjs} +16 -10
  50. package/dist/{macos-BwtbUtTN.mjs → macos-CSYEAFmF.mjs} +22 -17
  51. package/dist/macos-run.mjs +1 -1
  52. package/dist/maintenance-run.d.mts +4 -0
  53. package/dist/maintenance-run.mjs +254 -0
  54. package/dist/{metro-Di8EN2Tb.mjs → metro-BeK1ZPz1.mjs} +1 -1
  55. package/dist/metro-gateway-BJ-rxQAB.mjs +84 -0
  56. package/dist/{named-ports-Dj_XBNaL.mjs → named-ports-Bk_YXEmE.mjs} +1 -1
  57. package/dist/{native-run-DFDoIRbY.mjs → native-run-NaBrRFtQ.mjs} +2 -2
  58. package/dist/{native-runtime-i6OkVg-w.mjs → native-runtime-DBzRnEKS.mjs} +6 -6
  59. package/dist/{ndjson-CDY9IkVC.mjs → ndjson-PclOIwVY.mjs} +2 -2
  60. package/dist/offload-worker.d.mts +1 -0
  61. package/dist/offload-worker.mjs +13 -8
  62. package/dist/{ownership-BkYxUtuj.mjs → ownership-DiA16AsN.mjs} +2 -2
  63. package/dist/{stop-cause-CZ_Mr-v9.mjs → ownership-hCkPzGmr.mjs} +4 -98
  64. package/dist/{ports-DddwttKM.mjs → ports-R2k4Knz3.mjs} +2 -2
  65. package/dist/{prebuild-BvxDlD6C.mjs → prebuild-D2fsca_Z.mjs} +4 -4
  66. package/dist/preview-DhVbvPAZ.mjs +318 -0
  67. package/dist/{project-CV3tDFHl.mjs → project-Du91Ilvc.mjs} +67 -3
  68. package/dist/{recordings-GEXp4iBT.mjs → recordings-BIXOzVnr.mjs} +1 -1
  69. package/dist/{reload-ChPiwvot.mjs → reload-rM9jV1fz.mjs} +49 -12
  70. package/dist/{remote-cache-CO2elm_b.mjs → remote-cache-C8-V13XN.mjs} +4 -4
  71. package/dist/{run-B-xqRrsg.mjs → run-f4vI6EnK.mjs} +2 -2
  72. package/dist/{server-bare-X7PUgizL.mjs → server-bare-CpPGmnmO.mjs} +1 -1
  73. package/dist/server-expo-CVqO2c35.mjs +2 -0
  74. package/dist/{server-expo-DhtX6bom.mjs → server-expo-MC0rHlOC.mjs} +4 -4
  75. package/dist/settings-BBpMcDbD.mjs +25 -0
  76. package/dist/{settings-DbL6uhH8.mjs → settings-ZkDzkzvB.mjs} +10 -4
  77. package/dist/settings-oTSkkNrM.mjs +1439 -0
  78. package/dist/settings.schema.json +432 -39
  79. package/dist/{simslim-Ciluxlq4.mjs → simslim-B8-7AoTI.mjs} +3 -3
  80. package/dist/{slot-launch-BY0BrOLh.mjs → slot-launch-B8VrHxn1.mjs} +10 -7
  81. package/dist/{spawn-entry-DncpJvWL.mjs → spawn-entry-jw19m8Nl.mjs} +1 -0
  82. package/dist/{start-CPO4wRg7.mjs → start-CX8Ig-U2.mjs} +1 -1
  83. package/dist/{start-CUD9nasA.mjs → start-ClDfwXa4.mjs} +21 -17
  84. package/dist/{state-BxCgeTQu.mjs → state-3DwSQIBK.mjs} +2 -2
  85. package/dist/{state-DFm77wRn.mjs → state-C1jB7Q8q.mjs} +5 -5
  86. package/dist/{state-CaeaHbDA.mjs → state-DcW6vOd_.mjs} +1 -1
  87. package/dist/{stats-DBLLFmKd.mjs → stats-CEZ_i5je.mjs} +16 -5
  88. package/dist/{status-l84Gbvp2.mjs → status-BuvBFeII.mjs} +5 -3
  89. package/dist/{status-DYXxNOT0.mjs → status-Cs7VLg9D.mjs} +148 -44
  90. package/dist/status-DlKvVJBE.mjs +49 -0
  91. package/dist/{stop-Bvf1MflS.mjs → stop-B0Yxtmiy.mjs} +3 -3
  92. package/dist/{stop-Bad61z_2.mjs → stop-Bffozsac.mjs} +61 -20
  93. package/dist/{stop-CceYh1yG.mjs → stop-Dt_dM16A.mjs} +2 -2
  94. package/dist/stop-cause-CidxIqup.mjs +97 -0
  95. package/dist/supervisor-run.mjs +148 -17
  96. package/dist/{support-CDQbDkL5.mjs → support-CAP8qDUF.mjs} +4 -3
  97. package/dist/{support-WLOzVKUv.mjs → support-D4aYJniO.mjs} +34 -9
  98. package/dist/{teardown-BYuQafiA.mjs → teardown-CL6vDs2Y.mjs} +56 -47
  99. package/dist/{toolchain-DmeYjXD9.mjs → toolchain-DkUflduf.mjs} +10 -10
  100. package/dist/trigger-Bemu8ytw.mjs +65 -0
  101. package/dist/trigger-CXts6qt1.mjs +2 -0
  102. package/dist/{warm-progress-DVHLN4uC.mjs → warm-progress-CpGv3Wb6.mjs} +274 -5
  103. package/dist/{web-k6w8-HnE.mjs → web-DWUaMp8P.mjs} +15 -16
  104. package/dist/web-run.mjs +5 -5
  105. package/dist/{workspace-state-CL2V2Ons.mjs → workspace-state-B8-aJ1_5.mjs} +33 -33
  106. package/dist/workspaces-BV4pdHFz.mjs +306 -0
  107. package/dist/{worktree-qXKzADSC.mjs → worktree-K-xj9Db0.mjs} +497 -30
  108. package/dist/{worktree-BT_uULFx.mjs → worktree-jnG8Iph5.mjs} +1 -1
  109. package/package.json +5 -5
  110. package/dist/hosted-macos-DwowNU7W.mjs +0 -447
  111. package/dist/logs-h9h6MUvE.mjs +0 -568
  112. package/dist/machine-usage-D_YYMzfR.mjs +0 -283
  113. package/dist/merge-state-BnxLNQiM.mjs +0 -272
  114. package/dist/server-expo-KJeD109I.mjs +0 -2
  115. package/dist/settings-CduVcoxj.mjs +0 -680
  116. package/dist/watchman-Bx0wceoF.mjs +0 -238
  117. package/dist/worktree-C0c5Udwa.mjs +0 -753
@@ -1,8 +1,8 @@
1
- import { a as findProjectRoot$1 } from "./project-CV3tDFHl.mjs";
2
- import { t as ANDROID_AVD_CONFIG_HELP } from "./settings-CduVcoxj.mjs";
3
- import { t as RECENT_LAUNCH_MS } from "./status-l84Gbvp2.mjs";
4
- import { t as guideStatus } from "./guide-status-CIoWK6mG.mjs";
5
- import { SETTINGS_SCHEMA_URL } from "@stim-cli/core/state";
1
+ import { t as ANDROID_AVD_CONFIG_HELP } from "./settings-oTSkkNrM.mjs";
2
+ import { o as findProjectRoot$1 } from "./project-Du91Ilvc.mjs";
3
+ import { t as RECENT_LAUNCH_MS } from "./status-BuvBFeII.mjs";
4
+ import { t as guideStatus } from "./guide-status-CjEZMAD4.mjs";
5
+ import { SETTINGS, SETTINGS_SCHEMA_URL } from "@stim-cli/core/state";
6
6
  import chalk from "chalk";
7
7
  //#endregion
8
8
  //#region src/guide/index.ts
@@ -43,6 +43,22 @@ reach multiple devices. A launch counts a bundle delivery only when it can
43
43
  attribute it to its own device; read guide lifecycle options for the rule.
44
44
  Use stop --slot <name> for one slot, or plain stop for the whole workspace.
45
45
 
46
+ HOSTED IOS
47
+
48
+ For ios --remote <machine>, read guide lifecycle hosted-ios. Stop before switching
49
+ between a local simulator and a hosting Mac. Hosted Debug requires the local
50
+ Metro supervisor; restart a missing or older one with stim stop; stim start.
51
+ It never falls back here; stop reconciles the recorded host before changing placement.
52
+ For hosted iOS, use agent-device <command> --remote-config <file> from ios.host.agent
53
+ in stim status --json. Open the installed app first; never use the host UDID locally.
54
+ See guide lifecycle hosted-ios for the agent policy and setup.
55
+
56
+ When driving a device yourself with agent-device, set AGENT_DEVICE_STATE_DIR
57
+ from agentDevice.stateDir in stim ios|android|macos --json or status --json.
58
+ Its sessions and logs are then cleaned with the workspace. Do not set
59
+ AGENT_DEVICE_IOS_RUNNER_DERIVED_PATH or AGENT_DEVICE_CLAIMS_DIR; the runner
60
+ build cache and device claims stay shared. Read stim guide logs for examples.
61
+
46
62
  NORMAL WORKFLOW
47
63
 
48
64
  Create a linked worktree with Git and warm its ignored state. If a harness
@@ -114,6 +130,9 @@ To learn whether the next build is a cache hit and how long it should take,
114
130
  without building, run stim ios --plan (or stim android --plan); see stim guide
115
131
  lifecycle builds.
116
132
 
133
+ Agents never set STIM_ACCESS_TICKET, run stim-server setup, edit offload.*
134
+ or hosting.* for the user, or approve requests. Read stim guide settings for
135
+ Desktop access tickets and person-owned approval.
117
136
  Read stim guide settings to select a build machine. Keep a requested named
118
137
  placement when recovering STIM_OFFLOAD_REFUSED: report its reason and remedy.
119
138
 
@@ -140,6 +159,8 @@ waiting times out.
140
159
 
141
160
  RULES DURING THE LOOP
142
161
 
162
+ - Agents never run stim-server setup and never approve access requests.
163
+ A person on the worker approves builds and device hosting (guide settings).
143
164
  - Run Stim from the app directory: the one whose package.json depends on
144
165
  react-native or expo. Anywhere else -- a monorepo root, a tools package --
145
166
  start, ios and android refuse with STIM_NO_PROJECT naming that package.json,
@@ -370,6 +391,15 @@ stdout for \`--json\`. Every other line goes to stderr, so it is always safe
370
391
  to pipe. \`logs --json\` is the one exception: it is NDJSON, one record per
371
392
  line by design (see \`guide logs\`), not this single-payload contract.
372
393
 
394
+ status adds archived (newest first, bounded by archive.maxCount) and
395
+ archivedUsage { count, bytes, byKind: { logs, recordings, agentActions, record } }.
396
+ Each archive carries id, projectRoot, project, workspace, worktree facts,
397
+ removedAt, removedBy, lastUsedAt, builds, agents, bytes, expires and version.
398
+ replacedBy is the live environment path when its canonical root matches.
399
+ Older producers may omit both fields.
400
+ Plain status prints Archived: <count> workspace(s), <size> when count is positive;
401
+ status --watch reuses archive reads until archive changes and emits no duplicates.
402
+
373
403
  status's unprovisionedWorktrees lists the linked worktrees with no Stim
374
404
  environment in every repository with a registered environment, plus the
375
405
  repository status runs from. A worktree counts as having an
@@ -501,7 +531,7 @@ Plain status prints "ios: Old iPhone (physical, iPhone 12 Pro) connected --
501
531
  leased until <time>" for each one.`,
502
532
  sections: {
503
533
  payloads: {
504
- summary: "every field of the start, ios, android, web and reload payloads, the error contract, the device rules",
534
+ summary: "every field of the start, ios, android, macos, web and reload payloads, the error contract, the device rules",
505
535
  body: () => ` stim start --json
506
536
 
507
537
  port the Metro port RESERVED for this workspace
@@ -509,6 +539,8 @@ leased until <time>" for each one.`,
509
539
  already answering that Stim did not start
510
540
  mode "bare-inproc" | "expo-child" | null (see \`guide metro\`)
511
541
  logsDir where the NDJSON timeline is written
542
+ agentDevice { stateDir }: absolute workspace agent-device state path;
543
+ set AGENT_DEVICE_STATE_DIR to it (see guide logs)
512
544
  alreadyRunning true when nothing needed starting
513
545
  links { desktop }: a stim-desktop://workspace?path=<workspace>
514
546
  link that shows this workspace in Stim Desktop. Absent when
@@ -516,6 +548,13 @@ leased until <time>" for each one.`,
516
548
  prints it on stderr as "Open in Stim Desktop: <link>"; so
517
549
  does worktree warm, which has no JSON payload
518
550
 
551
+ stim macos --json
552
+
553
+ platform "macos"
554
+ product, launchId, build, host? the launch record (see guide macos)
555
+ agentDevice { stateDir }: absolute workspace agent-device state path
556
+ for local or hosted launches with a local workspace
557
+
519
558
  stim ios --json
520
559
 
521
560
  platform "ios"
@@ -669,6 +708,7 @@ leased until <time>" for each one.`,
669
708
  launch with no process id is "unverified", and
670
709
  \`stim logs --errors\` has the device log that says why
671
710
  logs { dir }
711
+ agentDevice { stateDir }: absolute workspace agent-device state path
672
712
  durationMs wall time for the whole run
673
713
 
674
714
  stim android --json
@@ -742,6 +782,7 @@ leased until <time>" for each one.`,
742
782
  error, and this field is separate from cacheHit. On an
743
783
  offloaded APK it is the build machine's ccache
744
784
  logs the workspace log directory
785
+ agentDevice { stateDir }: absolute workspace agent-device state path
745
786
  durationMs wall time for the whole run
746
787
  reclaimed present only when the run was over its disk or memory
747
788
  budget and reclaimed first (\`start\`, \`ios\` and
@@ -838,7 +879,7 @@ leased until <time>" for each one.`,
838
879
  null when off; plan lists what the next start, ios or
839
880
  android would reclaim, in the \`reclaimed\` shape without
840
881
  freedMb, and is empty while under budget
841
- deviceHosts one { machine, state, dnsName?, deviceId?, requestedAt? }
882
+ deviceHosts one { machine, state, dnsName?, deviceId?, requestedAt?, host? }
842
883
  per hosting.machines entry. state is "approved", "pending",
843
884
  "not-asked", "revoked", "node-changed", "not-on-tailnet",
844
885
  "tailscale-off", "unreachable", "invalid",
@@ -846,12 +887,15 @@ leased until <time>" for each one.`,
846
887
  Only --fix asks for access or forgets removed names.
847
888
  An approved hosting machine does not imply build approval
848
889
  or change ios/android placement; see \`guide settings\`.
890
+ host is { name, screenRecording, accessibility }, present
891
+ only for approved machines whose worker reports it.
849
892
  buildMachines one { machine, state, dnsName?, deviceId?, requestedAt?,
850
- offloadable?, reasons?, problems?, capacity? } per
893
+ offloadable?, reasons?, problems?, capacity?, host? } per
851
894
  offload.machines entry; state is "approved", "pending", "not-asked",
852
895
  "revoked" (revoked, or the request lapsed), "node-changed",
853
896
  "not-on-tailnet", "tailscale-off", "unreachable" or
854
- "invalid". An approved machine also carries offloadable,
897
+ "invalid". host has the same shape and presence rule as in
898
+ deviceHosts. An approved machine also carries offloadable,
855
899
  true when it would take this app's builds now (iOS
856
900
  simulator unless --platform android, Android emulator when
857
901
  --platform android or the app has android/ or uses Expo,
@@ -870,6 +914,7 @@ leased until <time>" for each one.`,
870
914
  optional fields
871
915
  findings the diagnostic findings; a lower resolved Stim is a
872
916
  costs-time finding with a PATH or installation remedy
917
+ offload-candidate is a note after 3+ successful local cold builds in 7 days average over 3 min, with no offload.machines and an online tailnet Mac; open Stim Desktop Settings > Build machines > Add
873
918
 
874
919
  ON FAILURE
875
920
  \`start\`, \`ios\`, \`android\` and \`web\` all print the error contract instead,
@@ -937,15 +982,17 @@ RULES
937
982
  that is the URL the launcher hands to the check; on the
938
983
  outer deep link it does nothing there. Android reads it on
939
984
  either.
940
- ON A SIMULATOR, before a local dev-client openurl, Stim
941
- preapproves CoreSimulatorBridge for exactly the installed
942
- bundle id and discovered scheme on its owned simulator. That
943
- suppresses iOS's first-launch confirmation;
944
- unrelated schemes remain unapproved. The approvals persist
945
- until the simulator is erased, so Stim records the ones it
946
- wrote on the workspace's device record and writes only the
947
- ones missing from it. Parking drops the record, so an
948
- adopted simulator gets them again once.
985
+ ON AN OWNED SIMULATOR, Stim preapproves CoreSimulatorBridge
986
+ for the installed bundle id and every URL scheme its Info.plist
987
+ declares, except system and third-party SDK schemes, also for
988
+ apps without expo-dev-client. The "Open in <App>?" alert can
989
+ still appear for an unapproved scheme, on a physical device or
990
+ user-created simulator, or from another opener such as Safari.
991
+ After \`simctl openurl\` or \`agent-device open <url>\`, expect
992
+ it for such schemes; take a snapshot and tap Open.
993
+ Approvals persist until erasure; Stim records them and writes
994
+ only missing ones. Parking drops the record, so adoption
995
+ rewrites them once.
949
996
  THE DEV MENU AND ITS BUTTON stay off a simulator two ways,
950
997
  which the onboarding flag does NOT cover. Stim's own
951
998
  \`simctl launch --initialUrl\` ends in
@@ -1119,6 +1166,10 @@ RULES
1119
1166
  timing out or an AVD or system image folder Stim
1120
1167
  cannot read. A macOS privacy denial (EPERM) names the
1121
1168
  Privacy & Security setting to grant
1169
+ maintenance top-level { mode, pressure, actions, blocked,
1170
+ skips, note, invalid? }, the next report-only maintenance plan
1171
+ from live pressure and cached sizes. note says when no
1172
+ pass has run yet. No du runs for this preview.
1122
1173
  sections one array per report section, in the text order. Every key
1123
1174
  is present, empty when there is nothing to report:
1124
1175
  deadProjects { path }
@@ -1206,6 +1257,11 @@ RULES
1206
1257
  directories that --delete removes whole (dead
1207
1258
  projects and orphanedWorkspaces), and --cache
1208
1259
  all does not
1260
+ agentDevice <AgentDeviceUsage|null> report-only; workspace state
1261
+ goes only with its workspace;
1262
+ null with any --cache scope
1263
+ swiftpmCache <SwiftpmCacheUsage|null> report-only, never deleted;
1264
+ null with any --cache scope
1209
1265
  caches { name, dir, source, bytes, note, willEmpty,
1210
1266
  emptySkipped, scopedEmpty } alive, not
1211
1267
  garbage; willEmpty marks the ones --delete
@@ -1267,6 +1323,11 @@ RULES
1267
1323
  summary: "the status payload's lifecycle phase and stage, issues and their codes, build and device activity fields: a running build, its estimate, each platform's last build, who drives each device, whether the app runs on it, and what uses CPU and memory now",
1268
1324
  body: () => ` stim status --json
1269
1325
 
1326
+ logs { dir, errorsSinceMarker }, or null without a log directory
1327
+ agentDevice { stateDir }: absolute workspace agent-device state path on
1328
+ every environment, shared by its slots. Reporting it creates
1329
+ no directory; agent-device creates it when used.
1330
+
1270
1331
  Each environment carries phase, where the workspace is in its lifecycle:
1271
1332
 
1272
1333
  phase "warming" stim worktree warm runs in it now
@@ -1280,6 +1341,15 @@ RULES
1280
1341
  for "live" and "idle"
1281
1342
  warmStep "refresh" or "copy", the step a warming workspace is in;
1282
1343
  absent in other phases
1344
+ platforms ordered supported app platforms: ios, android, macos, web;
1345
+ always present, [] when none are detected. Expo uses its
1346
+ explicit platforms list, otherwise ios and android plus web
1347
+ with react-native-web. Bare apps use ios/*.xcodeproj or
1348
+ *.xcworkspace and android/ Gradle project files. web.url adds
1349
+ web to any app and overrides an Expo list; a bare app has no
1350
+ other route to web. macOS needs Package.swift, macos.product
1351
+ and macos.infoPlist. Detection reads config literals and
1352
+ files, never runs project scripts.
1283
1353
  recording { enabled }: whether stim-server may record the workspace's
1284
1354
  device screens for replay, from recording.enabled
1285
1355
 
@@ -1544,7 +1614,8 @@ RULES
1544
1614
  run that holds this workspace's native-run.lock:
1545
1615
 
1546
1616
  build { platform, slot, state, phase, startedAt, phaseStartedAt,
1547
- outcome, outcomeKnown, cacheLookupOutcome?, expectedMs, expectedPhaseMs, basis,
1617
+ outcome, outcomeKnown, cacheLookupOutcome?, expectedMs, expectedPhaseMs,
1618
+ completedPhaseMs?, basis,
1548
1619
  plannedPhases, missReason?, missProvisional?, detail?, placement,
1549
1620
  waitingOn? }
1550
1621
 
@@ -1589,6 +1660,10 @@ RULES
1589
1660
  not change the estimate.
1590
1661
  expectedPhaseMs the median duration of this phase in those runs, or null
1591
1662
  when none of them entered it
1663
+ completedPhaseMs milliseconds spent in each phase the run already left,
1664
+ summing repeated visits. The current visit is excluded;
1665
+ its phase appears only if visited earlier. Absent before
1666
+ any phase completes and on older Stim versions
1592
1667
  basis how many runs the expectedMs median comes from (at most
1593
1668
  10 per kind: device-setup runs keep their own 10)
1594
1669
  plannedPhases [{ phase, expectedMs }] in phase order: the phases at
@@ -1818,6 +1893,19 @@ RULES
1818
1893
  and always uses the estimate. What is using CPU and memory now is the
1819
1894
  top-level machine section:
1820
1895
 
1896
+ maintenance { mode, lastChecks: { pressure, size }, pressure, sizes,
1897
+ lastPass, running, recent, plan, invalid?, claim? }
1898
+ Report-only observations; every plan action's kind starts with would-.
1899
+ lastChecks are epoch milliseconds or null. running comes only from a
1900
+ live maintenance/run.claims owner, never from a check stamp.
1901
+ lastPass carries startedAt, durationMs, trigger, mode, freedBytes (0),
1902
+ actions, stopped (0), blocked. recent holds the last 20 action, failure
1903
+ and blocked NDJSON records from maintenance/maintenance.ndjson.
1904
+ invalid names invalid settings; invalid cache caps use their defaults,
1905
+ while invalid maintenance settings disable passes. claim contains an
1906
+ unresolved claim's path and reason, plus removeCommand from the claim
1907
+ protocol; inspect the holder before running that command.
1908
+
1821
1909
  machine null, or { memorySource, owners: [{ kind, name, workspace,
1822
1910
  slot?, id, owned, cpuPercent, residentMb, memoryMb,
1823
1911
  processes }] }
@@ -1913,7 +2001,9 @@ RULES
1913
2001
  "offload": { "today": { "here", "offloaded", "fellBack" },
1914
2002
  "machines": { "<machine>": { "today": <day>,
1915
2003
  "total": <totals> } },
1916
- "placements": [<placement>, ...] } }
2004
+ "placements": [<placement>, ...] },
2005
+ "agentDevice": <AgentDeviceUsage|null>,
2006
+ "swiftpmCache": <SwiftpmCacheUsage|null> }
1917
2007
 
1918
2008
  \`project\` is null outside a project; a platform with no run yet is null.
1919
2009
  A bucket carries runs, failed, hits, misses, coldRuns, coldRunMs, hitRuns,
@@ -1924,6 +2014,44 @@ RULES
1924
2014
  average, time saved and build estimates stay local. Milliseconds are
1925
2015
  integers.
1926
2016
 
2017
+ AGENT-DEVICE DISK USAGE
2018
+ agentDevice: { version: 1, measuredAt, bytes, complete, stateDir,
2019
+ runnerBuilds, workspaces, hosted }
2020
+ bytes sums known state, separate runner builds, workspace and hosted bytes;
2021
+ complete is false when a measurement fails. Individual unknown byte fields
2022
+ are null. Absent directories have zero bytes and present: false.
2023
+ stateDir defaults to ~/.agent-device. AGENT_DEVICE_STATE_DIR overrides it
2024
+ only when it does not overlap a workspace agent-device dir or the hosted dir.
2025
+ stateDir: { dir, present, bytes, sessions: { dir, bytes, count },
2026
+ logs: { dir, bytes }, other: { bytes, largest: [{ name, bytes }] } }
2027
+ runnerBuilds: { dir, present, bytes, sharedBytes, platforms: [
2028
+ { platform, dir, bytes, entries: [{ name, dir, bytes, lastUsedAt,
2029
+ packageVersion, xcodeBuildVersion, inUse, inUseReason }] }] }
2030
+ inUseReason is lease, lock, unreadable or null. Lease means a live owner or
2031
+ runner matched by start time; lock means a live or unknown lock owner.
2032
+ Unreadable lock owners and unreadable or unknown leases use unreadable.
2033
+ Dead lock owners and leases do not mark entries in use.
2034
+ workspaces: [{ dir, projectRoot, bytes }]; hosted: { dir, bytes, sessions } | null.
2035
+ $STIM_HOME/agent-device-usage.json caches the measurement and resolved roots
2036
+ for 10 minutes. stats and unscoped gc measure; no other command measures this
2037
+ state. Server stats.get reads only the cached value.
2038
+ Stim never trims or deletes the shared runner builds, sessions, logs and other
2039
+ state or the hosted driver dir; a workspace's own agent-device dir goes only with its workspace.
2040
+
2041
+ SWIFTPM CACHE DISK USAGE
2042
+ swiftpmCache: { version: 1, measuredAt, dir, present, bytes, complete } | null.
2043
+ The user-level cache is ~/Library/Caches/org.swift.swiftpm on macOS and
2044
+ org.swift.swiftpm under XDG_CACHE_HOME (default ~/.cache) elsewhere.
2045
+ It is shared by every SwiftPM build on the machine; Stim never deletes it.
2046
+ bytes is null and complete is false if du fails; absent directories have
2047
+ present: false, bytes: 0 and complete: true. Plain output omits absent caches.
2048
+ $STIM_HOME/swiftpm-cache-usage.json caches the measurement for 10 minutes,
2049
+ keyed on the resolved dir. stats and unscoped gc alone run du -sk with a
2050
+ 20-second timeout. Server stats.get reads only the cached value. status,
2051
+ start, ios, android and the budget gate never measure it. gc reports it as
2052
+ sections.swiftpmCache, never in caches or actionable; --delete and
2053
+ --older-than leave it untouched, and any --cache scope reports null.
2054
+
1927
2055
  HOW A RUN IS COUNTED (\`stats\`)
1928
2056
  Every \`ios\` or \`android\` invocation that got as far as computing a
1929
2057
  cache key is one run, in this project's bucket and in the machine-wide one.
@@ -1968,9 +2096,9 @@ HOW A RUN IS COUNTED (\`stats\`)
1968
2096
  run's outcome.
1969
2097
 
1970
2098
  BUILD PLACEMENT (\`offload\`)
1971
- While at least one build machine is paired, every run that compiles
1972
- records where it built and why, in a placement: { at, project, platform,
1973
- decision, reason, machine?, buildMs?, localEstimateMs?, failed? }.
2099
+ Every run that compiles records where it built and why, in a placement: { at, project, platform,
2100
+ decision, reason, machine?, buildMs?, slotWaitMs?, localEstimateMs?, failed? }.
2101
+ slotWaitMs is whole milliseconds waiting for a build slot, present only when positive.
1974
2102
  decision is "here", "offloaded", or "fell-back" (it tried a build machine
1975
2103
  and built here). reason is why: the run's \`placement:\` reason, such as
1976
2104
  "load 0.6/core, 1 of 3 build slots busy here" (auto keeps the build here
@@ -2013,9 +2141,25 @@ another worktree's bundler.
2013
2141
  --remote expose Metro for a remote device
2014
2142
 
2015
2143
  Plain \`stim start\` is local and does not create a tunnel. Remote intent
2016
- comes from \`start --remote\`, \`ios.remote\`, or \`android.remote\`. The
2144
+ comes from \`start --remote\`, eas/proxy in \`ios.remote\`, or \`android.remote\`. The
2017
2145
  \`metro.tunnel\` setting selects the provider after remote intent exists.
2018
2146
 
2147
+ HOSTED IOS METRO
2148
+ ios --remote <machine> keeps Metro local and needs no start --remote.
2149
+ The supervisor watches private gateway requests in workspace state, binds
2150
+ only this Mac's Tailscale address and accepts only the pinned hosting peer
2151
+ with a per-session secret. The host's bridge is loopback-only. Port and secret
2152
+ stay in 0600 workspace state and never appear in status, JSON or logs.
2153
+ metro.tunnel and metro.publicUrl are ignored. Supervisor restart recreates
2154
+ the recorded gateway; stop or a stopped hosted session closes it. A recorded
2155
+ hosted session prevents Metro idle stop. Release runs skip the bridge.
2156
+ Hosted Debug runs require the supervisor's private gateway support before
2157
+ reservation. --no-metro-check refuses; restart a missing or older supervisor
2158
+ with stim stop; stim start.
2159
+ Native iOS logs are pulled from the host by stim logs, including --errors;
2160
+ JavaScript logs still arrive through Metro. Stop pulls native logs before
2161
+ deleting the simulator. Read guide lifecycle hosted-ios for placement and cleanup.
2162
+
2019
2163
  TAILNET-ONLY METRO
2020
2164
  Install Tailscale and sign in on this machine and the remote device or
2021
2165
  agent-device daemon's host, then select the mode explicitly:
@@ -2327,6 +2471,34 @@ instead of allocating a separate label in every worktree.`
2327
2471
  summary: "Querying the merged NDJSON timeline, and what --errors means",
2328
2472
  body: () => `LOGS
2329
2473
 
2474
+ If Stim is not installed globally, replace stim with npx stim.
2475
+
2476
+ AGENT-DEVICE WORKSPACE STATE
2477
+
2478
+ When you drive a device yourself with agent-device, opt in to the workspace's
2479
+ state directory reported as agentDevice.stateDir in the stim ios, android and
2480
+ macos output and in status. Read it once from status, run from the app root:
2481
+
2482
+ STATE_DIR="$(stim status --json | jq -r '.environments[] | select(.path=="'"$(pwd -P)"'") | .agentDevice.stateDir')"
2483
+
2484
+ Then prefix each agent-device command, so the choice survives shells that do
2485
+ not keep exports:
2486
+
2487
+ AGENT_DEVICE_STATE_DIR="$STATE_DIR" agent-device open <bundle id> --platform ios --session <name>
2488
+ AGENT_DEVICE_STATE_DIR="$STATE_DIR" agent-device snapshot --session <name>
2489
+
2490
+ The path is $STIM_HOME/workspaces/<name>/agent-device. agent-device creates it
2491
+ itself. Sessions (including events, runner logs and request text), logs,
2492
+ metro-sessions, allocations and daemon files move there. The apple-runner build
2493
+ cache and device claims stay shared; do not set
2494
+ AGENT_DEVICE_IOS_RUNNER_DERIVED_PATH or AGENT_DEVICE_CLAIMS_DIR.
2495
+ Each active workspace adds one agent-device daemon, which exits after five
2496
+ idle minutes with no open session. The variable is opt-in per command: forgetting it falls back to
2497
+ ~/.agent-device.
2498
+ stim stop closes owned-device sessions there, stim logs --source agent reads
2499
+ them, and stim worktree remove stops the daemon and removes the directory.
2500
+ Hosted/stim-server sessions use their own state directory and need none of this.
2501
+
2330
2502
  stim logs [filters]
2331
2503
 
2332
2504
  Reads every *.ndjson file in the global workspace logs directory, merges them into one timeline
@@ -2365,7 +2537,7 @@ FLAGS
2365
2537
  --slot <name> only this slot's records plus, once it has launched, the
2366
2538
  shared untagged Metro and app client records (not the web
2367
2539
  page's); default also includes untagged legacy records
2368
- --source <s...> metro, client, device, build, agent (one or more), or all.
2540
+ --source <s...> metro, client, device, build, agent, maintenance (one or more), or all.
2369
2541
  An unknown value is REJECTED rather than quietly matching
2370
2542
  nothing.
2371
2543
  --level <l> minimum level: debug, info, warn, error, fatal
@@ -2513,7 +2685,14 @@ THE RECORD
2513
2685
  there are none. Stim adds it at query time; it is not in the
2514
2686
  log files.
2515
2687
 
2688
+ Maintenance records use src: maintenance and appear in the plain timeline.
2689
+ stim logs --source maintenance
2690
+ Add --errors to --source maintenance to show only maintenance_failure events;
2691
+ failures before a later launch marker are hidden. Machine passes are reported in
2692
+ status and maintenance/maintenance.ndjson; this command reads workspace logs.
2693
+
2516
2694
  WHAT WRITES WHAT
2695
+ maintenance.ndjson workspace maintenance actions, failures and explaining skips
2517
2696
  metro.ndjson the bundler, in both supervisor modes
2518
2697
  client.ndjson in-app console logs and redboxes -- BARE PROJECTS ONLY.
2519
2698
  In expo-child mode everything Expo prints lands in
@@ -2655,7 +2834,8 @@ WHAT WRITES WHAT
2655
2834
  web.ndjson (\`guide web\`). For simulators and
2656
2835
  emulators, no file: each query reads agent-device's
2657
2836
  session records (~/.agent-device/sessions, or
2658
- AGENT_DEVICE_STATE_DIR) for this workspace's owned
2837
+ AGENT_DEVICE_STATE_DIR, plus existing workspace
2838
+ agent-device directories) for this workspace's owned
2659
2839
  simulators and emulators, read-only. An action is
2660
2840
  info (msg is agent-device's summary, e.g. "Tapped
2661
2841
  (201, 731)"; event agent_action with command, session,
@@ -2692,6 +2872,23 @@ WHAT WRITES WHAT
2692
2872
  Every refusal listed here carries a stable CODE, whichever command prints it.
2693
2873
  Branch on the code, never on the message.`,
2694
2874
  sections: {
2875
+ STIM_HOSTING_REFUSED: {
2876
+ summary: "a named hosting Mac refused or could not confirm its iOS session; no local fallback",
2877
+ body: () => `STIM_HOSTING_REFUSED
2878
+ The message names the hosting Mac and its reason: unreachable or changed
2879
+ tailnet node, no installed simulator choice, no capacity, elevated or unknown
2880
+ memory pressure, or an unresolved reservation or delivery. Stim boots nothing
2881
+ locally and never tries another Mac. Check stim-server and Tailscale there,
2882
+ then run stim doctor. Correct --device-type / --runtime when the offer names
2883
+ an unavailable choice. A session that exists stays recorded even if delivery
2884
+ fails: retry stim ios --remote <machine>, or run stim stop to reconcile it.
2885
+ An unreachable stop keeps the placement; rerun stim stop when the host answers.
2886
+ A failed build handoff uses upload instead. If native log queries are unavailable,
2887
+ logs prints a note on stderr and shows copied records. Update an older stim-server
2888
+ on the host to enable iOS handoff and native logs (hello feature hosted-ios-data).
2889
+ A name outside hosting.machines is STIM_BAD_ARG. Missing or pending hosting
2890
+ approval keeps the doctor --fix and stim-server devices grant remedies.`
2891
+ },
2695
2892
  STIM_EAS_BUILD_MISSING: {
2696
2893
  summary: "no completed EAS development build matches; build only with session authorization",
2697
2894
  separator: "--- EAS BUILD CODES (`ios --eas-profile` / `android --eas-profile`) ---",
@@ -3004,7 +3201,9 @@ no build and does not contact the selected machine.
3004
3201
 
3005
3202
  Run stim doctor --fix to ask for build access if not paired. Check
3006
3203
  stim settings get offload.machines. A person on the worker finds the id with
3007
- stim-server devices and approves this Mac with stim-server devices grant <id> --build.
3204
+ stim-server devices and approves this Mac with stim-server devices grant <id> --build,
3205
+ or runs stim-server setup on the worker with its node, ticket and expiry
3206
+ (guide settings). Agents never run setup or approve requests.
3008
3207
  Rerun with --build-machine auto for normal placement and local fallback, or
3009
3208
  --build-machine local to keep the build here.
3010
3209
  `
@@ -3730,7 +3929,7 @@ captured" (in metro.ndjson, bare RN)
3730
3929
  cannot be verified,
3731
3930
  \`--device\` with an empty
3732
3931
  serial or UDID, \`--device\` together with \`--remote\`, \`ios --runtime\`
3733
- on a remote run (\`--remote\` or ios.remote; the remote backend picks the
3932
+ on an eas/proxy run (\`--remote\` or ios.remote; that backend picks the
3734
3933
  iOS version), \`ios --device-type\` on the proxy backend or with an
3735
3934
  eas-cli older than 22.2.0 on the eas backend, \`android --system-image\` or
3736
3935
  \`--device-profile\` on a remote run (\`--remote\` or android.remote), a
@@ -4286,8 +4485,10 @@ apply. The run then reports \`devServer: { started: true, reason }\` in its
4286
4485
  JSON, with reason "not running" or "stopped (idle)". When that start fails,
4287
4486
  the run refuses with the start's own code (STIM_METRO_TIMEOUT,
4288
4487
  STIM_SUPERVISOR_EXITED, ...) before any build work. Release builds and
4289
- \`--no-metro-check\` neither check nor start the dev server. \`--remote\`
4290
- starts it the way \`stim start --remote\` does. Step 2 remains useful to warm
4488
+ \`--no-metro-check\` neither check nor start the dev server; hosted Debug iOS
4489
+ refuses --no-metro-check with STIM_BAD_ARG. \`--remote\`
4490
+ with eas or proxy starts it the way \`stim start --remote\` does. A named
4491
+ hosting Mac keeps Metro local through a private bridge; see hosted-ios. Step 2 remains useful to warm
4291
4492
  Metro while other work happens.
4292
4493
 
4293
4494
  Repeat step 3 whenever a NATIVE input changes. A JS-only edit needs nothing --
@@ -4451,6 +4652,166 @@ WAITING FOR A CHANGE
4451
4652
  still running, which it notices at the next change.
4452
4653
  Without --json it reprints the human view on change.`,
4453
4654
  sections: {
4655
+ "hosted-ios": {
4656
+ summary: "iOS on a named approved Mac: strict placement, private Metro, status and stop",
4657
+ body: () => `IOS ON A HOSTING MAC
4658
+
4659
+ If Stim is not installed globally, replace stim with npx stim.
4660
+
4661
+ stim settings set hosting.machines '["janics-mac-mini"]'
4662
+ stim doctor --fix
4663
+ # A person there approves: stim-server devices grant <id> --device-host
4664
+ # Or runs stim-server setup on the worker with its client node, ticket and expiry.
4665
+ stim doctor
4666
+ stim ios --remote janics-mac-mini --device-type "iPhone 17 Pro" --runtime "iOS 27.0"
4667
+ stim status --json
4668
+ stim reload ios
4669
+ stim stop
4670
+
4671
+ The same setup serves macos --remote. Approval is separate from build, read
4672
+ and phone control. Set ios.remote to the machine name for a workspace default;
4673
+ no flag or setting runs here. eas and proxy keep their remote backend meanings.
4674
+ auto is accepted but refuses with STIM_BAD_ARG until automatic placement ships.
4675
+ Android --remote <machine> refuses with STIM_BAD_ARG: Android on a paired Mac
4676
+ is not available yet.
4677
+
4678
+ A named Mac is strict: STIM_HOSTING_REFUSED names its reason; nothing boots
4679
+ here or elsewhere. --device cannot use a hosting Mac, and --simulator-app
4680
+ refuses. The host boots headless and ignores this Mac's iosSimulatorApp setting.
4681
+ --slot, --device-type and --runtime select the hosted simulator; --scheme,
4682
+ --configuration and --eas-profile select the app as usual. The build targets
4683
+ the offered simulator architecture, not this Mac's. --build-machine remains
4684
+ independent and selects a compatible build worker for a Debug cache miss.
4685
+
4686
+ Stim builds or fetches before reserving, records the session immediately, then
4687
+ uploads the app on every run. Debug keeps Metro here; its supervisor owns a
4688
+ private gateway bound to this Mac's Tailscale address, pinned to the host peer
4689
+ and protected by a per-session secret. The host exposes only a loopback bridge.
4690
+ No start --remote is needed; metro.tunnel and metro.publicUrl are ignored.
4691
+ Debug requires a running supervisor with private gateway support before reserving
4692
+ a simulator. --no-metro-check refuses with STIM_BAD_ARG for hosted Debug runs.
4693
+ If its supervisor is missing or older, run stim stop; stim start, then retry.
4694
+ Non-Debug runs skip Metro. Launch is unverified until this workspace's Metro
4695
+ provides bundle evidence, or the host proves a live release process.
4696
+
4697
+ A rerun on the same named Mac reattaches and delivers a new app attempt. A
4698
+ stopped or missing session is replaced; an unreachable or unknown owner refuses
4699
+ replacement. Switching between this Mac and a hosting Mac, or to a different
4700
+ hosting Mac, refuses for that slot: run stim stop first.
4701
+ Status adds ios.host { machine, session, selected, device: { name, runtime },
4702
+ agent, state } per slot. A shutdown local simulator is replaced in status; a
4703
+ booted or unknown local simulator stays visible alongside ios.host with a warning.
4704
+ The default slot appears only in ios, never in slots[]. No host UDID or gateway secret enters local device state or
4705
+ status. Ready is normal; stopped has a rerun hint; unknown or unreachable is
4706
+ unverified. A host server restart stops its sessions.
4707
+
4708
+ stop and worktree remove wait for the host to stop, delete exactly its owned
4709
+ simulator without parking, close the gateway and clear placement. stop --json
4710
+ reports each hosted slot under outcomes.device["ios:host:<slot>"], retaining
4711
+ local device outcomes and successful siblings when another slot fails. Revoked or
4712
+ missing sessions clear placement too. An unreachable host retains placement
4713
+ and fails cleanup: rerun stim stop when it answers. Missing or unapproved local
4714
+ credentials require stim doctor to restore access before retrying stop. An
4715
+ unreadable machine or session remains recorded; status warns and stop names the
4716
+ workspace-state key to inspect. Only that slot's ios run refuses; restore its
4717
+ recorded machine and session from the host, then run stim stop for that slot.
4718
+ Reload returns a JSON error with that remedy when no other app can reload;
4719
+ logs remain readable. Unknown selection or agent metadata is normalized
4720
+ without discarding the owner. devices.idleShutdownMinutes
4721
+ does not stop a hosted simulator in this phase; its recorded session keeps Metro
4722
+ from idle stopping. Reruns upload only missing manifest and file digests from a
4723
+ session-scoped store; the host verifies bytes before reuse. When the iOS build
4724
+ ran on the hosting Mac's pinned node, it takes matching files from that build.
4725
+ A refused or timed-out handoff falls back to upload, retrying a still-busy host
4726
+ for at most one minute.
4727
+
4728
+ stim logs and stim logs --errors pull native device records from bounded host
4729
+ queries. Concurrent followers share a collection, throttled per session, without
4730
+ blocking app delivery, viewing or control. Stop limits each log drain to 30 seconds with
4731
+ progress on stderr and a no-progress guard. The host collects a bounded final
4732
+ tail before deletion, including on revocation or server close; stop copies it
4733
+ back afterwards. If the final collection drops a backlog interval and eventually
4734
+ succeeds, a device warning record names that interval. A damaged collection checkpoint is ignored and
4735
+ rebuilt; it never blocks reading collected records. The client waits up to
4736
+ 180 seconds for stop. The final collection and worker termination paths fit within
4737
+ that wait; an in-flight handoff copy and closing Metro or view transports are
4738
+ outside those worker bounds.
4739
+ Collected records remain in the session home after deletion;
4740
+ app blobs and materialized bundles are removed. Native queries read persisted
4741
+ entries, overlap by five seconds and de-duplicate; info-level or later-persisted
4742
+ entries may be unavailable. JavaScript logs arrive through Metro.
4743
+ The host advertises hosted-ios-data for iOS handoff and native logs. An older
4744
+ host gets an update note, uses uploads and shows logs already copied here.
4745
+
4746
+ AGENT CONTROL
4747
+
4748
+ On the hosting Mac, set hosting.agentDriver to agent-device. Both Macs need
4749
+ agent-device 0.21.20 or later (the policy and remote-config contract). The host
4750
+ starts one loopback daemon per installed iOS session under a child-aware
4751
+ ownership claim. Its daemon policy pins exactly that simulator UDID and denies
4752
+ shutdown. Stop, close, revocation, reinstall and graceful server close end its
4753
+ daemon and grant. A server crash can leave detached daemon processes and claims;
4754
+ restart alone does not prove they stopped. A daemon exit restarts only that
4755
+ session and rotates its grant; rerun stim ios to reattach and refresh the config.
4756
+ An unresolved proxy or daemon retains its claim and blocks reinstall and native
4757
+ teardown; clear a named claim only after proving its owner and child are gone.
4758
+
4759
+ The client writes a separate 0600 remote-config file for each workspace slot.
4760
+ Status prints agent: <command>; ios.host.agent in --json carries driver,
4761
+ remoteConfig and command, never the token. Use the returned file:
4762
+
4763
+ agent-device open <bundleId> --remote-config <file>
4764
+ agent-device snapshot --remote-config <file>
4765
+ agent-device click <ref> --remote-config <file>
4766
+ agent-device screenshot --remote-config <file>
4767
+
4768
+ The command shape is agent-device <command> --remote-config <file>. Open leases
4769
+ the simulator automatically with ios-instance and the proxy provider, scoped
4770
+ to this session's tenant. Close releases it; Stim still owns the simulator.
4771
+ The config names daemonBaseUrl, daemonAuthToken, tenant, sessionIsolation,
4772
+ runId, clientId, deviceKey, leaseBackend, leaseProvider and platform; it carries
4773
+ no preallocated leaseId. stim stop closes the matching agent-device connection
4774
+ and removes that slot's config after the host confirms stop or revocation.
4775
+ The host stops the session daemon and identity-checked agent-device runners
4776
+ targeting its exact simulator UDID before deleting the simulator, even without close.
4777
+
4778
+ Allowed: devices, open, close, snapshot, diff, wait, find, get, is, click, fill,
4779
+ press, type, focus, scroll, screenshot, longpress, swipe, back, home, orientation,
4780
+ appstate, alert and batch. These inspect or interact with the simulator and
4781
+ installed apps. devices lists only the session's simulator, filtered by the
4782
+ daemon policy. Its device claims are session-local and record / as the workspace;
4783
+ a host-side agent-device using the same simulator is not visible to the hosted daemon.
4784
+ Client device selectors are stripped and requests are pinned to iOS and that
4785
+ UDID; an explicitly named foreign UDID is refused. Selecting another device,
4786
+ boot/shutdown/erase, installs, uninstall, uploads, push, record, logs, network,
4787
+ perf, trace, clipboard and settings are refused. Stim installs; the agent cannot
4788
+ enumerate other host devices or write host files. Ambient client fields (cwd,
4789
+ stateDir, config and remoteConfig) are stripped, including inside batch steps.
4790
+ Host paths and launch inputs (--out, baseline, launchConsole, developerDir,
4791
+ installSource and similar fields) are refused, including inside batch steps.
4792
+ Only agent-device's generated screenshot temp artifact is accepted and returned
4793
+ to the client; arbitrary host screenshot destinations are refused. Runtime
4794
+ hints are dropped and only reporting/artifact metadata passes through.
4795
+
4796
+ If hosting.agentDriver is none, the driver cannot enforce its policy, or the
4797
+ host does not advertise hosted-ios-agent, agent reports
4798
+ { driver: 'none', setting: 'hosting.agentDriver' }. Update the host and check
4799
+ its setting and agent-device installation. No other simulator becomes reachable.
4800
+ Client hello carries only name and version, not features, so the host cannot gate
4801
+ daemon startup or grant issuance on client support. The client checks the host's
4802
+ hosted-ios-agent feature before using a grant.
4803
+
4804
+ Stim Desktop and the phone app view and control the simulator through this
4805
+ Mac's stim-server relay, with an "on <machine>" label. Turn on Serve to phones
4806
+ in Desktop; pair the phone with this Mac, not the hosting Mac. frames.subscribe
4807
+ and control.begin address workspace, platform ios and slot, including named
4808
+ slots. The relay shares one authenticated host connection with hosted macOS
4809
+ viewers. Stopped sessions show a rerun remedy; unavailable hosts report why
4810
+ frames cannot arrive. Replay, duoFrame and physical targets are refused for
4811
+ hosted subscriptions. Desktop hides local Simulator.app, rotation, hardware
4812
+ buttons and simulator options; touch and text use the relay. The phone can also
4813
+ send the host's supported button, rotation and posture inputs.`
4814
+ },
4454
4815
  eas: {
4455
4816
  summary: "download a matching EAS development build; explicit profile, costs, cache and miss remedies",
4456
4817
  body: () => `EAS DEVELOPMENT BUILDS
@@ -4639,6 +5000,7 @@ result as proof instead of requiring an unrelated screenshot.`
4639
5000
  The labels are a closed set, and nothing else is ever printed in that
4640
5001
  column:
4641
5002
 
5003
+ agent-device
4642
5004
  branch budget build cache caches carry
4643
5005
  checkout daemons deps device
4644
5006
  devices error failed findings fingerprint gems
@@ -5524,7 +5886,7 @@ OPT-IN CONCURRENCY LIMITS (UNLIMITED BY DEFAULT)
5524
5886
  summary: "every flag per command, Android variants and flavors, the per-run simulator model, runtime and system image",
5525
5887
  body: () => `THE OPTION SURFACE, IN FULL
5526
5888
  start --json --wait <seconds> --remote --reset-cache
5527
- ios --build-machine <auto|local|name> --slot <name> --json --plan --no-metro-check --no-build-cache --scheme <name> --configuration <name> --device-type <name> --runtime <version> --simulator-app <xcode|siniulator|stim-desktop> --device [udid] --wait <seconds> --no-wait --remote <proxy|eas>
5889
+ ios --build-machine <auto|local|name> --slot <name> --json --plan --no-metro-check --no-build-cache --scheme <name> --configuration <name> --device-type <name> --runtime <version> --simulator-app <xcode|siniulator|stim-desktop> --device [udid] --wait <seconds> --no-wait --remote <eas|proxy|auto|machine>
5528
5890
  android --build-machine <auto|local|name> --slot <name> --json --plan --no-metro-check --no-build-cache --variant <name> --system-image <id> --device-profile <id> --device [serial] --wait <seconds> --no-wait --remote <proxy|eas>
5529
5891
  reload [ios|android] --json
5530
5892
  device lock <ios|android> [id] --slot <name> --for <duration> --wait <seconds> --json;
@@ -5559,7 +5921,7 @@ OPT-IN CONCURRENCY LIMITS (UNLIMITED BY DEFAULT)
5559
5921
  and prints what it waits for. \`stop --slot\` interrupts a build only for
5560
5922
  its own slot or when it stops the workspace's only device (see STOP DURING
5561
5923
  A BUILD).
5562
- Named slots support local devices; remote sessions use the default slot.
5924
+ Named slots support local devices and hosted iOS simulators; eas/proxy sessions use the default slot.
5563
5925
  Names use 1-64 letters, digits, underscores or hyphens, starting with a letter
5564
5926
  or digit. Reserved object-property names and web are refused.
5565
5927
 
@@ -5765,7 +6127,7 @@ OPT-IN CONCURRENCY LIMITS (UNLIMITED BY DEFAULT)
5765
6127
  \`--device-type "iPhone 8" --runtime 26.5\` that each half would pass alone.
5766
6128
  \`--runtime\` takes a version (\`26.5\`) or a runtime's full name
5767
6129
  (\`iOS 26.5\`), exactly; no prefix or suffix matches. On a remote run
5768
- (\`--remote\` or the ios.remote setting) \`--runtime\` refuses with
6130
+ (eas/proxy via \`--remote\` or ios.remote) \`--runtime\` refuses with
5769
6131
  STIM_BAD_ARG, because the remote backend picks the iOS version. So does
5770
6132
  \`--device-type\` on the proxy backend. The eas backend honors
5771
6133
  \`--device-type\`: Stim passes it to \`eas simulator:start --device\`,
@@ -5774,7 +6136,9 @@ OPT-IN CONCURRENCY LIMITS (UNLIMITED BY DEFAULT)
5774
6136
  recorded EAS session still running another model refuses with
5775
6137
  STIM_REMOTE_DEVICE_MISMATCH instead of being reused; one that already
5776
6138
  ended is replaced on the requested model. The ios.deviceType and
5777
- ios.runtime settings do not refuse on a remote run; it ignores them.
6139
+ ios.runtime settings do not refuse on an eas/proxy run; it ignores them.
6140
+ A named hosting Mac uses both selectors and its installed choices instead;
6141
+ an unavailable choice is STIM_HOSTING_REFUSED. See hosted-ios.
5778
6142
  \`android --system-image\` and \`--device-profile\` refuse with
5779
6143
  STIM_BAD_ARG on every remote run (\`--remote\` or the android.remote
5780
6144
  setting), and a remote run ignores android.systemImage and
@@ -6229,8 +6593,56 @@ wipe state to upgrade: it records ownership needed for safe teardown. Use the
6229
6593
  same slot-aware CLI for all commands while named assignments exist; older
6230
6594
  versions cannot reliably manage their assignments.
6231
6595
 
6596
+ ARCHIVE
6597
+
6598
+ Linked-worktree removal and gc dead-project pruning keep history under
6599
+ $STIM_HOME/archive by default. See stim guide cleanup archive.
6600
+
6601
+ MAINTENANCE
6602
+
6603
+ Automatic maintenance is report-only in this release: it measures, plans and
6604
+ logs, and never stops or deletes resources. The default mode is report; it is
6605
+ off in CI and scoped STIM_HOME homes unless STIM_MAINTENANCE is explicit.
6606
+ Commands trigger a detached pass when disk and memory checks (every minute)
6607
+ or directory sizes (hourly) are due. guide, settings and help do not trigger
6608
+ it; gc --delete also skips the hook. status --watch also triggers checks.
6609
+ Size checks defer under high load. Attempts back off for at least one minute.
6610
+ Measured directories are workspace build outputs and Stim's shared native,
6611
+ Metro, ccache, Swift compilation and registered caches. Pressure checks read
6612
+ free disk on the Stim home, projects and worker root volumes. On macOS the
6613
+ memory signal is the sysctl pressure level, with no signal if sysctl fails.
6614
+ Other platforms use os.freemem(); macOS never falls back to it.
6615
+ Memory pressure is recorded only; memory stops are deferred to a later phase.
6616
+
6617
+ stim status last checks, plan and running pass
6618
+ stim status --json maintenance observations and recent records
6619
+ stim logs --source maintenance this workspace's maintenance records
6620
+ stim gc live pressure and cached-size preview
6621
+ stim settings set maintenance.mode off
6622
+
6623
+ The machine log is $STIM_HOME/maintenance/maintenance.ndjson, rotated at
6624
+ maintenance.logMaxMb with the old generation retained for
6625
+ maintenance.logRetentionDays. Child crashes use maintenance/child.log.
6626
+ Actions and explaining skips are logged only when newly planned. A pass is
6627
+ logged after a size check or a change to actions, skips or blocked reasons.
6628
+ maintenance.logChecks enables debug observations; default false.
6629
+ status and doctor report invalid settings and unresolved claims with a removal
6630
+ command to run only after confirming the holder is gone. Invalid cache caps
6631
+ fall back to their own defaults; invalid maintenance settings disable passes.
6632
+ The run claim serializes passes with gc --delete; gc refuses a held claim
6633
+ with the holder and recovery guidance instead of waiting.
6634
+
6232
6635
  CLEANUP AND DISK
6233
6636
 
6637
+ worktree remove stops the agent-device daemon when the workspace's
6638
+ agent-device/daemon.json exists, then removes its state directory with the
6639
+ workspace. If agent-device is missing or the stop fails, it keeps the directory
6640
+ and reports the retry command: agent-device daemon stop --state-dir <dir> --clean.
6641
+ A daemon.json whose process is gone is stale and does not block removal.
6642
+ gc --delete of a finished worktree does the same.
6643
+ stop closes owned-device sessions in that directory without deleting it.
6644
+ See stim guide logs for AGENT_DEVICE_STATE_DIR examples.
6645
+
6234
6646
  WHAT RECLAIMS AN OWNED DEVICE
6235
6647
  stim worktree remove parks eligible owned simulators and emulators
6236
6648
  (\`guide lifecycle pool\`); deletes them when
@@ -6300,9 +6712,77 @@ written by a newer Stim -- costs one dim line on stderr and is otherwise left
6300
6712
  alone; only the next \`ios\` or \`android\` run moves an unparseable one aside
6301
6713
  to stats.json.corrupt-<unix ms> and starts a new one.`,
6302
6714
  sections: {
6715
+ archive: {
6716
+ summary: "retained workspace history and explicit archive cleanup",
6717
+ body: () => `ARCHIVE
6718
+ Linked-worktree removal and gc pruning a registered root that is gone keep
6719
+ state, ended agents, the error index, logs, closed recording segments and
6720
+ workspace-local agent-device sessions under $STIM_HOME/archive.
6721
+ Build outputs and open recording segments are deleted with the workspace.
6722
+ Archives have no checkout, devices, ports or running processes. A recreated
6723
+ path starts fresh; status links its earlier archives with replacedBy.
6724
+ Paired clients with read can select archived[].id with archive instead of
6725
+ workspace for server log queries and replay. Archived log subscriptions send
6726
+ retained records, then logs-ended. Archived frame subscriptions need at and
6727
+ cannot be physical or go live. Replay ranges report recording disabled.
6728
+ Reads stay on this Mac.
6729
+ An updated server is required; older servers refuse with bad-request when
6730
+ workspace is omitted. There is no CLI archive log flag.
6731
+ archive.enabled defaults true and accepts every settings layer. STIM_HOME
6732
+ disables archives unless STIM_ARCHIVE_ENABLED is set. Empty stubs and orphaned
6733
+ workspace directories are not archived. A failure prints one stderr line
6734
+ beginning "could not archive:" and removal continues with empty stdout.
6735
+
6736
+ Retention runs on every worktree removal, archived or not, once an archive
6737
+ exists. It deletes records past their age or count limit first, then expires
6738
+ recordings, agent actions and logs by age from removedAt; trims per-workspace
6739
+ logs (oldest rotated generations, then build logs, then oldest other files);
6740
+ and removes the largest kind of the oldest archive while over total caps.
6741
+ Archives with expired artifacts keep their record until the record limit. recording.enabled false at machine scope deletes archived
6742
+ recordings on the next retention pass. Sizes are binary GB/MB; 0 keeps none.
6743
+ See stim guide settings for the nine archive.* keys and overrides.
6744
+
6745
+ stim gc --cache archived list ids, kinds, bytes and expiry
6746
+ stim gc --delete --cache archived delete every archive
6747
+ stim gc --delete --cache archived --older-than 14
6748
+ only removed at least 14 days ago
6749
+ stim gc --delete --cache archived:<id> delete one; ignores --older-than
6750
+ stim gc --delete --cache archived-logs delete logs, keep records
6751
+ stim gc --delete --cache archived-recordings delete recordings, keep records
6752
+ stim gc --delete --cache archived-agent delete agent actions, keep records
6753
+ Per-kind selectors also accept --older-than, measured from removedAt.
6754
+ Normal gc, --cache all, --worktrees and unscoped --older-than exclude archives.
6755
+ Archive scopes report abandoned staging directories and delete only those
6756
+ with no live or unresolved claim. An unresolved claim names its removal command.
6757
+
6758
+ Directories are 0700 and files 0600. No redaction is applied; logs and agent
6759
+ actions can contain secrets. Use archive.enabled false for a sensitive repo.
6760
+ Archived logs and replay are not exposed through CLI or app readers yet.`
6761
+ },
6303
6762
  gc: {
6304
6763
  summary: "what gc and worktree remove delete, keep and refuse: orphans, stale records, locks, leases, EAS sessions",
6305
- body: () => `LINKED WORKTREES
6764
+ body: () => `AGENT-DEVICE (REPORT ONLY)
6765
+ Unscoped gc reports agent-device runner builds by platform and entry, last use,
6766
+ agent-device and Xcode versions, sessions, logs, other state, workspace state
6767
+ and hosted driver state. Stim never selects this state as a cache.
6768
+ Stim never trims or deletes the shared runner builds, sessions, logs and other
6769
+ state or the hosted driver dir; a workspace's own agent-device dir goes only with its workspace.
6770
+ A live owner or runner lease marks an entry in use. A lock marks it in use only
6771
+ when its owner is live or unknown. Unreadable lock owners and unreadable or unknown
6772
+ leases conservatively mark entries in use. Dead lock owners and leases do not.
6773
+ Use agent-device's own tooling or remove directories yourself to clear unused
6774
+ state. --cache scopes omit this report, including --cache all.
6775
+
6776
+ SWIFTPM CACHE (REPORT ONLY)
6777
+ Unscoped gc also reports the user-level SwiftPM cache after agent-device:
6778
+ ~/Library/Caches/org.swift.swiftpm on macOS, or org.swift.swiftpm under
6779
+ XDG_CACHE_HOME (default ~/.cache) elsewhere. It is shared by every SwiftPM
6780
+ build on this machine. Stim reports it and never deletes it. It is never in
6781
+ caches, never changes actionable, and --delete and --older-than never select
6782
+ it. Any --cache scope, including all, omits measurement and reports
6783
+ sections.swiftpmCache: null. Only stats and unscoped gc measure it.
6784
+
6785
+ LINKED WORKTREES
6306
6786
  \`stim worktree remove\` works with any linked worktree, warmed or not.
6307
6787
  Git registration identifies the worktree; a Stim registry entry is not
6308
6788
  required. Before it reclaims anything, the command refuses a worktree git
@@ -6900,7 +7380,7 @@ Resolution order, first match wins:
6900
7380
  4. machine ~/.stim/config.json, top-level optimizations,
6901
7381
  ios.deviceType, ios.runtime, android.systemImage,
6902
7382
  android.deviceProfile and devices.idleShutdownMinutes
6903
- only
7383
+ and archive.enabled
6904
7384
  5. Stim default
6905
7385
  An environment override, where a setting has one, wins over every layer.
6906
7386
 
@@ -6926,6 +7406,12 @@ An app's .stim.json can contain:
6926
7406
  "android": { "variant": "productionDebug" }
6927
7407
  }
6928
7408
 
7409
+ ARCHIVE SETTINGS
7410
+ ${SETTINGS.filter((setting) => setting.key.startsWith("archive.")).map((setting) => ` ${setting.key}: ${setting.description}; default ${setting.default}; layers ${setting.scopes.join(", ")}; ${setting.env}`).join("\n")}
7411
+ Sizes use binary GB/MB. Archive ages and caps accept 0 to keep none.
7412
+ STIM_HOME makes archive.enabled false unless STIM_ARCHIVE_ENABLED is set.
7413
+ See stim guide cleanup archive for retention and explicit deletion.
7414
+
6929
7415
  KEYS STIM READS
6930
7416
  ios.deviceType e.g. "iPhone 17 Pro" -- the simulator model this
6931
7417
  workspace's owned sim is created as, spelled exactly as
@@ -6952,9 +7438,10 @@ KEYS STIM READS
6952
7438
  embedded JS, no Metro, cache keyed -release-sim, and
6953
7439
  a JS-bundle swap on cache hits. The \`--configuration\`
6954
7440
  flag overrides this per invocation. Unset means Debug.
6955
- ios.remote "proxy" or "eas" to use that remote backend, the same
6956
- as passing \`--remote proxy\` or \`--remote eas\`. The
6957
- build still runs here; only the device is elsewhere.
7441
+ ios.remote "proxy", "eas", or a named approved Mac from
7442
+ hosting.machines, with the same meaning as --remote.
7443
+ "auto" is accepted but refuses until automatic placement
7444
+ ships. Unset runs here. See lifecycle hosted-ios.
6958
7445
  ios.simslimProfile a SimSlim JSON profile under the app directory,
6959
7446
  at most 64 KiB. Install the
6960
7447
  external tool once with
@@ -7235,7 +7722,7 @@ ${ANDROID_AVD_CONFIG_HELP.map((line) => ` ${line}`).joi
7235
7722
  them from the environment or the machine layers.
7236
7723
 
7237
7724
  Each setting takes its documented type: string, array of strings, number,
7238
- boolean, or object. ios.remote, android.remote, metro.tunnel, web.viewport,
7725
+ boolean, or object. android.remote, metro.tunnel, web.viewport,
7239
7726
  optimizations.android.compilerCache and optimizations.android.pch take only
7240
7727
  their listed choices. A value of the wrong
7241
7728
  type or outside those choices is refused by name on every command that resolves
@@ -7291,6 +7778,18 @@ overrides the file:
7291
7778
  budget is off. A value of the wrong shape refuses start, ios and android with
7292
7779
  STIM_BAD_ARG. See \`guide lifecycle budget\` for what each limit reclaims.
7293
7780
 
7781
+ AUTOMATIC MAINTENANCE IS MACHINE-LEVEL
7782
+ Report-only in this release: it measures and plans, and deletes or stops nothing.
7783
+ STIM_HOME and CI make the mode off unless STIM_MAINTENANCE is set.
7784
+ See \`guide cleanup\` for checks, plans and log paths.
7785
+
7786
+ ${SETTINGS.filter((setting) => setting.key.startsWith("maintenance.") || /^caches\..*MaxGb$/.test(setting.key)).map((setting) => ` ${setting.key} ${setting.env} ${setting.default === void 0 ? "unset" : `default ${setting.default}`}
7787
+ ${setting.description}`).join("\n")}
7788
+
7789
+ Maintenance uses the sysctl pressure level on macOS, with no memory signal
7790
+ when sysctl fails. os.freemem() and maintenance.minAvailableMemoryGb apply
7791
+ only on other platforms.
7792
+
7294
7793
  THE IOS SIMULATOR APP IS MACHINE-LEVEL
7295
7794
  Top-level \`iosSimulatorApp\` in ~/.stim/config.json selects the macOS app that
7296
7795
  displays Stim's owned local iOS simulator after boot. It is not a project
@@ -7386,6 +7885,17 @@ test suites and the end-to-end harness use one -- and a scoped config must not
7386
7885
  leave simulators on the machine it cannot account for. A redirected home that
7387
7886
  wants a pool says so with the variable.
7388
7887
 
7888
+ DESKTOP ACCESS TICKETS
7889
+ Stim Desktop's Add wizard sets STIM_ACCESS_TICKET for one \`doctor --fix\`
7890
+ run. Agents never set it, never run \`stim-server setup\`, never edit
7891
+ \`offload.*\` or \`hosting.*\` for the user, and never approve requests.
7892
+ Stim sends the ticket (43 base64url characters) only in build and device-host access
7893
+ requests and stores only its SHA-256 ticketHash. With --fix, a still-pending
7894
+ request is replaced when its ticketHash is missing or differs from the current
7895
+ ticket; matching hashes and approved credentials are preserved.
7896
+ Approved doctor entries under buildMachines and deviceHosts include host
7897
+ { name, screenRecording, accessibility } when the worker reports it.
7898
+
7389
7899
  HOSTING MACHINES ARE MACHINE-LEVEL
7390
7900
  \`hosting.machines\` names Macs that may host owned simulator sessions,
7391
7901
  by MagicDNS name with an optional serve port (default 7443). Name each node
@@ -7396,7 +7906,15 @@ and port once. This is separate from build offloading:
7396
7906
 
7397
7907
  Only \`doctor --fix\` asks for device-host access. A person on that Mac
7398
7908
  approves the printed id with \`stim-server devices grant <id> --device-host\`.
7399
- Hosting grants include no read, control or build capability.
7909
+ A person can also run \`stim-server setup\` on the worker: one node, one ticket,
7910
+ one expiry, at most one request per capability, with per-grant y/N in a
7911
+ terminal or \`--yes\` otherwise. Agents never run \`stim-server setup\` or
7912
+ approve requests. Setup never changes TCC or enables Funnel; an SSH-driven
7913
+ run is not offered. Without a terminal or --yes, setup refuses before installing
7914
+ unless every chosen capability already has a matching approval. Desktop reuse
7915
+ requires an existing tailnet route. Ctrl-C or SIGTERM completes the journal,
7916
+ releases the setup claim and exits 1; a typed N also exits 1. Hosting grants
7917
+ include no read, control or build capability.
7400
7918
  $STIM_HOME/device-host-machines.json stores a private token and pinned tailnet
7401
7919
  node. Doctor never prints the token; it reports each machine under deviceHosts
7402
7920
  in JSON. Stim sends tokens only to the pinned node's own tailnet address,
@@ -7415,13 +7933,15 @@ Access (Accessibility on macOS 26 and earlier) for Stim Host, the app
7415
7933
  \`stim-server service install\` runs the server under.
7416
7934
 
7417
7935
  On a hosting Mac, \`hosting.agentDriver\` names the tool it starts so a
7418
- client's coding agent can drive the macOS apps it hosts for that client.
7419
- The default, \`none\`, starts nothing. \`agent-device\` starts its
7420
- daemon only when that agent-device can lease a single macOS app (its
7936
+ client's coding agent can drive the macOS apps and iOS simulators it hosts for that client.
7937
+ The default, \`none\`, starts nothing. For macOS, \`agent-device\` starts its
7938
+ shared daemon only when that agent-device can lease a single app (its
7421
7939
  \`macos-app\` lease backend); otherwise agent control reports \`none\` with a
7422
7940
  notice, and no client is handed the Mac's desktop. STIM_AGENT_DEVICE_BIN in
7423
7941
  stim-server's environment names an agent-device binary to use instead of
7424
- ~/.local/bin/agent-device. \`doctor\` on that Mac notes a hosted macOS app that runs while
7942
+ ~/.local/bin/agent-device. Hosted iOS uses one daemon per simulator, pinned by
7943
+ its UDID policy; both Macs need agent-device 0.21.20 or later. See guide lifecycle
7944
+ hosted-ios. \`doctor\` on that Mac notes an installed hosted app that runs while
7425
7945
  the setting is \`none\`.
7426
7946
 
7427
7947
  BUILD MACHINES ARE MACHINE-LEVEL
@@ -7436,6 +7956,9 @@ route (default 7443):
7436
7956
  \`doctor --fix\`, run in any app directory, asks each named Mac for build access and pins its tailnet
7437
7957
  node in $STIM_HOME/build-machines.json. A person approves the request on that
7438
7958
  Mac with \`stim-server devices grant <id> --build\`; doctor prints the id.
7959
+ A person can instead run \`stim-server setup\` on the worker with a node,
7960
+ ticket and expiry, under the approval rule above. Build never includes read
7961
+ or control.
7439
7962
  Stim connects to a named Mac only while its name still belongs to the pinned
7440
7963
  node and never sends the token to another node. \`doctor\` reports each
7441
7964
  machine's pairing state and, for an approved machine, asks it for one build
@@ -7470,7 +7993,8 @@ Prebuild and pod install still run on this Mac before a named offload. A refusal
7470
7993
  starts no local xcodebuild, Gradle or SwiftPM compile.
7471
7994
  Run stim doctor --fix to ask for build access if not paired. A person on the
7472
7995
  worker finds the id with stim-server devices and approves it with
7473
- stim-server devices grant <id> --build. Check stim settings get offload.machines,
7996
+ stim-server devices grant <id> --build, or runs stim-server setup there with
7997
+ the client node, ticket and expiry. Check stim settings get offload.machines,
7474
7998
  or rerun with --build-machine auto or --build-machine local. Ctrl-C cancels
7475
7999
  the remote build without a local xcodebuild, Gradle or SwiftPM compile.
7476
8000
 
@@ -7499,14 +8023,16 @@ the ones it offloaded.
7499
8023
  counts as saturated, both here and on a build machine.
7500
8024
 
7501
8025
  STIM_OFFLOAD_MODE overrides it for one command. Device, Release and
7502
- \`--remote\` builds, Android builds with the Apple Clang CAS compiler cache,
7503
- and iOS/Android runs with the build cache off, always build here.
8026
+ \`--remote eas|proxy\` builds, Android builds with the Apple Clang CAS compiler cache,
8027
+ and iOS/Android runs with the build cache off, always build here. Hosted iOS
8028
+ Debug builds can use a separate --build-machine, targeting the hosting Mac's
8029
+ architecture and runtime.
7504
8030
  macOS uses matching Xcode and macOS SDK, with a per-client SwiftPM dependency
7505
8031
  cache on the worker. It skips JavaScript dependencies, prebuild and pods; see
7506
8032
  stim guide macos. macOS artifacts are not cached.
7507
8033
  An offloaded iOS/Android build first runs prebuild (and \`pod install\` for iOS) here,
7508
8034
  then asks every paired machine what it can build. It takes one whose Stim
7509
- build and CPU match this Mac exactly, with enough disk, that does not decline,
8035
+ build matches this Mac and CPU matches the target simulator, with enough disk, that does not decline,
7510
8036
  preferring the one that already holds this repository, then the least loaded,
7511
8037
  and moves to the next one in that order when a machine that offered fails the
7512
8038
  sync or refuses to start the build.
@@ -7575,12 +8101,16 @@ that exact stim-server release from the public npm registry once npm verifies
7575
8101
  its integrity and registry signatures; \`--from <dir>\` installs the packed
7576
8102
  packages of a checkout instead. It waits for offloaded builds and hosted
7577
8103
  sessions to finish, restarts the job, and switches back when the new server
7578
- does not answer within 90 seconds; \`stim-server service rollback\` returns
8104
+ exits or does not answer within 90 seconds; \`stim-server service rollback\` returns
7579
8105
  to the previous server. A client Mac approved for builds or device hosting
7580
8106
  there can ask for the same update over its tailnet connection: an npm release
7581
8107
  always, and its own packed build only while \`server.acceptClientBuilds\` is
7582
8108
  true on that machine (default false).
7583
- Installation alone does not prove readiness; check \`stim-server service status\`
8109
+ Replacing a loaded service waits up to 45 seconds for its existing listeners to
8110
+ release the port. Uninstall does not wait for the port. If stopping the old job
8111
+ or starting the new one fails, install restores the previous plist and attempts
8112
+ to restart a previously loaded LaunchAgent; if it cannot, it prints the
8113
+ launchctl bootout/bootstrap remedy. A running LaunchAgent does not prove readiness; check \`stim-server service status\`
7584
8114
  and its reported log. The server listens before it touches the Stim home; a
7585
8115
  read-only child process reads the Stim home, server and recording directories,
7586
8116
  and until it returns \`/health\` answers 503 with \`startup.state\` pending.
@@ -8081,6 +8611,9 @@ a new window is not captured.`
8081
8611
 
8082
8612
  If Stim is not installed globally, replace stim with npx stim.
8083
8613
 
8614
+ ios --remote <machine> shares hosting.machines and its separate device-host
8615
+ approval. Read guide lifecycle hosted-ios for simulator placement and Metro.
8616
+
8084
8617
  Run from the directory containing Package.swift. This prototype builds one
8085
8618
  explicit executable product in Debug, creates an isolated development app,
8086
8619
  and launches it. It does not use package scripts, Metro, simulators, Xcode
@@ -8295,7 +8828,8 @@ ON ANOTHER MAC
8295
8828
  stim macos --remote <machine> builds the Debug app on this Mac and runs it on an
8296
8829
  approved hosting Mac over the tailnet, without SSH. The machine must be listed
8297
8830
  in hosting.machines and approved: stim doctor --fix asks it, a person on that
8298
- Mac runs stim-server devices grant <id> --device-host, and stim doctor then
8831
+ Mac runs stim-server devices grant <id> --device-host or stim-server setup
8832
+ with that client node, ticket and expiry (guide settings), and stim doctor then
8299
8833
  records the approval. Stim connects only to the machine's pinned tailnet node.
8300
8834
  A refusal or an unreachable host fails the command; it never launches here
8301
8835
  instead.
@@ -8317,7 +8851,8 @@ valid NDJSON. The host's unified log is not collected: os.Logger output that is
8317
8851
  not written to stderr does not appear.
8318
8852
 
8319
8853
  Phones and Stim Desktop view and control the hosted app through this Mac's
8320
- stim-server, which relays to the host. The phone sends clicks, scrolls,
8854
+ stim-server, which relays to the host. Hosted iOS simulator tiles use the same
8855
+ relay and show on <machine>; see guide lifecycle hosted-ios. The phone sends clicks, scrolls,
8321
8856
  text and keys; Stim Desktop's Control sends clicks, drags and typed text.
8322
8857
 
8323
8858
  stim macos --remote mini # build here, deliver, launch on mini