@stim-cli/server 1.10.1 → 1.11.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.
- package/README.md +170 -27
- package/dist/{protocol-B3k9Ykuu.mjs → protocol-CmlG7X7_.mjs} +258 -11
- package/dist/protocol.d.mts +180 -10
- package/dist/protocol.mjs +2 -2
- package/dist/protocol.schema.json +464 -5
- package/dist/stim-frames/EmulatorDiscovery.swift +49 -0
- package/dist/stim-frames/EmulatorInput.swift +141 -0
- package/dist/stim-frames/GrpcFraming.swift +85 -0
- package/dist/stim-frames/InputMessages.swift +73 -0
- package/dist/stim-frames/ScreenshotMessages.swift +129 -0
- package/dist/stim-frames/ScreenshotStream.swift +130 -0
- package/dist/stim-frames/SimulatorDisplay.swift +116 -0
- package/dist/stim-frames/SimulatorHID.swift +101 -0
- package/dist/stim-frames/VideoEncoder.swift +282 -0
- package/dist/stim-frames/main.swift +389 -0
- package/dist/stim-server.mjs +1177 -106
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# @stim-cli/server
|
|
2
2
|
|
|
3
|
-
`stim-server` serves Stim state to paired
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
`stim-server` serves Stim state to paired clients, such as the Stim phone app,
|
|
4
|
+
and lets the clients the Mac grants control run `stim reload` and `stim stop`
|
|
5
|
+
in a workspace. It runs on the Mac, next to Stim. It changes Stim state only
|
|
6
|
+
through those two commands, and writes only its own pairing state and action
|
|
7
|
+
log under `$STIM_HOME/server/`.
|
|
6
8
|
|
|
7
9
|
The design is in
|
|
8
10
|
[`docs/specs/2026-09-25-stim-server-design.md`](../../docs/specs/2026-09-25-stim-server-design.md).
|
|
@@ -11,14 +13,20 @@ The design is in
|
|
|
11
13
|
|
|
12
14
|
```bash
|
|
13
15
|
stim-server [--port <n>] # serve paired clients, port 7787 by default
|
|
14
|
-
stim-server pair [--port <n>]
|
|
16
|
+
stim-server pair [--port <n>] [--control]
|
|
17
|
+
# print a single-use pairing payload
|
|
15
18
|
stim-server devices [list] # list paired devices
|
|
19
|
+
stim-server devices grant <id> --control|--read
|
|
20
|
+
# let a paired device run actions, or only read
|
|
16
21
|
stim-server devices revoke <id> # revoke a paired device
|
|
22
|
+
stim-server log # list the actions paired devices ran
|
|
17
23
|
```
|
|
18
24
|
|
|
19
25
|
`pair --json` prints `{ "qr": <payload>, "expiresAt": "<ISO time>" }`, and
|
|
20
26
|
`devices --json` prints `{ "devices": [...] }` with each device's `id`, `name`,
|
|
21
27
|
`identity`, `pairedAt`, `lastSeenAt` and `capabilities`, never its token hash.
|
|
28
|
+
`log --json` prints `{ "actions": [...] }`, the records described under
|
|
29
|
+
[Actions](#actions).
|
|
22
30
|
|
|
23
31
|
`GET http://127.0.0.1:7787/health` answers requests from this Mac with the
|
|
24
32
|
server's name, versions, protocol, `stimHome`, and the Tailscale state it
|
|
@@ -96,6 +104,18 @@ same peer within a minute block new connections from it for up to a minute.
|
|
|
96
104
|
Paired devices live in `$STIM_HOME/server/devices.json`. Revoking a device
|
|
97
105
|
closes its open connections.
|
|
98
106
|
|
|
107
|
+
## Scopes
|
|
108
|
+
|
|
109
|
+
A paired device has the `read` capability, which serves state, or also
|
|
110
|
+
`control`, which runs [actions](#actions). Pairing grants `read` only, unless
|
|
111
|
+
the pairing code came from `stim-server pair --control`. On the Mac,
|
|
112
|
+
`stim-server devices grant <id> --control` adds control to a paired device and
|
|
113
|
+
`--read` takes it away. Nothing a client sends changes its own capabilities.
|
|
114
|
+
The server checks the device's capabilities in `devices.json` on every action,
|
|
115
|
+
so taking control away applies to open connections at once. `hello` reports
|
|
116
|
+
the capabilities and actions of the connection's device when it connects; a
|
|
117
|
+
connection sees a new grant after it reconnects.
|
|
118
|
+
|
|
99
119
|
## Protocol
|
|
100
120
|
|
|
101
121
|
JSON messages over a WebSocket. Requests are `{ "id", "method", "params" }`,
|
|
@@ -105,8 +125,11 @@ Events are `{ "event", "subscription", ... }`.
|
|
|
105
125
|
- `hello` must come first. Params: `protocol` (1), `client` (`name`,
|
|
106
126
|
`version`), and `auth`, either `{ "pairingToken", "deviceName" }` or
|
|
107
127
|
`{ "deviceToken" }`. The result carries the server name and versions, the
|
|
108
|
-
|
|
109
|
-
|
|
128
|
+
device's `capabilities` (see [Scopes](#scopes)), the `actions` it may run
|
|
129
|
+
(none without `control`), the paired device, and the new `deviceToken` when
|
|
130
|
+
the hello paired. `server.home` is the home folder
|
|
131
|
+
of the user the server runs as, so clients can show paths under it as
|
|
132
|
+
`~/...`.
|
|
110
133
|
- `status.subscribe` returns a subscription id. Each `status` event carries a
|
|
111
134
|
full payload as `stim status --watch --json` prints it. All subscribers share
|
|
112
135
|
one `stim status --watch --json` child, which stops with the last
|
|
@@ -114,7 +137,7 @@ Events are `{ "event", "subscription", ... }`.
|
|
|
114
137
|
- `logs.query` returns `{ "records" }`, and `logs.subscribe` sends `logs`
|
|
115
138
|
events: first the last `tail` matching records, then new ones in batches.
|
|
116
139
|
Both take the Stim Desktop log viewer's filters: `workspace` (required),
|
|
117
|
-
`sources` (`metro`, `client`, `device`, `build`), `slot`, `level` (the
|
|
140
|
+
`sources` (`metro`, `client`, `device`, `build`, `agent`), `slot`, `level` (the
|
|
118
141
|
minimum), `grep` (a regular expression), `errors`, and `tail` (1 to 5000,
|
|
119
142
|
5000 by default). Without `sources`, `errors` keeps the CLI's default error
|
|
120
143
|
scope. They run `stim logs --json` and `stim logs --json --follow` in the
|
|
@@ -123,37 +146,157 @@ Events are `{ "event", "subscription", ... }`.
|
|
|
123
146
|
- `stats.get` and `settings.get` return the payload of `stim stats --json` and
|
|
124
147
|
`stim settings --json`, which masks sensitive values. Without `workspace`,
|
|
125
148
|
they run in the home directory and cover the machine only.
|
|
126
|
-
- `frames.subscribe` takes `workspace`, `platform` (`ios` or `android`)
|
|
127
|
-
`slot` (`default` when absent)
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
149
|
+
- `frames.subscribe` takes `workspace`, `platform` (`ios` or `android`),
|
|
150
|
+
`slot` (`default` when absent), `fps` (1 to 30, 5 by default) and `maxEdge`
|
|
151
|
+
(240 to 2048 pixels, 1280 by default), and sends `frame` events: a JPEG,
|
|
152
|
+
base64 in `data`, with `width`, `height` and `capturedAt`, at most `fps` a
|
|
153
|
+
second and only when the screen changed. It serves only a booted simulator
|
|
154
|
+
or a running emulator that `stim status` lists as owned by that workspace;
|
|
155
|
+
any other device ends the subscription with a `frames-failed` `error`
|
|
156
|
+
event, and so does a device that stops or changes owner. A client whose
|
|
157
|
+
socket has more than two frames unsent skips frames and gets the newest
|
|
158
|
+
once it catches up.
|
|
159
|
+
|
|
160
|
+
Frames come from the `stim-frames` helper. When it starts, the server
|
|
161
|
+
compiles it with `xcrun swiftc` from the Swift sources shipped in
|
|
162
|
+
`dist/stim-frames/` (its own `main.swift` and the frame and input code it
|
|
163
|
+
shares with Stim Desktop), which takes a few seconds, and keeps it in
|
|
164
|
+
`$STIM_HOME/server/helpers/`, named by a hash of the sources and the
|
|
165
|
+
compiler version. For a simulator it renders the display's framebuffer
|
|
166
|
+
(CoreSimulator's IOSurface) when the display reports damage, turned
|
|
167
|
+
upright; for an emulator it keeps one gRPC `streamScreenshot` call open,
|
|
135
168
|
found through the discovery file and token the emulator writes when Stim
|
|
136
|
-
boots it
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
169
|
+
boots it. It scales frames to fit the largest `maxEdge` and paces them to
|
|
170
|
+
the highest `fps` its subscribers asked for. All subscribers of a device
|
|
171
|
+
share one helper, which sends a new subscriber the latest frame and exits
|
|
172
|
+
with the last subscriber or when the server's end of its stdin closes.
|
|
173
|
+
|
|
174
|
+
A client that decodes H.264 adds `video: ["h264"]`. When the helper is
|
|
175
|
+
built, the result carries `video: "h264"`, `fps` may go up to 60, and
|
|
176
|
+
frames arrive as binary WebSocket messages instead of `frame` events: a
|
|
177
|
+
big-endian header (u8 version 1, u8 flags with bit 0 set on a keyframe,
|
|
178
|
+
u16 header length, u32 sequence number of the messages sent on this
|
|
179
|
+
subscription, f64 capture time in milliseconds since the epoch on the
|
|
180
|
+
Mac's clock, u16 width, u16 height, u8 subscription id length and the
|
|
181
|
+
ASCII id), then one Annex-B access unit. Every keyframe carries its SPS
|
|
182
|
+
and PPS, and the stream has no B-frames, so each access unit is shown as
|
|
183
|
+
it arrives. The helper encodes with VideoToolbox in real time: Main
|
|
184
|
+
profile, a keyframe at least every 2 seconds, straight from the
|
|
185
|
+
simulator's IOSurface or from the emulator's RGBA frames, only when the
|
|
186
|
+
screen changed. A subscriber starts at a keyframe, and `frames.keyframe`
|
|
187
|
+
with its `subscription` asks for another one, such as after its decoder
|
|
188
|
+
lost state; a device sends at most one requested keyframe every 250 ms.
|
|
189
|
+
A subscriber whose socket holds more than 256 KB unsent drops frames
|
|
190
|
+
until it drains, then gets a keyframe. While it is behind, the device's
|
|
191
|
+
bitrate halves every 2 seconds, from 3 Mbps down to 0.25 Mbps; it climbs
|
|
192
|
+
back by a quarter every 2 seconds without congestion, up to 8 Mbps. All
|
|
193
|
+
video subscribers of a device share one encoder and its bitrate, and JPEG
|
|
194
|
+
subscribers of the same device still get at most the `fps` they asked
|
|
195
|
+
for. Watching video needs only `read`. Without the helper, the result has
|
|
196
|
+
no `video`, `fps` above 30 is lowered to 30, and `frame` events arrive as
|
|
197
|
+
before; an iPhone Duo and a helper that fails before its first frame also
|
|
198
|
+
fall back to `frame` events within a video subscription.
|
|
199
|
+
|
|
200
|
+
Without the helper (the compiler is missing or fails, which the server
|
|
201
|
+
retries every 5 minutes, or the helper fails before its first frame), for a
|
|
202
|
+
subscription made while it is still being built, and for an iPhone Duo,
|
|
203
|
+
frames come from screenshots, and `fps` and `maxEdge` only cap the rate. Simulators are
|
|
204
|
+
captured with `xcrun simctl io <udid> screenshot`, of the primary display,
|
|
205
|
+
or of the default display when that `simctl` does not accept `primary`. An
|
|
206
|
+
iPhone Duo lights one of two panels: the capture follows the lit one
|
|
207
|
+
(`primary`, the cover, or `primary-1`, the inner panel) and the frame
|
|
208
|
+
carries `posture`, `folded` or `unfolded`. Emulators are captured through
|
|
209
|
+
their gRPC `getScreenshot`, scaled to fit 1280 pixels and converted with
|
|
210
|
+
`sips`. An emulator whose gRPC POSTURE physical model reports a
|
|
211
|
+
posture, such as a `pixel_fold` AVD, is a foldable: its frames carry
|
|
212
|
+
`posture`, `folded` while the screenshot reports a folded display and
|
|
213
|
+
`unfolded` otherwise. The server asks once per capture session, and a
|
|
214
|
+
failed query counts as no hinge until the session restarts. A screenshot is sent only when the screen changed: up to 5 per
|
|
215
|
+
second while it changes, backing off to one capture per second while it
|
|
216
|
+
does not, with capturing taking at most half of each device's time, and at
|
|
217
|
+
most two captures run at once. An emulator Stim booted before it passed
|
|
218
|
+
`-grpc` has no endpoint on either path.
|
|
219
|
+
|
|
220
|
+
- `build.plan` takes `workspace`, `platform` (`ios` or `android`) and `slot`
|
|
221
|
+
(`default` when absent), and returns the payload of
|
|
222
|
+
`stim <platform> --plan --json` run in the workspace: the fingerprint, the
|
|
223
|
+
cache result the next build would get (`local`, `remote` or `false`), the
|
|
224
|
+
prebuild decision, and `expectedMs` with its `basis`. It builds, boots and
|
|
225
|
+
installs nothing and writes no Stim state; a remote cache check can
|
|
226
|
+
download the artifact into a temporary directory, so it gets 150 seconds
|
|
227
|
+
instead of 60. A plan predicting that the build would refuse is a result
|
|
228
|
+
whose `refusal` holds the code, message and remedy. A plan that cannot be
|
|
229
|
+
computed is a `stim-failed` error.
|
|
230
|
+
- `machine.get` returns cheap machine usage, read in the server process
|
|
231
|
+
without running `stim`: `volumes`, one per volume that holds a Stim
|
|
232
|
+
workspace, Stim home, or the simulators, with `mount`, `holds`, `freeBytes`
|
|
233
|
+
(free space without purgeable space, which Stim's disk budget measures) and
|
|
234
|
+
`totalBytes`; `memory` with `totalBytes`, `usedBytes` (the Mac's memory in
|
|
235
|
+
use as Activity Monitor's "Memory Used" counts it: app memory, wired and
|
|
236
|
+
compressed; null off macOS) and the macOS `pressure` level (`normal`,
|
|
237
|
+
`warning`, `critical`, or null); `load` with the 1, 5 and 15
|
|
238
|
+
minute load averages and `cpus`; and `sampledAt`.
|
|
239
|
+
- `machine.history` returns `{ "intervalMs", "samples" }`: machine usage
|
|
240
|
+
sampled every 5 seconds while at least one client is connected, the last
|
|
241
|
+
720 samples (an hour of connected time), kept in memory only. Each sample
|
|
242
|
+
has `at` (epoch milliseconds), `cpu` (busy fraction since the previous
|
|
243
|
+
sample), `memoryUsedBytes`, `memoryPressure` (0 normal, 1 warning, 2
|
|
244
|
+
critical) and `diskFreeBytes` of the startup volume; a field is null when it
|
|
245
|
+
cannot be read. `sinceMs` returns only the samples taken after it.
|
|
144
246
|
- `unsubscribe` ends a subscription.
|
|
247
|
+
- `action` runs an [action](#actions) and returns
|
|
248
|
+
`{ "action", "workspace", "output" }`.
|
|
145
249
|
- An `error` event ends a subscription whose source failed, or whose client
|
|
146
250
|
fell behind (`slow-client`); subscribe again.
|
|
147
251
|
|
|
148
252
|
`workspace` is an environment `path` from a status payload. Any other path is
|
|
149
253
|
refused with `unknown-workspace` and runs nothing. A connection holds at most
|
|
150
|
-
32 subscriptions and runs at most 4 `logs.query`, `stats.get
|
|
151
|
-
`settings.get` requests at a time. Those requests fail after
|
|
152
|
-
32 MiB of output, and closing the
|
|
254
|
+
32 subscriptions and runs at most 4 `logs.query`, `stats.get`,
|
|
255
|
+
`settings.get` and `build.plan` requests at a time. Those requests fail after
|
|
256
|
+
60 seconds, `build.plan` after 150, or at 32 MiB of output, and closing the
|
|
257
|
+
connection stops them. When the command refuses with Stim's error contract on
|
|
258
|
+
stdout, the `stim-failed` message is its code, message and remedy; otherwise
|
|
259
|
+
it is the exit status and the end of stderr. A `stim` child that
|
|
153
260
|
ignores SIGTERM gets SIGKILL a second later. A log subscriber whose socket has more than
|
|
154
261
|
4 MiB unsent gets no more batches until it catches up; past 20,000
|
|
155
262
|
waiting records the server ends that subscription with `slow-client`.
|
|
156
263
|
|
|
264
|
+
## Actions
|
|
265
|
+
|
|
266
|
+
A device with `control` can send `action` with params `{ "action", "workspace" }`:
|
|
267
|
+
|
|
268
|
+
| Action | Params | Runs in the workspace |
|
|
269
|
+
| -------- | -------------------------------------------------------------------- | ------------------------------- |
|
|
270
|
+
| `reload` | `platform` (`ios` or `android`), optional; needed when both are live | `stim reload [platform] --json` |
|
|
271
|
+
| `stop` | none | `stim stop --json` |
|
|
272
|
+
|
|
273
|
+
Each action is one fixed argument list passed to the bundled `stim`, never
|
|
274
|
+
through a shell. `workspace` must be a project path Stim has registered, the
|
|
275
|
+
`path` a status payload lists; the command runs there. The result's `output`
|
|
276
|
+
is the JSON the command printed. The server refuses the request and runs
|
|
277
|
+
nothing with:
|
|
278
|
+
|
|
279
|
+
- `forbidden` when the device has only `read`;
|
|
280
|
+
- `unknown-action` for any other action;
|
|
281
|
+
- `bad-request` for a missing `workspace`, a `platform` that is not `ios` or
|
|
282
|
+
`android`, or any other param;
|
|
283
|
+
- `unknown-workspace` for a path Stim has not registered or that no longer
|
|
284
|
+
exists;
|
|
285
|
+
- `action-busy` while another action runs in that workspace. The server runs
|
|
286
|
+
one action per workspace at a time, across all connections.
|
|
287
|
+
|
|
288
|
+
A command that exits with an error fails with `action-failed` and the message
|
|
289
|
+
and remedy it printed. An action fails with `action-failed` after 120 seconds,
|
|
290
|
+
and its `stim` child gets SIGTERM. An action keeps running when its client
|
|
291
|
+
disconnects, and stopping `stim-server` stops it.
|
|
292
|
+
|
|
293
|
+
Every `action` request from a paired device, refused or run, appends one line
|
|
294
|
+
to `$STIM_HOME/server/actions.ndjson`: `at`, `device` (`id` and `name`),
|
|
295
|
+
`action`, `workspace`, `platform` when given, `ok`, `error` when it failed,
|
|
296
|
+
and `durationMs` when it ran. Strings from the client and error messages are
|
|
297
|
+
cut to 256 characters. `stim-server log` prints them, with control characters
|
|
298
|
+
replaced by `?`.
|
|
299
|
+
|
|
157
300
|
The error codes `unauthorized`, `pairing-expired`, and `protocol-unsupported`
|
|
158
301
|
refuse the client until it pairs again or updates; clients retry the others.
|
|
159
302
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
//#region src/protocol.ts
|
|
2
2
|
const PROTOCOL_VERSION = 1;
|
|
3
3
|
const PROTOCOL_SCHEMA_FILE = "protocol.schema.json";
|
|
4
|
-
|
|
4
|
+
/** `read` serves state. `control` also runs {@link ACTIONS}; only the Mac grants it. */
|
|
5
|
+
const CAPABILITIES = ["read", "control"];
|
|
5
6
|
const METHODS = [
|
|
6
7
|
"hello",
|
|
7
8
|
"status.subscribe",
|
|
@@ -10,7 +11,12 @@ const METHODS = [
|
|
|
10
11
|
"stats.get",
|
|
11
12
|
"settings.get",
|
|
12
13
|
"frames.subscribe",
|
|
13
|
-
"
|
|
14
|
+
"frames.keyframe",
|
|
15
|
+
"build.plan",
|
|
16
|
+
"machine.get",
|
|
17
|
+
"machine.history",
|
|
18
|
+
"unsubscribe",
|
|
19
|
+
"action"
|
|
14
20
|
];
|
|
15
21
|
/**
|
|
16
22
|
* `unauthorized`, `pairing-expired` and `protocol-unsupported` refuse the client until it pairs again or
|
|
@@ -31,13 +37,18 @@ const ERROR_CODES = [
|
|
|
31
37
|
"status-failed",
|
|
32
38
|
"logs-failed",
|
|
33
39
|
"stim-failed",
|
|
34
|
-
"frames-failed"
|
|
40
|
+
"frames-failed",
|
|
41
|
+
"forbidden",
|
|
42
|
+
"unknown-action",
|
|
43
|
+
"action-busy",
|
|
44
|
+
"action-failed"
|
|
35
45
|
];
|
|
36
46
|
const LOG_SOURCES = [
|
|
37
47
|
"metro",
|
|
38
48
|
"client",
|
|
39
49
|
"device",
|
|
40
|
-
"build"
|
|
50
|
+
"build",
|
|
51
|
+
"agent"
|
|
41
52
|
];
|
|
42
53
|
const LOG_LEVELS = [
|
|
43
54
|
"debug",
|
|
@@ -47,6 +58,22 @@ const LOG_LEVELS = [
|
|
|
47
58
|
"fatal"
|
|
48
59
|
];
|
|
49
60
|
const MAX_LOG_TAIL = 5e3;
|
|
61
|
+
const FRAME_FPS = {
|
|
62
|
+
default: 5,
|
|
63
|
+
max: 30,
|
|
64
|
+
video: 60
|
|
65
|
+
};
|
|
66
|
+
const FRAME_EDGE = {
|
|
67
|
+
min: 240,
|
|
68
|
+
default: 1280,
|
|
69
|
+
max: 2048
|
|
70
|
+
};
|
|
71
|
+
const VIDEO_CODECS = ["h264"];
|
|
72
|
+
const VIDEO_HEADER_VERSION = 1;
|
|
73
|
+
/** The keyframe bit of a {@link VideoPacket}'s flags. */
|
|
74
|
+
const VIDEO_KEYFRAME = 1;
|
|
75
|
+
/** Each action runs one fixed `stim` command in the workspace. */
|
|
76
|
+
const ACTIONS = ["reload", "stop"];
|
|
50
77
|
const requestId = { type: ["integer", "string"] };
|
|
51
78
|
const protocolError = {
|
|
52
79
|
type: "object",
|
|
@@ -140,6 +167,7 @@ function protocolJsonSchema() {
|
|
|
140
167
|
"protocol",
|
|
141
168
|
"server",
|
|
142
169
|
"capabilities",
|
|
170
|
+
"actions",
|
|
143
171
|
"device"
|
|
144
172
|
],
|
|
145
173
|
additionalProperties: false,
|
|
@@ -150,19 +178,25 @@ function protocolJsonSchema() {
|
|
|
150
178
|
required: [
|
|
151
179
|
"name",
|
|
152
180
|
"version",
|
|
153
|
-
"stim"
|
|
181
|
+
"stim",
|
|
182
|
+
"home"
|
|
154
183
|
],
|
|
155
184
|
additionalProperties: false,
|
|
156
185
|
properties: {
|
|
157
186
|
name: { type: "string" },
|
|
158
187
|
version: { type: "string" },
|
|
159
|
-
stim: { type: "string" }
|
|
188
|
+
stim: { type: "string" },
|
|
189
|
+
home: { type: "string" }
|
|
160
190
|
}
|
|
161
191
|
},
|
|
162
192
|
capabilities: {
|
|
163
193
|
type: "array",
|
|
164
194
|
items: { enum: [...CAPABILITIES] }
|
|
165
195
|
},
|
|
196
|
+
actions: {
|
|
197
|
+
type: "array",
|
|
198
|
+
items: { enum: [...ACTIONS] }
|
|
199
|
+
},
|
|
166
200
|
device: {
|
|
167
201
|
type: "object",
|
|
168
202
|
required: ["id", "name"],
|
|
@@ -231,6 +265,81 @@ function protocolJsonSchema() {
|
|
|
231
265
|
type: "string",
|
|
232
266
|
minLength: 1,
|
|
233
267
|
default: "default"
|
|
268
|
+
},
|
|
269
|
+
fps: {
|
|
270
|
+
type: "integer",
|
|
271
|
+
minimum: 1,
|
|
272
|
+
maximum: FRAME_FPS.video,
|
|
273
|
+
default: FRAME_FPS.default,
|
|
274
|
+
description: `At most ${FRAME_FPS.max} unless the result offers video.`
|
|
275
|
+
},
|
|
276
|
+
maxEdge: {
|
|
277
|
+
type: "integer",
|
|
278
|
+
minimum: FRAME_EDGE.min,
|
|
279
|
+
maximum: FRAME_EDGE.max,
|
|
280
|
+
default: FRAME_EDGE.default
|
|
281
|
+
},
|
|
282
|
+
video: {
|
|
283
|
+
type: "array",
|
|
284
|
+
items: { enum: [...VIDEO_CODECS] }
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
ActionParams: { oneOf: [{
|
|
289
|
+
type: "object",
|
|
290
|
+
required: ["action", "workspace"],
|
|
291
|
+
additionalProperties: false,
|
|
292
|
+
properties: {
|
|
293
|
+
action: { const: "reload" },
|
|
294
|
+
workspace: {
|
|
295
|
+
type: "string",
|
|
296
|
+
description: "An environment path from a status payload."
|
|
297
|
+
},
|
|
298
|
+
platform: { enum: ["ios", "android"] }
|
|
299
|
+
}
|
|
300
|
+
}, {
|
|
301
|
+
type: "object",
|
|
302
|
+
required: ["action", "workspace"],
|
|
303
|
+
additionalProperties: false,
|
|
304
|
+
properties: {
|
|
305
|
+
action: { const: "stop" },
|
|
306
|
+
workspace: {
|
|
307
|
+
type: "string",
|
|
308
|
+
description: "An environment path from a status payload."
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}] },
|
|
312
|
+
ActionResult: {
|
|
313
|
+
type: "object",
|
|
314
|
+
required: [
|
|
315
|
+
"action",
|
|
316
|
+
"workspace",
|
|
317
|
+
"output"
|
|
318
|
+
],
|
|
319
|
+
additionalProperties: false,
|
|
320
|
+
properties: {
|
|
321
|
+
action: { enum: [...ACTIONS] },
|
|
322
|
+
workspace: { type: "string" },
|
|
323
|
+
output: {
|
|
324
|
+
type: "object",
|
|
325
|
+
description: "The JSON the command printed."
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
BuildPlanParams: {
|
|
330
|
+
type: "object",
|
|
331
|
+
required: ["workspace", "platform"],
|
|
332
|
+
additionalProperties: false,
|
|
333
|
+
properties: {
|
|
334
|
+
workspace: {
|
|
335
|
+
type: "string",
|
|
336
|
+
description: "An environment path from a status payload."
|
|
337
|
+
},
|
|
338
|
+
platform: { enum: ["ios", "android"] },
|
|
339
|
+
slot: {
|
|
340
|
+
type: "string",
|
|
341
|
+
pattern: "^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$",
|
|
342
|
+
default: "default"
|
|
234
343
|
}
|
|
235
344
|
}
|
|
236
345
|
},
|
|
@@ -242,12 +351,142 @@ function protocolJsonSchema() {
|
|
|
242
351
|
description: "An environment path from a status payload."
|
|
243
352
|
} }
|
|
244
353
|
},
|
|
354
|
+
MachineHistory: {
|
|
355
|
+
type: "object",
|
|
356
|
+
required: ["intervalMs", "samples"],
|
|
357
|
+
additionalProperties: false,
|
|
358
|
+
properties: {
|
|
359
|
+
intervalMs: { type: "integer" },
|
|
360
|
+
samples: {
|
|
361
|
+
type: "array",
|
|
362
|
+
items: {
|
|
363
|
+
type: "object",
|
|
364
|
+
required: [
|
|
365
|
+
"at",
|
|
366
|
+
"cpu",
|
|
367
|
+
"memoryUsedBytes",
|
|
368
|
+
"memoryPressure",
|
|
369
|
+
"diskFreeBytes"
|
|
370
|
+
],
|
|
371
|
+
additionalProperties: false,
|
|
372
|
+
properties: {
|
|
373
|
+
at: { type: "integer" },
|
|
374
|
+
cpu: { type: ["number", "null"] },
|
|
375
|
+
memoryUsedBytes: { type: ["number", "null"] },
|
|
376
|
+
memoryPressure: { enum: [
|
|
377
|
+
0,
|
|
378
|
+
1,
|
|
379
|
+
2,
|
|
380
|
+
null
|
|
381
|
+
] },
|
|
382
|
+
diskFreeBytes: { type: ["number", "null"] }
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
MachineUsage: {
|
|
389
|
+
type: "object",
|
|
390
|
+
required: [
|
|
391
|
+
"volumes",
|
|
392
|
+
"memory",
|
|
393
|
+
"load",
|
|
394
|
+
"cpu",
|
|
395
|
+
"sampledAt"
|
|
396
|
+
],
|
|
397
|
+
additionalProperties: false,
|
|
398
|
+
properties: {
|
|
399
|
+
volumes: {
|
|
400
|
+
type: "array",
|
|
401
|
+
items: {
|
|
402
|
+
type: "object",
|
|
403
|
+
required: [
|
|
404
|
+
"mount",
|
|
405
|
+
"holds",
|
|
406
|
+
"freeBytes",
|
|
407
|
+
"totalBytes"
|
|
408
|
+
],
|
|
409
|
+
additionalProperties: false,
|
|
410
|
+
properties: {
|
|
411
|
+
mount: { type: "string" },
|
|
412
|
+
holds: {
|
|
413
|
+
type: "array",
|
|
414
|
+
items: { type: "string" }
|
|
415
|
+
},
|
|
416
|
+
freeBytes: { type: "number" },
|
|
417
|
+
totalBytes: { type: "number" }
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
},
|
|
421
|
+
memory: {
|
|
422
|
+
type: "object",
|
|
423
|
+
required: [
|
|
424
|
+
"totalBytes",
|
|
425
|
+
"usedBytes",
|
|
426
|
+
"pressure"
|
|
427
|
+
],
|
|
428
|
+
additionalProperties: false,
|
|
429
|
+
properties: {
|
|
430
|
+
totalBytes: { type: "number" },
|
|
431
|
+
usedBytes: { type: ["number", "null"] },
|
|
432
|
+
pressure: { enum: [
|
|
433
|
+
"normal",
|
|
434
|
+
"warning",
|
|
435
|
+
"critical",
|
|
436
|
+
null
|
|
437
|
+
] }
|
|
438
|
+
}
|
|
439
|
+
},
|
|
440
|
+
load: {
|
|
441
|
+
type: "object",
|
|
442
|
+
required: [
|
|
443
|
+
"avg1",
|
|
444
|
+
"avg5",
|
|
445
|
+
"avg15",
|
|
446
|
+
"cpus"
|
|
447
|
+
],
|
|
448
|
+
additionalProperties: false,
|
|
449
|
+
properties: {
|
|
450
|
+
avg1: { type: "number" },
|
|
451
|
+
avg5: { type: "number" },
|
|
452
|
+
avg15: { type: "number" },
|
|
453
|
+
cpus: { type: "integer" }
|
|
454
|
+
}
|
|
455
|
+
},
|
|
456
|
+
cpu: {
|
|
457
|
+
type: "object",
|
|
458
|
+
required: ["usage", "cores"],
|
|
459
|
+
additionalProperties: false,
|
|
460
|
+
properties: {
|
|
461
|
+
usage: { type: ["number", "null"] },
|
|
462
|
+
cores: { type: "integer" }
|
|
463
|
+
}
|
|
464
|
+
},
|
|
465
|
+
sampledAt: {
|
|
466
|
+
type: "string",
|
|
467
|
+
format: "date-time"
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
},
|
|
245
471
|
ClientRequest: { oneOf: [
|
|
246
472
|
request("hello", { $ref: "#/$defs/HelloParams" }),
|
|
247
473
|
request("status.subscribe"),
|
|
248
474
|
request("logs.query", { $ref: "#/$defs/LogFilter" }),
|
|
249
475
|
request("logs.subscribe", { $ref: "#/$defs/LogFilter" }),
|
|
250
476
|
request("frames.subscribe", { $ref: "#/$defs/FrameTarget" }),
|
|
477
|
+
request("frames.keyframe", {
|
|
478
|
+
type: "object",
|
|
479
|
+
required: ["subscription"],
|
|
480
|
+
additionalProperties: false,
|
|
481
|
+
properties: { subscription: { type: "string" } }
|
|
482
|
+
}),
|
|
483
|
+
request("build.plan", { $ref: "#/$defs/BuildPlanParams" }),
|
|
484
|
+
request("machine.get"),
|
|
485
|
+
optionalParams("machine.history", {
|
|
486
|
+
type: "object",
|
|
487
|
+
additionalProperties: false,
|
|
488
|
+
properties: { sinceMs: { type: "number" } }
|
|
489
|
+
}),
|
|
251
490
|
optionalParams("stats.get", { $ref: "#/$defs/WorkspaceParams" }),
|
|
252
491
|
optionalParams("settings.get", { $ref: "#/$defs/WorkspaceParams" }),
|
|
253
492
|
request("unsubscribe", {
|
|
@@ -255,7 +494,8 @@ function protocolJsonSchema() {
|
|
|
255
494
|
required: ["subscription"],
|
|
256
495
|
additionalProperties: false,
|
|
257
496
|
properties: { subscription: { type: "string" } }
|
|
258
|
-
})
|
|
497
|
+
}),
|
|
498
|
+
request("action", { $ref: "#/$defs/ActionParams" })
|
|
259
499
|
] },
|
|
260
500
|
ServerResponse: { oneOf: [{
|
|
261
501
|
type: "object",
|
|
@@ -265,11 +505,17 @@ function protocolJsonSchema() {
|
|
|
265
505
|
id: requestId,
|
|
266
506
|
result: { anyOf: [
|
|
267
507
|
{ $ref: "#/$defs/HelloResult" },
|
|
508
|
+
{ $ref: "#/$defs/ActionResult" },
|
|
509
|
+
{ $ref: "#/$defs/MachineUsage" },
|
|
510
|
+
{ $ref: "#/$defs/MachineHistory" },
|
|
268
511
|
{
|
|
269
512
|
type: "object",
|
|
270
513
|
required: ["subscription"],
|
|
271
514
|
additionalProperties: false,
|
|
272
|
-
properties: {
|
|
515
|
+
properties: {
|
|
516
|
+
subscription: { type: "string" },
|
|
517
|
+
video: { enum: [...VIDEO_CODECS] }
|
|
518
|
+
}
|
|
273
519
|
},
|
|
274
520
|
{
|
|
275
521
|
type: "object",
|
|
@@ -282,7 +528,7 @@ function protocolJsonSchema() {
|
|
|
282
528
|
},
|
|
283
529
|
{
|
|
284
530
|
type: "object",
|
|
285
|
-
description: "The payload of `stim stats --json` or `stim
|
|
531
|
+
description: "The payload of `stim stats --json`, `stim settings --json` or `stim ios|android --plan --json`, or {} for unsubscribe."
|
|
286
532
|
}
|
|
287
533
|
] }
|
|
288
534
|
}
|
|
@@ -363,7 +609,8 @@ function protocolJsonSchema() {
|
|
|
363
609
|
data: {
|
|
364
610
|
type: "string",
|
|
365
611
|
contentEncoding: "base64"
|
|
366
|
-
}
|
|
612
|
+
},
|
|
613
|
+
posture: { enum: ["folded", "unfolded"] }
|
|
367
614
|
}
|
|
368
615
|
},
|
|
369
616
|
{
|
|
@@ -404,4 +651,4 @@ function protocolJsonSchema() {
|
|
|
404
651
|
};
|
|
405
652
|
}
|
|
406
653
|
//#endregion
|
|
407
|
-
export {
|
|
654
|
+
export { FRAME_FPS as a, MAX_LOG_TAIL as c, PROTOCOL_VERSION as d, VIDEO_CODECS as f, protocolJsonSchema as h, FRAME_EDGE as i, METHODS as l, VIDEO_KEYFRAME as m, CAPABILITIES as n, LOG_LEVELS as o, VIDEO_HEADER_VERSION as p, ERROR_CODES as r, LOG_SOURCES as s, ACTIONS as t, PROTOCOL_SCHEMA_FILE as u };
|