@stim-cli/server 1.25.0 → 1.26.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
@@ -93,7 +93,7 @@ it appends the default Node of nvm, fnm, Volta, mise or asdf, Homebrew,
93
93
  `JAVA_HOME` to a Homebrew `openjdk` (17, then 21, then the newest), else Android
94
94
  Studio's bundled runtime, when no JDK is installed under
95
95
  `/Library/Java/JavaVirtualMachines`. It never replaces a variable it was given,
96
- and its own Node's directory is always the last `PATH` entry. CocoaPods uses the
96
+ and its own Node's directory is always the last `PATH` entry. The build host repeats this discovery for each build offer, toolchain read and build start, so a tool installed after the server started, such as Homebrew's directory, a JDK or the Android SDK, is found without a restart, within the minute the toolchain report is cached. A `PATH` line added to a shell profile is never read. CocoaPods uses the
97
97
  project's `.ruby-version` Ruby, else rvm's or rbenv's default Ruby, and reads the
98
98
  login shell only when neither exists. Anything else a shell profile sets, such as
99
99
  `STIM_*` variables, reaches the server only through its environment or `--env`.
@@ -275,7 +275,9 @@ reports without starting a CLI process. `desktop.reports.get` takes
275
275
  Doctor accepts a selected folder before it is registered with Stim. The result is
276
276
  `{ "pending": false, "value": { ... }, "measuredAt": "..." }`; a failed read
277
277
  returns `stim-failed`. Doctor never repairs the project or records a CLI run, and
278
- GC never deletes anything.
278
+ GC never deletes anything. The GC report, here and in `machine.details`, takes
279
+ worktree build-state sizes only from the disk-usage cache and runs no `du` for
280
+ them; a stale size is `null`.
279
281
 
280
282
  The local-only `desktop-report-platform` feature adds an optional `"platform": "ios"`
281
283
  or `"platform": "android"` to doctor targets. Omitting it checks all selected platforms.
@@ -291,12 +293,17 @@ Active subscriptions refresh after 60 seconds; no refresh timer runs without
291
293
  subscribers. `unsubscribe` and disconnect remove the subscription. Cancelling a
292
294
  get request does not cancel another reader's shared work.
293
295
 
294
- After changing settings, running an action, or explicitly refreshing a view,
295
- Desktop calls `desktop.reports.invalidate` without parameters. This invalidates
296
- all report targets and refreshes active subscriptions. Results started before
297
- invalidation cannot replace a newer result or satisfy a waiting get. The existing
298
- request and subscription caps, report byte quota, API execution and cleanup
299
- budgets, and 150-second server limit apply. At most four shared report probes run
296
+ `desktop.reports.invalidate` without parameters invalidates all report targets
297
+ and refreshes active subscriptions. With the local-only
298
+ `desktop-report-invalidate-target` feature it also takes a report target, as
299
+ `desktop.reports.get` does, and invalidates only that target, so a fresh read
300
+ of one report does not restart the others. Results started before
301
+ invalidation cannot replace a newer result or satisfy a waiting get; invalidation
302
+ aborts such a probe, so a waiting get waits only for the fresh one. A waiting get
303
+ does not use one of the connection's request slots: a connection can wait for 16
304
+ reports at a time, and more fail with `limit-exceeded`. The subscription cap,
305
+ report byte quota, API execution and cleanup budgets, and 150-second server limit
306
+ apply. At most four shared report probes run
300
307
  at once across connections and targets. A new target above that limit returns a
301
308
  busy error without starting or queuing work; invalidate and retry after a probe
302
309
  finishes. Unsubscribing does not release a probe's slot before it drains.
@@ -359,7 +366,10 @@ person approves does setup store the ticket's derived PSK and lookup in
359
366
  gets `unknown-secret`, and its `stim doctor` reports the machine as pending and
360
367
  tries again on every run. The requester's next first-contact handshake with the
361
368
  ticket records its public key once per approved capability, which spends the
362
- ticket. From then on the ticket admits only that key, until it expires, so a
369
+ ticket. While it waits, setup says so ("Waiting for the requesting machine to
370
+ connect. Stim Desktop sends this automatically.", or the `stim doctor`
371
+ instruction for a Doctor-made command) and confirms each approval when it
372
+ arrives. From then on the ticket admits only that key, until it expires, so a
363
373
  requester that lost the reply can retry; setup deletes an unspent secret when it
364
374
  ends. Build and
365
375
  device-host approvals are separate records. A build grant carries no `read` or
@@ -1360,7 +1370,10 @@ event loop. Until that read returns, `/health` answers HTTP 503 with
1360
1370
  requests get 503, and no status follower, native helper, file watcher or
1361
1371
  recorder starts. A read that does not return within 10 seconds, plus up to one
1362
1372
  second to stop the child, leaves the server listening with
1363
- `startup: { "state": "degraded", "reason": "..." }` and HTTP 503 on `/health`,
1373
+ `startup: { "state": "degraded", "reason": "...", "cause": "home-unreadable" }`
1374
+ (`start-failed` when starting failed after the read returned) and HTTP 503 on
1375
+ `/health` (a tailnet peer sees only `{ "version", "startup": { "state",
1376
+ "cause" } }`, because the reason names the Stim home path),
1364
1377
  logs the reason once, and reads again every 30 seconds. When a read returns,
1365
1378
  the server starts its watchers and recorder, `/health` answers 200 with
1366
1379
  `startup: { "state": "ready" }`, and no restart is needed. `service status`
@@ -1621,9 +1634,14 @@ connected since stim-server started keeps its directory when revoked.
1621
1634
  `arch`; `xcode`; `simulatorSdk`; `macosSdk` (from
1622
1635
  `xcrun --sdk macosx --show-sdk-version`); `cocoapods`; `runtimes`, the simulator
1623
1636
  runtimes with an iPhone simulator to build for; `jdk`, the major version of
1624
- the JDK in `JAVA_HOME` or the macOS default; and `androidSdk`, the `ndk`,
1637
+ the JDK in `JAVA_HOME` or the macOS default; `jdks`, the majors of every JDK it finds
1638
+ (`JAVA_HOME`, the system JVM directories, Homebrew, Android Studio's JBR, SDKMAN), the default included; `developerDir` (what `xcode-select -p` prints) and `xcodeApps` (the Xcode apps in
1639
+ `/Applications` with `version`, `build` and, while `xcode` is null, the `license` and `firstLaunch` checks of each), on a Mac; and `androidSdk`, the `ndk`,
1625
1640
  `buildTools` and `platforms` directories of the SDK in `ANDROID_HOME`, else `~/Library/Android/sdk` on macOS or
1626
- `~/Android/Sdk` on Linux, null without one), `capacity` and `warm`
1641
+ `~/Android/Sdk` on Linux, null without one), `androidHome` (that directory) and
1642
+ `androidLicenses` (whether `licenses/android-sdk-license` in it has content, so
1643
+ Gradle can download a missing package; both absent from a server older than
1644
+ them, and from a worker without an SDK), `capacity` and `warm`
1627
1645
  (`checkout`, `dependencies`, and the legacy `build` field, now always false)
1628
1646
  for that repository. For React Native and Expo builds, clients supporting `build-dependency-cache` can send
1629
1647
  `dependencies: { project, packageName }` alongside `lockfile`; the dependency
@@ -1715,7 +1733,9 @@ connected since stim-server started keeps its directory when revoked.
1715
1733
  simulator runtime identifier, required for `ios`, null for `android`),
1716
1734
  `fingerprint`, `packageName`, `isExpo`, `optimizations`, `android` (for
1717
1735
  `android`: `variant`, `abi`, `gradleBuildCache`, `pch` and `compilerCache`,
1718
- `ccache` or `none`), `stimBuild` and, from a caller that saw `build-compat`, `compat`, after the whole manifest of `repo` was
1736
+ `ccache` or `none`), `stimBuild` and, from a caller that saw `build-compat`, `compat`, and, for Android from a caller
1737
+ that saw `build-jdk`, `jdk` (the JDK major its Gradle runs on; the build runs with `JAVA_HOME` at an installed JDK of
1738
+ that major, and fails if there is none), after the whole manifest of `repo` was
1719
1739
  synced on this connection. A peer-cache-capable React Native caller can set
1720
1740
  `cacheArtifact: true` to retain a completed development archive when its cache
1721
1741
  write and remote policies allow it. With the `fingerprint-sources` feature, a
@@ -1945,7 +1965,7 @@ Events are `{ "event", "subscription", ... }`.
1945
1965
  retained logs and agent-device session actions locally through core, with the same JSON filters, ordering,
1946
1966
  markers and error context. Archive reads are synchronous and queued per
1947
1967
  connection, yielding to the event loop between requests. Pending archive
1948
- reads share the four-request limit with commands. Archived session actions
1968
+ reads share the connection's request limit with commands. Archived session actions
1949
1969
  retain available platform and device attribution; device ids and Stim slot
1950
1970
  assignments absent from retained files are unavailable after removal. They never follow files or use a hosted relay.
1951
1971
  An archive subscription sends its records in the same batches and limits,
@@ -2355,9 +2375,10 @@ Events are `{ "event", "subscription", ... }`.
2355
2375
 
2356
2376
  `workspace` is an environment `path` from a status payload. Any other path is
2357
2377
  refused with `unknown-workspace` and runs nothing. A connection holds at most
2358
- 32 subscriptions and runs at most 4 `logs.query`, `stats.get`,
2378
+ 32 subscriptions and runs at most 32 `logs.query`, `stats.get`,
2359
2379
  `settings.get`, `build.plan`, `workspace.files` and `workspace.diff` requests at
2360
- a time. `logs.query`, `stats.get` and `settings.get` fail after 60 seconds,
2380
+ a time, actions included. A tailnet device runs at most 16 across all its
2381
+ connections; more fail with `limit-exceeded`. `logs.query`, `stats.get` and `settings.get` fail after 60 seconds,
2361
2382
  `build.plan` after 150; these four fail at 32 MiB of output. Closing the
2362
2383
  connection stops running requests. When the command refuses with Stim's error
2363
2384
  contract on stdout, the `stim-failed` message is its code, message and remedy;
@@ -2427,10 +2427,10 @@ function protocolJsonSchema() {
2427
2427
  }),
2428
2428
  request("desktop.reports.get", { $ref: "#/$defs/DesktopReportTarget" }),
2429
2429
  request("desktop.reports.subscribe", { $ref: "#/$defs/DesktopReportTarget" }),
2430
- optionalParams("desktop.reports.invalidate", {
2430
+ optionalParams("desktop.reports.invalidate", { oneOf: [{
2431
2431
  type: "object",
2432
2432
  maxProperties: 0
2433
- }),
2433
+ }, { $ref: "#/$defs/DesktopReportTarget" }] }),
2434
2434
  optionalParams("desktop.settings.schema", {
2435
2435
  type: "object",
2436
2436
  maxProperties: 0
@@ -3316,6 +3316,11 @@ function protocolJsonSchema() {
3316
3316
  compat: {
3317
3317
  ...buildCompat,
3318
3318
  description: "Compared instead of stimBuild by a machine whose build.offer toolchain reports compat."
3319
+ },
3320
+ jdk: {
3321
+ type: "string",
3322
+ pattern: "^[0-9]{1,3}$",
3323
+ description: "The JDK major an Android build runs on, sent to a stim-server with the build-jdk feature."
3319
3324
  }
3320
3325
  }
3321
3326
  }),
package/dist/protocol.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { t as protocolJsonSchema } from "./protocol-VEZO2CyR.mjs";
1
+ import { t as protocolJsonSchema } from "./protocol-nY-u8rtw.mjs";
2
2
  export * from "@stim-cli/core/protocol";
3
3
  export { protocolJsonSchema };
@@ -1281,9 +1281,11 @@
1281
1281
  "hosted-metro-ports",
1282
1282
  "server-update",
1283
1283
  "build-compat",
1284
+ "build-jdk",
1284
1285
  "status-views",
1285
1286
  "desktop-reports",
1286
1287
  "desktop-report-platform",
1288
+ "desktop-report-invalidate-target",
1287
1289
  "desktop-settings",
1288
1290
  "desktop-settings-schema",
1289
1291
  "desktop-settings-write",
@@ -4030,8 +4032,15 @@
4030
4032
  "const": "desktop.reports.invalidate"
4031
4033
  },
4032
4034
  "params": {
4033
- "type": "object",
4034
- "maxProperties": 0
4035
+ "oneOf": [
4036
+ {
4037
+ "type": "object",
4038
+ "maxProperties": 0
4039
+ },
4040
+ {
4041
+ "$ref": "#/$defs/DesktopReportTarget"
4042
+ }
4043
+ ]
4035
4044
  }
4036
4045
  }
4037
4046
  },
@@ -7688,6 +7697,11 @@
7688
7697
  "type": "integer",
7689
7698
  "minimum": 1,
7690
7699
  "description": "Compared instead of stimBuild by a machine whose build.offer toolchain reports compat."
7700
+ },
7701
+ "jdk": {
7702
+ "type": "string",
7703
+ "pattern": "^[0-9]{1,3}$",
7704
+ "description": "The JDK major an Android build runs on, sent to a stim-server with the build-jdk feature."
7691
7705
  }
7692
7706
  }
7693
7707
  }
@@ -1,4 +1,4 @@
1
- import { n as protocol_exports } from "./protocol-VEZO2CyR.mjs";
1
+ import { n as protocol_exports } from "./protocol-nY-u8rtw.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";
@@ -661,16 +661,16 @@ function validateServeEnvironment(env, pathPrepend, serving = false) {
661
661
  * environment launchd gave the process; one that `source` lacks is skipped with a line on stderr. `STIM_HOME` and
662
662
  * `SHELL` entries are skipped the same way: the job takes them from the installing process.
663
663
  */
664
- function applyServeEnvironment(base, env, pathPrepend, source = process.env) {
664
+ function applyServeEnvironment(base, env, pathPrepend, source = process.env, warn = console.error) {
665
665
  const result = { ...base };
666
666
  for (const entry of env) {
667
667
  const parsed = parseEnvAssignment(entry);
668
668
  const name = typeof parsed === "string" ? entry : parsed.key;
669
- if (JOB_KEYS.includes(name)) console.error(`stim-server: ignoring --env ${name}; the service takes it from the process that installed it.`);
669
+ if (JOB_KEYS.includes(name)) warn(`stim-server: ignoring --env ${name}; the service takes it from the process that installed it.`);
670
670
  else if (typeof parsed !== "string") result[name] = parsed.value;
671
671
  else if (ENV_KEY_PATTERN.test(entry)) {
672
672
  const value = source[entry];
673
- if (value === void 0) console.error(`stim-server: --env ${entry} has no value in the service environment; skipping it.`);
673
+ if (value === void 0) warn(`stim-server: --env ${entry} has no value in the service environment; skipping it.`);
674
674
  else result[entry] = value;
675
675
  }
676
676
  }
@@ -3457,6 +3457,9 @@ var BuildHost = class {
3457
3457
  } catch {}
3458
3458
  }
3459
3459
  }
3460
+ environment() {
3461
+ return this.options.currentEnv?.() ?? this.options.env;
3462
+ }
3460
3463
  root() {
3461
3464
  return buildWorkerRoot();
3462
3465
  }
@@ -3472,7 +3475,7 @@ var BuildHost = class {
3472
3475
  const value = new Promise((resolve) => {
3473
3476
  const args = native ? [`offer-native-${native}`] : ["offer", ...rubyVersion ? [rubyVersion] : []];
3474
3477
  const child = spawn(process.execPath, [this.options.worker, ...args], {
3475
- env: this.options.env,
3478
+ env: this.environment(),
3476
3479
  stdio: [
3477
3480
  "ignore",
3478
3481
  "pipe",
@@ -3548,7 +3551,7 @@ var BuildHost = class {
3548
3551
  },
3549
3552
  warm: {
3550
3553
  checkout: existsSync(join(area, "src")),
3551
- dependencies: typeof params.lockfile === "string" && dependencies !== void 0 && reusableDependencies(area, dependencies.project, params.lockfile, this.options.env),
3554
+ dependencies: typeof params.lockfile === "string" && dependencies !== void 0 && reusableDependencies(area, dependencies.project, params.lockfile, this.environment()),
3552
3555
  build: false
3553
3556
  },
3554
3557
  ...artifact !== void 0 ? { artifact } : {}
@@ -3559,7 +3562,7 @@ var BuildHost = class {
3559
3562
  }
3560
3563
  workerEnvironment(clientDir, area) {
3561
3564
  return {
3562
- ...this.options.env,
3565
+ ...this.environment(),
3563
3566
  STIM_HOME: join(area, "home"),
3564
3567
  CP_HOME_DIR: join(clientDir, "cache", "cocoapods-home"),
3565
3568
  CP_CACHE_DIR: join(clientDir, "cache", "cocoapods"),
@@ -4373,6 +4376,7 @@ var BuildSession = class {
4373
4376
  cacheArtifact: params.cacheArtifact === true,
4374
4377
  stimBuild: params.stimBuild,
4375
4378
  ...compat !== void 0 && toolchain.compat !== void 0 ? { compat } : {},
4379
+ ...typeof params.jdk === "string" ? { jdk: params.jdk } : {},
4376
4380
  configuration: optional(params.configuration),
4377
4381
  scheme: optional(params.scheme),
4378
4382
  iosProjectPath: optional(params.iosProjectPath),
@@ -4972,7 +4976,7 @@ function toolChecks(tools, stimBuild, platform) {
4972
4976
  ],
4973
4977
  [
4974
4978
  "Android SDK",
4975
- !!tools.androidSdk?.platforms.length && !!tools.androidSdk.buildTools.length && !!tools.androidSdk.ndk.length,
4979
+ !!tools.androidSdk,
4976
4980
  tools.androidSdk ? JSON.stringify(tools.androidSdk) : "Missing Android SDK",
4977
4981
  platform === "linux" ? "Install the Android SDK in ~/Android/Sdk, or rerun setup with --env ANDROID_HOME=<sdk>" : platform === "win32" ? "Install the Android SDK in %LOCALAPPDATA%\\Android\\Sdk, or rerun setup with --env ANDROID_HOME=<sdk>" : "Install Android Studio or set ANDROID_HOME"
4978
4982
  ],
@@ -5314,7 +5318,8 @@ async function runSetup(args, version, deps) {
5314
5318
  try {
5315
5319
  deps.storeSecret(opts.ticket, approved, opts.expiresAt, deps.now());
5316
5320
  secretStored = true;
5317
- if (deps.fromDoctor) printer.line("Waiting for the requesting machine to reconnect. Run `stim doctor` on that machine to finish.");
5321
+ const waiting = deps.fromDoctor ? "Waiting for the requesting machine to reconnect. Run `stim doctor` on that machine to finish." : "Waiting for the requesting machine to connect. Stim Desktop sends this automatically.";
5322
+ step("approve", "running", "Access approval", waiting, void 0, { running: waiting });
5318
5323
  while (deps.now() < Date.parse(opts.expiresAt)) {
5319
5324
  changed = false;
5320
5325
  for (const grant of recorded(approved)) note(grant, false);
@@ -12136,6 +12141,7 @@ var UsageRecorder = class {
12136
12141
  //#endregion
12137
12142
  //#region src/desktop-reports.ts
12138
12143
  const MAX_REPORT_READS = 4;
12144
+ const reportKey = (target) => target.report === "gc" ? "gc" : `doctor:${target.platform ?? "all"}:${target.cwd}`;
12139
12145
  var DesktopReports = class {
12140
12146
  entries = /* @__PURE__ */ new Map();
12141
12147
  closed = false;
@@ -12206,9 +12212,13 @@ var DesktopReports = class {
12206
12212
  }
12207
12213
  };
12208
12214
  }
12209
- invalidate() {
12210
- for (const entry of this.entries.values()) {
12215
+ /** Invalidates `target`, or every target without one. */
12216
+ invalidate(target) {
12217
+ const only = target && this.entries.get(reportKey(target));
12218
+ if (target && !only) return;
12219
+ for (const entry of only ? [only] : this.entries.values()) {
12211
12220
  entry.generation++;
12221
+ entry.pending?.abort.abort();
12212
12222
  clearTimeout(entry.timer);
12213
12223
  entry.timer = void 0;
12214
12224
  if (entry.listeners.size && !entry.pending) this.start(entry);
@@ -12224,7 +12234,7 @@ var DesktopReports = class {
12224
12234
  }
12225
12235
  entry(target) {
12226
12236
  for (const [key, entry] of this.entries) if (!entry.readers && !entry.listeners.size && !entry.pending && (!entry.settled || this.now() - entry.settled.at >= this.ttlMs)) this.entries.delete(key);
12227
- const key = target.report === "gc" ? "gc" : `doctor:${target.platform ?? "all"}:${target.cwd}`;
12237
+ const key = reportKey(target);
12228
12238
  let entry = this.entries.get(key);
12229
12239
  if (!entry) {
12230
12240
  entry = {
@@ -12247,6 +12257,7 @@ var DesktopReports = class {
12247
12257
  const admitted = this.activeLoads < MAX_REPORT_READS;
12248
12258
  if (admitted) this.activeLoads++;
12249
12259
  const generation = entry.generation;
12260
+ const abort = new AbortController();
12250
12261
  const pending = {
12251
12262
  promise: Promise.resolve().then(() => {
12252
12263
  if (this.closed) return {
@@ -12257,7 +12268,7 @@ var DesktopReports = class {
12257
12268
  ok: false,
12258
12269
  message: `The server is already running ${MAX_REPORT_READS} shared reports. Try again after a report finishes.`
12259
12270
  };
12260
- return this.load(entry.target);
12271
+ return this.load(entry.target, abort.signal);
12261
12272
  }).catch((cause) => ({
12262
12273
  ok: false,
12263
12274
  message: cause instanceof Error ? cause.message : String(cause)
@@ -12278,7 +12289,8 @@ var DesktopReports = class {
12278
12289
  this.publish(entry);
12279
12290
  this.schedule(entry);
12280
12291
  }),
12281
- generation
12292
+ generation,
12293
+ abort
12282
12294
  };
12283
12295
  entry.pending = pending;
12284
12296
  this.publish(entry);
@@ -13787,7 +13799,12 @@ function lastCursor(page) {
13787
13799
  return last ? archiveCursor(last) : null;
13788
13800
  }
13789
13801
  const STATUS_READ_WAIT_MS = 3e4;
13790
- const MAX_COMMANDS = 4;
13802
+ const REQUEST_LIMITS = {
13803
+ connection: 32,
13804
+ device: 16
13805
+ };
13806
+ const MAX_REPORT_WAITS = 16;
13807
+ const MAX_RETAINED_PLANS = 4;
13791
13808
  function isWorkspaceChangeMethod(method) {
13792
13809
  return method === "workspace.files" || method === "workspace.diff";
13793
13810
  }
@@ -14438,6 +14455,11 @@ async function startServer(options) {
14438
14455
  return foldBuild;
14439
14456
  };
14440
14457
  const running = /* @__PURE__ */ new Set();
14458
+ const peerCommands = /* @__PURE__ */ new Map();
14459
+ const requestLimits = {
14460
+ ...REQUEST_LIMITS,
14461
+ ...options.requestLimits
14462
+ };
14441
14463
  const cancelling = new Pending();
14442
14464
  const logLimits = {
14443
14465
  ...LOG_LIMITS,
@@ -14454,8 +14476,10 @@ async function startServer(options) {
14454
14476
  const busyWorkspaces = /* @__PURE__ */ new Set();
14455
14477
  const retainedLifecycles = /* @__PURE__ */ new Set();
14456
14478
  const planQueues = /* @__PURE__ */ new Map();
14457
- const readDetailsReport = (method, cwd = homedir(), platform) => {
14479
+ const readDetailsReport = (method, cwd = homedir(), platform, signal) => {
14458
14480
  const abort = new AbortController();
14481
+ if (signal?.aborted) abort.abort();
14482
+ else signal?.addEventListener("abort", () => abort.abort(), { once: true });
14459
14483
  const timeoutMs = options.commandLimits?.timeoutMs ?? DETAILS_TIMEOUT_MS;
14460
14484
  let task;
14461
14485
  const cancel = async () => {
@@ -14468,10 +14492,14 @@ async function startServer(options) {
14468
14492
  projectRoot: cwd,
14469
14493
  env: options.env
14470
14494
  });
14471
- return method === "doctor" ? await stim.doctor({
14495
+ if (method === "doctor") return await stim.doctor({
14472
14496
  signal: abort.signal,
14473
14497
  platform
14474
- }) : await stim[method]({ signal: abort.signal });
14498
+ });
14499
+ return method === "gc" ? await stim.gc({
14500
+ signal: abort.signal,
14501
+ measureBuildState: false
14502
+ }) : await stim.stats({ signal: abort.signal });
14475
14503
  }).then((report) => {
14476
14504
  if (method !== "stats" && Buffer.byteLength(JSON.stringify(report)) > commandLimits.maxOutputBytes) return {
14477
14505
  ok: false,
@@ -14491,7 +14519,7 @@ async function startServer(options) {
14491
14519
  running.add(cancel);
14492
14520
  return task;
14493
14521
  };
14494
- const desktopReports = new DesktopReports((target) => readDetailsReport(target.report, target.report === "doctor" ? target.cwd : void 0, target.report === "doctor" ? target.platform : void 0));
14522
+ const desktopReports = new DesktopReports((target, signal) => readDetailsReport(target.report, target.report === "doctor" ? target.cwd : void 0, target.report === "doctor" ? target.platform : void 0, signal));
14495
14523
  const machineDetails = new MachineDetailsCache(() => loadMachineDetails(() => readDetailsReport("gc"), loadAudit, () => readDetailsReport("stats")));
14496
14524
  const buildMachines = new BuildMachinesCache();
14497
14525
  let recordingTurn = Promise.resolve();
@@ -14583,6 +14611,7 @@ async function startServer(options) {
14583
14611
  const builds = new BuildHost({
14584
14612
  worker: join(dirname(options.stimCli), "offload-worker.mjs"),
14585
14613
  env: options.env,
14614
+ currentEnv: options.currentEnv,
14586
14615
  limits: options.buildLimits,
14587
14616
  ready: () => startup.state === "ready",
14588
14617
  finished: ({ client, repo, ok, error, durationMs }) => auditSafely({
@@ -14763,6 +14792,19 @@ async function startServer(options) {
14763
14792
  const replays = /* @__PURE__ */ new Map();
14764
14793
  let keyframeReads = 0;
14765
14794
  const commands = /* @__PURE__ */ new Set();
14795
+ const reportWaits = /* @__PURE__ */ new Set();
14796
+ const holdSlot = (cancel) => {
14797
+ commands.add(cancel);
14798
+ if (peer !== null) peerCommands.set(peer, (peerCommands.get(peer) ?? 0) + 1);
14799
+ };
14800
+ const releaseSlot = (cancel) => {
14801
+ if (!commands.delete(cancel) || peer === null) return;
14802
+ const left = (peerCommands.get(peer) ?? 1) - 1;
14803
+ if (left > 0) peerCommands.set(peer, left);
14804
+ else peerCommands.delete(peer);
14805
+ };
14806
+ const slotsFull = (needed = 1) => commands.size + needed > requestLimits.connection || peer !== null && (peerCommands.get(peer) ?? 0) + needed > requestLimits.device;
14807
+ const slotsMessage = (needed = 1) => peer !== null && commands.size + needed <= requestLimits.connection ? `A remote device can run ${requestLimits.device} requests at a time.` : `A connection can run ${requestLimits.connection} requests at a time.`;
14766
14808
  const planRequests = /* @__PURE__ */ new Map();
14767
14809
  const persistentActions = /* @__PURE__ */ new Set();
14768
14810
  let nextSubscription = 1;
@@ -14862,6 +14904,7 @@ async function startServer(options) {
14862
14904
  "desktop-plan",
14863
14905
  "desktop-reports",
14864
14906
  "desktop-report-platform",
14907
+ "desktop-report-invalidate-target",
14865
14908
  "desktop-settings",
14866
14909
  "desktop-settings-schema",
14867
14910
  "desktop-settings-write",
@@ -15555,18 +15598,18 @@ async function startServer(options) {
15555
15598
  subscriptions.set(subscription, cleanup);
15556
15599
  }
15557
15600
  function archiveRead(id, read) {
15558
- if (commands.size >= MAX_COMMANDS) return error(id, "limit-exceeded", `A connection can run ${MAX_COMMANDS} requests at a time.`);
15601
+ if (slotsFull()) return error(id, "limit-exceeded", slotsMessage());
15559
15602
  let dropped = false;
15560
15603
  const cancel = async () => {
15561
15604
  dropped = true;
15562
15605
  await task;
15563
15606
  };
15564
- commands.add(cancel);
15607
+ holdSlot(cancel);
15565
15608
  running.add(cancel);
15566
15609
  const task = archiveReads.then(() => new Promise((resolve) => setImmediate(resolve))).then(() => {
15567
15610
  if (!dropped && socket.readyState === socket.OPEN) return read();
15568
15611
  }).finally(() => {
15569
- commands.delete(cancel);
15612
+ releaseSlot(cancel);
15570
15613
  running.delete(cancel);
15571
15614
  });
15572
15615
  archiveReads = task.catch(() => socket.close(1011, "internal error"));
@@ -15598,14 +15641,14 @@ async function startServer(options) {
15598
15641
  if (!isJsonObject(params) || typeof params.workspace !== "string" || !params.workspace.length || params.workspace.length > 4096 || params.workspace.includes("\0") || !needsPath && params.group !== "changed" && params.group !== "untracked" || needsPath && (typeof params.path !== "string" || !params.path.length || params.path.length > 4096 || params.path.includes("\0")) || Object.keys(params).some((key) => key !== "workspace" && !(needsPath ? key === "path" : key === "group"))) return error(id, "bad-request", "Workspace diff requires workspace and, for a patch, a file path.");
15599
15642
  const cwd = workspaceDir(id, params.workspace, true);
15600
15643
  if (!cwd) return;
15601
- if (commands.size >= MAX_COMMANDS) return error(id, "limit-exceeded", `A connection can run ${MAX_COMMANDS} requests at a time.`);
15644
+ if (slotsFull()) return error(id, "limit-exceeded", slotsMessage());
15602
15645
  const abort = new AbortController();
15603
15646
  let task;
15604
15647
  const cancel = async () => {
15605
15648
  abort.abort();
15606
15649
  await task;
15607
15650
  };
15608
- commands.add(cancel);
15651
+ holdSlot(cancel);
15609
15652
  running.add(cancel);
15610
15653
  task = readWorkspaceDiff(cwd, needsPath ? params.path : void 0, options.env, abort.signal, !needsPath ? params.group : void 0).then((result) => {
15611
15654
  if (!abort.signal.aborted) send(socket, {
@@ -15615,13 +15658,13 @@ async function startServer(options) {
15615
15658
  }, (cause) => {
15616
15659
  if (!abort.signal.aborted) error(id, "stim-failed", cause.message);
15617
15660
  }).finally(() => {
15618
- commands.delete(cancel);
15661
+ releaseSlot(cancel);
15619
15662
  running.delete(cancel);
15620
15663
  });
15621
15664
  }
15622
- function inProcessRead(id, label, read, timeoutMs = commandLimits.timeoutMs, turn = null) {
15623
- if (commands.size >= MAX_COMMANDS) {
15624
- error(id, "limit-exceeded", `A connection can run ${MAX_COMMANDS} requests at a time.`);
15665
+ function inProcessRead(id, label, read, timeoutMs = commandLimits.timeoutMs, turn = null, report = false) {
15666
+ if (report ? reportWaits.size >= MAX_REPORT_WAITS : slotsFull()) {
15667
+ error(id, "limit-exceeded", report ? `A connection can wait for ${MAX_REPORT_WAITS} reports at a time.` : slotsMessage());
15625
15668
  return null;
15626
15669
  }
15627
15670
  const abort = new AbortController();
@@ -15645,14 +15688,16 @@ async function startServer(options) {
15645
15688
  if (!abort.signal.aborted) error(id, "stim-failed", readFailure(cause));
15646
15689
  }).finally(() => {
15647
15690
  clearTimeout(deadline);
15648
- commands.delete(cancel);
15691
+ releaseSlot(cancel);
15692
+ reportWaits.delete(cancel);
15649
15693
  running.delete(cancel);
15650
15694
  });
15651
15695
  const cancel = async () => {
15652
15696
  abort.abort();
15653
15697
  await task;
15654
15698
  };
15655
- commands.add(cancel);
15699
+ if (report) reportWaits.add(cancel);
15700
+ else holdSlot(cancel);
15656
15701
  running.add(cancel);
15657
15702
  return {
15658
15703
  done: task,
@@ -15721,7 +15766,7 @@ async function startServer(options) {
15721
15766
  const { action } = parsed;
15722
15767
  const resolved = registeredWorkspace(action.workspace);
15723
15768
  if ("code" in resolved) return refuseAction(resolved.code, resolved.message);
15724
- if (commands.size >= MAX_COMMANDS) return refuseAction("limit-exceeded", `A connection can run ${MAX_COMMANDS} requests at a time.`);
15769
+ if (slotsFull()) return refuseAction("limit-exceeded", slotsMessage());
15725
15770
  if (busyWorkspaces.has(resolved.dir)) return refuseAction("action-busy", `An action is already running in ${resolved.dir}. Try again when it finishes.`);
15726
15771
  let admittedCwd = resolved.dir;
15727
15772
  let run;
@@ -15755,7 +15800,7 @@ async function startServer(options) {
15755
15800
  let finished = false;
15756
15801
  const release = (outcome) => {
15757
15802
  if (outcome.unresolved) return;
15758
- commands.delete(cancel);
15803
+ releaseSlot(cancel);
15759
15804
  persistentActions.delete(cancel);
15760
15805
  running.delete(cancel);
15761
15806
  busyWorkspaces.delete(resolved.dir);
@@ -15793,7 +15838,7 @@ async function startServer(options) {
15793
15838
  await run.cancel();
15794
15839
  release(await run.drained);
15795
15840
  };
15796
- commands.add(cancel);
15841
+ holdSlot(cancel);
15797
15842
  persistentActions.add(cancel);
15798
15843
  running.add(cancel);
15799
15844
  run.outcome.then((outcome) => finish(!outcome.ok && "code" in outcome && outcome.code === "STIM_ACTION_BUSY" ? {
@@ -15920,7 +15965,7 @@ async function startServer(options) {
15920
15965
  } catch (cause) {
15921
15966
  return error(id, "bad-request", `Cannot reload this workspace: ${readFailure(cause)}`);
15922
15967
  }
15923
- if (commands.size >= MAX_COMMANDS) return error(id, "limit-exceeded", `A connection can run ${MAX_COMMANDS} requests at a time.`);
15968
+ if (slotsFull()) return error(id, "limit-exceeded", slotsMessage());
15924
15969
  if (busyWorkspaces.has(cwd)) return error(id, "action-busy", `An action is already running in ${cwd}.`);
15925
15970
  const subscription = openSubscription(id);
15926
15971
  if (!subscription) return;
@@ -15961,7 +16006,7 @@ async function startServer(options) {
15961
16006
  }
15962
16007
  });
15963
16008
  const cancel = () => run.cancel();
15964
- commands.add(cancel);
16009
+ holdSlot(cancel);
15965
16010
  running.add(cancel);
15966
16011
  busyWorkspaces.add(cwd);
15967
16012
  subscriptions.set(subscription, () => {
@@ -15971,7 +16016,7 @@ async function startServer(options) {
15971
16016
  run.outcome.then(finish);
15972
16017
  run.drained.then((outcome) => {
15973
16018
  if (!outcome.unresolved) {
15974
- commands.delete(cancel);
16019
+ releaseSlot(cancel);
15975
16020
  running.delete(cancel);
15976
16021
  busyWorkspaces.delete(cwd);
15977
16022
  busyWorkspaces.delete(admittedCwd);
@@ -16089,7 +16134,7 @@ async function startServer(options) {
16089
16134
  } catch (cause) {
16090
16135
  return error(id, "bad-request", `Cannot ${kind} this workspace: ${readFailure(cause)}`);
16091
16136
  }
16092
- if (commands.size >= MAX_COMMANDS) return error(id, "limit-exceeded", `A connection can run ${MAX_COMMANDS} requests at a time.`);
16137
+ if (slotsFull()) return error(id, "limit-exceeded", slotsMessage());
16093
16138
  if (busyWorkspaces.has(cwd)) return error(id, "action-busy", `An action is already running in ${cwd}.`);
16094
16139
  const subscription = openSubscription(id);
16095
16140
  if (!subscription) return;
@@ -16130,7 +16175,7 @@ async function startServer(options) {
16130
16175
  const cancel = async () => {
16131
16176
  await run.cancel();
16132
16177
  };
16133
- commands.add(cancel);
16178
+ holdSlot(cancel);
16134
16179
  running.add(cancel);
16135
16180
  busyWorkspaces.add(cwd);
16136
16181
  subscriptions.set(subscription, async () => {
@@ -16145,7 +16190,7 @@ async function startServer(options) {
16145
16190
  run.drained.then((outcome) => {
16146
16191
  if (kind === "gc" || kind === "worktree-remove" || kind === "worktree-warm" || kind === "machine-access") desktopReports.invalidate();
16147
16192
  const release = () => {
16148
- commands.delete(cancel);
16193
+ releaseSlot(cancel);
16149
16194
  running.delete(cancel);
16150
16195
  busyWorkspaces.delete(cwd);
16151
16196
  for (const workspace of admittedWorkspaces) busyWorkspaces.delete(workspace);
@@ -16199,7 +16244,7 @@ async function startServer(options) {
16199
16244
  } catch (cause) {
16200
16245
  return error(id, "bad-request", `Cannot change settings here: ${readFailure(cause)}`);
16201
16246
  }
16202
- if (commands.size >= MAX_COMMANDS) return error(id, "limit-exceeded", `A connection can run ${MAX_COMMANDS} requests at a time.`);
16247
+ if (slotsFull()) return error(id, "limit-exceeded", slotsMessage());
16203
16248
  if (busyWorkspaces.has(cwd)) return error(id, "action-busy", `An action is already running in ${cwd}.`);
16204
16249
  const { key, action } = params;
16205
16250
  const value = typeof params.value === "string" ? params.value : void 0;
@@ -16287,7 +16332,7 @@ async function startServer(options) {
16287
16332
  } });
16288
16333
  }).finally(() => {
16289
16334
  clearTimeout(deadline);
16290
- commands.delete(cancel);
16335
+ releaseSlot(cancel);
16291
16336
  running.delete(cancel);
16292
16337
  busyWorkspaces.delete(cwd);
16293
16338
  busyWorkspaces.delete(admittedCwd);
@@ -16313,7 +16358,7 @@ async function startServer(options) {
16313
16358
  abort.abort();
16314
16359
  await task;
16315
16360
  };
16316
- commands.add(cancel);
16361
+ holdSlot(cancel);
16317
16362
  running.add(cancel);
16318
16363
  busyWorkspaces.add(cwd);
16319
16364
  }
@@ -16325,7 +16370,7 @@ async function startServer(options) {
16325
16370
  abort.abort();
16326
16371
  await task;
16327
16372
  };
16328
- commands.add(cancel);
16373
+ holdSlot(cancel);
16329
16374
  running.add(cancel);
16330
16375
  task = Promise.resolve().then(() => createStim({
16331
16376
  projectRoot: cwd,
@@ -16335,7 +16380,7 @@ async function startServer(options) {
16335
16380
  signal: abort.signal
16336
16381
  })).then((records) => Buffer.byteLength(records.map((record) => JSON.stringify(record)).join("\n")) > commandLimits.maxOutputBytes ? `stim logs printed more than ${commandLimits.maxOutputBytes} bytes.` : records, (cause) => readFailure(cause)).finally(() => {
16337
16382
  clearTimeout(deadline);
16338
- commands.delete(cancel);
16383
+ releaseSlot(cancel);
16339
16384
  running.delete(cancel);
16340
16385
  });
16341
16386
  return task;
@@ -16377,7 +16422,7 @@ async function startServer(options) {
16377
16422
  archive
16378
16423
  });
16379
16424
  if (!cwd) return;
16380
- if (archive === void 0 && commands.size + 2 > MAX_COMMANDS) return error(id, "limit-exceeded", `A connection can run ${MAX_COMMANDS} requests at a time.`);
16425
+ if (archive === void 0 && slotsFull(2)) return error(id, "limit-exceeded", slotsMessage(2));
16381
16426
  const slotName = slot ?? "default";
16382
16427
  const spans = recordedSpans(listSegments(recordingDir(workspace, platform, slotName, archive), archive !== void 0));
16383
16428
  const replayTarget = {
@@ -16568,7 +16613,7 @@ async function startServer(options) {
16568
16613
  }, "build.plan");
16569
16614
  }
16570
16615
  function queuePlan(id, cwd, plan, method, request) {
16571
- if (request !== void 0 && planRequests.size >= MAX_COMMANDS) {
16616
+ if (request !== void 0 && planRequests.size >= MAX_RETAINED_PLANS) {
16572
16617
  error(id, "limit-exceeded", "Acknowledge retained plan refusals before requesting more previews.");
16573
16618
  return;
16574
16619
  }
@@ -16831,8 +16876,7 @@ async function startServer(options) {
16831
16876
  }).settings({ signal }));
16832
16877
  }
16833
16878
  function desktopReport(id, method, params) {
16834
- if (method === "desktop.reports.invalidate") {
16835
- if (params !== void 0 && (!isJsonObject(params) || Object.keys(params).length)) return error(id, "bad-request", "desktop.reports.invalidate takes no parameters.");
16879
+ if (method === "desktop.reports.invalidate" && (params === void 0 || isJsonObject(params) && !Object.keys(params).length)) {
16836
16880
  desktopReports.invalidate();
16837
16881
  return send(socket, {
16838
16882
  id,
@@ -16845,12 +16889,19 @@ async function startServer(options) {
16845
16889
  cwd: resolve(String(params.cwd)),
16846
16890
  ...params.platform === "ios" || params.platform === "android" ? { platform: params.platform } : {}
16847
16891
  };
16892
+ if (method === "desktop.reports.invalidate") {
16893
+ desktopReports.invalidate(target);
16894
+ return send(socket, {
16895
+ id,
16896
+ result: {}
16897
+ });
16898
+ }
16848
16899
  if (method === "desktop.reports.get") {
16849
16900
  inProcessRead(id, `stim ${target.report}`, async (signal) => {
16850
16901
  const snapshot = await desktopReports.get(target, signal);
16851
16902
  if (snapshot.error) throw new Error(snapshot.error);
16852
16903
  return snapshot;
16853
- }, options.commandLimits?.timeoutMs ?? DETAILS_TIMEOUT_MS);
16904
+ }, options.commandLimits?.timeoutMs ?? DETAILS_TIMEOUT_MS, null, true);
16854
16905
  return;
16855
16906
  }
16856
16907
  const subscription = openSubscription(id);
@@ -17222,12 +17273,14 @@ async function startServer(options) {
17222
17273
  controllers.delete(socket);
17223
17274
  for (const unsubscribe of subscriptions.values()) unsubscribe();
17224
17275
  subscriptions.clear();
17225
- for (const cancel of commands) {
17226
- if (persistentActions.has(cancel)) continue;
17276
+ for (const cancel of [...commands, ...reportWaits]) {
17277
+ if (persistentActions.has(cancel)) {
17278
+ releaseSlot(cancel);
17279
+ continue;
17280
+ }
17227
17281
  running.delete(cancel);
17228
17282
  cancelling.track(cancel());
17229
17283
  }
17230
- commands.clear();
17231
17284
  });
17232
17285
  }
17233
17286
  function upgrade(request, socket, head) {
@@ -17415,7 +17468,13 @@ async function startServer(options) {
17415
17468
  return;
17416
17469
  }
17417
17470
  if (request.method === "GET" && request.url === "/health" && peerAddress(request) !== null && request.headers.origin === void 0 && request.headers["sec-fetch-site"] === void 0) {
17418
- const peerHealth = { version: health.version };
17471
+ const peerHealth = {
17472
+ version: health.version,
17473
+ ...startup.state === "ready" ? {} : { startup: startup.state === "degraded" ? {
17474
+ state: "degraded",
17475
+ cause: startup.cause
17476
+ } : { state: "pending" } }
17477
+ };
17419
17478
  response.writeHead(startup.state === "ready" ? 200 : 503, { "content-type": "application/json" }).end(JSON.stringify(peerHealth));
17420
17479
  return;
17421
17480
  }
@@ -17600,6 +17659,7 @@ async function startServer(options) {
17600
17659
  }
17601
17660
  startupProbe = probeDirectories(directories, options.env, options.startupProbeMs ?? 1e4, configDir());
17602
17661
  let reason = await startupProbe.result;
17662
+ let cause = "home-unreadable";
17603
17663
  startupProbe = null;
17604
17664
  if (closing) return settle(startup);
17605
17665
  if (reason === null) try {
@@ -17611,6 +17671,7 @@ async function startServer(options) {
17611
17671
  becomeReady();
17612
17672
  } catch (error) {
17613
17673
  reason = error.message;
17674
+ cause = "start-failed";
17614
17675
  }
17615
17676
  if (reason === null) {
17616
17677
  if (startup.state === "degraded") console.error("stim-server: the Stim home answers again; serving clients.");
@@ -17635,7 +17696,8 @@ async function startServer(options) {
17635
17696
  if (startup.state !== "degraded" || startup.reason !== reason) console.error(`stim-server: listening, but not serving clients until the Stim home answers. ${reason}`);
17636
17697
  startup = {
17637
17698
  state: "degraded",
17638
- reason
17699
+ reason,
17700
+ cause
17639
17701
  };
17640
17702
  settle(startup);
17641
17703
  startupRetry = setTimeout(() => void checkHome(), options.startupRetryMs ?? 3e4);
@@ -17786,12 +17848,21 @@ async function serve(port, extraEnv, pathPrepend, loopbackOnly) {
17786
17848
  const desktopInput = bootstrapFd === "0" ? await readDesktopBootstrap(process.stdin).catch(() => fail("Desktop bootstrap input is invalid or unavailable.")) : void 0;
17787
17849
  delete process.env.NODE_COMPILE_CACHE;
17788
17850
  const captureHost = hostFromExecutable(process.env.STIM_HOST_EXECUTABLE);
17789
- const env = applyServeEnvironment(discoverToolchain(process.env).env, extraEnv, pathPrepend);
17790
- delete env.CLAUDE_CODE_SESSION_ID;
17791
- delete env.CODEX_THREAD_ID;
17851
+ const inherited = { ...process.env };
17852
+ const serveEnvironment = (warn) => {
17853
+ const found = applyServeEnvironment(discoverToolchain(inherited).env, extraEnv, pathPrepend, process.env, warn);
17854
+ delete found.CLAUDE_CODE_SESSION_ID;
17855
+ delete found.CODEX_THREAD_ID;
17856
+ return found;
17857
+ };
17858
+ const env = serveEnvironment();
17792
17859
  if (!process.env.STIM_HOME && env.STIM_HOME) process.env.STIM_HOME = env.STIM_HOME;
17793
17860
  if (process.env.STIM_HOME) env.STIM_HOME = process.env.STIM_HOME;
17794
17861
  Object.assign(process.env, env);
17862
+ const currentEnv = () => ({
17863
+ ...serveEnvironment(() => {}),
17864
+ ...env.STIM_HOME ? { STIM_HOME: env.STIM_HOME } : {}
17865
+ });
17795
17866
  const loaded = await loadTransport({
17796
17867
  reference: "@stim-cli/transport-tailscale",
17797
17868
  load: () => import("@stim-cli/transport-tailscale"),
@@ -17818,6 +17889,7 @@ async function serve(port, extraEnv, pathPrepend, loopbackOnly) {
17818
17889
  stimVersion: stim.version,
17819
17890
  serverVersion: pkg.version,
17820
17891
  env,
17892
+ currentEnv,
17821
17893
  transports: [transport],
17822
17894
  service: {
17823
17895
  label: serviceManagerOrNull()?.runningLabel(process.env) ?? null,
@@ -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-DowAx-6v.mjs");
7
+ } else import("./stim-server-DU1htjam.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.25.0",
3
+ "version": "1.26.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.25.0",
32
- "@stim-cli/agent-driver-agent-device": "1.25.0",
33
- "@stim-cli/cache": "1.25.0",
34
- "@stim-cli/core": "1.25.0",
35
- "@stim-cli/transport": "1.25.0",
36
- "@stim-cli/transport-tailscale": "1.25.0",
37
- "stim": "1.25.0",
31
+ "@stim-cli/agent-driver": "1.26.0",
32
+ "@stim-cli/agent-driver-agent-device": "1.26.0",
33
+ "@stim-cli/cache": "1.26.0",
34
+ "@stim-cli/core": "1.26.0",
35
+ "@stim-cli/transport": "1.26.0",
36
+ "@stim-cli/transport-tailscale": "1.26.0",
37
+ "stim": "1.26.0",
38
38
  "ws": "^8.21.3"
39
39
  },
40
40
  "devDependencies": {