@stim-cli/server 1.23.0 → 1.24.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 CHANGED
@@ -547,8 +547,11 @@ are not carried over: phones pair again, and remote machines are approved
547
547
  again with a new setup ticket.
548
548
 
549
549
  Stim Desktop and other clients of the server's own user on the same machine do
550
- not pair. They connect to the Unix socket `$STIM_HOME/server/local.sock`, mode
551
- 0600 in a 0700 directory, which serves only this user and needs no handshake.
550
+ not pair. They connect to the Unix socket `$STIM_HOME/server/local.sock`. If
551
+ that path exceeds the platform's limit (103 UTF-8 bytes on macOS, 107 on Linux), the socket instead lives at
552
+ `/tmp/stim-sockets-<uid>/<sha256-of-normalized-socket-path>.sock`, so long hosted
553
+ session homes remain usable and distinct homes keep distinct sockets. The
554
+ socket is mode 0600 in a mode 0700 directory owned by this user and needs no handshake.
552
555
  That connection gets read and control. The Desktop-only methods (`route.setup`,
553
556
  `machines.update.*`, `device-host.sessions` and `device-host.sessions.stop`)
554
557
  work only there; on a server Desktop started, only after the private Desktop
@@ -556,8 +559,13 @@ bootstrap proof (see [Private Desktop bootstrap](#private-desktop-bootstrap)).
556
559
  Windows has no socket.
557
560
 
558
561
  The network address, `tailscale whois` and the `X-Forwarded-For` header that
559
- `tailscale serve` adds never authenticate. They only label a record (`from` in
560
- `stim-server devices`) and key the rate limits below.
562
+ `tailscale serve` adds never authenticate. They label a record (`from` in
563
+ `stim-server devices`) and key the rate limits below. Tailscale rewrites the
564
+ header to the real tailnet source, so remote clients cannot forge it. A local
565
+ process on the host can connect to the loopback port and forge it to lock one
566
+ named peer, or the shared local bucket, out for the lockout window. It
567
+ authenticates nothing and reads nothing. This is accepted because it needs
568
+ local code execution on the host.
561
569
 
562
570
  A connection must finish the handshake and send `hello` within 5 seconds of
563
571
  each step. Five failed attempts from the same peer address within a minute
@@ -1143,6 +1151,12 @@ stream over WireGuard with socket backpressure; no public tunnel, Funnel or
1143
1151
  Tailscale serve configuration change is needed. The 64 KiB server message
1144
1152
  limit remains unchanged.
1145
1153
 
1154
+ An approved client calls `device-host.metro.ports` with `{ports}`, at most 256
1155
+ of them, before it starts Metro for an iOS Expo dev client, and the host
1156
+ answers `{busy}`: the listed ports that already accept loopback connections,
1157
+ apart from the client's own dev-client bridges. The feature
1158
+ `hosted-metro-ports` advertises it.
1159
+
1146
1160
  Replaying the same open request keeps the port. Client disconnection leaves
1147
1161
  the bridge available for the same session while its server owner lives. Call
1148
1162
  `device-host.metro.close` to replace a gateway, then reopen on the same port;
@@ -1700,7 +1714,17 @@ connected since stim-server started keeps its directory when revoked.
1700
1714
  `ccache` or `none`) and `stimBuild`, after the whole manifest of `repo` was
1701
1715
  synced on this connection. A peer-cache-capable React Native caller can set
1702
1716
  `cacheArtifact: true` to retain a completed development archive when its cache
1703
- write and remote policies allow it. It returns `{ "job" }`. It fails with
1717
+ write and remote policies allow it. With the `fingerprint-sources` feature, a
1718
+ caller can send `fingerprintSources`, the `id` and `hash` of each source of
1719
+ `fingerprint`, and a `fingerprint-mismatch` failure names the sources that
1720
+ differ and the synced files prebuild or `pod install` rewrote. With the
1721
+ `fingerprint-ignored` feature, it can send `fingerprintIgnored`, the paths
1722
+ relative to `project` that its git ignores for `fingerprint` through
1723
+ `.git/info/exclude` or a global excludes file, which the machine's synced
1724
+ checkout has no copy of. With the `fingerprint-tracked` feature, it can
1725
+ send `fingerprintTracked`, the top-level entry names of `project` that its
1726
+ git tracks although an ignore pattern matches them, which the machine's
1727
+ empty index would skip. It returns `{ "job" }`. It fails with
1704
1728
  `build-busy` while the offer would be declined, naming the reason, or while
1705
1729
  another build of the same client and repository runs, or while every one
1706
1730
  of this machine's `concurrency.maxBuilds` slots is held, and with
@@ -2497,7 +2521,6 @@ silent only when every notification it sums up is. An older server ignores
2497
2521
  `levels` and alerts for every event but `started`.
2498
2522
 
2499
2523
  The server and phone import the same pure rules from `@stim-cli/core/oversight`.
2500
- The phone formats home attention messages locally from the facts the shared rule selects.
2501
2524
  `__tests__/oversight-agreement.test.ts` checks their categories, workspace names
2502
2525
  and needs-attention vectors. Notification delivery remains local to each.
2503
2526
 
@@ -2275,6 +2275,20 @@ function protocolJsonSchema() {
2275
2275
  pattern: "^[a-f0-9]{64}$"
2276
2276
  }
2277
2277
  }, ["gatewayPort", "secret"])),
2278
+ request("device-host.metro.ports", {
2279
+ type: "object",
2280
+ required: ["ports"],
2281
+ additionalProperties: false,
2282
+ properties: { ports: {
2283
+ type: "array",
2284
+ maxItems: 256,
2285
+ items: {
2286
+ type: "integer",
2287
+ minimum: 1,
2288
+ maximum: 65535
2289
+ }
2290
+ } }
2291
+ }),
2278
2292
  request("device-host.metro.close", session({})),
2279
2293
  request("device-host.frames.subscribe", session({
2280
2294
  fps: {
@@ -3106,6 +3120,48 @@ function protocolJsonSchema() {
3106
3120
  type: ["string", "null"],
3107
3121
  minLength: 1
3108
3122
  },
3123
+ fingerprintSources: {
3124
+ type: "array",
3125
+ maxItems: 2e4,
3126
+ description: "The id and hash of each source of fingerprint, to name the sources that differ.",
3127
+ items: {
3128
+ type: "object",
3129
+ required: ["id", "hash"],
3130
+ additionalProperties: false,
3131
+ properties: {
3132
+ id: {
3133
+ type: "string",
3134
+ minLength: 1,
3135
+ maxLength: 1024
3136
+ },
3137
+ hash: {
3138
+ type: ["string", "null"],
3139
+ pattern: "^[0-9a-f]{1,128}$"
3140
+ }
3141
+ }
3142
+ }
3143
+ },
3144
+ fingerprintIgnored: {
3145
+ type: "array",
3146
+ maxItems: 2e4,
3147
+ description: "Paths relative to project that git ignores for fingerprint on the caller, such as through .git/info/exclude.",
3148
+ items: {
3149
+ type: "string",
3150
+ minLength: 1,
3151
+ maxLength: 1024
3152
+ }
3153
+ },
3154
+ fingerprintTracked: {
3155
+ type: "array",
3156
+ maxItems: 2e4,
3157
+ description: "Top-level entries of project that the caller git tracks for fingerprint although an ignore pattern matches them.",
3158
+ items: {
3159
+ type: "string",
3160
+ minLength: 1,
3161
+ maxLength: 255,
3162
+ pattern: "^[^/\\u0000]+$"
3163
+ }
3164
+ },
3109
3165
  native: { oneOf: [{
3110
3166
  type: "object",
3111
3167
  required: [
package/dist/protocol.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { t as protocolJsonSchema } from "./protocol-BKpAsa9G.mjs";
1
+ import { t as protocolJsonSchema } from "./protocol-B8Ztv1K4.mjs";
2
2
  export * from "@stim-cli/core/protocol";
3
3
  export { protocolJsonSchema };
@@ -1271,10 +1271,14 @@
1271
1271
  "native-gradle-artifact-cache",
1272
1272
  "native-gradle-peer-cache",
1273
1273
  "react-native-peer-cache",
1274
+ "fingerprint-sources",
1275
+ "fingerprint-ignored",
1276
+ "fingerprint-tracked",
1274
1277
  "swiftpm-peer-cache",
1275
1278
  "build-dependency-cache",
1276
1279
  "hosted-android-data",
1277
1280
  "hosted-android-process",
1281
+ "hosted-metro-ports",
1278
1282
  "server-update",
1279
1283
  "status-views",
1280
1284
  "desktop-reports",
@@ -3183,6 +3187,44 @@
3183
3187
  }
3184
3188
  }
3185
3189
  },
3190
+ {
3191
+ "type": "object",
3192
+ "required": [
3193
+ "id",
3194
+ "method",
3195
+ "params"
3196
+ ],
3197
+ "additionalProperties": false,
3198
+ "properties": {
3199
+ "id": {
3200
+ "type": [
3201
+ "integer",
3202
+ "string"
3203
+ ]
3204
+ },
3205
+ "method": {
3206
+ "const": "device-host.metro.ports"
3207
+ },
3208
+ "params": {
3209
+ "type": "object",
3210
+ "required": [
3211
+ "ports"
3212
+ ],
3213
+ "additionalProperties": false,
3214
+ "properties": {
3215
+ "ports": {
3216
+ "type": "array",
3217
+ "maxItems": 256,
3218
+ "items": {
3219
+ "type": "integer",
3220
+ "minimum": 1,
3221
+ "maximum": 65535
3222
+ }
3223
+ }
3224
+ }
3225
+ }
3226
+ }
3227
+ },
3186
3228
  {
3187
3229
  "type": "object",
3188
3230
  "required": [
@@ -7303,6 +7345,54 @@
7303
7345
  ],
7304
7346
  "minLength": 1
7305
7347
  },
7348
+ "fingerprintSources": {
7349
+ "type": "array",
7350
+ "maxItems": 20000,
7351
+ "description": "The id and hash of each source of fingerprint, to name the sources that differ.",
7352
+ "items": {
7353
+ "type": "object",
7354
+ "required": [
7355
+ "id",
7356
+ "hash"
7357
+ ],
7358
+ "additionalProperties": false,
7359
+ "properties": {
7360
+ "id": {
7361
+ "type": "string",
7362
+ "minLength": 1,
7363
+ "maxLength": 1024
7364
+ },
7365
+ "hash": {
7366
+ "type": [
7367
+ "string",
7368
+ "null"
7369
+ ],
7370
+ "pattern": "^[0-9a-f]{1,128}$"
7371
+ }
7372
+ }
7373
+ }
7374
+ },
7375
+ "fingerprintIgnored": {
7376
+ "type": "array",
7377
+ "maxItems": 20000,
7378
+ "description": "Paths relative to project that git ignores for fingerprint on the caller, such as through .git/info/exclude.",
7379
+ "items": {
7380
+ "type": "string",
7381
+ "minLength": 1,
7382
+ "maxLength": 1024
7383
+ }
7384
+ },
7385
+ "fingerprintTracked": {
7386
+ "type": "array",
7387
+ "maxItems": 20000,
7388
+ "description": "Top-level entries of project that the caller git tracks for fingerprint although an ignore pattern matches them.",
7389
+ "items": {
7390
+ "type": "string",
7391
+ "minLength": 1,
7392
+ "maxLength": 255,
7393
+ "pattern": "^[^/\\u0000]+$"
7394
+ }
7395
+ },
7306
7396
  "native": {
7307
7397
  "oneOf": [
7308
7398
  {
@@ -1,4 +1,4 @@
1
- import { n as protocol_exports } from "./protocol-BKpAsa9G.mjs";
1
+ import { n as protocol_exports } from "./protocol-B8Ztv1K4.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import { execFile, execFileSync, spawn } from "node:child_process";
4
4
  import { appendFileSync, chmodSync, closeSync, constants, copyFileSync, cpSync, createReadStream, existsSync, futimesSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, rmdirSync, statSync, statfsSync, watch, writeFileSync, writeSync } from "node:fs";
@@ -3226,6 +3226,32 @@ function validFile(file) {
3226
3226
  const validRepo = (repo) => typeof repo === "string" && new RegExp(protocol_exports.BUILD_REPO_PATTERN).test(repo);
3227
3227
  const gb = (bytes) => (bytes / 1024 ** 3).toFixed(1);
3228
3228
  const optional = (value) => typeof value === "string" ? value : null;
3229
+ const MAX_FINGERPRINT_SOURCES = 2e4;
3230
+ function fingerprintSources(value) {
3231
+ if (value === void 0) return void 0;
3232
+ if (!Array.isArray(value) || value.length > MAX_FINGERPRINT_SOURCES) return null;
3233
+ return value.every((source) => isJsonObject(source) && Object.keys(source).every((key) => key === "id" || key === "hash") && typeof source.id === "string" && source.id.length > 0 && source.id.length <= MAX_PATH_CHARS && (source.hash === null || typeof source.hash === "string" && /^[0-9a-f]{1,128}$/.test(source.hash))) ? value : null;
3234
+ }
3235
+ function fingerprintIgnored(value) {
3236
+ if (value === void 0) return void 0;
3237
+ if (!Array.isArray(value) || value.length > MAX_FINGERPRINT_SOURCES) return null;
3238
+ const paths = [];
3239
+ for (const path of value) {
3240
+ if (typeof path !== "string" || path.length === 0 || path.length > MAX_PATH_CHARS) return null;
3241
+ paths.push(path);
3242
+ }
3243
+ return paths;
3244
+ }
3245
+ function fingerprintTracked(value) {
3246
+ if (value === void 0) return void 0;
3247
+ if (!Array.isArray(value) || value.length > MAX_FINGERPRINT_SOURCES) return null;
3248
+ const names = [];
3249
+ for (const name of value) {
3250
+ if (!(typeof name === "string" && name.length > 0 && name.length <= 255 && name !== "." && name !== ".." && !/[/\0]/.test(name))) return null;
3251
+ names.push(name);
3252
+ }
3253
+ return names;
3254
+ }
3229
3255
  const GRADLE_NAME = /^[A-Za-z0-9_-]{1,100}$/;
3230
3256
  const GRADLE_ROOT = /^[A-Za-z0-9._/ -]{1,200}$/;
3231
3257
  const GRADLE_MODULE = /^(:(?!\.\.?(?::|$))[A-Za-z0-9._-]+){1,20}$/;
@@ -3522,7 +3548,7 @@ var BuildHost = class {
3522
3548
  },
3523
3549
  warm: {
3524
3550
  checkout: existsSync(join(area, "src")),
3525
- dependencies: typeof params.lockfile === "string" && dependencies !== void 0 && reusableDependencies(area, dependencies.project, dependencies.packageName, params.lockfile),
3551
+ dependencies: typeof params.lockfile === "string" && dependencies !== void 0 && reusableDependencies(area, dependencies.project, params.lockfile, this.options.env),
3526
3552
  build: false
3527
3553
  },
3528
3554
  ...artifact !== void 0 ? { artifact } : {}
@@ -4305,7 +4331,13 @@ var BuildSession = class {
4305
4331
  native = input;
4306
4332
  }
4307
4333
  if (native?.provider !== "gradle" && (typeof params.fingerprint !== "string" || !params.fingerprint)) return refusal("bad-request", "build.start needs params.fingerprint.");
4334
+ const expectedSources = fingerprintSources(params.fingerprintSources);
4335
+ if (expectedSources === null) return refusal("bad-request", `params.fingerprintSources must be at most ${MAX_FINGERPRINT_SOURCES} sources, each an id and a hex hash or null.`);
4308
4336
  if (!native && [...this.files.values()].some((file) => file.kind === "directory")) return refusal("bad-request", "Directory entries require a native build request.");
4337
+ const expectedIgnored = fingerprintIgnored(params.fingerprintIgnored);
4338
+ if (expectedIgnored === null) return refusal("bad-request", `params.fingerprintIgnored must be at most ${MAX_FINGERPRINT_SOURCES} paths of at most ${MAX_PATH_CHARS} characters.`);
4339
+ const expectedTracked = fingerprintTracked(params.fingerprintTracked);
4340
+ if (expectedTracked === null) return refusal("bad-request", `params.fingerprintTracked must be at most ${MAX_FINGERPRINT_SOURCES} top-level entry names.`);
4309
4341
  const android = platform === "android" ? androidOptions(params.android) : null;
4310
4342
  if (platform === "android" && !android) return refusal("bad-request", "An android build needs params.android: variant and abi (string or null), gradleBuildCache, pch and compilerCache.");
4311
4343
  let macos = null;
@@ -4333,7 +4365,6 @@ var BuildSession = class {
4333
4365
  android,
4334
4366
  macos,
4335
4367
  project: params.project,
4336
- packageName: optional(params.packageName),
4337
4368
  isExpo: params.isExpo === true,
4338
4369
  cacheArtifact: params.cacheArtifact === true,
4339
4370
  stimBuild: params.stimBuild,
@@ -4342,6 +4373,9 @@ var BuildSession = class {
4342
4373
  iosProjectPath: optional(params.iosProjectPath),
4343
4374
  runtime: optional(params.runtime),
4344
4375
  expectedFingerprint: params.fingerprint,
4376
+ ...expectedSources ? { expectedSources } : {},
4377
+ ...expectedIgnored ? { expectedIgnored } : {},
4378
+ ...expectedTracked ? { expectedTracked } : {},
4345
4379
  optimizations: isJsonObject(params.optimizations) ? params.optimizations : null
4346
4380
  }
4347
4381
  });
@@ -6823,6 +6857,28 @@ function hostedDeviceExclusions(records) {
6823
6857
  key: hostedDeviceId(record.device)
6824
6858
  }] : []);
6825
6859
  }
6860
+ /** macOS lets 127.0.0.1 bind beside a wildcard listener and takes its loopback traffic, so probe by connecting. */
6861
+ function loopbackAccepts(port) {
6862
+ const accepts = (host) => new Promise((resolve) => {
6863
+ const socket = connect({
6864
+ host,
6865
+ port
6866
+ });
6867
+ const done = (open) => {
6868
+ socket.destroy();
6869
+ resolve(open);
6870
+ };
6871
+ socket.setTimeout(1e3, () => done(true));
6872
+ socket.once("connect", () => done(true));
6873
+ socket.once("error", (error) => done(![
6874
+ "ECONNREFUSED",
6875
+ "EADDRNOTAVAIL",
6876
+ "EAFNOSUPPORT",
6877
+ "ENETUNREACH"
6878
+ ].includes(error.code ?? "")));
6879
+ });
6880
+ return Promise.all([accepts("127.0.0.1"), accepts("::1")]).then((open) => open.includes(true));
6881
+ }
6826
6882
  /** Owns hosted reservations, not build jobs or viewer connections. Native work runs in the packaged CLI child. */
6827
6883
  var DeviceHost = class {
6828
6884
  owned = /* @__PURE__ */ new Map();
@@ -7190,15 +7246,20 @@ var DeviceHost = class {
7190
7246
  await this.restoreAndroidMetro(record, owned);
7191
7247
  return { result: { port } };
7192
7248
  }
7249
+ const devClientPort = record.platform === "ios" ? params.clientMetroPort : void 0;
7193
7250
  const bridge = createMetroBridge({
7194
7251
  peer,
7195
7252
  gatewayPort: params.gatewayPort,
7196
7253
  secret: params.secret
7197
7254
  });
7198
- const ready = new Promise((resolve, reject) => {
7199
- bridge.server.once("error", reject);
7200
- bridge.server.listen(record.metroPort ?? 0, "127.0.0.1", () => resolve(bridge.server.address().port));
7201
- });
7255
+ const inUse = () => Object.assign(/* @__PURE__ */ new Error(`Port ${devClientPort} is in use on the hosting Mac, and an Expo dev client loads its bundle from the client's Metro port.`), { code: "device-busy" });
7256
+ const ready = (async () => {
7257
+ if (devClientPort !== void 0 && await loopbackAccepts(devClientPort)) throw inUse();
7258
+ return new Promise((resolve, reject) => {
7259
+ bridge.server.once("error", (error) => reject(devClientPort !== void 0 && error.code === "EADDRINUSE" ? inUse() : error));
7260
+ bridge.server.listen(devClientPort ?? record.metroPort ?? 0, "127.0.0.1", () => resolve(bridge.server.address().port));
7261
+ });
7262
+ })();
7202
7263
  const metro = {
7203
7264
  bridge,
7204
7265
  ready,
@@ -7224,7 +7285,7 @@ var DeviceHost = class {
7224
7285
  throw error;
7225
7286
  }
7226
7287
  } catch (error) {
7227
- return refused$1("action-failed", error.message);
7288
+ return refused$1(error.code === "device-busy" ? "device-busy" : "action-failed", error.message);
7228
7289
  }
7229
7290
  }
7230
7291
  async restoreAndroidMetro(record, owned) {
@@ -7253,6 +7314,24 @@ var DeviceHost = class {
7253
7314
  if (owned.data === settled) delete owned.data;
7254
7315
  }
7255
7316
  }
7317
+ /** Names the ports a new dev-client bridge could not bind, apart from this client's own dev-client bridges. */
7318
+ async metroPorts(client, params) {
7319
+ if (this.closed || !this.options.allowed(client)) return refused$1("forbidden", "Current device-host approval is required.");
7320
+ if (!isJsonObject(params) || !Array.isArray(params.ports) || params.ports.length > 256 || params.ports.some((port) => typeof port !== "number" || !Number.isInteger(port) || port < 1 || port > 65535)) return refused$1("bad-request", "Metro ports needs at most 256 TCP ports.");
7321
+ try {
7322
+ const ports = [...new Set(params.ports)];
7323
+ const sessions = new Set(readHostedSessions().flatMap((record) => record.client === client ? [record.id] : []));
7324
+ const own = new Set([...this.owned].flatMap(([id, { metro }]) => sessions.has(id) && metro?.port !== void 0 && metro.port === metro.clientMetroPort ? [metro.port] : []));
7325
+ const busy = [];
7326
+ const queue = ports.filter((port) => !own.has(port));
7327
+ await Promise.all(Array.from({ length: 16 }, async () => {
7328
+ for (let port = queue.shift(); port !== void 0; port = queue.shift()) if (await loopbackAccepts(port)) busy.push(port);
7329
+ }));
7330
+ return { result: { busy: busy.toSorted((a, b) => a - b) } };
7331
+ } catch (error) {
7332
+ return refused$1("action-failed", error.message);
7333
+ }
7334
+ }
7256
7335
  async metroClose(client, params) {
7257
7336
  if (!this.options.allowed(client)) return refused$1("forbidden", "Current device-host approval is required.");
7258
7337
  if (!isJsonObject(params) || typeof params.session !== "string") return refused$1("bad-request", "Metro close needs a session.");
@@ -16909,7 +16988,7 @@ async function startServer(options) {
16909
16988
  });
16910
16989
  }
16911
16990
  if (method.startsWith("device-host.input.")) return input(id, method.slice(12), raw, session);
16912
- const answer = method === "device-host.offer" ? await hostedDevices.offer(session.id, raw) : method === "device-host.reserve" ? await hostedDevices.reserve(session.id, raw) : method === "device-host.attach" ? hostedDevices.attach(session.id, raw) : method === "device-host.stop" ? hostedDevices.stop(session.id, raw) : method === "device-host.app.offer" ? hostedDevices.appOffer(session.id, raw) : method === "device-host.app.chunk" ? await hostedDevices.appChunk(session.id, raw) : method === "device-host.app.handoff" ? await hostedDevices.appHandoff(session.id, raw) : method === "device-host.app.launch" ? hostedDevices.appLaunch(session.id, raw) : method === "device-host.app.attach" ? hostedDevices.appAttach(session.id, raw) : method === "device-host.logs.query" ? await hostedDevices.logsQuery(session.id, raw) : method === "device-host.metro.open" ? await hostedDevices.metroOpen(session.id, raw, peer) : await hostedDevices.metroClose(session.id, raw);
16991
+ const answer = method === "device-host.offer" ? await hostedDevices.offer(session.id, raw) : method === "device-host.reserve" ? await hostedDevices.reserve(session.id, raw) : method === "device-host.attach" ? hostedDevices.attach(session.id, raw) : method === "device-host.stop" ? hostedDevices.stop(session.id, raw) : method === "device-host.app.offer" ? hostedDevices.appOffer(session.id, raw) : method === "device-host.app.chunk" ? await hostedDevices.appChunk(session.id, raw) : method === "device-host.app.handoff" ? await hostedDevices.appHandoff(session.id, raw) : method === "device-host.app.launch" ? hostedDevices.appLaunch(session.id, raw) : method === "device-host.app.attach" ? hostedDevices.appAttach(session.id, raw) : method === "device-host.logs.query" ? await hostedDevices.logsQuery(session.id, raw) : method === "device-host.metro.open" ? await hostedDevices.metroOpen(session.id, raw, peer) : method === "device-host.metro.ports" ? await hostedDevices.metroPorts(session.id, raw) : await hostedDevices.metroClose(session.id, raw);
16913
16992
  return send(socket, "error" in answer ? {
16914
16993
  id,
16915
16994
  error: answer.error
@@ -4,6 +4,6 @@ const encode = (version) => version.split(".", 3).reduce((sum, part) => sum * 1e
4
4
  if (encode(process.versions.node) < encode("22.12.0")) {
5
5
  process.stderr.write(`STIM_NODE_UNSUPPORTED: stim-server needs Node 22.12.0 or later; this is Node ${process.versions.node} at ${process.execPath}. To run stim-server where a project pins an older Node, see https://stim.appandflow.com/docs/requirements#older-node-pins\n`);
6
6
  process.exitCode = 1;
7
- } else import("./stim-server-aGzVV4rC.mjs");
7
+ } else import("./stim-server-Z40-_Git.mjs");
8
8
  //#endregion
9
9
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stim-cli/server",
3
- "version": "1.23.0",
3
+ "version": "1.24.0",
4
4
  "description": "Serves Stim status to paired clients over Tailscale, and runs reload and stop for clients granted control.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -28,13 +28,13 @@
28
28
  "./protocol.schema.json": "./dist/protocol.schema.json"
29
29
  },
30
30
  "dependencies": {
31
- "@stim-cli/agent-driver": "^1.23.0",
32
- "@stim-cli/agent-driver-agent-device": "^1.23.0",
33
- "@stim-cli/cache": "^1.23.0",
34
- "@stim-cli/core": "^1.23.0",
35
- "@stim-cli/transport": "^1.23.0",
36
- "@stim-cli/transport-tailscale": "^1.23.0",
37
- "stim": "1.23.0",
31
+ "@stim-cli/agent-driver": "^1.24.0",
32
+ "@stim-cli/agent-driver-agent-device": "^1.24.0",
33
+ "@stim-cli/cache": "^1.24.0",
34
+ "@stim-cli/core": "^1.24.0",
35
+ "@stim-cli/transport": "^1.24.0",
36
+ "@stim-cli/transport-tailscale": "^1.24.0",
37
+ "stim": "1.24.0",
38
38
  "ws": "^8.21.3"
39
39
  },
40
40
  "devDependencies": {