stim 1.18.0 → 1.19.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 (140) hide show
  1. package/README.md +1 -1
  2. package/dist/{activity-BxnkDZDi.mjs → activity-BSw0x84R.mjs} +3 -3
  3. package/dist/{agent-device-usage-output-CZ66YA4T.mjs → agent-device-usage-output-Bjxu_VmX.mjs} +5 -5
  4. package/dist/{android-BvwuMUJ9.mjs → android-BXc6BmEi.mjs} +507 -347
  5. package/dist/{android-DC3ZLuw0.mjs → android-BdQC6zs1.mjs} +6 -6
  6. package/dist/{android-BKA16DTf.mjs → android-BvV1gGdN.mjs} +3 -3
  7. package/dist/{android-CBwCNicO.mjs → android-DE5g_5gv.mjs} +2 -2
  8. package/dist/{android-cas-BBFyr9CA.mjs → android-cas-C87geAg-.mjs} +3 -3
  9. package/dist/android-cas-compiler.mjs +1 -1
  10. package/dist/{api-D0VtFhqk.mjs → api-DIf_s86s.mjs} +5 -7
  11. package/dist/api-run.mjs +8 -8
  12. package/dist/api.d.mts +8 -2
  13. package/dist/api.mjs +1 -1
  14. package/dist/{app-install-D-PUENug.mjs → app-install-CFI4ptUL.mjs} +33 -13
  15. package/dist/{web-DTU0uiWj.mjs → browser-web-CDhqQxhv.mjs} +134 -172
  16. package/dist/{budget-DIhL2BKx.mjs → budget-Dz-kyXLv.mjs} +15 -202
  17. package/dist/{build-facts-DcLv2HSd.d.mts → build-facts-B87vo-Im.d.mts} +32 -1
  18. package/dist/{cache-manifest-BI7ozfmH.mjs → cache-manifest-DiwKSyv9.mjs} +1 -1
  19. package/dist/cache-manifest.mjs +1 -1
  20. package/dist/{chrome-DJnmnS5q.mjs → chrome-BTUOo8p2.mjs} +1 -1
  21. package/dist/{cli-DNJMwADx.mjs → cli-C9Zvgbdc.mjs} +20 -20
  22. package/dist/cli.mjs +1 -1
  23. package/dist/{client-DzhapoMB.mjs → client-CuUo8Nvg.mjs} +248 -23
  24. package/dist/collector-run.d.mts +6 -0
  25. package/dist/collector-run.mjs +6 -6
  26. package/dist/{command-output-CBtzyIgF.mjs → command-output-Bt8ygHE7.mjs} +1 -1
  27. package/dist/{config-CgYMahz7.mjs → config-DC5jEtd1.mjs} +6 -2
  28. package/dist/{created-devices-DKbeWsiK.mjs → created-devices-D0s26Hx5.mjs} +2 -2
  29. package/dist/{dependency-state-xKoOypBm.mjs → dependency-state-DKaS1OM9.mjs} +2 -2
  30. package/dist/{detached-entry-BLfcgc4O.mjs → detached-entry-pB_IPfV4.mjs} +1 -1
  31. package/dist/{dev-client-CLnIH02x.mjs → dev-client-CDY_zAmD.mjs} +2 -2
  32. package/dist/{device-D7oCHGWi.mjs → device-BHw0WlHl.mjs} +8 -8
  33. package/dist/{device-capacity-EUOOYr8T.mjs → device-capacity-BoN1jQkO.mjs} +23 -12
  34. package/dist/device-host-worker.mjs +14 -14
  35. package/dist/{gradle-C6GKnunQ.mjs → device-ios-B2ruohE-.mjs} +659 -105
  36. package/dist/{device-lease-Co-EwifR.mjs → device-lease-Cu7PtQpZ.mjs} +5 -5
  37. package/dist/{device-lease-run-w1xhCqIW.mjs → device-lease-run-D38SnZtt.mjs} +4 -4
  38. package/dist/{device-pool-F5WvRPtD.mjs → device-pool-CP4rAsHx.mjs} +3 -3
  39. package/dist/{device-remote-tHbd1fKy.mjs → device-remote-B5GiUifk.mjs} +15 -16
  40. package/dist/{doctor-B9ckYUoU.mjs → doctor-PRJH94v6.mjs} +53 -29
  41. package/dist/doctor-watchman-wZhyTdp4.mjs +259 -0
  42. package/dist/{error-diagnostics-BRXOqDzG.mjs → error-diagnostics--v5HGOU0.mjs} +10 -10
  43. package/dist/{exec-DWhCZDGm.mjs → exec-i8GDPwq9.mjs} +32 -8
  44. package/dist/{gc-Dh6mXOKC.mjs → gc-CyHMcKcn.mjs} +19 -19
  45. package/dist/{guide-tm6igAaa.mjs → guide-dRaQJJk9.mjs} +409 -472
  46. package/dist/{hosted-android-Bv285t_t.mjs → hosted-android-Bs7fytj7.mjs} +3 -3
  47. package/dist/{hosted-client-C-rMtppF.mjs → hosted-client-C5G4h_OJ.mjs} +1 -1
  48. package/dist/{hosted-ios-D5mNmtj4.mjs → hosted-ios-Bik035eL.mjs} +1 -1
  49. package/dist/{hosted-logs-DN8Iz4mJ.mjs → hosted-logs-CI9Az9a4.mjs} +7 -7
  50. package/dist/{hosted-macos-B-wEjlwG.mjs → hosted-macos-DABm3ScO.mjs} +4 -4
  51. package/dist/{hosted-native-DeeOG8xZ.mjs → hosted-native-DbV2aUVw.mjs} +12 -8
  52. package/dist/{idle-shutdown-jjtxAn5C.mjs → idle-shutdown-DYiMBn34.mjs} +7 -7
  53. package/dist/{ios-B3SbOWdw.mjs → ios-BWbHFZVz.mjs} +5 -5
  54. package/dist/{ios-Bvbf0SGY.mjs → ios-DCrkgXEM.mjs} +1 -1
  55. package/dist/{ios-DNpdygiv.mjs → ios-DYUr9LGc.mjs} +7 -6
  56. package/dist/ios-ITYno9rO.mjs +2241 -0
  57. package/dist/{ios-device-vOaByMhw.mjs → ios-device-CJ_ZOGky.mjs} +3 -3
  58. package/dist/{ios-device-Bn7jWDB1.mjs → ios-device-CrW8aMb5.mjs} +1 -1
  59. package/dist/{ios-state-B2pES6Vs.mjs → ios-state-By0XuLTQ.mjs} +1 -1
  60. package/dist/{launch-verify-hskNcWkZ.mjs → launch-verify-DCveSE6d.mjs} +4 -4
  61. package/dist/{logs-1Ylc1Ri6.mjs → logs-Bao1HOMy.mjs} +10 -10
  62. package/dist/{macos-DF6C7wUU.mjs → macos-ciyBdTY5.mjs} +108 -130
  63. package/dist/macos-nIOATJmR.mjs +2 -0
  64. package/dist/macos-run.mjs +1 -1
  65. package/dist/maintenance-run.mjs +7 -7
  66. package/dist/manifest-CX0d1IkZ.mjs +148 -0
  67. package/dist/{metro-Dh8T4Ytd.mjs → metro-DI2AjPF_.mjs} +194 -41
  68. package/dist/{metro-gateway-CEUw9RMH.mjs → metro-gateway-BxpJLIwh.mjs} +2 -2
  69. package/dist/{plan-placement-CtxkCO54.mjs → miss-reason-CjQCOKqq.mjs} +180 -296
  70. package/dist/{named-ports-BFIz1Shz.mjs → named-ports-B4iWH4E-.mjs} +12 -5
  71. package/dist/{native-runtime-D_603JiC.mjs → native-runtime-BGxsbfny.mjs} +6 -6
  72. package/dist/offload-worker.d.mts +1 -29
  73. package/dist/offload-worker.mjs +9 -8
  74. package/dist/{ownership-D6Se7gg_.mjs → ownership-EB0ySdGK.mjs} +2 -2
  75. package/dist/{placement-log-BcPhRwR4.mjs → placement-log-Dxe34rJm.mjs} +7 -3
  76. package/dist/plan-placement-BVY59qa8.mjs +290 -0
  77. package/dist/{ports-BLTtfBq-.mjs → ports-ByPc0_97.mjs} +3 -3
  78. package/dist/{prebuild-juCyreaR.mjs → prebuild-BKJ3wJI1.mjs} +4 -4
  79. package/dist/{preview-D5V07Nrl.mjs → preview-BbSottmO.mjs} +16 -17
  80. package/dist/project-B5eQ1USs.mjs +612 -0
  81. package/dist/{pull-request-u4vp7zmK.mjs → pull-request-IHNdMnG8.mjs} +1 -1
  82. package/dist/pull-requests.mjs +1 -1
  83. package/dist/react-native-ios-BMh2i25I.mjs +2 -0
  84. package/dist/react-native-ios-CSpIxexZ.mjs +2083 -0
  85. package/dist/{recordings-BAWltsVt.mjs → recordings-Bv8McZN4.mjs} +1 -1
  86. package/dist/{reload-DvyWzVjJ.mjs → reload-rAPdqeRR.mjs} +13 -14
  87. package/dist/{remote-cache-BGg7aGIZ.mjs → remote-cache-N3hAEaAL.mjs} +4 -4
  88. package/dist/{run-vhtDgpHO.mjs → run-lAkle1-c.mjs} +2 -2
  89. package/dist/{server-bare-B9E7S1aR.mjs → server-bare-CkIjnegq.mjs} +3 -3
  90. package/dist/server-expo-DGsdUGX2.mjs +2 -0
  91. package/dist/{server-expo-DoBHhuzK.mjs → server-expo-n3tUi3Em.mjs} +3 -3
  92. package/dist/{settings-Byp2AQ_E.mjs → settings-2am7TSxp.mjs} +5 -6
  93. package/dist/{settings-CQyMElEP.mjs → settings-CxgngUZk.mjs} +767 -15
  94. package/dist/settings.schema.json +72 -6
  95. package/dist/{simslim-EHBQ2R4G.mjs → simslim-D9LkJxIO.mjs} +5 -5
  96. package/dist/{slot-launch-C1nqh4mu.mjs → slot-launch-HV_KtPIw.mjs} +6 -6
  97. package/dist/{stage-g_WqWeaE.mjs → stage-BgaZJVZY.mjs} +1 -1
  98. package/dist/{start-NTc1Ifqs.mjs → start-D8NbxNBb.mjs} +1 -1
  99. package/dist/{start-D-IeOyoK.mjs → start-DtxthMfI.mjs} +29 -22
  100. package/dist/{state-BG-aAk4k.mjs → state-D2KtnALG.mjs} +2 -2
  101. package/dist/{state-xnqpx3AD.mjs → state-Tp4uImDS.mjs} +4 -2
  102. package/dist/{stats-DY6ekz8F.mjs → stats-8EJ5ivFT.mjs} +6 -6
  103. package/dist/{status-DCjZeE-K.mjs → status-BOyp0CUl.mjs} +25 -27
  104. package/dist/{stim-desktop-Behws_fi.mjs → stim-desktop-C65jk_q2.mjs} +1 -1
  105. package/dist/{stim-installations-aR_WsNOA.mjs → stim-installations-DbmUjsLv.mjs} +1 -1
  106. package/dist/stop-D0UIpZuh.mjs +72 -0
  107. package/dist/{stop-DIJtD2GL.mjs → stop-DRipAg-e.mjs} +2 -2
  108. package/dist/{stop-BTjs3xbc.mjs → stop-zDlknFoz.mjs} +78 -31
  109. package/dist/supervisor-run.mjs +14 -14
  110. package/dist/{support-BaayeYCf.mjs → support-CNqYkV0y.mjs} +7 -7
  111. package/dist/{support-Bwvbrq5-.mjs → support-DsNaFg6D.mjs} +5 -5
  112. package/dist/swiftpm-macos-aJzPAj1t.mjs +119 -0
  113. package/dist/{toolchain-Bnyd_K3A.mjs → toolchain-C3xSoyUl.mjs} +27 -270
  114. package/dist/{trigger-CJMhwXe8.mjs → trigger-CAhmG-4s.mjs} +3 -3
  115. package/dist/trigger-CoalsMTV.mjs +2 -0
  116. package/dist/{warm-progress-CjB-MP6H.mjs → warm-progress-pFLB2e6P.mjs} +133 -36
  117. package/dist/{status-Dcr0Qexd.mjs → watchman-BL1IdOtq.mjs} +75 -7
  118. package/dist/web-DFpFeZu5.mjs +2 -0
  119. package/dist/web-DbbrPUn0.mjs +127 -0
  120. package/dist/web-run.mjs +7 -7
  121. package/dist/{workspace-state-C59dqG-Z.mjs → workspace-state-CR9X86xJ.mjs} +1 -1
  122. package/dist/{workspaces-xXItD8Dj.mjs → workspaces-Djb2gfi9.mjs} +102 -18
  123. package/dist/{worktree-BSYlofkq.mjs → worktree-BL4M1rhB.mjs} +45 -42
  124. package/dist/{worktree-BJfnU6r5.mjs → worktree-DV-A0LNL.mjs} +1 -1
  125. package/package.json +5 -5
  126. package/shim/bundle-response.cjs +2 -1
  127. package/shim/bundle-response.d.cts +1 -1
  128. package/shim/expo-metro-config.cjs +1 -1
  129. package/dist/device-ios-6MewKKnc.mjs +0 -412
  130. package/dist/guide-status-Bhf9SJXc.mjs +0 -125
  131. package/dist/ios-CPdkoKaa.mjs +0 -3953
  132. package/dist/macos-Ck7OBMNY.mjs +0 -2
  133. package/dist/native-run-B06FkmZx.mjs +0 -158
  134. package/dist/project-C3ChRLZy.mjs +0 -212
  135. package/dist/projects-DD7dBTCj.mjs +0 -92
  136. package/dist/server-expo-Db9HBUoa.mjs +0 -2
  137. package/dist/stop-BGhKMOuH.mjs +0 -48
  138. package/dist/trigger-DWAq9hkw.mjs +0 -2
  139. package/dist/web-B6_bFDn7.mjs +0 -2
  140. package/dist/worktree-CsKYexJM.mjs +0 -753
@@ -1,8 +1,8 @@
1
- import { s as findProjectRoot$1 } from "./project-C3ChRLZy.mjs";
2
- import { t as RECENT_LAUNCH_MS } from "./status-Dcr0Qexd.mjs";
3
- import { t as ANDROID_AVD_CONFIG_HELP } from "./settings-CQyMElEP.mjs";
4
- import { t as guideStatus } from "./guide-status-Bhf9SJXc.mjs";
5
- import { SETTINGS, SETTINGS_SCHEMA_URL } from "@stim-cli/core/state";
1
+ import { r as findProjectRoot } from "./project-B5eQ1USs.mjs";
2
+ import { t as ANDROID_AVD_CONFIG_HELP } from "./settings-CxgngUZk.mjs";
3
+ import { s as RECENT_LAUNCH_MS } from "./watchman-BL1IdOtq.mjs";
4
+ import { o as guideStatus, t as WATCHMAN_NESTED_WORKTREES } from "./doctor-watchman-wZhyTdp4.mjs";
5
+ import { SETTINGS, SETTINGS_SCHEMA_URL, TUTORIAL_VERSION } from "@stim-cli/core/state";
6
6
  import chalk from "chalk";
7
7
  //#region src/guide/agent.ts
8
8
  var agent_default = {
@@ -1432,8 +1432,9 @@ RULES
1432
1432
  body: () => ` stim status --json
1433
1433
 
1434
1434
  logs { dir, errorsSinceMarker }, or null without a log directory
1435
- agentDevice { stateDir }: absolute workspace agent-device state path on
1436
- every environment, shared by its slots. Reporting it creates
1435
+ agentDevice { stateDir, installed }: absolute workspace agent-device state
1436
+ path on every environment, shared by its slots, and whether
1437
+ the agent-device executable is on PATH. Reporting it creates
1437
1438
  no directory; agent-device creates it when used.
1438
1439
 
1439
1440
  Each environment carries phase, where the workspace is in its lifecycle:
@@ -1749,7 +1750,10 @@ RULES
1749
1750
  once the app is ready and covers waiting for the
1750
1751
  device: its boot, adoption cleanup, or a physical
1751
1752
  device's lease and connection check. A boot that
1752
- finishes during the build adds no device time. An
1753
+ finishes during the build adds no device time. For a
1754
+ device hosted on another Mac, device covers reserving
1755
+ and preparing it there, install covers delivering the
1756
+ app, and launch starts when the host launches it. An
1753
1757
  --eas-profile run has no cache lookup, so its outcome
1754
1758
  stays the project's most recent one until install.
1755
1759
  A stim macos run enters only prepare, compile (SwiftPM,
@@ -1942,7 +1946,8 @@ RULES
1942
1946
  disk (environment) { worktreeBytes, nodeModulesBytes, buildBytes,
1943
1947
  measuredAt }
1944
1948
  worktreeBytes the linked worktree, or else the checkout holding the
1945
- workspace, node_modules included
1949
+ workspace, node_modules included; the linked
1950
+ worktrees nested inside a checkout are not counted
1946
1951
  nodeModulesBytes node_modules at that root and at the workspace path;
1947
1952
  part of worktreeBytes
1948
1953
  buildBytes Stim's folder for the workspace: Xcode derived data,
@@ -1952,11 +1957,13 @@ RULES
1952
1957
  disk (device) { bytes, measuredAt }: the simulator's data folder
1953
1958
  under CoreSimulator/Devices, or the AVD's .avd folder
1954
1959
 
1955
- \`status --watch\` runs one du at a time off its refresh path, and measures
1956
- a folder at most every 5 minutes while its environment is active and every
1957
- hour otherwise. It caches each size under $STIM_HOME/disk-usage, which
1958
- one-shot status only reads, so the fields appear once a watcher, such as
1959
- stim-server or Stim Desktop, has measured.
1960
+ \`status --watch\` runs one du at a time on the whole machine, at low
1961
+ priority, off its refresh path, and measures a folder at most every 5
1962
+ minutes while its environment is active and every hour otherwise. A walk
1963
+ that fails or times out is not repeated by any watcher for that period. It
1964
+ caches each size under $STIM_HOME/disk-usage, which one-shot status only
1965
+ reads, so the fields appear once a watcher, such as stim-server or Stim
1966
+ Desktop, has measured.
1960
1967
 
1961
1968
  An environment carries agents when coding-agent sessions work in it, most
1962
1969
  recently active first:
@@ -2252,8 +2259,9 @@ HOW A RUN IS COUNTED (\`stats\`)
2252
2259
 
2253
2260
  DEVICE PLACEMENT (\`ios|android --remote auto\`)
2254
2261
  Auto runs include devicePlacement: { decision, reason, machine? } in the run
2255
- facts, lastBuilds and build history. decision is "local", "hosted" or
2256
- "waited-locally" (the local run actually waited for a device slot). The same
2262
+ facts, lastBuilds and build history. decision is "local", "hosted",
2263
+ "waited-locally" (the local run actually waited for a device slot) or "eas"
2264
+ (remote.easFallback put it on an EAS Simulator, reported like --remote eas). The same
2257
2265
  optional devicePlacement appears on the status device entry for each slot.
2258
2266
  Hosted host facts include selected: "auto" or the named machine, and reason
2259
2267
  for automatic placement. Plain status prints (auto: <reason>) after the host.
@@ -2600,9 +2608,15 @@ then 8081, without probing or reserving; an invalid pin still refuses.
2600
2608
 
2601
2609
  New allocations scan TCP ports 8900-8999. They skip registry reservations
2602
2610
  and existing listeners, announcing occupied ports and upward retries on
2603
- stderr. Listener checks require lsof, or netstat on Windows. All 100 ports
2604
- occupied or reserved is a refusal; stop or release unused allocations in
2605
- their owning workspaces.
2611
+ stderr. All 100 ports occupied or reserved is a refusal; stop or release
2612
+ unused allocations in their owning workspaces.
2613
+
2614
+ Metro allocation (from 8082 up) and named allocation find listeners in the
2615
+ native TCP table: netstat on macOS and Windows, /proc/net on Linux. When that
2616
+ table is denied, empty or unreadable, as in some sandboxes, each candidate is
2617
+ checked with an lsof listener scan and connects to 127.0.0.1 and ::1 instead.
2618
+ Allocation refuses only when none of them can answer: start refuses with
2619
+ STIM_PORT_INSPECTION_FAILED, and ports get prints the same message.
2606
2620
 
2607
2621
  The machine registry, under STIM_HOME, serializes allocation and cleanup.
2608
2622
  The workspace is the nearest package.json directory, resolved through
@@ -2625,6 +2639,7 @@ ports lists named labels and ports, plus Metro marked managed.
2625
2639
  ports stop [label] kills TCP listeners on those named ports and releases
2626
2640
  the allocations. It sends SIGTERM, waits two seconds, then SIGKILL if needed;
2627
2641
  on Windows it terminates the listener's process tree with taskkill.
2642
+ Stopping listeners requires lsof on macOS and Linux, or netstat on Windows.
2628
2643
  It prints the PID and command (the image name on Windows) for each stopped
2629
2644
  process. The listener's cwd can be anywhere: the named reservation is
2630
2645
  permission to stop that listener.
@@ -2915,18 +2930,22 @@ remote Mac and a named --remote target write none:
2915
2930
  stim logs --source placement --json
2916
2931
  Fields: kind (build or device), platform, settings [{ key, value, from }] with
2917
2932
  from flag, env, setting or default (remote.build and remote.buildMode for a
2918
- build, ios.remote or android.remote for a device), candidates [{ machine, code,
2933
+ build, ios.remote or android.remote, plus remote.easFallback when it is on, for
2934
+ a device), candidates [{ machine, code,
2919
2935
  msg, detail? }], choice { machine, code, msg } with machine local when the run
2920
2936
  stays on this Mac, and fallback { code, msg, machine? } when a remote Mac that
2921
2937
  was meant to take the run did not (level warn). The msg is the same text the
2922
2938
  placement: and build: phase lines print.
2923
2939
  Candidate codes: accepted, unreachable, busy, disk, load, version-mismatch
2924
2940
  (detail lists the toolchain parts: xcode, arch, jdk, ...), no-matching-device,
2925
- declined, memory, no-capacity. Choice codes for a build: placed, named,
2941
+ declined, memory, no-capacity; with machine eas, why an EAS Simulator was not
2942
+ used: eas-named-slot, eas-local-flags, eas-no-agent-device, eas-no-cli,
2943
+ eas-cli-too-old, eas-session-busy, eas-metro-unreachable, eas-logged-out, eas-not-enabled,
2944
+ eas-unavailable. Choice codes for a build: placed, named,
2926
2945
  forced, this-mac-busy, this-mac-free, mode-off, local-selected, no-remote-mac,
2927
2946
  unsupported; fallback codes: no-remote-mac-took-it, offload-failed, fallback.
2928
2947
  Choice codes for a device: placed, sticky, this-mac-free, no-remote-mac,
2929
- device-count-unknown, no-host-admits. Machine names appear in these records;
2948
+ device-count-unknown, no-host-admits, eas-fallback (machine eas). Machine names appear in these records;
2930
2949
  they stay in the local logs. These records carry no tokens.
2931
2950
 
2932
2951
  WHAT WRITES WHAT
@@ -3440,9 +3459,14 @@ Branch on the code, never on the message.`,
3440
3459
  works. See stim guide macos.`
3441
3460
  },
3442
3461
  STIM_OFFLOAD_REFUSED: {
3443
- summary: "the selected remote Mac cannot build this app",
3462
+ summary: "the selected remote Mac or automatic build pool cannot build this app",
3444
3463
  body: () => `STIM_OFFLOAD_REFUSED
3445
3464
 
3465
+ Automatic placement also refuses when local is excluded in remote.buildPoolDisabled
3466
+ and no enabled remote can finish this build, or when the build requires a local
3467
+ compiler. Enable local or choose an explicit --remote-build placement. Existing
3468
+ builds are not interrupted and a cache hit needs no compiler.
3469
+
3446
3470
  A named --remote-build selection is strict. The message names the machine
3447
3471
  and why it cannot take or finish the build: not configured or paired, approval
3448
3472
  pending or denied, unreachable or changed pinned identity, incompatible
@@ -3888,7 +3912,9 @@ so a Debug run on one is wired to a LAN origin instead of localhost.`,
3888
3912
  physical, hosted, remote, parked and other homes' devices and never deletes.
3889
3913
  A failed reclaim is logged and skipped; the run keeps waiting.
3890
3914
  Stop an environment (\`stim stop\`), pass a longer \`--wait <seconds>\`,
3891
- or raise concurrency.maxDevices. Waiting prints holder names and elapsed
3915
+ or raise concurrency.maxDevices. With --remote auto, remote.machines and
3916
+ the opt-in remote.easFallback (billed EAS Simulator) are tried before this
3917
+ wait; \`stim logs --source placement\` says why neither took the run. Waiting prints holder names and elapsed
3892
3918
  time; status JSON exposes build.waitingFor independently of phase.
3893
3919
  Stats records capacityWaits for waits and capacityRefusals for this code.
3894
3920
  See \`guide lifecycle concurrency\`.`
@@ -4175,6 +4201,17 @@ captured" (in metro.ndjson, bare RN)
4175
4201
  printed the last lines of the global workspace logs/supervisor.log above this -- read
4176
4202
  them. A cold Metro on a large graph can genuinely need more than the default
4177
4203
  60s: re-run with \`--wait 180\`. Otherwise \`stim stop\`, then \`start\`.`
4204
+ },
4205
+ STIM_PORT_INSPECTION_FAILED: {
4206
+ summary: "no free Metro port could be confirmed: netstat, lsof and loopback connects all failed",
4207
+ body: () => `STIM_PORT_INSPECTION_FAILED
4208
+ Stim reads the native TCP listener table (netstat on macOS and Windows,
4209
+ /proc/net on Linux) to find a free Metro port. When that table is denied,
4210
+ empty or unreadable, it checks each candidate with an lsof listener scan and
4211
+ connects to 127.0.0.1 and ::1. This refusal means none of them could answer,
4212
+ usually because a sandbox denies them. Allow Stim to run netstat or lsof, or
4213
+ to connect to loopback, then retry. A metro.port pin skips the scan for
4214
+ Metro. \`stim ports get\` prints the same message.`
4178
4215
  },
4179
4216
  STIM_SUPERVISOR_EXITED: {
4180
4217
  summary: "the dev server failed outright; the quoted supervisor.log tail is the real error",
@@ -4959,6 +4996,46 @@ WAITING FOR A CHANGE
4959
4996
  still running, which it notices at the next change.
4960
4997
  Without --json it reprints the human view on change.`,
4961
4998
  sections: {
4999
+ ci: {
5000
+ summary: "Run an app and tests through @stim-cli/ci with diagnostics and scoped cleanup",
5001
+ body: () => `CONTINUOUS INTEGRATION
5002
+
5003
+ Install app dependencies and platform tools first. Use a dedicated checkout:
5004
+ cleanup stops that workspace, including resources created before a build fails.
5005
+
5006
+ npx --yes --package @stim-cli/ci stim-ci run --platform ios --project ./app --timeout 1800 -- pnpm test:e2e
5007
+
5008
+ Or install npm install --global @stim-cli/ci and use stim-ci directly.
5009
+ Platforms: ios, android, macos, web. Everything after -- is argv, not a shell.
5010
+ --artifacts selects the result directory (a fresh temporary directory by default).
5011
+ An explicit artifacts directory must be empty; use a new directory per run.
5012
+ Stdout is one JSON result; progress and test output go to stderr.
5013
+
5014
+ Tests receive STIM_CI_PLATFORM, STIM_CI_DEVICE_ID, STIM_CI_APP_ID,
5015
+ STIM_CI_METRO_PORT, STIM_CI_ARTIFACTS_DIR and STIM_CI_RUN_RESULT. The last is
5016
+ run.json with the exact public API result. Use that target and check app
5017
+ readiness; launched can still be bundling or unverified.
5018
+
5019
+ result.json preserves the test exit code even if diagnostics or cleanup fail.
5020
+ Passing tests with failed cleanup return 1; timeout returns 124; cancellation
5021
+ returns 130. Leave 70 seconds before the job's hard timeout for diagnostics and
5022
+ stop. SIGKILL and runner loss cannot run cleanup. Stop shuts down owned devices;
5023
+ it never deletes them.
5024
+
5025
+ GitHub-hosted jobs default to $RUNNER_TEMP/stim-ci/home and
5026
+ $RUNNER_TEMP/stim-ci/build-cache, reused between steps in the job. Detection
5027
+ requires GITHUB_ACTIONS=true, RUNNER_ENVIRONMENT=github-hosted and RUNNER_TEMP.
5028
+ An explicit --home or STIM_HOME keeps the selected home and its normal cache
5029
+ configuration; --build-cache or STIM_BUILD_CACHE overrides the cache path.
5030
+ Self-hosted runners and local runs keep normal Stim configuration. Other
5031
+ exclusive disposable providers can set their job-local paths explicitly.
5032
+ CI=true alone does not change defaults, ownership or coordination. Persist only
5033
+ cache artifacts, not state, claims or device ledgers. Independent homes must
5034
+ not share a writable filesystem cache because its claims live in the home.
5035
+
5036
+ The programmatic runner is import { runCI } from '@stim-cli/ci'. See its package
5037
+ README for result types, cancellation, cache policy, and coordination findings.`
5038
+ },
4962
5039
  "hosted-android": {
4963
5040
  summary: "Android on a named approved Mac: strict placement, private Metro, status and stop",
4964
5041
  body: () => `ANDROID ON A HOSTING MAC
@@ -5125,7 +5202,10 @@ there is a free concurrency.maxDevices slot (0 means unlimited), no device
5125
5202
  waiter ahead, normal host memory pressure, no budget shortfall,
5126
5203
  and 5-minute load per core below server.maxLoadPerCore. Without remote.machines,
5127
5204
  auto is local. An unknown local device count also stays local; boot admission
5128
- still decides. The default without a flag or setting stays local.
5205
+ still decides. The default without a flag or setting stays local. remote.devicePoolDisabled excludes
5206
+ members from new automatic placement only; an excluded local member is never a
5207
+ fallback. Existing sessions keep their owner and named placement bypasses the pool.
5208
+ See stim guide settings for separate build/device membership controls.
5129
5209
 
5130
5210
  Otherwise Stim asks every approved remote.machines host in parallel, with a
5131
5211
  3-second probe timeout and this run's model/runtime or image/profile selectors.
@@ -5136,9 +5216,24 @@ Hosts rank by the build preference (flag > STIM_REMOTE_BUILD > remote.build)
5136
5216
  when it names a machine, then lowest load, most free memory and remote.machines order. Automatic build
5137
5217
  offload resolves later, after the device architecture is known.
5138
5218
 
5139
- When no host admits, auto runs here and may wait in the existing FIFO device
5219
+ When no host admits and this Mac is at its concurrency.maxDevices cap (or runs
5220
+ are queued ahead), remote.easFallback (default false; EAS Simulator is billed)
5221
+ runs the device on an EAS Simulator exactly as --remote eas would: the build
5222
+ stays here, nothing starts an EAS cloud build, stop ends the session, and
5223
+ status reports it under remoteDevices. A busy Mac with a free slot never uses
5224
+ it. Before choosing it Stim checks, without starting a session, that eas-cli
5225
+ has simulator commands (and 22.2.0+ for --device-type), eas
5226
+ simulator:availability says this project's account can use it, agent-device
5227
+ is on PATH, the slot is default, no --runtime, --system-image or
5228
+ --device-profile flag is given, no EAS Simulator session of this workspace runs
5229
+ another platform or model, and a Debug run's Metro is reachable (not
5230
+ metro.tunnel off; on an Expo tunnel, run stim start --remote first). A recorded
5231
+ EAS session is not sticky: once this Mac has room, auto runs here and the
5232
+ session bills until stim stop. If any check fails, or the
5233
+ setting is off, auto runs here and may wait in the existing FIFO device
5140
5234
  slot queue; --no-wait and --wait 0 refuse with STIM_AT_CAPACITY. The placement
5141
- line explains the decision and the skipped hosts. JSON progress goes to stderr.
5235
+ line explains the decision, the skipped hosts and why EAS was not used
5236
+ (machine "eas"). JSON progress goes to stderr.
5142
5237
  The same decision, with a reason code per host, is a src: placement record in
5143
5238
  stim logs (stim guide logs).
5144
5239
  A recorded hosted session wins over load; a live local owned slot stays here.
@@ -5899,8 +5994,9 @@ PREDICTING THE NEXT BUILD (--plan)
5899
5994
  this Mac and a listed Mac would build for another architecture, placement
5900
5995
  says so, since that Mac's key differs. The plan only predicts the key: a
5901
5996
  run reads the architecture of the device it actually gets. A Mac that does
5902
- not answer, or ios.remote or android.remote set to eas or proxy, refuses
5903
- with STIM_BAD_ARG. Without --eas-profile an Android plan also
5997
+ not answer, ios.remote or android.remote set to eas or proxy, or auto that
5998
+ would use an EAS Simulator now (remote.easFallback), refuses with
5999
+ STIM_BAD_ARG. Without --eas-profile an Android plan also
5904
6000
  refuses the experimental compiler CAS with STIM_BAD_ARG, and refuses with
5905
6001
  STIM_NO_DEVICE when no system image is installed.
5906
6002
 
@@ -7873,7 +7969,23 @@ THE ONE CASE GC WILL NOT REAP
7873
7969
 
7874
7970
  --older-than does not apply to these kinds and is refused with
7875
7971
  STIM_BAD_ARG. With STIM_HOME set, gc skips them: they are machine-global.
7876
- \`stim doctor\` notes a watchman footprint over 2 GiB.`
7972
+ \`stim doctor\` notes a watchman footprint over 2 GiB.
7973
+
7974
+ A checkout that holds its linked worktrees inside it, such as
7975
+ .worktrees/<name>, makes a watchman root there crawl every worktree's files,
7976
+ node_modules and build output. \`stim doctor\` reports each such checkout
7977
+ whose .watchmanconfig ignore_dirs does not exclude them, as
7978
+ ${WATCHMAN_NESTED_WORKTREES}, and says when watchman watches it now.
7979
+ \`stim doctor --fix\` merges the worktrees' shared parent (.worktrees) when
7980
+ git tracks nothing in it, otherwise each worktree path, into that
7981
+ checkout's .watchmanconfig and keeps every other key; it refuses a file
7982
+ that is not a JSON object or whose ignore_dirs is not an array. Watchman
7983
+ matches entries literally: \`.worktrees/\` or \`./.worktrees\` ignores
7984
+ nothing. Commit the file. Watchman reads ignore_dirs only from a root's
7985
+ .watchmanconfig, never from a global config, and only when it adds the
7986
+ root, so an existing root needs \`watchman watch-del <root>\` before the
7987
+ ignore applies. Doctor never runs watch-del or shutdown-server; a watch-del
7988
+ plus a watchman restart frees the memory now.`
7877
7989
  },
7878
7990
  disk: {
7879
7991
  summary: "disk usage, workspace build outputs, logs and device recordings, AVD and build-log sizes, the data partition, trimming the shared caches",
@@ -8130,7 +8242,8 @@ KEYS STIM READS
8130
8242
  ios.remote "proxy", "eas", or a named approved Mac from
8131
8243
  remote.machines, with the same meaning as --remote.
8132
8244
  "auto" places on an approved Mac when this Mac is full or
8133
- busy. Unset runs here. See lifecycle hosted-ios.
8245
+ busy. Unset runs here; "local" runs here even when a
8246
+ lower layer says otherwise. See lifecycle hosted-ios.
8134
8247
  ios.simslimProfile a SimSlim JSON profile under the app directory,
8135
8248
  at most 64 KiB. Install the
8136
8249
  external tool once with
@@ -8272,8 +8385,23 @@ ${ANDROID_AVD_CONFIG_HELP.map((line) => ` ${line}`).joi
8272
8385
  means the debug keystore's fixed "android".
8273
8386
  android.remote "proxy", "eas", or a named approved Mac in
8274
8387
  remote.machines; "auto" places on an approved Mac when
8275
- this Mac is full or busy. Unset runs here.
8388
+ this Mac is full or busy. Unset runs here; "local"
8389
+ runs here even when a lower layer says otherwise.
8276
8390
  See lifecycle hosted-android.
8391
+ remote.easFallback true lets "auto" (ios.remote, android.remote or
8392
+ --remote auto) run the simulator or emulator on a
8393
+ billed EAS Simulator when this Mac is at its
8394
+ concurrency.maxDevices cap (or has runs queued) and no
8395
+ remote.machines Mac takes the run, instead of waiting
8396
+ or refusing with STIM_AT_CAPACITY. Default false. A
8397
+ busy Mac with a free slot never uses it. Stim checks
8398
+ first that the run could use --remote eas (eas-cli
8399
+ with simulator commands, eas simulator:availability,
8400
+ agent-device, the default slot, a reachable Metro);
8401
+ otherwise it waits or refuses as before. Machine or
8402
+ project scope; a committed value opts in everyone who
8403
+ runs the app. Only the user enables it. See lifecycle
8404
+ hosted-ios.
8277
8405
  metro.tunnel selects how a remote device reaches this workspace's
8278
8406
  Metro after remote intent exists. Plain \`start\` stays
8279
8407
  local. For Expo and bare React Native, "auto" (default)
@@ -8522,8 +8650,9 @@ To show the booted simulator in Stim Desktop and open no simulator window:
8522
8650
 
8523
8651
  { "iosSimulatorApp": "stim-desktop" }
8524
8652
 
8525
- Stim Desktop selects the workspace that owns the simulator and focuses that
8526
- device. It only displays the simulator; it never boots or shuts it down.
8653
+ Stim Desktop keeps the current page and shows a launch card. Clicking Show
8654
+ opens the workspace that owns the simulator and focuses that device.
8655
+ It only displays the simulator; it never boots or shuts it down.
8527
8656
  When Stim Desktop is not running, Stim starts it without the command's
8528
8657
  \`STIM_HOME\`, so it reads the same Stim home as when you open it yourself.
8529
8658
  It shows only devices from that home: under another \`STIM_HOME\`, pick
@@ -8552,7 +8681,8 @@ headlessly and show it in Stim Desktop:
8552
8681
  { "androidEmulatorApp": "stim-desktop" }
8553
8682
 
8554
8683
  Stim then starts the emulator with \`-no-window -gpu host\` and opens
8555
- \`stim-desktop://open?serial=<serial>\` in the background. Stim Desktop reads
8684
+ \`stim-desktop://open?serial=<serial>\` in the background. Stim Desktop shows a
8685
+ launch card and opens the emulator only when Show is clicked. It reads
8556
8686
  frames and sends input over the emulator's gRPC endpoint. The setting applies
8557
8687
  only when Stim boots the emulator: one that is already running keeps its
8558
8688
  current display until it next boots, and physical devices are unaffected.
@@ -8635,6 +8765,32 @@ simulator sessions on, by MagicDNS name with an optional serve port (default
8635
8765
  stim settings set remote.machines '["janics-mac-mini"]'
8636
8766
  stim doctor --fix
8637
8767
 
8768
+ Automatic membership is separate from approval. In Desktop Settings > Remote Macs,
8769
+ use Automatic builds and Automatic simulators for this Mac or a configured remote.
8770
+ The equivalent machine settings list the excluded members; both default to []:
8771
+
8772
+ stim settings set remote.buildPoolDisabled '["local"]'
8773
+ stim settings set remote.devicePoolDisabled '["janics-mac-mini"]'
8774
+ stim settings unset remote.buildPoolDisabled
8775
+
8776
+ Use local for this Mac and exact remote.machines entries for remotes, including
8777
+ case and port. Names are not trimmed; unmatched entries exclude nothing. Copy the
8778
+ configured name or use Desktop's switches. Each pool
8779
+ must retain local or at least one configured remote already approved for that role.
8780
+ Offline approved members count as configured members, but placement still requires
8781
+ an available compatible host. Settings refuses removing the last member, including
8782
+ removing it from remote.machines. Disabling does not unpair a machine or stop a run.
8783
+
8784
+ These settings apply only to new automatic work requested by this Mac. They do not
8785
+ change which work other requesters send to a host. Named placement and --remote-build
8786
+ local bypass membership; the default device placement without --remote auto remains
8787
+ local. Existing local and hosted device sessions keep their owner. Cache hits remain
8788
+ usable without compiling. If local is excluded, automatic placement cannot fall back
8789
+ to a local compile or boot; unavailable hosts and unsupported offloads refuse. Excluding
8790
+ local alone does not trigger billed EAS fallback; an existing explicit EAS opt-in still
8791
+ requires the physical device cap or queue condition. Restore membership before retrying or choose
8792
+ an explicit placement. Build and simulator memberships are independent.
8793
+
8638
8794
  A remote Mac is used for a capability only after it grants that approval. Build
8639
8795
  and device-host approvals are separate: a Mac in the list that never granted
8640
8796
  one is not an error, it is not used for that capability, and doctor reports
@@ -8726,7 +8882,7 @@ value is unset. Trimmed auto/local are case-insensitive. Machine names match
8726
8882
  configured names case-insensitively, with port 7443 when omitted; reports use
8727
8883
  the configured entry.
8728
8884
 
8729
- auto follows remote.buildMode and keeps its local fallback behavior
8885
+ auto follows remote.buildMode, considering only enabled automatic pool members
8730
8886
  local builds only on this Mac for this invocation
8731
8887
  name requires a matching entry in remote.machines, already paired and
8732
8888
  approved for builds; ignores remote.buildMode and this Mac's load/slot gating
@@ -8765,7 +8921,7 @@ emulator debug build or a stim macos SwiftPM Debug build compiles:
8765
8921
  Mac's. A Mac too old to report its load counts only while every
8766
8922
  slot here is busy.
8767
8923
  force on a remote Mac whenever one accepts it
8768
- off always here
8924
+ off here when local remains enabled in the automatic build pool
8769
8925
 
8770
8926
  Load per core is the 5-minute load average divided by the CPU count; a Mac's
8771
8927
  native builds are its Stim runs in prebuild, pods or compile on that Mac, not
@@ -9416,18 +9572,39 @@ executables, including Stim Desktop's sim-fold helper, are not built.
9416
9572
 
9417
9573
  stim macos # fixed SwiftPM Debug build, then launch
9418
9574
  stim macos --json # one launch record; progress goes to stderr
9575
+ stim macos --plan --json # validate the next build without running it
9419
9576
  stim status --json # environments[].macos and build state
9420
9577
  stim logs --source build
9421
9578
  stim logs --errors
9422
9579
  stim stop # stop this workspace's owned app and supervisor
9423
9580
 
9424
- Each invocation stops its previous owned app, rebuilds and launches. SwiftPM
9581
+ Each invocation validates the project settings, development plist and resource
9582
+ entries before stopping its previous owned app, rebuilding and launching. SwiftPM
9425
9583
  keeps incremental outputs in the workspace's runtime directory under STIM_HOME.
9584
+ Every process running an app bundle from that directory is the workspace's
9585
+ owned app, including copies opened through LaunchServices (open, agent-device
9586
+ open). stim macos, stim stop, stim worktree remove and stim gc --delete stop all
9587
+ of them: SIGTERM, up to 5 s, then SIGKILL, and success only once each has
9588
+ exited. When an app cannot be verified or does not exit, stop reports
9589
+ STIM_MACOS_OWNER_UNVERIFIED or a failure, and removal keeps the workspace.
9426
9590
  Local stim macos starts the app in the background without activating it or
9427
9591
  changing focus: it sets STIM_BACKGROUND_LAUNCH=1 in the app's environment, which
9428
- Stim Desktop honors. An app that activates itself at launch still takes focus.
9592
+ Stim Desktop honors, including for reopen events from open -g. An app that
9593
+ activates itself at launch or on reopen still takes focus.
9429
9594
  Hosted launches (macos --remote) do not set it.
9430
- macOS artifacts are not cached. This prototype has no --plan, --slot, or reload command.
9595
+ macOS artifacts are not cached. This prototype has no --slot or reload command.
9596
+ --plan validates the Swift Package directory, macos settings, development plist
9597
+ and declared resources without building, signing, staging, stopping or launching
9598
+ an app, writing workspace state or claiming a build slot. It does not execute
9599
+ Package.swift, so it cannot validate the executable product or package dependencies.
9600
+ --remote-build is respected, including named-machine setup refusals; no worker
9601
+ is contacted and live worker availability is unknown. --remote is launch-only
9602
+ and refuses with --plan. The JSON plan has platform "macos", product and
9603
+ buildMachine; fingerprint, cacheKey, provider, prebuild, outcome and expectedMs
9604
+ are null, cacheHit and cacheSkipped are false, and basis is 0. SwiftPM decides
9605
+ incremental compile work when a build runs; the plan predicts no cache outcome
9606
+ or duration. Desktop checks automatically while build details are visible,
9607
+ reusing a completed build or check for 60 seconds and skipping running builds.
9431
9608
  A failed build records its error and compiler output without launching an app.
9432
9609
  While it runs, stim status --json reports it as environments[].build with
9433
9610
  platform "macos": phase prepare, compile, install, then launch, and during
@@ -9453,7 +9630,10 @@ refuse before stopping the app or changing its build record. See stim guide erro
9453
9630
 
9454
9631
  With remote.build auto, remote.buildMode places these SwiftPM Debug builds:
9455
9632
  auto builds here while this Mac has capacity, force uses an approved build
9456
- machine when one accepts, and off always builds here. Configure remote.machines
9633
+ machine when one accepts, and off builds here when local remains in the automatic
9634
+ build pool. remote.buildPoolDisabled excludes members from automatic placement,
9635
+ including its local fallback. Explicit local or named placement bypasses membership.
9636
+ Configure remote.machines
9457
9637
  and approve build access as described in stim guide settings. The worker needs
9458
9638
  matching Stim, CPU architecture, Xcode and macOS SDK, plus network access to
9459
9639
  fetch package dependencies the first time. It keeps SwiftPM dependencies in a
@@ -9771,290 +9951,147 @@ this workspace's app. Do not change permissions or use custom build scripts."
9771
9951
  };
9772
9952
  //#endregion
9773
9953
  //#region src/guide/tutorial-data.ts
9774
- const TUTORIAL_PINS = {
9775
- createExpoApp: "5.0.0",
9776
- template: "expo-template-blank@58.0.15"
9777
- };
9778
- const TUTORIAL_FILES = {
9779
- "App.js": `import { useEffect, useState } from 'react';
9780
- import { Pressable, StyleSheet, Text, View } from 'react-native';
9781
- import { StatusBar } from 'expo-status-bar';
9782
- import { TITLE_COLOR } from './theme';
9783
-
9784
- const TAG = '[stim:tutorial]';
9785
-
9786
- function Button({ label, onPress }) {
9787
- return (
9788
- <Pressable accessibilityRole="button" onPress={onPress} style={styles.button}>
9789
- <Text style={styles.buttonText}>{label}</Text>
9790
- </Pressable>
9791
- );
9792
- }
9793
-
9794
- export default function App() {
9795
- const [note, setNote] = useState('');
9796
-
9797
- useEffect(() => {
9798
- console.log(\`\${TAG} title color=\${TITLE_COLOR}\`);
9799
- }, [TITLE_COLOR]);
9800
-
9801
- const logError = () => {
9802
- console.error(\`\${TAG} error-button test error\`);
9803
- setNote('Logged an error.');
9804
- };
9805
-
9806
- const crash = () => {
9807
- setTimeout(() => {
9808
- throw new Error(\`\${TAG} crash-button uncaught test error\`);
9809
- }, 0);
9810
- };
9811
-
9812
- const slowRequest = async () => {
9813
- const started = Date.now();
9814
- setNote('Waiting 3 seconds...');
9815
- await new Promise((resolve) => setTimeout(resolve, 3000));
9816
- const elapsed = Date.now() - started;
9817
- console.warn(\`\${TAG} slow-request \${elapsed}ms\`);
9818
- setNote(\`Slow request took \${elapsed}ms.\`);
9819
- };
9820
-
9821
- return (
9822
- <View style={styles.container}>
9823
- <Text style={[styles.title, { color: TITLE_COLOR }]}>Stim Tutorial</Text>
9824
- <Text style={styles.body}>Tap a button, then look at Stim Desktop &gt; Logs.</Text>
9825
- <Button label="Log an error" onPress={logError} />
9826
- <Button label="Crash me" onPress={crash} />
9827
- <Button label="Slow request" onPress={slowRequest} />
9828
- <Text style={styles.note}>{note}</Text>
9829
- <StatusBar style="auto" />
9830
- </View>
9831
- );
9832
- }
9833
-
9834
- const styles = StyleSheet.create({
9835
- container: { flex: 1, backgroundColor: '#fff', alignItems: 'center', justifyContent: 'center', padding: 24 },
9836
- title: { fontSize: 32, fontWeight: '700', marginBottom: 8 },
9837
- body: { fontSize: 16, color: '#4b5563', textAlign: 'center', marginBottom: 24 },
9838
- button: { backgroundColor: '#111827', borderRadius: 10, paddingVertical: 14, paddingHorizontal: 28, marginBottom: 12 },
9839
- buttonText: { color: '#fff', fontSize: 17, fontWeight: '600' },
9840
- note: { marginTop: 12, fontSize: 15, color: '#374151' },
9841
- });
9842
- `,
9843
- "theme.js": `export const TITLE_COLOR = '#1f2937';
9844
- `,
9845
- "app.json": `{
9846
- "expo": {
9847
- "name": "Stim Tutorial",
9848
- "slug": "stim-tutorial",
9849
- "version": "1.0.0",
9850
- "orientation": "portrait",
9851
- "icon": "./assets/icon.png",
9852
- "userInterfaceStyle": "light",
9853
- "ios": {
9854
- "supportsTablet": true,
9855
- "bundleIdentifier": "dev.stim.tutorial"
9856
- },
9857
- "android": {
9858
- "adaptiveIcon": {
9859
- "backgroundColor": "#E6F4FE",
9860
- "foregroundImage": "./assets/android-icon-foreground.png",
9861
- "backgroundImage": "./assets/android-icon-background.png",
9862
- "monochromeImage": "./assets/android-icon-monochrome.png"
9863
- },
9864
- "package": "dev.stim.tutorial"
9865
- },
9866
- "web": {
9867
- "favicon": "./assets/favicon.png"
9868
- },
9869
- "extra": {
9870
- "stimTutorial": 1
9871
- }
9872
- }
9873
- }
9874
- `,
9875
- ".gitignore": `/ios
9876
- /android
9877
- /tutorial*.ad
9878
- /tutorial*.png
9879
- `
9880
- };
9881
- const TUTORIAL_PROMPTS = {
9882
- begin: "Run the Stim tutorial.",
9883
- rebuild: "Continue the Stim tutorial: rebuild",
9884
- agent: "Continue the Stim tutorial: agent",
9885
- refresh: "Continue the Stim tutorial: refresh",
9886
- machine: "Continue the Stim tutorial: machine",
9887
- finish: "Continue the Stim tutorial: finish"
9888
- };
9954
+ const TUTORIAL_REPO = "appandflow/stim-tutorial";
9955
+ const TUTORIAL_BUNDLE_ID = "dev.stim.tutorial";
9889
9956
  const TUTORIAL_RESTART_PROMPT = "Restart the Stim tutorial.";
9957
+ /**
9958
+ * The requests Stim Desktop offers to copy, written the way a developer would ask. {base}, {tour}, {worktrees} and
9959
+ * {machine} are filled in by Desktop. Only the first and last name a guide section, which holds the folder and cleanup safety rules.
9960
+ */
9961
+ const TUTORIAL_ASKS = {
9962
+ begin: `Clone ${TUTORIAL_REPO} into {base} and install its dependencies, then run stim doctor for iOS there so Stim registers it. Use a fresh folder: if {base} already exists or is inside another git repository, stop and ask me for another folder, and never git add in my own repo. Follow stim guide tutorial run.`,
9963
+ agent: "Open the app on the iOS simulator, take a screenshot and confirm the title color.",
9964
+ machine: "Build the app for iOS on {machine} with stim instead of on this Mac. Do not approve or pair anything.",
9965
+ finish: "I'm done with these experiments in {base} and don't need the changes. Stop the apps and remove the worktrees {worktrees}, and keep the clone. Follow stim guide tutorial finish.",
9966
+ share: `Open a pull request to ${TUTORIAL_REPO} with my title color change, and include a screenshot of it running in the simulator. See stim guide tutorial share.`,
9967
+ retry: "The first iOS build of the tutorial app in {tour} failed. Find out why and run it on iOS again."
9968
+ };
9890
9969
  const TUTORIAL_STEPS = [
9891
9970
  {
9892
9971
  id: "begin",
9893
- title: "Create the Tutorial",
9972
+ title: "Get the Test App",
9894
9973
  who: "agent",
9895
9974
  optional: false,
9896
- prompt: TUTORIAL_PROMPTS.begin,
9975
+ ask: TUTORIAL_ASKS.begin,
9897
9976
  section: "run",
9898
- manual: [
9899
- "base=\"{base}\"",
9900
- "mkdir -p \"${base%/*}\"",
9901
- "if [ ! -e \"$base\" ]; then",
9902
- "cd \"${base%/*}\"",
9903
- "if git -C \"${base%/*}\" rev-parse --is-inside-work-tree >/dev/null 2>&1; then echo \"Stop: inside another repository. Ask for another folder; never git add in the user repository.\"; exit 1; fi",
9904
- `npx --yes create-expo-app@${TUTORIAL_PINS.createExpoApp} stim-tutorial --template ${TUTORIAL_PINS.template} --no-install --no-agents-md --yes`,
9905
- "cd \"$base\"",
9906
- "stim guide tutorial app",
9907
- ...Object.entries(TUTORIAL_FILES).flatMap(([name, content]) => [`cat ${name === ".gitignore" ? ">>" : ">"} ${name} <<'STIM_TUTORIAL_EOF'`].concat(content.trimEnd().split("\n"), "STIM_TUTORIAL_EOF")),
9908
- "npm install --prefer-offline",
9909
- "npm pkg set scripts.ios=\"expo run:ios\" scripts.android=\"expo run:android\"",
9910
- "git init",
9911
- "git add -A",
9912
- "git -c user.name=Stim -c user.email=stim@localhost -c commit.gpgsign=false commit -m \"Stim tutorial\"",
9913
- "else",
9914
- "cd \"$base\"",
9915
- `node -e 'const fs = require("node:fs"); const app = fs.existsSync("app.json") ? JSON.parse(fs.readFileSync("app.json", "utf8")) : null; if (app?.expo?.extra?.stimTutorial !== ${JSON.parse(TUTORIAL_FILES["app.json"]).expo.extra.stimTutorial}) { console.error("Stop: existing non-tutorial folder or another version. Ask for another folder; never overwrite or delete it."); process.exit(1); }'`,
9916
- `git rev-parse --show-toplevel | node -e 'const fs = require("node:fs"); const root = fs.readFileSync(0, "utf8").trim(); if (fs.realpathSync(root) !== fs.realpathSync(process.cwd())) { console.error("Stop: inside another repository. Ask for another folder; never git add in the user repository."); process.exit(1); }'`,
9917
- "fi"
9918
- ]
9977
+ commands: []
9919
9978
  },
9920
9979
  {
9921
- id: "sidebar",
9922
- title: "Workspace in Sidebar",
9980
+ id: "build",
9981
+ title: "Make a Change",
9923
9982
  who: "you",
9924
9983
  optional: false,
9925
- prompt: null,
9984
+ ask: null,
9926
9985
  section: null,
9927
- manual: []
9986
+ commands: []
9928
9987
  },
9929
9988
  {
9930
- id: "build",
9931
- title: "First iOS Build",
9989
+ id: "parallel",
9990
+ title: "Change It Again in Parallel",
9932
9991
  who: "you",
9933
9992
  optional: false,
9934
- prompt: null,
9993
+ ask: null,
9935
9994
  section: null,
9936
- manual: [
9937
- "cd \"{base}\"",
9938
- "git worktree add -B stim-tutorial/tour \"{tour}\" HEAD",
9939
- "cd \"{tour}\"",
9940
- "stim worktree warm",
9941
- "stim guide agent",
9942
- "stim doctor --platform ios",
9943
- "stim start",
9944
- "stim ios"
9945
- ]
9946
- },
9947
- {
9948
- id: "rebuild",
9949
- title: "Rebuild from Cache",
9950
- who: "agent",
9951
- optional: false,
9952
- prompt: TUTORIAL_PROMPTS.rebuild,
9953
- section: "rebuild",
9954
- manual: [
9955
- "cd \"{tour}\"",
9956
- "stim ios",
9957
- "stim status --json"
9958
- ]
9995
+ commands: []
9959
9996
  },
9960
9997
  {
9961
9998
  id: "device",
9962
9999
  title: "Live View and Control",
9963
10000
  who: "you",
9964
- optional: false,
9965
- prompt: null,
9966
- section: null,
9967
- manual: ["stim status"]
9968
- },
9969
- {
9970
- id: "logs",
9971
- title: "App Logs",
9972
- who: "you",
9973
- optional: false,
9974
- prompt: null,
10001
+ optional: true,
10002
+ ask: null,
9975
10003
  section: null,
9976
- manual: ["stim logs --errors", "stim logs --grep '\\[stim:tutorial\\]'"]
10004
+ commands: ["stim status"]
9977
10005
  },
9978
10006
  {
9979
10007
  id: "agent",
9980
10008
  title: "Agent Actions and Replay",
9981
10009
  who: "agent",
9982
- optional: false,
9983
- prompt: TUTORIAL_PROMPTS.agent,
9984
- section: "agent",
9985
- manual: [
10010
+ optional: true,
10011
+ ask: TUTORIAL_ASKS.agent,
10012
+ section: null,
10013
+ commands: [
9986
10014
  "cd \"{tour}\"",
9987
10015
  "export AGENT_DEVICE_STATE_DIR=\"{stateDir}\"",
9988
- "stim status --json",
9989
- "read -r iosUdid",
9990
- "agent-device open dev.stim.tutorial --platform ios --udid \"$iosUdid\" --save-script=tutorial.ad",
9991
- "agent-device react-native dismiss-overlay || true",
9992
- `agent-device press 'label="Log an error"' --settle`,
10016
+ `agent-device open ${TUTORIAL_BUNDLE_ID} --platform ios --udid {udid}`,
9993
10017
  "agent-device screenshot tutorial.png",
9994
10018
  "agent-device close",
9995
- "grep -v -e 'target-v1' -e 'dismiss-overlay' tutorial.ad > tutorial-replay.ad",
9996
- "agent-device replay tutorial-replay.ad --platform ios --udid \"$iosUdid\"",
9997
10019
  "stim logs --source agent --tail 10"
9998
10020
  ]
9999
10021
  },
10000
10022
  {
10001
- id: "refresh",
10002
- title: "Fast Refresh",
10003
- who: "agent",
10004
- optional: false,
10005
- prompt: TUTORIAL_PROMPTS.refresh,
10006
- section: "refresh",
10007
- manual: [
10008
- "cd \"{tour}\"",
10009
- "cat > theme.js <<'STIM_TUTORIAL_EOF'",
10010
- "export const TITLE_COLOR = '#7c3aed';",
10011
- "STIM_TUTORIAL_EOF",
10012
- "sleep 6",
10013
- "stim logs --errors",
10014
- "stim logs --grep 'title color'"
10015
- ]
10023
+ id: "logs",
10024
+ title: "App Logs",
10025
+ who: "you",
10026
+ optional: true,
10027
+ ask: null,
10028
+ section: null,
10029
+ commands: ["stim logs --errors", "stim logs --grep stim:tutorial"]
10016
10030
  },
10017
10031
  {
10018
10032
  id: "phone",
10019
10033
  title: "Watch on Your Phone",
10020
10034
  who: "you",
10021
10035
  optional: true,
10022
- prompt: null,
10036
+ ask: null,
10023
10037
  section: null,
10024
- manual: []
10038
+ commands: []
10025
10039
  },
10026
10040
  {
10027
10041
  id: "machine",
10028
10042
  title: "Build on Another Mac",
10029
10043
  who: "both",
10030
10044
  optional: true,
10031
- prompt: TUTORIAL_PROMPTS.machine,
10032
- section: "machine",
10033
- manual: ["cd \"{tour}\"", "stim ios --remote-build \"{machine}\" --no-build-cache"]
10045
+ ask: TUTORIAL_ASKS.machine,
10046
+ section: null,
10047
+ commands: ["cd \"{tour}\"", "stim ios --remote local --remote-build \"{machine}\" --no-build-cache"]
10048
+ },
10049
+ {
10050
+ id: "share",
10051
+ title: "Share Your Finish",
10052
+ who: "you",
10053
+ optional: true,
10054
+ ask: TUTORIAL_ASKS.share,
10055
+ section: "share",
10056
+ commands: [
10057
+ "cd \"{tour}\"",
10058
+ "git commit -am \"Tutorial change\" -m \"Build <time>, second build cache <hit or miss>\"",
10059
+ "xcrun simctl io {udid} screenshot finish.png",
10060
+ `gh repo fork ${TUTORIAL_REPO} --remote --remote-name fork`,
10061
+ "git push -u fork HEAD",
10062
+ `gh pr create --repo ${TUTORIAL_REPO} --fill --attach "finish.png#The change running in the simulator"`
10063
+ ]
10034
10064
  },
10035
10065
  {
10036
10066
  id: "finish",
10037
10067
  title: "Finish and Archive",
10038
10068
  who: "agent",
10039
10069
  optional: false,
10040
- prompt: TUTORIAL_PROMPTS.finish,
10070
+ ask: TUTORIAL_ASKS.finish,
10041
10071
  section: "finish",
10042
- manual: [
10043
- "git -C \"{tour}\" checkout -- theme.js",
10072
+ commands: [
10044
10073
  "cd \"{tour}\"",
10045
10074
  "stim stop",
10075
+ "cd \"{second}\"",
10076
+ "stim stop",
10046
10077
  "cd \"{base}\"",
10047
- "stim worktree remove \"{tour}\""
10078
+ "stim worktree remove \"{tour}\"",
10079
+ "stim worktree remove \"{second}\""
10048
10080
  ]
10049
10081
  }
10050
10082
  ];
10051
10083
  //#endregion
10052
10084
  //#region src/guide/tutorial.ts
10053
- const paths = `Use {base} = ~/stim-tutorial and {tour} = ~/stim-tutorial-tour unless the
10054
- user named another parent folder. Expand ~ to the absolute home path when
10055
- substituting inside quotes. Keep the tutorial outside the user's project.`;
10085
+ const paths = `Use {base} = ~/stim-tutorial unless the user named another folder. Expand ~
10086
+ to the absolute home path when substituting inside quotes. Keep the tutorial
10087
+ outside the user's own projects.`;
10088
+ const local = `The tutorial runs on this Mac. Every time you run the tutorial app on iOS
10089
+ from a worktree of {base}, use stim ios --remote local --remote-build local,
10090
+ even when the user's settings would place the device or the build on another
10091
+ Mac. Only an explicit request to build on another Mac changes the build, with
10092
+ stim ios --remote local --remote-build "<machine>"; the device stays here.`;
10056
10093
  function commands(id) {
10057
- return TUTORIAL_STEPS.find((step) => step.id === id).manual.join("\n");
10094
+ return TUTORIAL_STEPS.find((step) => step.id === id).commands.join("\n");
10058
10095
  }
10059
10096
  //#endregion
10060
10097
  //#region src/guide/index.ts
@@ -10072,260 +10109,160 @@ const TOPICS = {
10072
10109
  web: web_default,
10073
10110
  macos: macos_default,
10074
10111
  tutorial: {
10075
- summary: "An iOS tutorial: isolated worktree, builds, devices, logs, agent actions, and cleanup",
10112
+ summary: "A cloned test app: a first build, a second change in parallel with a cache hit, and cleanup",
10076
10113
  sectionHint: "run",
10077
10114
  preamble: () => `STIM TUTORIAL
10078
10115
 
10079
- Create a small Expo app in its own repository and tour worktree. Follow the
10080
- normal Stim flow on iOS, then inspect builds, cache reuse, device control,
10081
- logs, agent actions, Fast Refresh, and cleanup.
10116
+ The tutorial clones ${TUTORIAL_REPO}, a tiny Expo app, into {base}; the clone is
10117
+ never run or removed. The user then asks for a visual change, and for another
10118
+ change while the first builds. Each change runs in its own linked worktree of
10119
+ {base}, so each has its own simulator and Metro port, and the second worktree's
10120
+ first iOS build reuses the first one's native build. Only the clone, the
10121
+ optional share and the cleanup need this guide: the changes are ordinary
10122
+ requests, so follow stim guide agent for them, and check each change on the
10123
+ device.
10082
10124
 
10083
- Run one section per user request. Then end the turn, say what to look at in
10084
- Stim Desktop, and give the next prompt. Pause never means stim stop.
10085
- "${TUTORIAL_PROMPTS.begin}" means stim guide tutorial run.
10086
- "Continue the Stim tutorial: <section>" means stim guide tutorial <section>.
10087
- "${TUTORIAL_RESTART_PROMPT}" means stim guide tutorial restart.
10125
+ ${local}
10088
10126
 
10089
- Without Stim Desktop, use the simulator, stim status for the workspace and
10090
- device, stim logs --errors for errors, and stim stats for build performance.
10091
- Follow stim guide agent for doctor, errors, consent, and cleanup. Never pair
10092
- phones, approve machines, or grant access on the user's behalf.
10127
+ "Follow stim guide tutorial run" means stim guide tutorial run.
10128
+ "Follow stim guide tutorial finish" means stim guide tutorial finish.
10129
+ "${TUTORIAL_RESTART_PROMPT}" means stim guide tutorial restart.
10093
10130
 
10094
10131
  ${paths}
10095
10132
 
10096
- Start with stim guide tutorial run. For commands to type yourself, read
10097
- stim guide tutorial manual. The first run needs network access unless the
10098
- required npm and native dependencies are already cached.`,
10133
+ Never pair phones, approve machines, or grant access on the user's behalf.
10134
+ For commands to type yourself, read stim guide tutorial manual.`,
10099
10135
  sections: {
10100
10136
  run: {
10101
- summary: "Create or reuse the app, warm a tour worktree, and build on iOS",
10137
+ summary: "Clone the test app into a fresh folder and install its dependencies",
10102
10138
  body: () => `RUN THE TUTORIAL
10103
10139
 
10104
10140
  ${paths}
10105
10141
 
10106
- Folder safety: create the base folder only if absent. Reuse an existing
10107
- folder only if app.json's expo.extra.stimTutorial equals this guide's version,
10108
- ${JSON.parse(TUTORIAL_FILES["app.json"]).expo.extra.stimTutorial}; skip creation and file writes when reusing it. For an existing non-tutorial
10109
- folder or another tutorial version, stop and ask the user for another folder.
10110
- Never overwrite or delete it.
10111
-
10112
- For a new app, first check the selected parent folder: create it if absent,
10113
- then run git -C <parent> rev-parse --is-inside-work-tree. If it succeeds, the
10114
- folder is inside another repository: stop and ask the user for another folder.
10115
- Never git add in the user's repo. Then, from the parent folder, run:
10116
-
10117
- npx --yes create-expo-app@${TUTORIAL_PINS.createExpoApp} stim-tutorial --template ${TUTORIAL_PINS.template} --no-install --no-agents-md --yes
10118
-
10119
- Enter the base folder. Run stim guide tutorial app, write its three app files
10120
- verbatim, and append its .gitignore lines. Then run:
10121
-
10122
- npm install --prefer-offline
10123
- npm pkg set scripts.ios="expo run:ios" scripts.android="expo run:android"
10124
- git init
10125
- git add -A
10126
- git -c user.name=Stim -c user.email=stim@localhost -c commit.gpgsign=false commit -m "Stim tutorial"
10127
-
10128
- On reuse, run git rev-parse --show-toplevel in the base folder before adding
10129
- a worktree; if it does not equal the base folder, the folder is inside another
10130
- repository: stop and ask the user for another folder.
10131
-
10132
- Set the scripts before the commit because Expo prebuild rewrites them to
10133
- expo run:ios and expo run:android; otherwise the dirty worktree blocks removal.
10134
- On npm or network failure, report stderr and stop.
10135
-
10136
- From the base folder, add the tour worktree and run:
10137
-
10138
- git worktree add -B stim-tutorial/tour "{tour}" HEAD
10139
- cd "{tour}"
10140
- stim worktree warm
10141
- stim guide agent
10142
-
10143
- Apply the guide agent doctor rule before native work: if its STATUS block
10144
- says doctor is due, run stim doctor --platform ios from the tour worktree and
10145
- resolve its findings. No STATUS block means doctor is current. Then run:
10146
-
10147
- stim start
10148
- stim ios
10149
-
10150
- Tell the user the first build can take about four minutes on a cold cache.
10151
- Relay the Open in Stim Desktop link printed by stim ios once.
10152
-
10153
- PAUSE: end the turn. Ask the user to look at the Build section, then the
10154
- device, then Logs in Stim Desktop. Without Desktop, use stim status,
10155
- stim stats, and stim logs --errors. Give the next prompt:
10156
- "${TUTORIAL_PROMPTS.rebuild}". Leave the workspace running.`
10157
- },
10158
- app: {
10159
- summary: "Pinned template, verbatim app files, and .gitignore additions",
10160
- body: () => `TUTORIAL APP
10161
-
10162
- create-expo-app: ${TUTORIAL_PINS.createExpoApp}
10163
- Template: ${TUTORIAL_PINS.template}
10164
- Write the app files verbatim. Append the .gitignore lines to the template's
10165
- existing file. App log lines start with [stim:tutorial]. Crash me raises an
10166
- uncaught JavaScript error, and Slow request times a local three-second timer;
10167
- Stim does not capture native network requests.
10168
-
10169
- ${Object.entries(TUTORIAL_FILES).map(([name, content]) => `${name}\n\n\`\`\`${name.endsWith(".json") ? "json" : name.endsWith(".js") ? "js" : "text"}\n${content}\`\`\``).join("\n\n")}`
10170
- },
10171
- rebuild: {
10172
- summary: "Repeat the iOS build and explain the cache result",
10173
- body: () => `REBUILD
10174
-
10175
- ${paths}
10176
-
10177
- Run the same build again, never with --no-build-cache:
10178
-
10179
- ${commands("rebuild")}
10180
-
10181
- Read this workspace's environments[].lastBuilds.ios.cacheHit from
10182
- stim status --json, and the miss reason printed by stim ios. Explain a local or remote hit, or the actual miss
10183
- reason when cacheHit is false. A repeat run can miss; report the evidence.
10184
-
10185
- PAUSE: end the turn. Point at the Build section and cache badge, or stim stats.
10186
- Ask the user to open the device's live view and tap Log an error; without
10187
- Desktop use the simulator. Then inspect Logs or stim logs --errors. Crash me
10188
- and Slow request are optional: the former shows a red box, the latter prints
10189
- a timing line. Give the next prompt: "${TUTORIAL_PROMPTS.agent}".`
10190
- },
10191
- agent: {
10192
- summary: "Record a tap and screenshot, replay it, and inspect agent logs",
10193
- body: () => `AGENT ACTIONS
10194
-
10195
- ${paths}
10196
-
10197
- Set {stateDir} to this workspace's agentDevice.stateDir from stim ios or
10198
- stim status --json. Replace <ios.udid> with this workspace's ios.udid from
10199
- stim status --json. Use that exact owned simulator, not a guessed one.
10200
- Run from the tour worktree:
10201
-
10202
- ${commands("agent").replace("read -r iosUdid", "iosUdid=\"<ios.udid>\"")}
10203
-
10204
- Use --save-script=tutorial.ad with the equals sign: agent-device treats a
10205
- separate path as a URL and refuses. The dismiss-overlay step clears a red box that would cover the buttons.
10206
- Replay with the same --platform and --udid so its steps reach the agent log.
10207
- Remove the target-v1 evidence and dismiss-overlay lines before replay: replaying
10208
- them can fail with REPLAY_DIVERGENCE on the recorded button identity.
10209
- The scripts and screenshot stay in the tour worktree and are git-ignored.
10142
+ Folder safety: use a fresh folder. If {base} exists, stop and ask the user for
10143
+ another folder; never overwrite or delete it. First check the parent folder:
10144
+ create it if absent, then run git -C <parent> rev-parse --is-inside-work-tree.
10145
+ If that succeeds, the folder is inside another repository: stop and ask the
10146
+ user for another folder. Never git add in the user's repo.
10210
10147
 
10211
- PAUSE: end the turn. Point at Agent Actions, or the agent log records just
10212
- printed. Expect another error-button line after replay. When screen recording
10213
- is enabled, the user can scrub Replay in Desktop. Give the next prompt:
10214
- "${TUTORIAL_PROMPTS.refresh}".`
10215
- },
10216
- refresh: {
10217
- summary: "Change the title to purple and verify Fast Refresh through logs",
10218
- body: () => `FAST REFRESH
10219
-
10220
- ${paths}
10148
+ Then, from the parent folder, run:
10221
10149
 
10222
- Set TITLE_COLOR in theme.js to '#7c3aed' (purple). Wait a few seconds for
10223
- Fast Refresh, without reloading the app:
10150
+ git clone https://github.com/${TUTORIAL_REPO}.git stim-tutorial
10224
10151
 
10225
- ${commands("refresh")}
10152
+ Check that app.json in the clone has expo.extra.stimTutorial equal to
10153
+ ${TUTORIAL_VERSION}. If it does not, report the mismatch and stop; the user needs a newer Stim.
10154
+ Enter the clone and install its dependencies (npm ci) so the worktrees made for
10155
+ the changes inherit them, then run stim doctor --platform ios there: it registers
10156
+ the clone with Stim so Stim Desktop sees it, and builds or boots nothing. Do not
10157
+ run the app: the clone is only the base for the user's changes and is never removed.
10158
+ Report the findings of stim doctor and do not act on them: no SimSlim install, no
10159
+ --fix, nothing that changes the machine during the tutorial. On npm or network failure, report stderr
10160
+ and stop.
10226
10161
 
10227
- The line [stim:tutorial] title color=#7c3aed is the proof that the edit reached
10228
- the app. The intentional button errors may still be in the error log; check
10229
- whether the edit introduced a new error.
10162
+ ${local}
10230
10163
 
10231
- PAUSE: end the turn. Point at the purple title in the device view or simulator
10232
- and the title color log line. Phone viewing and an approved remote Mac are
10233
- optional user steps; skip them if unwanted. Never pair, approve, or grant
10234
- anything. If the user names an approved machine, the next prompt is
10235
- "${TUTORIAL_PROMPTS.machine}". Otherwise give
10236
- "${TUTORIAL_PROMPTS.finish}".`
10237
- },
10238
- machine: {
10239
- summary: "Optionally build using a machine the user names and has approved",
10240
- body: () => `REMOTE MAC (OPTIONAL)
10241
-
10242
- ${paths}
10243
-
10244
- Proceed only when the user names an approved remote Mac. Substitute that
10245
- name for {machine}. Never approve, pair, or grant anything. If none is named,
10246
- ask for the name or let the user skip this step.
10247
-
10248
- ${commands("machine")}
10249
-
10250
- This step bypasses the artifact cache so the build can use the named machine.
10251
- A named machine refuses without a local fallback. If it refuses, report the
10252
- refusal and offer --remote-build auto or local; do not retry silently.
10253
-
10254
- PAUSE: end the turn. Point at the build's machine in Desktop or its report in
10255
- stim status --json. Give the next prompt: "${TUTORIAL_PROMPTS.finish}".`
10164
+ PAUSE: end the turn. Tell the user to ask for a visual change next, such as
10165
+ making the title purple, in their own words. Each change runs in a new linked
10166
+ worktree of {base} (stim guide agent), and its first iOS build takes a few
10167
+ minutes.`
10256
10168
  },
10257
10169
  finish: {
10258
- summary: "Revert the tutorial edit, stop, and remove only the tour worktree",
10170
+ summary: "Stop and remove the two tutorial worktrees, keeping the clone",
10259
10171
  body: () => `FINISH
10260
10172
 
10261
10173
  ${paths}
10262
10174
 
10263
- The user's finish request authorizes removing this tour worktree only.
10264
- Revert the refresh edit before removal; worktree remove refuses dirty trees.
10265
- Stop from the tour path, then remove from the base checkout:
10175
+ The worktrees to remove are the two the tutorial tracked: the linked
10176
+ worktrees of {base} made for the user's changes. The finish request names them
10177
+ as {tour} and {second}. Never touch any other worktree, an earlier tutorial
10178
+ clone, or the clone itself. For each, stop from its path, then remove it from
10179
+ the clone:
10266
10180
 
10267
10181
  ${commands("finish")}
10268
10182
 
10269
- Never use --force. On a refusal, report it and stop. Keep the base folder
10270
- and branch. Print these optional cleanup commands for the user; do not run. Deleting
10271
- the base folder also removes the branch, so they are alternatives:
10183
+ Use a plain remove first. The user's finish request says they do not need the
10184
+ changes, which is the consent stim guide agent asks for before worktree remove
10185
+ --force, for exactly those two paths: if the plain remove refuses one of them
10186
+ only because of uncommitted changes or commits found nowhere else, remove that
10187
+ worktree with --force. Never use --force on the clone or on any other
10188
+ worktree, and on any other refusal report it and stop. Keep the clone. Print
10189
+ these optional cleanup commands for the user; do not run them:
10272
10190
 
10273
10191
  rm -rf "{base}"
10274
- git -C "{base}" branch -D stim-tutorial/tour
10275
10192
 
10276
- If archive is enabled, tell the user the tour appears under Archived in Stim
10277
- Desktop. With archive disabled, report removal without promising an archive.
10278
- Without Desktop, inspect stim status --json for the removed environment and,
10279
- when enabled, its archived entry. End the turn.`
10193
+ If archive is enabled, tell the user the worktrees appear under Archived in
10194
+ Stim Desktop. With archive disabled, report removal without promising an
10195
+ archive. Without Desktop, inspect stim status --json for the removed
10196
+ environments. End the turn.`
10197
+ },
10198
+ share: {
10199
+ summary: "Optionally open a public pull request with a screenshot of the change",
10200
+ body: () => `SHARE YOUR FINISH (OPTIONAL)
10201
+
10202
+ Only on the user's explicit request, which the share prompt is, and before the
10203
+ finish step removes the worktrees. The pull request is public: the user's GitHub
10204
+ name and change appear on ${TUTORIAL_REPO}, and a bot replies and closes it. It
10205
+ needs gh signed in. Never open it unprompted or from any other step.
10206
+
10207
+ Work from the worktree holding the user's change, {tour}. Commit the change
10208
+ there, fork the repository and push the branch to the fork, since the user has
10209
+ no write access:
10210
+
10211
+ gh repo fork ${TUTORIAL_REPO} --remote --remote-name fork
10212
+ git push -u fork HEAD
10213
+
10214
+ Screenshot: take a PNG under 1 MB of the app showing the change, with
10215
+ agent-device screenshot when it is installed, otherwise
10216
+ xcrun simctl io <ios.udid> screenshot finish.png, using the udid of that
10217
+ worktree from stim status --json, never booted. If the app is no longer
10218
+ running, run it again from the branch first.
10219
+
10220
+ Open the pull request with gh pr create --repo ${TUTORIAL_REPO} --fill. Put one
10221
+ short line with the build time and whether the second worktree's first iOS build
10222
+ was a cache hit, when you know them from stim status --json or stim stats, in the
10223
+ commit message body (git commit -m "<title>" -m "<that line>"), so --fill carries it
10224
+ into the pull request body.
10225
+
10226
+ Attach the screenshot with gh: gh pr create --attach "finish.png#The change
10227
+ running in the simulator" uploads the image and appends it to the body. gh
10228
+ 2.99.0 (2026-09-01) has --attach; confirm with gh pr create --help rather than
10229
+ guessing a version. When gh has no --attach, commit the PNG on the PR branch as
10230
+ finish/<github-login>.png before pushing and embed it in the body with a
10231
+ relative link: ![the change](finish/<github-login>.png).`
10280
10232
  },
10281
10233
  restart: {
10282
- summary: "Remove the existing tour safely and repeat from the worktree step",
10234
+ summary: "Start the tutorial again without removing anything",
10283
10235
  body: () => `RESTART
10284
10236
 
10285
10237
  ${paths}
10286
10238
 
10287
- For "${TUTORIAL_RESTART_PROMPT}", if the tour worktree is present, revert
10288
- the refresh edit, stop, and remove it using the finish section's commands:
10289
-
10290
- ${commands("finish")}
10291
-
10292
- Never use --force. On a refusal, report it and stop. Keep the base repository.
10293
- Read stim guide tutorial run, recheck its folder and repository safety rules,
10294
- and redo run from the worktree step. Pause after the build as run instructs.`
10239
+ For "${TUTORIAL_RESTART_PROMPT}", remove nothing: the user may still want the
10240
+ earlier worktrees. Finish only removes the pair named in its request, so leave
10241
+ any earlier worktrees and tell the user they stay until they ask for each by
10242
+ path; never use --force for them. Reuse the clone at {base} only if its
10243
+ stimTutorial marker equals ${TUTORIAL_VERSION}; otherwise follow stim guide
10244
+ tutorial run into a fresh folder. Pause as run instructs. Stim Desktop starts
10245
+ over: only worktrees and builds after the restart count.`
10295
10246
  },
10296
10247
  manual: {
10297
- summary: "Commands for every step, including heredocs for the app files",
10248
+ summary: "The commands behind each step, for typing yourself",
10298
10249
  body: () => `MANUAL TUTORIAL
10299
10250
 
10300
10251
  ${paths}
10301
10252
 
10302
- These commands are for a person typing them, one step at a time,
10303
- as scripts: save a block to a file and run it with sh -e, so it stops on the
10304
- first failure. Read stderr and do not continue to later commands. Follow stim guide tutorial run for folder and repository
10305
- safety. Reuse only this version's tutorial app, never overwrite another folder.
10306
- The creation block skips writes on reuse and checks the repository root.
10307
-
10308
- Replace {base} and {tour} with absolute paths; {base} must end in stim-tutorial. For Agent Actions, replace
10309
- {stateDir} with agentDevice.stateDir from stim ios or stim status --json;
10310
- when read -r iosUdid waits, type this workspace's ios.udid from that status.
10311
- For the optional machine step,
10312
- replace {machine} with a machine you have already approved, or skip it.
10313
-
10314
- Look at the sidebar during warm and Build during the first build (about four
10315
- minutes on a cold cache). Compare cacheHit and missReason on rebuild. At Live
10316
- view and control, open the device viewer and tap Log an error; without Desktop
10317
- use the simulator. At App Logs, try Crash me or Slow request if wanted. A JS
10318
- crash shows a red box; the slow request is a local timer, not network capture.
10319
- At Watch on Your Phone, optionally open an already paired Stim phone to see
10320
- the tour workspace; phone setup and machine approval stay with you.
10321
- Use stim status, stim logs --errors, and stim stats without Desktop.
10322
-
10323
- ${TUTORIAL_STEPS.map((step) => `${step.title}${step.optional ? " (optional)" : ""}\n\n${step.manual.length ? `\`\`\`sh\n${step.manual.join("\n")}\n\`\`\`` : "Observe this step in Stim Desktop, or skip it without Desktop."}`).join("\n\n")}
10324
-
10325
- Finish removes only the tour worktree. Never use --force; report a refusal.
10326
- With archive enabled, find the tour under Archived in Desktop or archived[]
10327
- in stim status --json. Keep the base repository and branch. To delete them,
10328
- read stim guide tutorial finish for commands to review and run yourself.`
10253
+ Replace {base}, {tour} and {second} with absolute paths: {base} is the clone,
10254
+ {tour} the first worktree and {second} the second. For Agent Actions, replace
10255
+ {stateDir} with agentDevice.stateDir from stim ios or stim status --json and
10256
+ {udid} with the workspace's ios.udid. For the optional machine step, replace
10257
+ {machine} with a machine you have already approved, or skip it. Clone with git
10258
+ clone https://github.com/${TUTORIAL_REPO}.git into a fresh folder outside any
10259
+ repository. Each change runs in its own worktree of that clone (stim guide
10260
+ agent); the clone itself is never run. Run each worktree on iOS with
10261
+ stim ios --remote local --remote-build local so it stays on this Mac.
10262
+
10263
+ ${TUTORIAL_STEPS.map((step) => `${step.title}${step.optional ? " (optional)" : ""}\n\n${step.commands.length ? `\`\`\`sh\n${step.commands.join("\n")}\n\`\`\`` : "Ask your agent in your own words, or observe this step in Stim Desktop."}`).join("\n\n")}
10264
+
10265
+ Finish removes only the two tutorial worktrees, never the clone; stim guide tutorial finish says when --force is allowed for them.`
10329
10266
  }
10330
10267
  }
10331
10268
  }
@@ -10409,7 +10346,7 @@ function renderIndex(version) {
10409
10346
  return lines.join("\n");
10410
10347
  }
10411
10348
  function guideCommand(program, version, status = (running) => guideStatus({
10412
- projectRoot: findProjectRoot$1(process.cwd()),
10349
+ projectRoot: findProjectRoot(process.cwd()),
10413
10350
  running
10414
10351
  })) {
10415
10352
  program.command("guide [topic] [section]").description("Print reference documentation for THIS version of Stim (topics: " + topicNames().join(", ") + "). A topic with sections prints its section index; name a section to print only it, e.g. `stim guide errors STIM_NO_METRO` or `stim guide lifecycle builds`. Generated by the binary, so it cannot drift from the installed CLI.").action(async (topic, section) => {