@stim-cli/server 1.16.0 → 1.17.1
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.
- package/README.md +160 -57
- package/dist/{protocol-BHSl7bQ_.mjs → protocol-Btu-PwQS.mjs} +122 -12
- package/dist/protocol.mjs +1 -1
- package/dist/protocol.schema.json +263 -3
- package/dist/stim-frames/DuoModelAsset.swift +34 -0
- package/dist/stim-frames/DuoModelView.swift +1 -9
- package/dist/{stim-server-CvqlgkBK.mjs → stim-server-CGqz0EFG.mjs} +3548 -2824
- package/dist/stim-server.mjs +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ The design is in
|
|
|
16
16
|
stim-server [--port <n>] # serve paired clients, port 7787 by default
|
|
17
17
|
stim-server pair [--port <n>] [--control]
|
|
18
18
|
# print a single-use pairing payload
|
|
19
|
-
stim-server devices [list] # list
|
|
19
|
+
stim-server devices [list] # list pairings, approvals, requests and hosted sessions
|
|
20
20
|
stim-server devices grant <id> --control|--read|--build|--device-host
|
|
21
21
|
# let a paired device run actions, or only read;
|
|
22
22
|
# --build approves builds; --device-host approves device hosting
|
|
@@ -57,7 +57,7 @@ app (checked at most every 5 seconds), or `null` when the check fails. Stim
|
|
|
57
57
|
Desktop uses it to find a running server and show its route. A request through `tailscale serve` or on a Tailscale address
|
|
58
58
|
without an `Origin` or `Sec-Fetch-Site` header, which a web page's request
|
|
59
59
|
carries, gets only `{ "server": "stim-server", "version", "protocol" }`, which Stim
|
|
60
|
-
Desktop's
|
|
60
|
+
Desktop's Remote Macs list uses to find stim-server on the other Macs of the
|
|
61
61
|
tailnet. A request from this Mac with a `Host` other than `127.0.0.1` or
|
|
62
62
|
`localhost` gets HTTP 426, like any other plain HTTP request.
|
|
63
63
|
|
|
@@ -91,7 +91,7 @@ A person on the worker runs the command generated for their client Mac:
|
|
|
91
91
|
```bash
|
|
92
92
|
stim-server setup --client <node-id> --ticket <43-base64url-characters> --expires <ISO-time> \
|
|
93
93
|
--build --device-host [--port <n>] [--label <name>] \
|
|
94
|
-
[--env KEY=VALUE]... [--path-prepend <dir>]... [--yes] [--json]
|
|
94
|
+
[--env KEY=VALUE]... [--path-prepend <dir>]... [--yes] [--json] [--verbose]
|
|
95
95
|
```
|
|
96
96
|
|
|
97
97
|
If the server is not installed globally, use `npx --yes --package @stim-cli/server@<version> stim-server`
|
|
@@ -144,8 +144,17 @@ unreadable routes refuse with a remedy. Tool checks print fixes for Xcode,
|
|
|
144
144
|
simulator runtimes, CocoaPods, JDK, Android SDK and the Stim build; they install
|
|
145
145
|
nothing. Android and CocoaPods checks are not needed for hosting alone.
|
|
146
146
|
|
|
147
|
-
|
|
148
|
-
|
|
147
|
+
In a terminal, setup prints a banner, then one line per step with a green check,
|
|
148
|
+
a yellow arrow while a step runs, or a red cross; `NO_COLOR`, `TERM=dumb` and a
|
|
149
|
+
non-terminal give plain `[ok]`, `[failed]` and `[pending]` marks, no colors and no
|
|
150
|
+
in-place line. The y/N questions stay, one line each. The run ends with a
|
|
151
|
+
headline such as "janics-mac-mini is ready to build for Janic's MacBook Pro", one
|
|
152
|
+
line per approved capability, a `Fix` line for each skipped permission or missing
|
|
153
|
+
tool, and one `To undo` line. `--verbose` prints every step with its detail, the
|
|
154
|
+
install, service and route notes, and the long summary with the undo commands, as
|
|
155
|
+
earlier releases did. Plain progress goes to stdout; errors use
|
|
156
|
+
`stim-server: <message>` on stderr (when stdout is a terminal, the refusal appears on stdout only).
|
|
157
|
+
`--json` sends the long (verbose) progress to stderr, without the banner, and prints one final payload with `ok`,
|
|
149
158
|
`label`, `port`, `route` (state, DNS name, HTTPS port), `server` (version and
|
|
150
159
|
Stim build), `managed`, separate `granted` request ids and client identities,
|
|
151
160
|
`permissions`, `tools` and `warnings`. Final permission values are `granted`,
|
|
@@ -172,7 +181,7 @@ with detail `interrupted`, completes the journal, releases the setup claim,
|
|
|
172
181
|
and exits 1. A typed N also exits 1; a question timeout follows ticket expiry
|
|
173
182
|
and exits 2.
|
|
174
183
|
|
|
175
|
-
The
|
|
184
|
+
The `To undo` line (or the `--verbose` summary) lists the undo commands:
|
|
176
185
|
|
|
177
186
|
```bash
|
|
178
187
|
stim-server devices revoke <build-request-id>
|
|
@@ -284,7 +293,7 @@ registration check. `hello` reports
|
|
|
284
293
|
the capabilities and actions of the connection's device when it connects; a
|
|
285
294
|
connection sees a new grant after it reconnects. Under Stim Host, an approved
|
|
286
295
|
`hello` also carries the same `host` grants as `/health`, which `stim doctor`
|
|
287
|
-
reports for
|
|
296
|
+
reports for remote Macs.
|
|
288
297
|
|
|
289
298
|
Every method other than `hello` needs `read`. A device with only `build`
|
|
290
299
|
gets `forbidden` for all of them.
|
|
@@ -331,16 +340,15 @@ tokens.
|
|
|
331
340
|
Device hosting has a separate `device-host` capability. An approved client can
|
|
332
341
|
reserve, boot, reconnect to and stop its own iOS simulator or Android emulator
|
|
333
342
|
through the protocol. It can deliver, install and launch a compatible iOS app
|
|
334
|
-
bundle, Android APK or prebuilt macOS app, stream the iOS simulator or macOS app and control it.
|
|
335
|
-
|
|
336
|
-
Android Metro and viewing remain in [#2266](https://github.com/appandflow/stim/issues/2266).
|
|
343
|
+
bundle, Android APK or prebuilt macOS app, stream the iOS simulator, Android emulator or macOS app and control it. Hosted
|
|
344
|
+
iOS and Android apps connect back to Metro on the client Mac. Automatic placement remains in [#2266](https://github.com/appandflow/stim/issues/2266).
|
|
337
345
|
|
|
338
346
|
The Stim client can name expected hosts with
|
|
339
|
-
`stim settings set
|
|
347
|
+
`stim settings set remote.machines '["<mac>"]'` and request access with
|
|
340
348
|
`stim doctor --fix`. It stores a separate private credential in
|
|
341
349
|
`$STIM_HOME/device-host-machines.json`, pins the worker's tailnet node, and
|
|
342
350
|
reports approval under `deviceHosts` in doctor JSON. Plain doctor makes no
|
|
343
|
-
approval request.
|
|
351
|
+
approval request. Use `stim ios --remote <machine>` or `stim android --remote <machine>` for explicit placement.
|
|
344
352
|
|
|
345
353
|
A client on the tailnet sends `hello` with
|
|
346
354
|
`auth: { "request": "device-host", "deviceName": "Laptop" }`. As with a build
|
|
@@ -365,6 +373,31 @@ from phone pairings and build clients. A hosting token grants no `read`,
|
|
|
365
373
|
read, control and build tokens cannot gain hosting through `devices grant`.
|
|
366
374
|
Loopback requests and `pair --device-host` are refused.
|
|
367
375
|
|
|
376
|
+
### Hosted here
|
|
377
|
+
|
|
378
|
+
`stim-server devices` adds a **Hosted here** block after pairings and approvals,
|
|
379
|
+
with the client name, platform, device label, installed app bundle id (or `-`),
|
|
380
|
+
state and start time. `--json` adds `hostedSessions` next to `devices`, including
|
|
381
|
+
an empty array when there are none. Rows are newest first; plain stopped
|
|
382
|
+
sessions are omitted, while parked and unresolved sessions stay visible.
|
|
383
|
+
A parked row uses its parking time. A removed client's name falls back to its id.
|
|
384
|
+
The command reads the host journal directly and needs no server connection.
|
|
385
|
+
|
|
386
|
+
`device-host.sessions` takes no params (or `{}`) and returns `{ sessions }`.
|
|
387
|
+
Each row has `id`, `client: { id, name }`, `platform`, nullable `device` and `app`,
|
|
388
|
+
`state`, `parked`, `since` (ISO time), and `workspace`.
|
|
389
|
+
`device-host.sessions.stop` takes `{ "session": "<id>" }` and returns
|
|
390
|
+
`{ "id": "<id>", "state": "stopping" | "stopped" }`. It stops any client's
|
|
391
|
+
session through the existing stop path, closing Metro, view, agent and logs
|
|
392
|
+
before owned-device teardown. Repeating a stopped session returns `stopped`;
|
|
393
|
+
an absent id gets `unknown-session`.
|
|
394
|
+
Both requests require an authenticated loopback Desktop connection with local
|
|
395
|
+
identity and control access. Local read-only connections, paired phones and
|
|
396
|
+
remote hosting clients get `forbidden`. A session whose active owner is not
|
|
397
|
+
attached to this server reads as `unknown`; stop reconciles it through the same
|
|
398
|
+
ownership checks. The approved-client `device-host.stop` remains limited to
|
|
399
|
+
that client's own sessions.
|
|
400
|
+
|
|
368
401
|
### Hosted availability offers
|
|
369
402
|
|
|
370
403
|
Before reserving, an approved hosting client can send:
|
|
@@ -572,7 +605,7 @@ session before starting another transfer. Manifest, file and partial blobs are s
|
|
|
572
605
|
by attempts of one session in its content-addressed store; offers recheck complete
|
|
573
606
|
blob digests and request only missing bytes.
|
|
574
607
|
|
|
575
|
-
An iOS simulator or macOS session can take its content from a build this Mac ran instead: after
|
|
608
|
+
An iOS simulator, Android emulator or macOS session can take its content from a build this Mac ran instead: after
|
|
576
609
|
the manifest arrives, send `device-host.app.handoff` with
|
|
577
610
|
`{session, attempt, build: {handoff, sha256}}`, where `handoff` is the token
|
|
578
611
|
and `sha256` the archive digest from that job's `build.artifact` answer. The
|
|
@@ -583,7 +616,10 @@ declares links, whose directory resolves inside the staged bundle and whose
|
|
|
583
616
|
bytes match their manifest digest; it answers `{files, bytes}` for what it
|
|
584
617
|
took. The token is spent once the handoff starts. Re-offer and upload whatever
|
|
585
618
|
is still missing; an older server answers `unknown-method`. For iOS, check the
|
|
586
|
-
`hosted-ios-data` hello feature before requesting handoff;
|
|
619
|
+
`hosted-ios-data` hello feature before requesting handoff; for Android check
|
|
620
|
+
`hosted-android-data`. Without the feature, upload. Android takes only a regular
|
|
621
|
+
APK file matching the single `App.apk` manifest entry. Handoff failure falls back
|
|
622
|
+
to upload. Reused APK blobs skip transfer, while every new attempt still installs.
|
|
587
623
|
|
|
588
624
|
After every digest is verified, call `device-host.app.launch` with
|
|
589
625
|
`{session, attempt}`. Poll `device-host.app.attach` for `installed` or `unknown`.
|
|
@@ -619,11 +655,31 @@ signature or installation conflict refuses; hosting does not uninstall an
|
|
|
619
655
|
existing app to resolve it.
|
|
620
656
|
|
|
621
657
|
Release launch reports `true` only after observing a live package process.
|
|
622
|
-
Development launch remains `unverified
|
|
623
|
-
|
|
658
|
+
Development launch remains `unverified` until the client Metro bridge provides
|
|
659
|
+
bundle evidence. Replaying an installed app attempt does
|
|
624
660
|
not install or launch again. Owner loss, stop, revocation and uncertain outcomes
|
|
625
|
-
retain the same reconciliation rules as iOS.
|
|
626
|
-
|
|
661
|
+
retain the same reconciliation rules as iOS.
|
|
662
|
+
|
|
663
|
+
With `hosting.agentDriver: agent-device`, each installed Android session gets
|
|
664
|
+
its own daemon and child-aware claim under `device-host/sessions/<session>/agent-device.claims`.
|
|
665
|
+
Both Macs need agent-device 0.21.22 or later. The host verifies the serial policy
|
|
666
|
+
digest and `android-instance` backend and advertises `hosted-android-agent`.
|
|
667
|
+
`device-host.app.attach` and session attach carry a session grant with
|
|
668
|
+
`android:mobile:<serial>` and the same token and client-node checks as iOS.
|
|
669
|
+
The client writes a 0600 remote config under `hosted-android/<slot>/` and reports
|
|
670
|
+
it as `android.host.agent`. Use `agent-device <command> --remote-config <file>`.
|
|
671
|
+
Missing or unsupported agent-device reports `driver: none` with its reason.
|
|
672
|
+
|
|
673
|
+
The policy pins the ledger-owned serial and filters `devices` to that emulator.
|
|
674
|
+
Inspection and interaction commands match iOS. Installs, reinstall, uninstall,
|
|
675
|
+
boot/shutdown, `close --shutdown`, record, logs, uploads, other serials (including
|
|
676
|
+
nested batches and leases), and host paths are refused. Ambient fields are
|
|
677
|
+
stripped and replaced with host-owned platform, serial and lease scope.
|
|
678
|
+
Stim installs apps. Android snapshots use one-shot helpers without adb forwards;
|
|
679
|
+
after daemon exit the host stops and verifies only the two fixed agent-device
|
|
680
|
+
helper packages on that serial. Their APKs leave with emulator deletion.
|
|
681
|
+
Reinstall, stop, revocation and close await agent teardown before native work;
|
|
682
|
+
unresolved agent claims refuse replacement and deletion after restart too.
|
|
627
683
|
|
|
628
684
|
### Hosted macOS app sessions
|
|
629
685
|
|
|
@@ -672,12 +728,12 @@ service despite the isolated home. Stop removes the app home and delivered bytes
|
|
|
672
728
|
from every app attempt, keeping logs and receipts so `app.attach` can still report
|
|
673
729
|
the recorded state.
|
|
674
730
|
|
|
675
|
-
Once a
|
|
731
|
+
Once a hosted app is `installed`, `device-host.app.launch` and
|
|
676
732
|
`device-host.app.attach` return the agent grant the host's driver issued for that
|
|
677
733
|
session, or `agent: { driver: 'none' }` while no driver runs or it cannot scope
|
|
678
734
|
the app; the driver's reason, if any, is the result's `notice`. The field is
|
|
679
735
|
built for the response and never stored in the receipt or journal; it is absent
|
|
680
|
-
before installation
|
|
736
|
+
before installation and after stop. The worker
|
|
681
737
|
reports the app's pid, `DeviceHost` registers the running app with the agent
|
|
682
738
|
driver before the receipt reads `installed`, and ends that registration when the
|
|
683
739
|
session stops, is revoked or the server closes.
|
|
@@ -686,9 +742,19 @@ session stops, is revoked or the server closes.
|
|
|
686
742
|
the NDJSON records the session's macOS app wrote to its captured log (stdout and
|
|
687
743
|
stderr as `client` records, and its exit). iOS sessions capture app-filtered native
|
|
688
744
|
logs as `device` records through bounded `simctl log show` worker queries, with
|
|
689
|
-
a persisted time checkpoint in the isolated home.
|
|
745
|
+
a persisted time checkpoint in the isolated home. Android sessions use
|
|
746
|
+
`logcat -d -v epoch -T <epoch> --pid <pid>` on the exact ledger-owned serial after
|
|
747
|
+
re-verifying its AVD name. Each collection resolves `pidof <package>` and also
|
|
748
|
+
queries the last observed PID of that app attempt, to drain a crash or restart.
|
|
749
|
+
Without any observed PID, logs cannot be attributed to the app. PID-based logcat
|
|
750
|
+
cannot distinguish historical reuse of an exited PID. Output is bounded
|
|
751
|
+
by the executor's 64 MiB limit per command and the collection's ten-second budget;
|
|
752
|
+
an oversized query retries the recent tail and emits a `device` warning naming
|
|
753
|
+
the dropped interval. Logcat entries already evicted from its finite buffer are
|
|
754
|
+
unavailable. Both native collectors use the same checkpoint and NDJSON writer.
|
|
755
|
+
Windows adapt to the backlog,
|
|
690
756
|
overlap by five seconds and de-duplicate event identities. `checkpoint` is the last
|
|
691
|
-
completed
|
|
757
|
+
completed native window in epoch milliseconds. `log show` reads persisted entries;
|
|
692
758
|
info-level entries and persistence delayed beyond the overlap may be unavailable.
|
|
693
759
|
The worker holds a separate child-aware log claim. Queries coalesce onto an
|
|
694
760
|
in-flight collection or read captured files; collection starts at most once every
|
|
@@ -713,8 +779,9 @@ byte offset after the last complete line read; pass the previous result's cursor
|
|
|
713
779
|
receive only newer records, and repeat while `more` is true. Without a cursor, each
|
|
714
780
|
file starts at most 4 MiB before its end. A page holds at most 1 MiB; a file that
|
|
715
781
|
rotated since the cursor is read from the end of its previous generation. The
|
|
716
|
-
session's own client only, for iOS and macOS sessions, and also after the session stopped,
|
|
717
|
-
because stop keeps captured logs. iOS clients check hello feature `hosted-ios-data
|
|
782
|
+
session's own client only, for iOS, Android and macOS sessions, and also after the session stopped,
|
|
783
|
+
because stop keeps captured logs. iOS clients check hello feature `hosted-ios-data`
|
|
784
|
+
and Android clients check `hosted-android-data`;
|
|
718
785
|
without it, they show an update note and copied logs. Servers that predate macOS queries answer `forbidden` or
|
|
719
786
|
`unknown-method`.
|
|
720
787
|
|
|
@@ -731,10 +798,18 @@ gateway accepts only that worker address and authenticates each connection
|
|
|
731
798
|
before forwarding to the fixed local Metro port. It never targets a client
|
|
732
799
|
supplied URL. Close the gateway when its session ends.
|
|
733
800
|
|
|
734
|
-
Call `device-host.metro.open` with `{session, gatewayPort, secret}` on the
|
|
801
|
+
Call `device-host.metro.open` with `{session, gatewayPort, secret, clientMetroPort?}` on the
|
|
735
802
|
approved hosted connection. The server connects only to that connection's
|
|
736
803
|
authenticated tailnet peer and returns `{port}` for the worker's loopback
|
|
737
|
-
endpoint.
|
|
804
|
+
endpoint. `clientMetroPort` is required for Android and validated as an integer
|
|
805
|
+
from 1 through 65535. The session records it alongside the bridge port. Android
|
|
806
|
+
development installation reverses `tcp:<clientMetroPort>` to `tcp:<port>` on
|
|
807
|
+
the exact ledger-owned serial and sets `debug_http_host` to
|
|
808
|
+
`localhost:<clientMetroPort>`. Reopening an installed development session runs
|
|
809
|
+
a fixed reverse worker under its child-aware claim, serialized with install
|
|
810
|
+
and stop, so a client reload restores the mapping after a host adb server
|
|
811
|
+
restart. The worker never restarts the shared adb server. iOS development
|
|
812
|
+
installation uses the bridge port for `RCT_jsLocation` and,
|
|
738
813
|
when offered, the Expo development-client deep link. HTTP and WebSocket bytes
|
|
739
814
|
stream over WireGuard with socket backpressure; no public tunnel, Funnel or
|
|
740
815
|
Tailscale serve configuration change is needed. The 64 KiB server message
|
|
@@ -756,18 +831,23 @@ placement still needs to check that contract before selecting hosted Metro.
|
|
|
756
831
|
Bare React Native uses the worker `RCT_jsLocation`. Bridge readiness and
|
|
757
832
|
manifest requests are not launch proof; development remains `unverified` until
|
|
758
833
|
the workspace observes the app's own bundle delivery.
|
|
759
|
-
|
|
760
|
-
|
|
834
|
+
Approved clients use `stim ios|android --remote auto` for automatic device placement.
|
|
835
|
+
They stay local while there is a free slot, no queued device run, normal memory
|
|
836
|
+
pressure, no budget refusal and load below `server.maxLoadPerCore`. Otherwise
|
|
837
|
+
they rank compatible offers from `remote.machines` and select a host, or wait
|
|
838
|
+
locally if none admits. A reserve refusal after an offer fails with
|
|
839
|
+
`STIM_HOSTING_REFUSED` and asks to retry; it does not re-place the built app.
|
|
840
|
+
Automatic native macOS placement remains separate work.
|
|
761
841
|
|
|
762
842
|
### Hosted viewer relay
|
|
763
843
|
|
|
764
|
-
The client's stim-server relays a hosted workspace's iOS simulator or macOS view and input to
|
|
844
|
+
The client's stim-server relays a hosted workspace's iOS simulator, Android emulator or macOS view and input to
|
|
765
845
|
its host using the client's approved device-host credential over the pinned
|
|
766
846
|
tailnet connection. Stim Desktop and phones keep talking only to their own
|
|
767
|
-
server. The `macos-hosted` and `
|
|
768
|
-
iOS resolves `ios.host` by the requested slot; macOS retains its default slot.
|
|
847
|
+
server. The `macos-hosted`, `ios-hosted` and `android-hosted` features advertise these relays.
|
|
848
|
+
iOS resolves `ios.host` and Android resolves `android.host` by the requested slot; macOS retains its default slot.
|
|
769
849
|
The local platform and slot replace the host's private target fields in events
|
|
770
|
-
and iOS control audit records. H.264 packets retain their flags and payload and
|
|
850
|
+
and iOS/Android control audit records. H.264 packets retain their flags and payload and
|
|
771
851
|
carry the local subscription ID, which identifies the platform and slot.
|
|
772
852
|
The relay forwards iOS device artwork and orientation metadata. Hosted targets
|
|
773
853
|
refuse replay (`at`/`rate`), `duoFrame` and `physical` with `bad-request`.
|
|
@@ -783,14 +863,14 @@ its packets until the next keyframe and, when the host advertises the
|
|
|
783
863
|
`hosted-congestion` feature, sends `device-host.frames.congested` at most every
|
|
784
864
|
250 ms, so the host lowers that app's bitrate as it does for a local subscriber
|
|
785
865
|
whose socket backs up.
|
|
786
|
-
Hosted macOS frames and control require the default slot; hosted iOS
|
|
866
|
+
Hosted macOS frames and control require the default slot; hosted iOS and Android support named slots.
|
|
787
867
|
`control.begin` still needs the local `control` grant. For macOS apps, Screen Recording for
|
|
788
868
|
viewing and Accessibility for control are granted on the host, not the client.
|
|
789
869
|
|
|
790
|
-
### Hosted iOS and macOS view and input
|
|
870
|
+
### Hosted iOS, Android and macOS view and input
|
|
791
871
|
|
|
792
872
|
An approved hosting client can subscribe to its ready session's exact owned
|
|
793
|
-
iOS simulator or running macOS app without access to the worker's registered
|
|
873
|
+
iOS simulator, Android emulator or running macOS app without access to the worker's registered
|
|
794
874
|
workspaces:
|
|
795
875
|
|
|
796
876
|
```json
|
|
@@ -810,7 +890,7 @@ subscription ID as `params.subscription`. `device-host.frames.congested`
|
|
|
810
890
|
behind on that H.264 subscription; it lowers the bitrate the same way a backed-up
|
|
811
891
|
socket does.
|
|
812
892
|
Hosted capture requires the compiled `stim-frames` helper and does not support
|
|
813
|
-
replay
|
|
893
|
+
replay or screenshot fallback. iOS supports device artwork. macOS frames show only the one
|
|
814
894
|
window of the hosted app. Viewing is refused before launch and after the app
|
|
815
895
|
exits. On the hosting Mac, `stim-server service install` runs the server and
|
|
816
896
|
`stim-frames` under Stim Host, whose Screen Recording and control grants they
|
|
@@ -820,13 +900,27 @@ System Audio Recording ends the stream; missing Device Control and Data Access
|
|
|
820
900
|
(Accessibility on macOS 26 and earlier) leaves viewing available.
|
|
821
901
|
See [Run as a service](#run-as-a-service) for the settings panes and Dev caveat.
|
|
822
902
|
|
|
903
|
+
Android capture uses the same `android-device <serial> <adb> <scrcpy-server>`
|
|
904
|
+
helper transport as physical Android, addressed only to the session's exact
|
|
905
|
+
`emulator-<port>` serial. It bypasses workspace target resolution and device
|
|
906
|
+
leases. The server rechecks the private home's created-device ledger, recorded
|
|
907
|
+
serial and system image, the running AVD identity and ABI. Unknown state refuses
|
|
908
|
+
capture and input. The helper uses the host SDK from `ANDROID_HOME`,
|
|
909
|
+
`ANDROID_SDK_ROOT` or `$HOME/Library/Android/sdk`, including under the LaunchAgent.
|
|
910
|
+
It pushes the built scrcpy jar to `/data/local/tmp` and removes both that jar
|
|
911
|
+
and its adb forward when capture stops. One viewer pool serves each session;
|
|
912
|
+
the lifetime claim tracks the helper as its child. Android capture requires no
|
|
913
|
+
macOS screen-recording permission.
|
|
914
|
+
|
|
823
915
|
Start control with `device-host.control.begin` and
|
|
824
916
|
`{"session":"<hosted-session-id>"}`. Its result returns a connection-bound
|
|
825
917
|
control session ID and `lease: null`: the hosted lifetime claim protects this
|
|
826
918
|
private device. Only one controller can drive the device; `takeOver: true`
|
|
827
919
|
replaces the previous controller. Use the returned control ID with
|
|
828
920
|
`device-host.input.touch|text`, using the same parameters as ordinary input.
|
|
829
|
-
iOS also supports `device-host.input.button|rotate|posture`.
|
|
921
|
+
iOS also supports `device-host.input.button|rotate|posture`. Android supports
|
|
922
|
+
`device-host.input.button` through scrcpy; rotation and posture are unavailable
|
|
923
|
+
on this path, as for physical Android devices. macOS also supports
|
|
830
924
|
`device-host.input.scroll|key`; these two methods are for macOS sessions only.
|
|
831
925
|
Touch coordinates range from 0 to 1 on the streamed display.
|
|
832
926
|
End it with `device-host.control.end` and `{"session":"<control-id>"}`.
|
|
@@ -847,14 +941,13 @@ Hosted posture commands hold a separate child-aware input claim. A surviving
|
|
|
847
941
|
command or unresolved child identity blocks replacement ownership, install and
|
|
848
942
|
stop even after the server and capture helper exit; the refusal names the claim
|
|
849
943
|
and its manual cleanup command. An unknown or lost owner requires explicit stop
|
|
850
|
-
before replacement.
|
|
851
|
-
protocol does not add CLI placement or a local viewer relay; those remain in
|
|
944
|
+
before replacement. Automatic placement remains in
|
|
852
945
|
[#2266](https://github.com/appandflow/stim/issues/2266).
|
|
853
946
|
|
|
854
947
|
## Run as a service
|
|
855
948
|
|
|
856
949
|
`stim-server service install` runs stim-server as a per-user LaunchAgent on
|
|
857
|
-
macOS, so a
|
|
950
|
+
macOS, so a remote Mac or a phone-serving Mac keeps it running without a
|
|
858
951
|
terminal:
|
|
859
952
|
|
|
860
953
|
```bash
|
|
@@ -995,18 +1088,18 @@ and uninstall of one label run one at a time.
|
|
|
995
1088
|
installed and, when `install` created it, the serve route. Logs, pairings and the host app stay; other labels may use the app. Use `--label` and `--port` to run a
|
|
996
1089
|
second service beside the first, for example with another `STIM_HOME`.
|
|
997
1090
|
|
|
998
|
-
To set up a
|
|
1091
|
+
To set up a remote Mac: install Stim and stim-server on the Mac, run
|
|
999
1092
|
`stim-server service install --serve` (with the pins its toolchain needs), set
|
|
1000
|
-
`
|
|
1001
|
-
`stim settings set
|
|
1002
|
-
Approve the request on the
|
|
1093
|
+
`server.workerRoot` there, and on each client run
|
|
1094
|
+
`stim settings set remote.machines '["<mac>"]'` and `stim doctor --fix`.
|
|
1095
|
+
Approve the request on the remote Mac with
|
|
1003
1096
|
`stim-server devices grant <id> --build`.
|
|
1004
1097
|
|
|
1005
1098
|
## Remote update
|
|
1006
1099
|
|
|
1007
1100
|
A Mac this one approved for builds (`--build`) or device hosting
|
|
1008
1101
|
(`--device-host`) can update this Mac's `stim-server service` over its tailnet
|
|
1009
|
-
connection, so the
|
|
1102
|
+
connection, so the remote Mac keeps up with the client without ssh. A
|
|
1010
1103
|
server that offers it lists `server-update` in its `hello` features. A paired
|
|
1011
1104
|
phone, a `read` or `control` device and a connection from this Mac cannot. The
|
|
1012
1105
|
methods need that approval, not `read`:
|
|
@@ -1042,8 +1135,8 @@ of an existing hosted session still answers during the drain.
|
|
|
1042
1135
|
|
|
1043
1136
|
Stim Desktop starts these updates through its own local stim-server with two
|
|
1044
1137
|
methods that only the local Desktop control connection may call:
|
|
1045
|
-
`machines.update.start` takes `{ "machine" }`, an approved `
|
|
1046
|
-
|
|
1138
|
+
`machines.update.start` takes `{ "machine" }`, an approved `remote.machines`
|
|
1139
|
+
entry. It connects to that machine's pinned node with
|
|
1047
1140
|
the stored credential and asks it for this server's npm release. When this
|
|
1048
1141
|
server runs from a Stim checkout, it instead packs the checkout's workspace
|
|
1049
1142
|
packages as `npm pack` would and sends them. `machines.update.status` takes
|
|
@@ -1076,7 +1169,7 @@ closes its connections and cancels its builds.
|
|
|
1076
1169
|
average per CPU), `builds` (this Mac's own Stim runs in prebuild, pods or
|
|
1077
1170
|
compile that it did not offload, plus the offloaded builds it runs), `maxBuilds` (its
|
|
1078
1171
|
`concurrency.maxBuilds`, 0 when unlimited), `maxLoadPerCore` (its
|
|
1079
|
-
`
|
|
1172
|
+
`server.maxLoadPerCore`, default 2), and `declined`: why it would refuse a
|
|
1080
1173
|
build now, or null. It declines while it runs its limit of offloaded builds,
|
|
1081
1174
|
while the worker root's volume has less than 10 GiB free, while `builds`
|
|
1082
1175
|
reaches a non-zero `maxBuilds`, or while `loadPerCore` is at or above
|
|
@@ -1113,7 +1206,7 @@ closes its connections and cancels its builds.
|
|
|
1113
1206
|
`assemble<variant>` for `abi` with ccache under the area's Stim home. A
|
|
1114
1207
|
Gradle daemon leaves the build's process group, so no claim or slot tracks
|
|
1115
1208
|
it. An Android build keeps the client's daemon warm, with Gradle's idle
|
|
1116
|
-
timeout set to `
|
|
1209
|
+
timeout set to `server.gradleDaemonIdleMinutes` (default 30; 0 stops it
|
|
1117
1210
|
when the build ends), and stops it when the build gets SIGTERM. Gradle
|
|
1118
1211
|
fixes a daemon's idle timeout when the daemon starts, so a new value
|
|
1119
1212
|
applies to the next daemon. Once no build of that client runs, stim-server
|
|
@@ -1177,15 +1270,15 @@ closes its connections and cancels its builds.
|
|
|
1177
1270
|
another client, gets `bad-request`.
|
|
1178
1271
|
- `build.artifact` takes the `job` of a successful build and sends the archive
|
|
1179
1272
|
as binary frames, each 32 bytes of its sha256 and then the next bytes, then
|
|
1180
|
-
answers `{ "name", "size", "sha256" }`, and deletes it here. For an iOS simulator or macOS
|
|
1273
|
+
answers `{ "name", "size", "sha256" }`, and deletes it here. For an iOS simulator, Android emulator or macOS
|
|
1181
1274
|
job the answer also carries `handoff`, a single-use token, and the staged
|
|
1182
|
-
`.app` stays for 10 minutes so a hosted session on this Mac can take it with
|
|
1275
|
+
`.app` or APK stays for 10 minutes so a hosted session on this Mac can take it with
|
|
1183
1276
|
`device-host.app.handoff`. A client keeps at most one such bundle; its next
|
|
1184
|
-
fetched
|
|
1277
|
+
fetched native build or a revoked approval deletes it. An archive nobody fetched is deleted when its job
|
|
1185
1278
|
is cancelled; one left by a server that crashed stays under
|
|
1186
1279
|
`repos/<repo>/out/` until you delete it.
|
|
1187
1280
|
|
|
1188
|
-
The worker root is `
|
|
1281
|
+
The worker root is `server.workerRoot` in this Mac's Stim settings, or
|
|
1189
1282
|
`$STIM_HOME/build-worker`. Each client gets `<root>/<device id>/`, with its
|
|
1190
1283
|
blobs, caches (`CP_HOME_DIR`, `CP_CACHE_DIR`, the pnpm store and
|
|
1191
1284
|
`GRADLE_USER_HOME` and SwiftPM) and one area
|
|
@@ -1472,6 +1565,16 @@ Events are `{ "event", "subscription", ... }`.
|
|
|
1472
1565
|
`at` and `frames.seek` on it fail with `no-recording`, and it counts
|
|
1473
1566
|
toward no idle check.
|
|
1474
1567
|
|
|
1568
|
+
- **Archive detail.** `archive.detail` takes `{ archive: archived[].id }` and needs
|
|
1569
|
+
`read`. It returns `{ builds, recordings }`: the archive's own iOS and Android
|
|
1570
|
+
build history in the live environment's `builds` shape, and every retained
|
|
1571
|
+
recording slot as `{ platform, slot, spans }`. Spans use the same ranges as
|
|
1572
|
+
`replay.range`; devices without spans are omitted. Missing or unreadable state
|
|
1573
|
+
yields `builds: {}`, and no recordings yields `recordings: []`. Reads share the
|
|
1574
|
+
archive request queue, refuse unknown archives with `unknown-workspace`, and
|
|
1575
|
+
skip symlinks. Older servers refuse it with `unknown-method`.
|
|
1576
|
+
`status` keeps only the archive's build summary.
|
|
1577
|
+
|
|
1475
1578
|
- **Replay.** `replay.range` takes exactly one of `workspace` or `archive`, plus `platform` and `slot`
|
|
1476
1579
|
(`default` when absent), like `frames.subscribe`, and returns what can be
|
|
1477
1580
|
replayed of that device slot's [recording](#recording):
|
|
@@ -1618,10 +1721,10 @@ Events are `{ "event", "subscription", ... }`.
|
|
|
1618
1721
|
one result for 60 seconds, shared by every connection: a request while the
|
|
1619
1722
|
commands run waits for them, and `measuredAt` says when they started. The
|
|
1620
1723
|
commands fail after 150 seconds. Servers that predate it answer
|
|
1621
|
-
`unknown-method`. It also carries `buildMachines`, the `
|
|
1724
|
+
`unknown-method`. It also carries `buildMachines`, the `remoteMachines` list
|
|
1622
1725
|
of `stim doctor --json --platform ios` (never with `--fix`), so a phone can
|
|
1623
|
-
show whether each
|
|
1624
|
-
when `
|
|
1726
|
+
show whether each remote Mac takes builds. The server runs doctor only
|
|
1727
|
+
when `remote.machines` names a machine, else the list is empty; it runs it
|
|
1625
1728
|
in the registered workspace that ran doctor for iOS most recently, because
|
|
1626
1729
|
doctor judges a machine against an app. Like a doctor run from a terminal,
|
|
1627
1730
|
that records the run for the workspace. `buildMachines` is null with
|
|
@@ -1633,7 +1736,7 @@ Events are `{ "event", "subscription", ... }`.
|
|
|
1633
1736
|
at a time, and sets `buildMachinesPending` while that background run has not
|
|
1634
1737
|
settled a result yet, or the settled one is 60 seconds old or older. A client
|
|
1635
1738
|
that wants the refreshed result asks `machine.details` again. It also
|
|
1636
|
-
carries `buildClients`, the builds this Mac ran
|
|
1739
|
+
carries `buildClients`, the builds this Mac ran for other Macs, one entry
|
|
1637
1740
|
per client Mac read from the `build` records of the audit log, with `id`,
|
|
1638
1741
|
`name`, `builds`, `failed`, `buildMs`, `lastAt` and the same three counts
|
|
1639
1742
|
for `today`, most recent client first, with `today` on this Mac's local
|