@slock-ai/computer 0.0.16-alpha.0 → 0.0.17

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/dist/index.js CHANGED
@@ -5729,9 +5729,9 @@ var require_dispatcher_base = __commonJS({
5729
5729
  }
5730
5730
  close(callback) {
5731
5731
  if (callback === void 0) {
5732
- return new Promise((resolve, reject) => {
5732
+ return new Promise((resolve2, reject) => {
5733
5733
  this.close((err, data) => {
5734
- return err ? reject(err) : resolve(data);
5734
+ return err ? reject(err) : resolve2(data);
5735
5735
  });
5736
5736
  });
5737
5737
  }
@@ -5769,9 +5769,9 @@ var require_dispatcher_base = __commonJS({
5769
5769
  err = null;
5770
5770
  }
5771
5771
  if (callback === void 0) {
5772
- return new Promise((resolve, reject) => {
5772
+ return new Promise((resolve2, reject) => {
5773
5773
  this.destroy(err, (err2, data) => {
5774
- return err2 ? reject(err2) : resolve(data);
5774
+ return err2 ? reject(err2) : resolve2(data);
5775
5775
  });
5776
5776
  });
5777
5777
  }
@@ -9264,8 +9264,8 @@ var require_promise = __commonJS({
9264
9264
  function createDeferredPromise() {
9265
9265
  let res;
9266
9266
  let rej;
9267
- const promise = new Promise((resolve, reject) => {
9268
- res = resolve;
9267
+ const promise = new Promise((resolve2, reject) => {
9268
+ res = resolve2;
9269
9269
  rej = reject;
9270
9270
  });
9271
9271
  return { promise, resolve: res, reject: rej };
@@ -10568,12 +10568,12 @@ upgrade: ${upgrade}\r
10568
10568
  cb();
10569
10569
  }
10570
10570
  }
10571
- const waitForDrain = () => new Promise((resolve, reject) => {
10571
+ const waitForDrain = () => new Promise((resolve2, reject) => {
10572
10572
  assert(callback === null);
10573
10573
  if (socket[kError]) {
10574
10574
  reject(socket[kError]);
10575
10575
  } else {
10576
- callback = resolve;
10576
+ callback = resolve2;
10577
10577
  }
10578
10578
  });
10579
10579
  socket.on("close", onDrain).on("drain", onDrain);
@@ -11419,12 +11419,12 @@ var require_client_h2 = __commonJS({
11419
11419
  cb();
11420
11420
  }
11421
11421
  }
11422
- const waitForDrain = () => new Promise((resolve, reject) => {
11422
+ const waitForDrain = () => new Promise((resolve2, reject) => {
11423
11423
  assert(callback === null);
11424
11424
  if (socket[kError]) {
11425
11425
  reject(socket[kError]);
11426
11426
  } else {
11427
- callback = resolve;
11427
+ callback = resolve2;
11428
11428
  }
11429
11429
  });
11430
11430
  h2stream.on("close", onDrain).on("drain", onDrain);
@@ -11736,16 +11736,16 @@ var require_client = __commonJS({
11736
11736
  return this[kNeedDrain] < 2;
11737
11737
  }
11738
11738
  [kClose]() {
11739
- return new Promise((resolve) => {
11739
+ return new Promise((resolve2) => {
11740
11740
  if (this[kSize]) {
11741
- this[kClosedResolve] = resolve;
11741
+ this[kClosedResolve] = resolve2;
11742
11742
  } else {
11743
- resolve(null);
11743
+ resolve2(null);
11744
11744
  }
11745
11745
  });
11746
11746
  }
11747
11747
  [kDestroy](err) {
11748
- return new Promise((resolve) => {
11748
+ return new Promise((resolve2) => {
11749
11749
  const requests = this[kQueue].splice(this[kPendingIdx]);
11750
11750
  for (let i = 0; i < requests.length; i++) {
11751
11751
  const request = requests[i];
@@ -11756,7 +11756,7 @@ var require_client = __commonJS({
11756
11756
  this[kClosedResolve]();
11757
11757
  this[kClosedResolve] = null;
11758
11758
  }
11759
- resolve(null);
11759
+ resolve2(null);
11760
11760
  };
11761
11761
  if (this[kHTTPContext]) {
11762
11762
  this[kHTTPContext].destroy(err, callback);
@@ -12163,8 +12163,8 @@ var require_pool_base = __commonJS({
12163
12163
  }
12164
12164
  return Promise.all(closeAll);
12165
12165
  } else {
12166
- return new Promise((resolve) => {
12167
- this[kClosedResolve] = resolve;
12166
+ return new Promise((resolve2) => {
12167
+ this[kClosedResolve] = resolve2;
12168
12168
  });
12169
12169
  }
12170
12170
  }
@@ -13263,10 +13263,10 @@ var require_socks5_proxy_agent = __commonJS({
13263
13263
  const proxyHost = this[kProxyUrl].hostname;
13264
13264
  const proxyPort = parseInt(this[kProxyUrl].port) || 1080;
13265
13265
  debug("creating SOCKS5 connection to", proxyHost, proxyPort);
13266
- const socket = await new Promise((resolve, reject) => {
13266
+ const socket = await new Promise((resolve2, reject) => {
13267
13267
  const onConnect = () => {
13268
13268
  socket2.removeListener("error", onError);
13269
- resolve(socket2);
13269
+ resolve2(socket2);
13270
13270
  };
13271
13271
  const onError = (err) => {
13272
13272
  socket2.removeListener("connect", onConnect);
@@ -13285,14 +13285,14 @@ var require_socks5_proxy_agent = __commonJS({
13285
13285
  socket.destroy();
13286
13286
  });
13287
13287
  await socks5Client.handshake();
13288
- await new Promise((resolve, reject) => {
13288
+ await new Promise((resolve2, reject) => {
13289
13289
  const timeout = setTimeout(() => {
13290
13290
  reject(new Error("SOCKS5 authentication timeout"));
13291
13291
  }, 5e3);
13292
13292
  const onAuthenticated = () => {
13293
13293
  clearTimeout(timeout);
13294
13294
  socks5Client.removeListener("error", onError);
13295
- resolve();
13295
+ resolve2();
13296
13296
  };
13297
13297
  const onError = (err) => {
13298
13298
  clearTimeout(timeout);
@@ -13301,14 +13301,14 @@ var require_socks5_proxy_agent = __commonJS({
13301
13301
  };
13302
13302
  if (socks5Client.state === "authenticated") {
13303
13303
  clearTimeout(timeout);
13304
- resolve();
13304
+ resolve2();
13305
13305
  } else {
13306
13306
  socks5Client.once("authenticated", onAuthenticated);
13307
13307
  socks5Client.once("error", onError);
13308
13308
  }
13309
13309
  });
13310
13310
  await socks5Client.connect(targetHost, targetPort);
13311
- await new Promise((resolve, reject) => {
13311
+ await new Promise((resolve2, reject) => {
13312
13312
  const timeout = setTimeout(() => {
13313
13313
  reject(new Error("SOCKS5 connection timeout"));
13314
13314
  }, 5e3);
@@ -13316,7 +13316,7 @@ var require_socks5_proxy_agent = __commonJS({
13316
13316
  debug("SOCKS5 tunnel established to", targetHost, targetPort, "via", info2);
13317
13317
  clearTimeout(timeout);
13318
13318
  socks5Client.removeListener("error", onError);
13319
- resolve();
13319
+ resolve2();
13320
13320
  };
13321
13321
  const onError = (err) => {
13322
13322
  clearTimeout(timeout);
@@ -13357,8 +13357,8 @@ var require_socks5_proxy_agent = __commonJS({
13357
13357
  servername: targetHost,
13358
13358
  ...connectOpts.tls || {}
13359
13359
  });
13360
- await new Promise((resolve, reject) => {
13361
- finalSocket.once("secureConnect", resolve);
13360
+ await new Promise((resolve2, reject) => {
13361
+ finalSocket.once("secureConnect", resolve2);
13362
13362
  finalSocket.once("error", reject);
13363
13363
  });
13364
13364
  }
@@ -14389,7 +14389,7 @@ var require_readable = __commonJS({
14389
14389
  if (this._readableState.closeEmitted) {
14390
14390
  return Promise.resolve(null);
14391
14391
  }
14392
- return new Promise((resolve, reject) => {
14392
+ return new Promise((resolve2, reject) => {
14393
14393
  if (this[kContentLength] && this[kContentLength] > limit || this[kBytesRead] > limit) {
14394
14394
  this.destroy(new AbortError());
14395
14395
  }
@@ -14403,11 +14403,11 @@ var require_readable = __commonJS({
14403
14403
  if (signal.aborted) {
14404
14404
  reject(signal.reason ?? new AbortError());
14405
14405
  } else {
14406
- resolve(null);
14406
+ resolve2(null);
14407
14407
  }
14408
14408
  });
14409
14409
  } else {
14410
- this.on("close", resolve);
14410
+ this.on("close", resolve2);
14411
14411
  }
14412
14412
  this.on("error", noop).on("data", () => {
14413
14413
  if (this[kBytesRead] > limit) {
@@ -14435,7 +14435,7 @@ var require_readable = __commonJS({
14435
14435
  }
14436
14436
  function consume(stream, type) {
14437
14437
  assert(!stream[kConsume]);
14438
- return new Promise((resolve, reject) => {
14438
+ return new Promise((resolve2, reject) => {
14439
14439
  if (isUnusable(stream)) {
14440
14440
  const rState = stream._readableState;
14441
14441
  if (rState.destroyed && rState.closeEmitted === false) {
@@ -14450,7 +14450,7 @@ var require_readable = __commonJS({
14450
14450
  stream[kConsume] = {
14451
14451
  type,
14452
14452
  stream,
14453
- resolve,
14453
+ resolve: resolve2,
14454
14454
  reject,
14455
14455
  length: 0,
14456
14456
  body: []
@@ -14524,18 +14524,18 @@ var require_readable = __commonJS({
14524
14524
  return buffer;
14525
14525
  }
14526
14526
  function consumeEnd(consume2, encoding) {
14527
- const { type, body, resolve, stream, length } = consume2;
14527
+ const { type, body, resolve: resolve2, stream, length } = consume2;
14528
14528
  try {
14529
14529
  if (type === "text") {
14530
- resolve(chunksDecode(body, length, encoding));
14530
+ resolve2(chunksDecode(body, length, encoding));
14531
14531
  } else if (type === "json") {
14532
- resolve(JSON.parse(chunksDecode(body, length, encoding)));
14532
+ resolve2(JSON.parse(chunksDecode(body, length, encoding)));
14533
14533
  } else if (type === "arrayBuffer") {
14534
- resolve(chunksConcat(body, length).buffer);
14534
+ resolve2(chunksConcat(body, length).buffer);
14535
14535
  } else if (type === "blob") {
14536
- resolve(new Blob(body, { type: stream[kContentType] }));
14536
+ resolve2(new Blob(body, { type: stream[kContentType] }));
14537
14537
  } else if (type === "bytes") {
14538
- resolve(chunksConcat(body, length));
14538
+ resolve2(chunksConcat(body, length));
14539
14539
  }
14540
14540
  consumeFinish(consume2);
14541
14541
  } catch (err) {
@@ -14726,9 +14726,9 @@ var require_api_request = __commonJS({
14726
14726
  };
14727
14727
  function request(opts, callback) {
14728
14728
  if (callback === void 0) {
14729
- return new Promise((resolve, reject) => {
14729
+ return new Promise((resolve2, reject) => {
14730
14730
  request.call(this, opts, (err, data) => {
14731
- return err ? reject(err) : resolve(data);
14731
+ return err ? reject(err) : resolve2(data);
14732
14732
  });
14733
14733
  });
14734
14734
  }
@@ -14942,9 +14942,9 @@ var require_api_stream = __commonJS({
14942
14942
  };
14943
14943
  function stream(opts, factory, callback) {
14944
14944
  if (callback === void 0) {
14945
- return new Promise((resolve, reject) => {
14945
+ return new Promise((resolve2, reject) => {
14946
14946
  stream.call(this, opts, factory, (err, data) => {
14947
- return err ? reject(err) : resolve(data);
14947
+ return err ? reject(err) : resolve2(data);
14948
14948
  });
14949
14949
  });
14950
14950
  }
@@ -15234,9 +15234,9 @@ var require_api_upgrade = __commonJS({
15234
15234
  };
15235
15235
  function upgrade(opts, callback) {
15236
15236
  if (callback === void 0) {
15237
- return new Promise((resolve, reject) => {
15237
+ return new Promise((resolve2, reject) => {
15238
15238
  upgrade.call(this, opts, (err, data) => {
15239
- return err ? reject(err) : resolve(data);
15239
+ return err ? reject(err) : resolve2(data);
15240
15240
  });
15241
15241
  });
15242
15242
  }
@@ -15330,9 +15330,9 @@ var require_api_connect = __commonJS({
15330
15330
  };
15331
15331
  function connect(opts, callback) {
15332
15332
  if (callback === void 0) {
15333
- return new Promise((resolve, reject) => {
15333
+ return new Promise((resolve2, reject) => {
15334
15334
  connect.call(this, opts, (err, data) => {
15335
- return err ? reject(err) : resolve(data);
15335
+ return err ? reject(err) : resolve2(data);
15336
15336
  });
15337
15337
  });
15338
15338
  }
@@ -16618,7 +16618,7 @@ var require_snapshot_recorder = __commonJS({
16618
16618
  "use strict";
16619
16619
  init_esm_shims();
16620
16620
  var { writeFile: writeFile13, readFile: readFile17, mkdir: mkdir17 } = __require("fs/promises");
16621
- var { dirname: dirname14, resolve } = __require("path");
16621
+ var { dirname: dirname14, resolve: resolve2 } = __require("path");
16622
16622
  var { setTimeout: setTimeout2, clearTimeout: clearTimeout2 } = __require("timers");
16623
16623
  var { InvalidArgumentError: InvalidArgumentError2, UndiciError } = require_errors();
16624
16624
  var { hashId, isUrlExcludedFactory, normalizeHeaders, createHeaderFilters } = require_snapshot_utils();
@@ -16819,7 +16819,7 @@ var require_snapshot_recorder = __commonJS({
16819
16819
  throw new InvalidArgumentError2("Snapshot path is required");
16820
16820
  }
16821
16821
  try {
16822
- const data = await readFile17(resolve(path3), "utf8");
16822
+ const data = await readFile17(resolve2(path3), "utf8");
16823
16823
  const parsed = JSON.parse(data);
16824
16824
  if (Array.isArray(parsed)) {
16825
16825
  this.#snapshots.clear();
@@ -16848,7 +16848,7 @@ var require_snapshot_recorder = __commonJS({
16848
16848
  if (!path3) {
16849
16849
  throw new InvalidArgumentError2("Snapshot path is required");
16850
16850
  }
16851
- const resolvedPath = resolve(path3);
16851
+ const resolvedPath = resolve2(path3);
16852
16852
  await mkdir17(dirname14(resolvedPath), { recursive: true });
16853
16853
  const data = Array.from(this.#snapshots.entries()).map(([hash, snapshot]) => ({
16854
16854
  hash,
@@ -23714,7 +23714,7 @@ var require_fetch = __commonJS({
23714
23714
  const agent = fetchParams.controller.dispatcher;
23715
23715
  const path3 = url.pathname + url.search;
23716
23716
  const hasTrailingQuestionMark = url.search.length === 0 && url.href[url.href.length - url.hash.length - 1] === "?";
23717
- return new Promise((resolve, reject) => agent.dispatch(
23717
+ return new Promise((resolve2, reject) => agent.dispatch(
23718
23718
  {
23719
23719
  path: hasTrailingQuestionMark ? `${path3}?` : path3,
23720
23720
  origin: url.origin,
@@ -23802,7 +23802,7 @@ var require_fetch = __commonJS({
23802
23802
  }
23803
23803
  }
23804
23804
  const onError = this.onError.bind(this);
23805
- resolve({
23805
+ resolve2({
23806
23806
  status,
23807
23807
  statusText,
23808
23808
  headersList,
@@ -23855,7 +23855,7 @@ var require_fetch = __commonJS({
23855
23855
  headersList.append(headerName, String(value), true);
23856
23856
  }
23857
23857
  }
23858
- resolve({
23858
+ resolve2({
23859
23859
  status,
23860
23860
  statusText: STATUS_CODES[status],
23861
23861
  headersList,
@@ -23879,7 +23879,7 @@ var require_fetch = __commonJS({
23879
23879
  headersList.append(nameStr, value.toString("latin1"), true);
23880
23880
  }
23881
23881
  }
23882
- resolve({
23882
+ resolve2({
23883
23883
  status,
23884
23884
  statusText: STATUS_CODES[status],
23885
23885
  headersList,
@@ -29193,11 +29193,11 @@ var require_mtime_precision = __commonJS({
29193
29193
  function probe(file, fs, callback) {
29194
29194
  const cachedPrecision = fs[cacheSymbol];
29195
29195
  if (cachedPrecision) {
29196
- return fs.stat(file, (err, stat4) => {
29196
+ return fs.stat(file, (err, stat5) => {
29197
29197
  if (err) {
29198
29198
  return callback(err);
29199
29199
  }
29200
- callback(null, stat4.mtime, cachedPrecision);
29200
+ callback(null, stat5.mtime, cachedPrecision);
29201
29201
  });
29202
29202
  }
29203
29203
  const mtime = new Date(Math.ceil(Date.now() / 1e3) * 1e3 + 5);
@@ -29205,13 +29205,13 @@ var require_mtime_precision = __commonJS({
29205
29205
  if (err) {
29206
29206
  return callback(err);
29207
29207
  }
29208
- fs.stat(file, (err2, stat4) => {
29208
+ fs.stat(file, (err2, stat5) => {
29209
29209
  if (err2) {
29210
29210
  return callback(err2);
29211
29211
  }
29212
- const precision = stat4.mtime.getTime() % 1e3 === 0 ? "s" : "ms";
29212
+ const precision = stat5.mtime.getTime() % 1e3 === 0 ? "s" : "ms";
29213
29213
  Object.defineProperty(fs, cacheSymbol, { value: precision });
29214
- callback(null, stat4.mtime, precision);
29214
+ callback(null, stat5.mtime, precision);
29215
29215
  });
29216
29216
  });
29217
29217
  }
@@ -29266,14 +29266,14 @@ var require_lockfile = __commonJS({
29266
29266
  if (options.stale <= 0) {
29267
29267
  return callback(Object.assign(new Error("Lock file is already being held"), { code: "ELOCKED", file }));
29268
29268
  }
29269
- options.fs.stat(lockfilePath, (err2, stat4) => {
29269
+ options.fs.stat(lockfilePath, (err2, stat5) => {
29270
29270
  if (err2) {
29271
29271
  if (err2.code === "ENOENT") {
29272
29272
  return acquireLock(file, { ...options, stale: 0 }, callback);
29273
29273
  }
29274
29274
  return callback(err2);
29275
29275
  }
29276
- if (!isLockStale(stat4, options)) {
29276
+ if (!isLockStale(stat5, options)) {
29277
29277
  return callback(Object.assign(new Error("Lock file is already being held"), { code: "ELOCKED", file }));
29278
29278
  }
29279
29279
  removeLock(file, options, (err3) => {
@@ -29285,8 +29285,8 @@ var require_lockfile = __commonJS({
29285
29285
  });
29286
29286
  });
29287
29287
  }
29288
- function isLockStale(stat4, options) {
29289
- return stat4.mtime.getTime() < Date.now() - options.stale;
29288
+ function isLockStale(stat5, options) {
29289
+ return stat5.mtime.getTime() < Date.now() - options.stale;
29290
29290
  }
29291
29291
  function removeLock(file, options, callback) {
29292
29292
  options.fs.rmdir(getLockFile(file, options), (err) => {
@@ -29304,7 +29304,7 @@ var require_lockfile = __commonJS({
29304
29304
  lock2.updateDelay = lock2.updateDelay || options.update;
29305
29305
  lock2.updateTimeout = setTimeout(() => {
29306
29306
  lock2.updateTimeout = null;
29307
- options.fs.stat(lock2.lockfilePath, (err, stat4) => {
29307
+ options.fs.stat(lock2.lockfilePath, (err, stat5) => {
29308
29308
  const isOverThreshold = lock2.lastUpdate + options.stale < Date.now();
29309
29309
  if (err) {
29310
29310
  if (err.code === "ENOENT" || isOverThreshold) {
@@ -29313,7 +29313,7 @@ var require_lockfile = __commonJS({
29313
29313
  lock2.updateDelay = 1e3;
29314
29314
  return updateLock(file, options);
29315
29315
  }
29316
- const isMtimeOurs = lock2.mtime.getTime() === stat4.mtime.getTime();
29316
+ const isMtimeOurs = lock2.mtime.getTime() === stat5.mtime.getTime();
29317
29317
  if (!isMtimeOurs) {
29318
29318
  return setLockAsCompromised(
29319
29319
  file,
@@ -29438,11 +29438,11 @@ var require_lockfile = __commonJS({
29438
29438
  if (err) {
29439
29439
  return callback(err);
29440
29440
  }
29441
- options.fs.stat(getLockFile(file2, options), (err2, stat4) => {
29441
+ options.fs.stat(getLockFile(file2, options), (err2, stat5) => {
29442
29442
  if (err2) {
29443
29443
  return err2.code === "ENOENT" ? callback(null, false) : callback(err2);
29444
29444
  }
29445
- return callback(null, !isLockStale(stat4, options));
29445
+ return callback(null, !isLockStale(stat5, options));
29446
29446
  });
29447
29447
  });
29448
29448
  }
@@ -29489,12 +29489,12 @@ var require_adapter = __commonJS({
29489
29489
  return newFs;
29490
29490
  }
29491
29491
  function toPromise(method) {
29492
- return (...args) => new Promise((resolve, reject) => {
29492
+ return (...args) => new Promise((resolve2, reject) => {
29493
29493
  args.push((err, result) => {
29494
29494
  if (err) {
29495
29495
  reject(err);
29496
29496
  } else {
29497
- resolve(result);
29497
+ resolve2(result);
29498
29498
  }
29499
29499
  });
29500
29500
  method(...args);
@@ -29799,6 +29799,52 @@ var ComputerAttachClient = class {
29799
29799
  return { ok: false, code };
29800
29800
  }
29801
29801
  };
29802
+ var LegacyMachinesClient = class {
29803
+ constructor(baseUrl, accessToken) {
29804
+ this.baseUrl = baseUrl;
29805
+ this.accessToken = accessToken;
29806
+ }
29807
+ url(p) {
29808
+ return new URL(p, this.baseUrl).toString();
29809
+ }
29810
+ async list(serverSlug) {
29811
+ let res;
29812
+ try {
29813
+ res = await (0, import_undici.fetch)(
29814
+ this.url(`/api/computer/legacy-machines?serverSlug=${encodeURIComponent(serverSlug)}`),
29815
+ {
29816
+ method: "GET",
29817
+ headers: { Authorization: `Bearer ${this.accessToken}` }
29818
+ }
29819
+ );
29820
+ } catch {
29821
+ return { status: "error", code: "request_failed" };
29822
+ }
29823
+ const body = await res.json().catch(() => null);
29824
+ if (res.status === 200 && body && Array.isArray(body.entries)) {
29825
+ const entries = body.entries.map((raw) => {
29826
+ if (!raw || typeof raw !== "object") return null;
29827
+ const e = raw;
29828
+ if (typeof e.daemonId !== "string" || typeof e.apiKeyFingerprint !== "string" || typeof e.machineName !== "string") {
29829
+ return null;
29830
+ }
29831
+ return {
29832
+ daemonId: e.daemonId,
29833
+ apiKeyFingerprint: e.apiKeyFingerprint,
29834
+ machineName: e.machineName,
29835
+ hostname: typeof e.hostname === "string" ? e.hostname : null,
29836
+ lastSeenAt: typeof e.lastSeenAt === "string" ? e.lastSeenAt : null
29837
+ };
29838
+ }).filter((entry) => entry !== null);
29839
+ return { status: "success", entries };
29840
+ }
29841
+ if (res.status === 401) return { status: "auth_required" };
29842
+ if (res.status === 403) return { status: "not_authorized" };
29843
+ if (res.status === 404) return { status: "disabled" };
29844
+ const code = body && typeof body.code === "string" ? body.code : `http_${res.status}`;
29845
+ return { status: "error", code };
29846
+ }
29847
+ };
29802
29848
  var RunnersClient = class {
29803
29849
  constructor(baseUrl, computerApiKey) {
29804
29850
  this.baseUrl = baseUrl;
@@ -29879,11 +29925,11 @@ function serverDir(slockHome, serverId) {
29879
29925
  function serverAttachmentPath(slockHome, serverId) {
29880
29926
  return path2.join(serverDir(slockHome, serverId), "runner.state.json");
29881
29927
  }
29882
- function serverDaemonPidPath(slockHome, serverId) {
29883
- return path2.join(serverDir(slockHome, serverId), "daemon.pid");
29928
+ function serverRunnerPidPath(slockHome, serverId) {
29929
+ return path2.join(serverDir(slockHome, serverId), "server-runner.pid");
29884
29930
  }
29885
- function serverDaemonLogPath(slockHome, serverId) {
29886
- return path2.join(serverDir(slockHome, serverId), "daemon.log");
29931
+ function serverRunnerLogPath(slockHome, serverId) {
29932
+ return path2.join(serverDir(slockHome, serverId), "server-runner.log");
29887
29933
  }
29888
29934
  function serverManagedFlagPath(slockHome, serverId) {
29889
29935
  return path2.join(serverDir(slockHome, serverId), "managed.flag");
@@ -29891,17 +29937,30 @@ function serverManagedFlagPath(slockHome, serverId) {
29891
29937
  function serverHealthPath(slockHome, serverId) {
29892
29938
  return path2.join(serverDir(slockHome, serverId), "health.json");
29893
29939
  }
29940
+ function serviceRunDir(slockHome) {
29941
+ return path2.join(computerDir(slockHome), "run");
29942
+ }
29894
29943
  function serviceStatePath(slockHome) {
29895
- return path2.join(computerDir(slockHome), "service.state.json");
29944
+ return path2.join(serviceRunDir(slockHome), "service.state.json");
29896
29945
  }
29897
29946
  function servicePidPath(slockHome) {
29947
+ return path2.join(serviceRunDir(slockHome), "service.pid");
29948
+ }
29949
+ function legacyServicePidPath(slockHome) {
29898
29950
  return path2.join(computerDir(slockHome), "service.pid");
29899
29951
  }
29900
29952
  function legacySupervisorPidPath(slockHome) {
29901
29953
  return path2.join(computerDir(slockHome), "supervisor.pid");
29902
29954
  }
29955
+ function servicePidReadFallback(slockHome) {
29956
+ return [
29957
+ servicePidPath(slockHome),
29958
+ legacyServicePidPath(slockHome),
29959
+ legacySupervisorPidPath(slockHome)
29960
+ ];
29961
+ }
29903
29962
  function serviceLogPath(slockHome) {
29904
- return path2.join(computerDir(slockHome), "service.log");
29963
+ return path2.join(serviceRunDir(slockHome), "service.log");
29905
29964
  }
29906
29965
  function serviceVersionPath(slockHome) {
29907
29966
  return path2.join(computerDir(slockHome), "service-version.json");
@@ -29962,14 +30021,14 @@ function resolveServerUrl(...candidates) {
29962
30021
 
29963
30022
  // src/services/login.ts
29964
30023
  function sleep(ms, signal) {
29965
- return new Promise((resolve, reject) => {
30024
+ return new Promise((resolve2, reject) => {
29966
30025
  if (signal?.aborted) {
29967
30026
  reject(abortError(signal));
29968
30027
  return;
29969
30028
  }
29970
30029
  const t = setTimeout(() => {
29971
30030
  signal?.removeEventListener("abort", onAbort);
29972
- resolve();
30031
+ resolve2();
29973
30032
  }, ms);
29974
30033
  const onAbort = () => {
29975
30034
  clearTimeout(t);
@@ -30390,68 +30449,153 @@ import { dirname as dirname9 } from "path";
30390
30449
 
30391
30450
  // src/lib/migration.ts
30392
30451
  init_esm_shims();
30393
- import { readdir as readdir2, readFile as readFile3 } from "fs/promises";
30394
- import { join } from "path";
30452
+ import { readdir as readdir2, readFile as readFile3, stat } from "fs/promises";
30453
+ import { join, resolve } from "path";
30395
30454
  var MACHINE_DIR_PREFIX = "machine-";
30396
- async function readOwnerEvidence(installRoot, machineDirName) {
30397
- const ownerFile = join(
30398
- installRoot,
30399
- "machines",
30400
- machineDirName,
30401
- "daemon.lock",
30402
- "owner.json"
30403
- );
30455
+ var FINGERPRINT_HEX_RE = /^[0-9a-f]{16}$/;
30456
+ async function readLocalOwners(installRoot) {
30457
+ const machinesDir = join(installRoot, "machines");
30458
+ let entries;
30459
+ try {
30460
+ entries = await readdir2(machinesDir);
30461
+ } catch {
30462
+ return [];
30463
+ }
30464
+ const owners = [];
30465
+ for (const name of entries) {
30466
+ if (!name.startsWith(MACHINE_DIR_PREFIX)) continue;
30467
+ const ownerPath = join(machinesDir, name, "daemon.lock", "owner.json");
30468
+ let raw;
30469
+ try {
30470
+ raw = await readFile3(ownerPath, "utf8");
30471
+ } catch {
30472
+ continue;
30473
+ }
30474
+ let parsed;
30475
+ try {
30476
+ parsed = JSON.parse(raw);
30477
+ } catch {
30478
+ continue;
30479
+ }
30480
+ const fp = parsed.apiKeyFingerprint;
30481
+ if (typeof fp !== "string" || !FINGERPRINT_HEX_RE.test(fp)) continue;
30482
+ owners.push({ apiKeyFingerprint: fp, localPath: ownerPath });
30483
+ }
30484
+ return owners;
30485
+ }
30486
+ function indexLocalByFingerprint(owners) {
30487
+ const map = /* @__PURE__ */ new Map();
30488
+ for (const owner of owners) {
30489
+ if (!map.has(owner.apiKeyFingerprint)) {
30490
+ map.set(owner.apiKeyFingerprint, owner);
30491
+ }
30492
+ }
30493
+ return map;
30494
+ }
30495
+ function intersect(localByFp, roster) {
30496
+ const out = [];
30497
+ for (const entry of roster) {
30498
+ const local = localByFp.get(entry.apiKeyFingerprint);
30499
+ if (!local) continue;
30500
+ out.push({
30501
+ apiKeyFingerprint: entry.apiKeyFingerprint,
30502
+ daemonId: entry.daemonId,
30503
+ localPath: local.localPath,
30504
+ machineName: entry.machineName,
30505
+ ...entry.hostname ? { hostname: entry.hostname } : {},
30506
+ ...entry.lastSeenAt ? { lastSeenAt: entry.lastSeenAt } : {}
30507
+ });
30508
+ }
30509
+ out.sort((a, b) => {
30510
+ const aSeen = a.lastSeenAt ?? "";
30511
+ const bSeen = b.lastSeenAt ?? "";
30512
+ if (aSeen !== bSeen) {
30513
+ if (aSeen === "") return 1;
30514
+ if (bSeen === "") return -1;
30515
+ return aSeen < bSeen ? 1 : -1;
30516
+ }
30517
+ return a.apiKeyFingerprint < b.apiKeyFingerprint ? -1 : a.apiKeyFingerprint > b.apiKeyFingerprint ? 1 : 0;
30518
+ });
30519
+ return out;
30520
+ }
30521
+ async function detectLegacyMigration(installRoot, serverSlug, client) {
30522
+ const localOwners = await readLocalOwners(installRoot);
30523
+ if (localOwners.length === 0) {
30524
+ return { kind: "candidates", candidates: [] };
30525
+ }
30526
+ const result = await client.list(serverSlug);
30527
+ if (result.status !== "success") {
30528
+ return { kind: "server-unavailable" };
30529
+ }
30530
+ const localByFp = indexLocalByFingerprint(localOwners);
30531
+ return { kind: "candidates", candidates: intersect(localByFp, result.entries) };
30532
+ }
30533
+ async function validateManualMigratePath(inputPath, roster) {
30534
+ const absInput = resolve(inputPath);
30535
+ let stats;
30536
+ try {
30537
+ stats = await stat(absInput);
30538
+ } catch {
30539
+ return { ok: false, code: "MIGRATE_FROM_NOT_FOUND" };
30540
+ }
30541
+ const ownerPath = stats.isDirectory() ? join(absInput, "daemon.lock", "owner.json") : absInput;
30542
+ const candidateOwnerPath = await firstReadableOwner([
30543
+ ownerPath,
30544
+ join(absInput, "owner.json"),
30545
+ absInput
30546
+ ]);
30547
+ if (!candidateOwnerPath) {
30548
+ return { ok: false, code: "MIGRATE_FROM_INVALID" };
30549
+ }
30404
30550
  let raw;
30405
30551
  try {
30406
- raw = await readFile3(ownerFile, "utf8");
30552
+ raw = await readFile3(candidateOwnerPath, "utf8");
30407
30553
  } catch {
30408
- return {};
30554
+ return { ok: false, code: "MIGRATE_FROM_INVALID" };
30409
30555
  }
30410
30556
  let parsed;
30411
30557
  try {
30412
30558
  parsed = JSON.parse(raw);
30413
30559
  } catch {
30414
- return {};
30560
+ return { ok: false, code: "MIGRATE_FROM_INVALID" };
30415
30561
  }
30416
- const machineName = typeof parsed.hostname === "string" && parsed.hostname.length > 0 ? parsed.hostname : void 0;
30417
- const serverUrl = typeof parsed.serverUrl === "string" && parsed.serverUrl.length > 0 ? parsed.serverUrl : void 0;
30418
- return { machineName, serverUrl };
30419
- }
30420
- async function detectLegacyMigration(installRoot, loggedInUserId) {
30421
- void loggedInUserId;
30422
- const machinesDir = join(installRoot, "machines");
30423
- let entries;
30424
- try {
30425
- entries = await readdir2(machinesDir);
30426
- } catch {
30427
- return { kind: "no-match" };
30562
+ const fp = parsed.apiKeyFingerprint;
30563
+ if (typeof fp !== "string" || !FINGERPRINT_HEX_RE.test(fp)) {
30564
+ return { ok: false, code: "MIGRATE_FROM_INVALID" };
30428
30565
  }
30429
- const candidates = [];
30430
- for (const name of entries) {
30431
- if (!name.startsWith(MACHINE_DIR_PREFIX)) continue;
30432
- const evidence = await readOwnerEvidence(installRoot, name);
30433
- candidates.push({
30434
- machineId: name,
30435
- machineName: evidence.machineName,
30436
- serverUrl: evidence.serverUrl
30437
- });
30566
+ const match = roster.find((r) => r.apiKeyFingerprint === fp);
30567
+ if (!match) {
30568
+ return { ok: false, code: "MIGRATE_FROM_NOT_OWNED" };
30438
30569
  }
30439
- if (candidates.length === 0) return { kind: "no-match" };
30440
- if (candidates.length === 1) {
30441
- const only = candidates[0];
30442
- return {
30443
- kind: "match",
30444
- machineId: only.machineId,
30445
- machineName: only.machineName,
30446
- serverUrl: only.serverUrl
30447
- };
30570
+ return {
30571
+ ok: true,
30572
+ candidate: {
30573
+ apiKeyFingerprint: match.apiKeyFingerprint,
30574
+ daemonId: match.daemonId,
30575
+ localPath: candidateOwnerPath,
30576
+ machineName: match.machineName,
30577
+ ...match.hostname ? { hostname: match.hostname } : {},
30578
+ ...match.lastSeenAt ? { lastSeenAt: match.lastSeenAt } : {}
30579
+ }
30580
+ };
30581
+ }
30582
+ async function firstReadableOwner(paths) {
30583
+ const seen = /* @__PURE__ */ new Set();
30584
+ for (const p of paths) {
30585
+ if (seen.has(p)) continue;
30586
+ seen.add(p);
30587
+ try {
30588
+ const st = await stat(p);
30589
+ if (st.isFile()) return p;
30590
+ } catch {
30591
+ }
30448
30592
  }
30449
- return { kind: "ambiguous", candidates };
30593
+ return null;
30450
30594
  }
30451
30595
 
30452
30596
  // src/services/adoptLegacy.ts
30453
30597
  init_esm_shims();
30454
- import { chmod as chmod3, mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4, appendFile, stat } from "fs/promises";
30598
+ import { chmod as chmod3, mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4, appendFile, stat as stat2 } from "fs/promises";
30455
30599
  import { createHash as createHash2 } from "crypto";
30456
30600
  import { dirname as dirname4, join as join2 } from "path";
30457
30601
  import { setTimeout as delay } from "timers/promises";
@@ -30794,7 +30938,7 @@ async function appendAdoptionLog(slockHome, line) {
30794
30938
  const path3 = adoptionLogPath(slockHome);
30795
30939
  await appendFile(path3, fields.join(" ") + "\n", { mode: 384 });
30796
30940
  try {
30797
- const st = await stat(path3);
30941
+ const st = await stat2(path3);
30798
30942
  if ((st.mode & 63) !== 0) await chmod3(path3, 384);
30799
30943
  } catch {
30800
30944
  }
@@ -30811,7 +30955,7 @@ import { fileURLToPath as fileURLToPath2 } from "url";
30811
30955
 
30812
30956
  // src/cleanup.ts
30813
30957
  init_esm_shims();
30814
- import { readdir as readdir3, stat as stat2, unlink as unlink3, rm, rmdir, rename, mkdir as mkdir6 } from "fs/promises";
30958
+ import { readdir as readdir3, stat as stat3, unlink as unlink3, rm, rmdir, rename, mkdir as mkdir6 } from "fs/promises";
30815
30959
  import { spawn } from "child_process";
30816
30960
  import { join as join3 } from "path";
30817
30961
 
@@ -30877,7 +31021,7 @@ async function cleanupAllStalePidfiles(slockHome) {
30877
31021
  }
30878
31022
  const attached = await listAttachedServerIds(slockHome);
30879
31023
  for (const sid of attached) {
30880
- const p = serverDaemonPidPath(slockHome, sid);
31024
+ const p = serverRunnerPidPath(slockHome, sid);
30881
31025
  if (await cleanupStalePidfile(p)) cleaned.push(p);
30882
31026
  }
30883
31027
  return cleaned;
@@ -30905,7 +31049,7 @@ async function readPsTable(psSpawn = defaultPsSpawn) {
30905
31049
  }
30906
31050
  }
30907
31051
  function defaultPsSpawn() {
30908
- return new Promise((resolve) => {
31052
+ return new Promise((resolve2) => {
30909
31053
  const child = spawn("ps", ["-o", "pid,ppid,comm", "-A"], {
30910
31054
  stdio: ["ignore", "pipe", "ignore"]
30911
31055
  });
@@ -30913,8 +31057,8 @@ function defaultPsSpawn() {
30913
31057
  child.stdout.on("data", (chunk) => {
30914
31058
  out += String(chunk);
30915
31059
  });
30916
- child.on("error", () => resolve({ stdout: "", exitCode: 1 }));
30917
- child.on("close", (code) => resolve({ stdout: out, exitCode: code ?? 1 }));
31060
+ child.on("error", () => resolve2({ stdout: "", exitCode: 1 }));
31061
+ child.on("close", (code) => resolve2({ stdout: out, exitCode: code ?? 1 }));
30918
31062
  });
30919
31063
  }
30920
31064
  async function cleanupOrphanProcesses(slockHome, psSpawn) {
@@ -30925,7 +31069,7 @@ async function cleanupOrphanProcesses(slockHome, psSpawn) {
30925
31069
  const supPid = await readPidfileAt(servicePidPath(slockHome));
30926
31070
  if (supPid !== null) knownPids.add(supPid);
30927
31071
  for (const sid of managed) {
30928
- const pid = await readPidfileAt(serverDaemonPidPath(slockHome, sid));
31072
+ const pid = await readPidfileAt(serverRunnerPidPath(slockHome, sid));
30929
31073
  if (pid !== null) knownPids.add(pid);
30930
31074
  }
30931
31075
  if (supPid === null) return signaled;
@@ -30993,7 +31137,7 @@ async function cleanupTmpFiles(slockHome) {
30993
31137
  for (const v of versions) {
30994
31138
  const vdir = join3(stagingDir, v);
30995
31139
  try {
30996
- const s = await stat2(vdir);
31140
+ const s = await stat3(vdir);
30997
31141
  if (Date.now() - s.mtimeMs > TMP_MAX_AGE_MS) {
30998
31142
  await rm(vdir, { recursive: true, force: true });
30999
31143
  removed.push(vdir);
@@ -31010,7 +31154,7 @@ async function cleanupTmpFiles(slockHome) {
31010
31154
  }
31011
31155
  const snap = join3(cdir, "upgrade-snapshot.json");
31012
31156
  try {
31013
- const s = await stat2(snap);
31157
+ const s = await stat3(snap);
31014
31158
  if (Date.now() - s.mtimeMs > TMP_MAX_AGE_MS) {
31015
31159
  await unlink3(snap);
31016
31160
  removed.push(snap);
@@ -31022,7 +31166,7 @@ async function cleanupTmpFiles(slockHome) {
31022
31166
  async function cleanupStaleLock(slockHome) {
31023
31167
  const lockDir = join3(computerDir(slockHome), ".lock");
31024
31168
  try {
31025
- const s = await stat2(lockDir);
31169
+ const s = await stat3(lockDir);
31026
31170
  if (Date.now() - s.mtimeMs > 60 * 1e3) {
31027
31171
  await rm(lockDir, { recursive: true, force: true });
31028
31172
  return [lockDir];
@@ -31034,7 +31178,7 @@ async function cleanupStaleLock(slockHome) {
31034
31178
  async function forceReleaseLock(slockHome) {
31035
31179
  const lockDir = join3(computerDir(slockHome), ".lock");
31036
31180
  try {
31037
- await stat2(lockDir);
31181
+ await stat3(lockDir);
31038
31182
  await rm(lockDir, { recursive: true, force: true });
31039
31183
  return [lockDir];
31040
31184
  } catch {
@@ -31168,6 +31312,50 @@ async function emitRunnerStateTransition(slockHome, serverId, fromState, toState
31168
31312
 
31169
31313
  // src/services/start.ts
31170
31314
  init_esm_shims();
31315
+
31316
+ // src/internal/service-pid-fallback.ts
31317
+ init_esm_shims();
31318
+ async function findLiveServicePid(slockHome, deps = {}) {
31319
+ const readPidfile = deps.readPidfile ?? readPidfileAt;
31320
+ const isAlive = deps.isProcessAlive ?? isProcessAlive2;
31321
+ const clearStale = deps.clearPidfile ?? clearPidfileAt;
31322
+ return walkFallback(slockHome, readPidfile, isAlive, clearStale);
31323
+ }
31324
+ async function findLiveServicePidReadOnly(slockHome, deps = {}) {
31325
+ const readPidfile = deps.readPidfile ?? readPidfileAt;
31326
+ const isAlive = deps.isProcessAlive ?? isProcessAlive2;
31327
+ return walkFallback(slockHome, readPidfile, isAlive, async () => void 0);
31328
+ }
31329
+ async function walkFallback(slockHome, readPidfile, isAlive, clearStale) {
31330
+ const candidates = servicePidReadFallback(slockHome);
31331
+ let firstStalePidfile = null;
31332
+ let firstStalePid = null;
31333
+ for (const candidate of candidates) {
31334
+ const candidatePid = await readPidfile(candidate);
31335
+ if (candidatePid === null) continue;
31336
+ if (isAlive(candidatePid)) {
31337
+ return {
31338
+ pid: candidatePid,
31339
+ pidfilePath: candidate,
31340
+ firstStalePidfile,
31341
+ firstStalePid
31342
+ };
31343
+ }
31344
+ await clearStale(candidate);
31345
+ if (firstStalePidfile === null) {
31346
+ firstStalePidfile = candidate;
31347
+ firstStalePid = candidatePid;
31348
+ }
31349
+ }
31350
+ return {
31351
+ pid: null,
31352
+ pidfilePath: candidates[0],
31353
+ firstStalePidfile,
31354
+ firstStalePid
31355
+ };
31356
+ }
31357
+
31358
+ // src/services/start.ts
31171
31359
  var START_ENSURE_TIMEOUT_MS = 15e3;
31172
31360
  var START_ENSURE_POLL_INTERVAL_MS = 100;
31173
31361
  function emit4(opts, event) {
@@ -31181,7 +31369,7 @@ function emit4(opts, event) {
31181
31369
  async function waitForManagedDaemonPids(slockHome, serverIds, opts) {
31182
31370
  const readPidfile = opts.readPidfile ?? readPidfileAt;
31183
31371
  const isAlive = opts.isProcessAlive ?? isProcessAlive2;
31184
- const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
31372
+ const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve2) => setTimeout(resolve2, ms)));
31185
31373
  const timeoutMs = opts.ensureTimeoutMs ?? START_ENSURE_TIMEOUT_MS;
31186
31374
  const pollIntervalMs = opts.ensurePollIntervalMs ?? START_ENSURE_POLL_INTERVAL_MS;
31187
31375
  const deadline = Date.now() + timeoutMs;
@@ -31189,7 +31377,7 @@ async function waitForManagedDaemonPids(slockHome, serverIds, opts) {
31189
31377
  while (ready.size < serverIds.length) {
31190
31378
  for (const serverId of serverIds) {
31191
31379
  if (ready.has(serverId)) continue;
31192
- const pid = await readPidfile(serverDaemonPidPath(slockHome, serverId));
31380
+ const pid = await readPidfile(serverRunnerPidPath(slockHome, serverId));
31193
31381
  if (pid && isAlive(pid)) ready.set(serverId, pid);
31194
31382
  }
31195
31383
  if (ready.size === serverIds.length) return ready;
@@ -31201,8 +31389,8 @@ async function waitForManagedDaemonPids(slockHome, serverIds, opts) {
31201
31389
  }
31202
31390
  function buildTimeoutMessage(slockHome, serverIds, ready, input) {
31203
31391
  const missing = serverIds.filter((id) => !ready.has(id));
31204
- const target = input.serverId && missing.length === 1 ? `${input.serverLabel ?? input.serverId}` : `${missing.length} daemon(s): ${missing.join(", ")}`;
31205
- return `Timed out waiting for ${target} to start. Run \`slock-computer status\` and inspect ${serviceLogPath(slockHome)} plus per-server daemon logs under ~/.slock/computer/servers/<serverId>/daemon.log.`;
31392
+ const target = input.serverId && missing.length === 1 ? `${input.serverLabel ?? input.serverId}` : `${missing.length} server runner(s): ${missing.join(", ")}`;
31393
+ return `Timed out waiting for ${target} to start. Run \`slock-computer status\` and inspect ${serviceLogPath(slockHome)} plus per-server server-runner logs under ~/.slock/computer/servers/<serverId>/server-runner.log.`;
31206
31394
  }
31207
31395
  async function start(input, options = {}) {
31208
31396
  options.signal?.throwIfAborted?.();
@@ -31230,8 +31418,11 @@ async function start(input, options = {}) {
31230
31418
  for (const id of managedTargets) {
31231
31419
  await setServerManaged(slockHome, id);
31232
31420
  }
31233
- const existing = await readPidfileAt(servicePidPath(slockHome));
31234
- if (existing && isProcessAlive2(existing)) {
31421
+ const { pid: existing } = await findLiveServicePid(slockHome, {
31422
+ readPidfile: options.readPidfile,
31423
+ isProcessAlive: options.isProcessAlive
31424
+ });
31425
+ if (existing !== null) {
31235
31426
  emit4(options, {
31236
31427
  type: "already_running",
31237
31428
  servicePid: existing,
@@ -31327,7 +31518,7 @@ async function pollReadyOnce(slockHome, serverIds, opts) {
31327
31518
  const isAlive = opts.isProcessAlive ?? isProcessAlive2;
31328
31519
  const ready = /* @__PURE__ */ new Map();
31329
31520
  for (const serverId of serverIds) {
31330
- const pid = await readPidfile(serverDaemonPidPath(slockHome, serverId));
31521
+ const pid = await readPidfile(serverRunnerPidPath(slockHome, serverId));
31331
31522
  if (pid && isAlive(pid)) ready.set(serverId, pid);
31332
31523
  }
31333
31524
  return ready;
@@ -31354,29 +31545,26 @@ async function stop(input = {}, options = {}) {
31354
31545
  const killer = options.killService ?? ((pid2) => {
31355
31546
  process.kill(pid2, "SIGTERM");
31356
31547
  });
31357
- const sleep2 = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
31548
+ const sleep2 = options.sleep ?? ((ms) => new Promise((resolve2) => setTimeout(resolve2, ms)));
31358
31549
  const pollIntervalMs = options.pollIntervalMs ?? STOP_POLL_INTERVAL_MS;
31359
31550
  const timeoutMs = options.timeoutMs ?? STOP_TIMEOUT_MS;
31360
- let pidfilePath = servicePidPath(slockHome);
31361
- let pid = await readPidfile(pidfilePath);
31362
- if (pid === null) {
31363
- const legacyPidfilePath = legacySupervisorPidPath(slockHome);
31364
- const legacyPid = await readPidfile(legacyPidfilePath);
31365
- if (legacyPid !== null) {
31366
- pidfilePath = legacyPidfilePath;
31367
- pid = legacyPid;
31368
- }
31369
- }
31551
+ const { pid, pidfilePath, firstStalePidfile, firstStalePid } = await findLiveServicePid(slockHome, {
31552
+ readPidfile,
31553
+ isProcessAlive: isAlive
31554
+ });
31370
31555
  emit5(options, { type: "stopping", pid });
31371
31556
  if (pid === null) {
31557
+ if (firstStalePidfile !== null && firstStalePid !== null) {
31558
+ emit5(options, { type: "stale_pidfile_cleared", pid: firstStalePid });
31559
+ return {
31560
+ status: "stale_pidfile_cleared",
31561
+ pid: firstStalePid,
31562
+ pidfilePath: firstStalePidfile
31563
+ };
31564
+ }
31372
31565
  emit5(options, { type: "not_running" });
31373
31566
  return { status: "not_running", pid: null, pidfilePath };
31374
31567
  }
31375
- if (!isAlive(pid)) {
31376
- await clearPidfileAt(pidfilePath);
31377
- emit5(options, { type: "stale_pidfile_cleared", pid });
31378
- return { status: "stale_pidfile_cleared", pid, pidfilePath };
31379
- }
31380
31568
  options.signal?.throwIfAborted?.();
31381
31569
  try {
31382
31570
  killer(pid);
@@ -31504,8 +31692,8 @@ async function detach(input, options = {}) {
31504
31692
  await clearServerManaged(slockHome, serverId);
31505
31693
  const subtree = [
31506
31694
  serverAttachmentPath(slockHome, serverId),
31507
- serverDaemonPidPath(slockHome, serverId),
31508
- serverDaemonLogPath(slockHome, serverId)
31695
+ serverRunnerPidPath(slockHome, serverId),
31696
+ serverRunnerLogPath(slockHome, serverId)
31509
31697
  ];
31510
31698
  for (const p of subtree) await clearPidfileAt(p);
31511
31699
  emit6(options, { type: "subtree_cleared", serverId, serverLabel });
@@ -31526,7 +31714,7 @@ function buildResidentSpawn(mode, serverId, selfEntry = process.argv[1] ?? "", e
31526
31714
  }
31527
31715
  var PARENT_LOCK_HELD_ENV_VAR = "SLOCK_COMPUTER_PARENT_MUTATION_LOCK_HELD";
31528
31716
  async function spawnDetachedService(slockHome) {
31529
- await mkdir8(computerDir(slockHome), { recursive: true });
31717
+ await mkdir8(serviceRunDir(slockHome), { recursive: true });
31530
31718
  const supLogFd = await open(serviceLogPath(slockHome), "a");
31531
31719
  const { command, args } = buildResidentSpawn("__service", null);
31532
31720
  const child = spawn2(command, args, {
@@ -31703,14 +31891,14 @@ async function readServiceVersionEvidence(slockHome) {
31703
31891
  }
31704
31892
  async function runService() {
31705
31893
  const slockHome = resolveSlockHome();
31706
- await mkdir8(computerDir(slockHome), { recursive: true });
31894
+ await mkdir8(serviceRunDir(slockHome), { recursive: true });
31707
31895
  await runServiceStartupRecovery(slockHome);
31708
31896
  await writePidfileAt(servicePidPath(slockHome), process.pid);
31709
31897
  await writeServiceVersionEvidence(slockHome);
31710
31898
  const children = /* @__PURE__ */ new Map();
31711
31899
  const { [PARENT_LOCK_HELD_ENV_VAR]: _parentLockMarker, ...childEnv } = process.env;
31712
31900
  const spawnChild = async (serverId) => {
31713
- const logPath = serverDaemonLogPath(slockHome, serverId);
31901
+ const logPath = serverRunnerLogPath(slockHome, serverId);
31714
31902
  await mkdir8(dirname8(logPath), { recursive: true });
31715
31903
  const logFd = await open(logPath, "a");
31716
31904
  const { command, args } = buildResidentSpawn("__run", serverId);
@@ -31723,11 +31911,11 @@ async function runService() {
31723
31911
  if (!child.pid) return;
31724
31912
  const handle = { serverId, child, stopping: false };
31725
31913
  children.set(serverId, handle);
31726
- await writePidfileAt(serverDaemonPidPath(slockHome, serverId), child.pid);
31914
+ await writePidfileAt(serverRunnerPidPath(slockHome, serverId), child.pid);
31727
31915
  child.on("exit", (code, signal) => {
31728
31916
  void (async () => {
31729
31917
  children.delete(serverId);
31730
- await clearPidfileAt(serverDaemonPidPath(slockHome, serverId));
31918
+ await clearPidfileAt(serverRunnerPidPath(slockHome, serverId));
31731
31919
  if (handle.stopping) return;
31732
31920
  const classification = classifyRunnerExit(code, signal);
31733
31921
  if (classification === "config-error") {
@@ -31736,7 +31924,7 @@ async function runService() {
31736
31924
  } catch {
31737
31925
  }
31738
31926
  process.stderr.write(
31739
- `Service: server ${serverId} child exited with EX_CONFIG (${EX_CONFIG_EXIT_CODE}); marked degraded, NOT auto-restarting. See ${serverDaemonLogPath(slockHome, serverId)} for the actionable error.
31927
+ `Service: server ${serverId} child exited with EX_CONFIG (${EX_CONFIG_EXIT_CODE}); marked degraded, NOT auto-restarting. See ${serverRunnerLogPath(slockHome, serverId)} for the actionable error.
31740
31928
  `
31741
31929
  );
31742
31930
  return;
@@ -31847,7 +32035,7 @@ async function runStart(opts = {}, deps = {}) {
31847
32035
  info(
31848
32036
  `Managing ${sb.managedCount} of ${sb.attachedCount} attached server(s). Logs: ${sb.logPath}`
31849
32037
  );
31850
- info(`Per-server daemon logs: ~/.slock/computer/servers/<serverId>/daemon.log`);
32038
+ info(`Per-server server-runner logs: ~/.slock/computer/servers/<serverId>/server-runner.log`);
31851
32039
  info(`Check state with \`slock-computer status\`.`);
31852
32040
  }
31853
32041
  }
@@ -31910,12 +32098,15 @@ async function runDetach(serverId, serverLabel = serverId) {
31910
32098
 
31911
32099
  // src/setup.ts
31912
32100
  var USER_SESSION_EXPIRY_LEEWAY_MS = 3e4;
31913
- async function readUserSessionUserId(slockHome) {
32101
+ async function readUserSessionAuth(slockHome) {
31914
32102
  try {
31915
32103
  const parsed = JSON.parse(await readFile8(userSessionPath(slockHome), "utf8"));
31916
- return typeof parsed.userId === "string" ? parsed.userId : "";
32104
+ return {
32105
+ accessToken: typeof parsed.accessToken === "string" ? parsed.accessToken : "",
32106
+ ...typeof parsed.serverUrl === "string" ? { serverUrl: parsed.serverUrl } : {}
32107
+ };
31917
32108
  } catch {
31918
- return "";
32109
+ return { accessToken: "" };
31919
32110
  }
31920
32111
  }
31921
32112
  async function hasValidUserSession(slockHome) {
@@ -31985,15 +32176,60 @@ async function refreshUserSession(slockHome, serverUrl) {
31985
32176
  return false;
31986
32177
  }
31987
32178
  }
31988
- async function defaultConfirmMigrate(prompt) {
31989
- process.stdout.write(prompt);
31990
- const line = await readLine(false);
31991
- const norm = line.trim().toLowerCase();
31992
- return norm === "y" || norm === "yes";
32179
+ async function pickMigrationCandidateFromInput(candidates, read, write) {
32180
+ write(
32181
+ "Migration: detected legacy daemon(s) on this Computer that match the target server.\n"
32182
+ );
32183
+ candidates.forEach((c, i) => {
32184
+ const host = c.hostname ? ` (${c.hostname})` : "";
32185
+ const seen = c.lastSeenAt ? ` last seen ${c.lastSeenAt}` : "";
32186
+ write(` ${i + 1}. ${c.machineName}${host}${seen}
32187
+ `);
32188
+ write(` local: ${c.localPath}
32189
+ `);
32190
+ });
32191
+ write(" 0. Fresh attach (skip migration)\n");
32192
+ write(" m. Migrate from a different on-disk path\n");
32193
+ while (true) {
32194
+ write("Choose [1]: ");
32195
+ const { line, eof } = await read();
32196
+ if (eof) return { kind: "fresh" };
32197
+ const trimmed = line.trim();
32198
+ const norm = trimmed.toLowerCase();
32199
+ if (norm === "m") {
32200
+ write("Path to legacy machine dir or owner.json: ");
32201
+ const { line: pathLine } = await read();
32202
+ return { kind: "manual", path: pathLine.trim() };
32203
+ }
32204
+ if (norm.length === 0) return { kind: "candidate", index: 0 };
32205
+ if (norm === "0") return { kind: "fresh" };
32206
+ const n = Number.parseInt(norm, 10);
32207
+ if (Number.isFinite(n) && String(n) === norm && n >= 1 && n <= candidates.length) {
32208
+ return { kind: "candidate", index: n - 1 };
32209
+ }
32210
+ write(
32211
+ `Invalid selection "${trimmed}". Enter 1..${candidates.length}, 0, or m.
32212
+ `
32213
+ );
32214
+ }
32215
+ }
32216
+ async function defaultPickMigrationCandidate(candidates) {
32217
+ return pickMigrationCandidateFromInput(
32218
+ candidates,
32219
+ () => readLine(false),
32220
+ (s) => {
32221
+ process.stdout.write(s);
32222
+ }
32223
+ );
31993
32224
  }
31994
- async function defaultReadMigrateSecret(prompt) {
31995
- process.stdout.write(prompt);
31996
- const line = await readLine(true);
32225
+ async function readLegacyApiKey(isTty) {
32226
+ const fromEnv = process.env.SLOCK_LEGACY_API_KEY;
32227
+ if (typeof fromEnv === "string" && fromEnv.length > 0) return fromEnv.trim();
32228
+ if (!isTty) return "";
32229
+ process.stdout.write(
32230
+ "Paste legacy api key (sk_machine_* or sk_daemon_*); input is hidden: "
32231
+ );
32232
+ const { line } = await readLine(true);
31997
32233
  process.stdout.write("\n");
31998
32234
  return line.trim();
31999
32235
  }
@@ -32003,7 +32239,7 @@ async function readLine(masked) {
32003
32239
  const enterRaw = masked && stdin.isTTY === true && typeof stdin.setRawMode === "function";
32004
32240
  if (enterRaw) stdin.setRawMode(true);
32005
32241
  stdin.resume();
32006
- return await new Promise((resolve) => {
32242
+ return await new Promise((resolve2) => {
32007
32243
  let buf = "";
32008
32244
  const cleanup = () => {
32009
32245
  stdin.off("data", onData);
@@ -32016,12 +32252,17 @@ async function readLine(masked) {
32016
32252
  for (const ch of text) {
32017
32253
  if (ch === "\n" || ch === "\r") {
32018
32254
  cleanup();
32019
- resolve(buf);
32255
+ resolve2({ line: buf, eof: false });
32256
+ return;
32257
+ }
32258
+ if (ch === "") {
32259
+ cleanup();
32260
+ resolve2({ line: buf, eof: true });
32020
32261
  return;
32021
32262
  }
32022
32263
  if (ch === "") {
32023
32264
  cleanup();
32024
- resolve("");
32265
+ resolve2({ line: "", eof: false });
32025
32266
  return;
32026
32267
  }
32027
32268
  if (ch === "\x7F" || ch === "\b") {
@@ -32033,12 +32274,88 @@ async function readLine(masked) {
32033
32274
  };
32034
32275
  const onEnd = () => {
32035
32276
  cleanup();
32036
- resolve(buf);
32277
+ resolve2({ line: buf, eof: true });
32037
32278
  };
32038
32279
  stdin.on("data", onData);
32039
32280
  stdin.on("end", onEnd);
32040
32281
  });
32041
32282
  }
32283
+ async function runMigrateAdoption(opts, candidate, isTty, adoptLegacyImpl, readLegacyApiKeyImpl) {
32284
+ const where = candidate.hostname ? ` (${candidate.hostname})` : "";
32285
+ const seen = candidate.lastSeenAt ? ` last seen ${candidate.lastSeenAt}` : "";
32286
+ info(`Migration: adopting legacy daemon "${candidate.machineName}"${where}${seen}.`);
32287
+ info(` local owner.json: ${candidate.localPath}`);
32288
+ const rawKey = await readLegacyApiKeyImpl(isTty);
32289
+ if (rawKey.length === 0) {
32290
+ fail(
32291
+ "LEGACY_KEY_REQUIRED",
32292
+ "No legacy api key provided. Set `SLOCK_LEGACY_API_KEY` or paste the legacy `sk_machine_*` / `sk_daemon_*` key when prompted."
32293
+ );
32294
+ }
32295
+ if (!rawKey.startsWith("sk_machine_") && !rawKey.startsWith("sk_daemon_")) {
32296
+ fail(
32297
+ "LEGACY_KEY_INVALID",
32298
+ "Provided key does not look like a legacy machine key (expected `sk_machine_*` or `sk_daemon_*`)."
32299
+ );
32300
+ }
32301
+ try {
32302
+ await adoptLegacyImpl(
32303
+ {
32304
+ serverSlug: opts.serverSlug,
32305
+ ...opts.serverUrl ? { serverUrl: opts.serverUrl } : {},
32306
+ ...opts.name ? { name: opts.name } : {},
32307
+ rawKey,
32308
+ mode: "legacy_key_stdin",
32309
+ redactedPrefix: rawKey.slice(0, 8)
32310
+ },
32311
+ {
32312
+ onEvent: (event) => {
32313
+ if (event.type === "adopting") {
32314
+ info(
32315
+ `Adopting legacy daemon for ${formatServerSlugDisplay(event.serverSlug)} via ${event.mode}\u2026`
32316
+ );
32317
+ } else if (event.type === "preflight") {
32318
+ info(
32319
+ event.resumed ? "Adopted (resumed prior attachment); running preflight\u2026" : "Adopted; running preflight\u2026"
32320
+ );
32321
+ } else if (event.type === "adopted") {
32322
+ info(`Adopted. Computer state written to ${event.attachmentPath}`);
32323
+ info(` server: ${formatServerSlugDisplay(event.serverSlug)}`);
32324
+ info(` serverMachine: ${event.serverMachineId}`);
32325
+ info(` legacyMachine: ${event.legacyMachineId}`);
32326
+ switch (event.legacyStop.outcome) {
32327
+ case "absent":
32328
+ info(" legacy daemon: not detected on this Computer (no local lock file)");
32329
+ break;
32330
+ case "already_dead":
32331
+ info(` legacy daemon: already stopped (pid ${event.legacyStop.pid} not running)`);
32332
+ break;
32333
+ case "stopped":
32334
+ info(` legacy daemon: stopped (pid ${event.legacyStop.pid}, SIGTERM)`);
32335
+ break;
32336
+ }
32337
+ }
32338
+ }
32339
+ }
32340
+ );
32341
+ } catch (err) {
32342
+ if (err instanceof CliExit) throw err;
32343
+ if (err instanceof ComputerServiceError) {
32344
+ fail(err.code, err.message);
32345
+ }
32346
+ throw err;
32347
+ }
32348
+ }
32349
+ function manualPathErrorMessage(code, inputPath) {
32350
+ switch (code) {
32351
+ case "MIGRATE_FROM_NOT_FOUND":
32352
+ return `--migrate-from path not found: ${inputPath}. Pass the legacy machine directory, its daemon.lock/, or its owner.json file.`;
32353
+ case "MIGRATE_FROM_INVALID":
32354
+ return `--migrate-from path is not a valid legacy daemon install: ${inputPath}. Expected an owner.json with a 16-hex-char apiKeyFingerprint field.`;
32355
+ case "MIGRATE_FROM_NOT_OWNED":
32356
+ return `--migrate-from path's apiKeyFingerprint is not in this server's legacy machine roster for the logged-in user: ${inputPath}. Either the daemon is already migrated, the row is unowned by this user, or the fingerprint is from a different server.`;
32357
+ }
32358
+ }
32042
32359
  async function runSetup(opts, deps = {}) {
32043
32360
  const slockHome = resolveSlockHome();
32044
32361
  const isTty = deps.isTty ?? Boolean(process.stdin.isTTY && process.stdout.isTTY);
@@ -32047,8 +32364,11 @@ async function runSetup(opts, deps = {}) {
32047
32364
  const start2 = deps.runStart ?? runStart;
32048
32365
  const refreshSession = deps.refreshUserSession ?? refreshUserSession;
32049
32366
  const detectMigration = deps.detectLegacyMigration ?? detectLegacyMigration;
32050
- const confirmMigrate = deps.confirmMigrate ?? defaultConfirmMigrate;
32051
- const readMigrateSecret = deps.readMigrateSecret ?? defaultReadMigrateSecret;
32367
+ const validateManualPath = deps.validateManualMigratePath ?? validateManualMigratePath;
32368
+ const buildRoster = deps.buildRosterClient ?? ((baseUrl, accessToken) => new LegacyMachinesClient(baseUrl, accessToken));
32369
+ const pickCandidate = deps.pickMigrationCandidate ?? defaultPickMigrationCandidate;
32370
+ const adoptLegacy2 = deps.adoptLegacy ?? adoptLegacy;
32371
+ const readLegacyKey = deps.readLegacyApiKey ?? readLegacyApiKey;
32052
32372
  if (!isTty && !opts.yes) {
32053
32373
  fail(
32054
32374
  "NON_INTERACTIVE_SETUP_REQUIRES_FLAGS",
@@ -32077,78 +32397,85 @@ async function runSetup(opts, deps = {}) {
32077
32397
  if (attachment) {
32078
32398
  info(`Attachment: already attached to ${label}.`);
32079
32399
  } else {
32400
+ const session = await readUserSessionAuth(slockHome);
32401
+ const baseUrl = resolveServerUrl(
32402
+ opts.serverUrl,
32403
+ session.serverUrl,
32404
+ process.env.SLOCK_SERVER_URL
32405
+ );
32406
+ const rosterClient = buildRoster(baseUrl, session.accessToken);
32080
32407
  let migrated = false;
32081
- if (isTty) {
32082
- const detection = await detectMigration(slockHome, await readUserSessionUserId(slockHome));
32083
- if (detection.kind === "match") {
32084
- const where = detection.serverUrl ? ` attached to ${detection.serverUrl}` : "";
32085
- const who = detection.machineName ? ` "${detection.machineName}"` : "";
32086
- const accepted = await confirmMigrate(
32087
- `Migration: detected legacy daemon machine${who}${where}. Migrate it under Computer instead of creating a fresh attachment? [y/N] `
32408
+ if (typeof opts.migrateFrom === "string" && opts.migrateFrom.length > 0) {
32409
+ const rosterResult = await rosterClient.list(opts.serverSlug);
32410
+ if (rosterResult.status !== "success") {
32411
+ fail(
32412
+ "MIGRATE_FROM_ROSTER_UNAVAILABLE",
32413
+ `Cannot validate --migrate-from path: legacy machine roster unavailable (${rosterResult.status}). Retry when the server is reachable, or remove --migrate-from to fall through to fresh attach.`
32088
32414
  );
32089
- if (accepted) {
32090
- const rawKey = await readMigrateSecret(
32091
- "Paste legacy api key (sk_machine_* or sk_daemon_*); input is hidden: "
32092
- );
32093
- if (rawKey.length === 0) {
32094
- info("Migration: no key provided; falling back to fresh attach.");
32095
- } else if (!rawKey.startsWith("sk_machine_") && !rawKey.startsWith("sk_daemon_")) {
32096
- fail(
32097
- "LEGACY_KEY_INVALID",
32098
- "Provided key does not look like a legacy machine key (expected `sk_machine_*` or `sk_daemon_*`)."
32099
- );
32100
- } else {
32101
- try {
32102
- await adoptLegacy(
32103
- {
32104
- serverSlug: opts.serverSlug,
32105
- serverUrl: opts.serverUrl,
32106
- name: opts.name,
32107
- rawKey,
32108
- mode: "legacy_key_stdin",
32109
- redactedPrefix: rawKey.slice(0, 8)
32110
- },
32111
- {
32112
- onEvent: (event) => {
32113
- if (event.type === "adopting") {
32114
- info(
32115
- `Adopting legacy daemon for ${formatServerSlugDisplay(event.serverSlug)} via ${event.mode}\u2026`
32116
- );
32117
- } else if (event.type === "preflight") {
32118
- info(
32119
- event.resumed ? "Adopted (resumed prior attachment); running preflight\u2026" : "Adopted; running preflight\u2026"
32120
- );
32121
- } else if (event.type === "adopted") {
32122
- info(`Adopted. Computer state written to ${event.attachmentPath}`);
32123
- info(` server: ${formatServerSlugDisplay(event.serverSlug)}`);
32124
- info(` serverMachine: ${event.serverMachineId}`);
32125
- info(` legacyMachine: ${event.legacyMachineId}`);
32126
- switch (event.legacyStop.outcome) {
32127
- case "absent":
32128
- info(" legacy daemon: not detected on this Computer (no local lock file)");
32129
- break;
32130
- case "already_dead":
32131
- info(` legacy daemon: already stopped (pid ${event.legacyStop.pid} not running)`);
32132
- break;
32133
- case "stopped":
32134
- info(` legacy daemon: stopped (pid ${event.legacyStop.pid}, SIGTERM)`);
32135
- break;
32136
- }
32137
- }
32138
- }
32139
- }
32415
+ }
32416
+ const validation = await validateManualPath(opts.migrateFrom, rosterResult.entries);
32417
+ if (!validation.ok) {
32418
+ fail(validation.code, manualPathErrorMessage(validation.code, opts.migrateFrom));
32419
+ }
32420
+ await runMigrateAdoption(opts, validation.candidate, isTty, adoptLegacy2, readLegacyKey);
32421
+ migrated = true;
32422
+ } else {
32423
+ const detection = await detectMigration(slockHome, opts.serverSlug, rosterClient);
32424
+ if (detection.kind === "server-unavailable") {
32425
+ info(
32426
+ "Migration: legacy machine roster unavailable (fresh trigger: server-unavailable); falling back to fresh attach."
32427
+ );
32428
+ } else if (detection.candidates.length === 0) {
32429
+ } else if (!isTty) {
32430
+ info(
32431
+ `Migration: ${detection.candidates.length} legacy daemon(s) match this server but setup is non-interactive (fresh trigger: non-tty); falling back to fresh attach. Re-run on a TTY or pass --migrate-from <path> to adopt a specific install.`
32432
+ );
32433
+ } else {
32434
+ pickerLoop: while (true) {
32435
+ const selection = await pickCandidate(detection.candidates);
32436
+ if (selection.kind === "candidate") {
32437
+ const candidate = detection.candidates[selection.index];
32438
+ if (!candidate) {
32439
+ fail(
32440
+ "MIGRATE_PICKER_OUT_OF_RANGE",
32441
+ `Picker returned candidate index ${selection.index} but only ${detection.candidates.length} candidate(s) were detected.`
32140
32442
  );
32141
- migrated = true;
32142
- } catch (err) {
32143
- if (err instanceof CliExit) throw err;
32144
- if (err instanceof ComputerServiceError) {
32145
- fail(err.code, err.message);
32146
- }
32147
- throw err;
32148
32443
  }
32444
+ await runMigrateAdoption(opts, candidate, isTty, adoptLegacy2, readLegacyKey);
32445
+ migrated = true;
32446
+ break pickerLoop;
32447
+ } else if (selection.kind === "manual") {
32448
+ if (selection.path.length === 0) {
32449
+ info("Migration: no path provided; returning to picker.");
32450
+ continue pickerLoop;
32451
+ }
32452
+ const rosterResult = await rosterClient.list(opts.serverSlug);
32453
+ if (rosterResult.status !== "success") {
32454
+ info(
32455
+ `Migration: MIGRATE_FROM_ROSTER_UNAVAILABLE \u2014 legacy machine roster unavailable (${rosterResult.status}); returning to picker. Pick "0" to fall through to fresh attach if the server stays unreachable.`
32456
+ );
32457
+ continue pickerLoop;
32458
+ }
32459
+ const validation = await validateManualPath(selection.path, rosterResult.entries);
32460
+ if (!validation.ok) {
32461
+ info(
32462
+ `Migration: ${validation.code} \u2014 ${manualPathErrorMessage(validation.code, selection.path)} Returning to picker.`
32463
+ );
32464
+ continue pickerLoop;
32465
+ }
32466
+ await runMigrateAdoption(
32467
+ opts,
32468
+ validation.candidate,
32469
+ isTty,
32470
+ adoptLegacy2,
32471
+ readLegacyKey
32472
+ );
32473
+ migrated = true;
32474
+ break pickerLoop;
32475
+ } else {
32476
+ info("Migration: fresh attach selected.");
32477
+ break pickerLoop;
32149
32478
  }
32150
- } else {
32151
- info("Migration: declined; falling back to fresh attach.");
32152
32479
  }
32153
32480
  }
32154
32481
  }
@@ -32208,6 +32535,10 @@ async function pidStatus(pidfile) {
32208
32535
  const pid = await readPidfileAt(pidfile);
32209
32536
  return pid !== null && isProcessAlive2(pid) ? { running: true, pid } : { running: false };
32210
32537
  }
32538
+ async function serviceState(slockHome) {
32539
+ const { pid } = await findLiveServicePidReadOnly(slockHome);
32540
+ return pid !== null ? { running: true, pid } : { running: false };
32541
+ }
32211
32542
  async function deriveHealth(slockHome, serverId, daemon) {
32212
32543
  if (!daemon.running) return "offline";
32213
32544
  if (await isDegraded(slockHome, serverId)) return "degraded";
@@ -32218,19 +32549,19 @@ async function buildStatusReport(installRoot) {
32218
32549
  const session = sessionRead.session;
32219
32550
  const attachments = await listServerAttachments(installRoot);
32220
32551
  const service = {
32221
- ...await pidStatus(servicePidPath(installRoot)),
32552
+ ...await serviceState(installRoot),
32222
32553
  logPath: serviceLogPath(installRoot)
32223
32554
  };
32224
32555
  const servers = [];
32225
32556
  for (const a of attachments) {
32226
- const daemon = await pidStatus(serverDaemonPidPath(installRoot, a.serverId));
32557
+ const daemon = await pidStatus(serverRunnerPidPath(installRoot, a.serverId));
32227
32558
  servers.push({
32228
32559
  serverId: a.serverId,
32229
32560
  serverSlug: a.serverSlug ?? null,
32230
32561
  serverMachineId: a.serverMachineId,
32231
32562
  serverUrl: a.serverUrl,
32232
32563
  attachedAt: a.attachedAt ?? null,
32233
- daemonLogPath: serverDaemonLogPath(installRoot, a.serverId),
32564
+ serverRunnerLogPath: serverRunnerLogPath(installRoot, a.serverId),
32234
32565
  daemon,
32235
32566
  health: await deriveHealth(installRoot, a.serverId, daemon)
32236
32567
  });
@@ -32277,7 +32608,7 @@ async function runStatus(opts) {
32277
32608
  info(
32278
32609
  ` ${pad(formatServerSlugDisplay(s.serverSlug), 24)}${pad(s.health, 12)}${pad(dcol, 24)}${pad(s.serverMachineId, 38)}${s.serverUrl}`
32279
32610
  );
32280
- info(` Daemon log: ${s.daemonLogPath}`);
32611
+ info(` Server runner log: ${s.serverRunnerLogPath}`);
32281
32612
  }
32282
32613
  if (report.servers.some((s) => s.health === "degraded")) {
32283
32614
  info("");
@@ -32618,14 +32949,14 @@ import { readFile as readFile10 } from "fs/promises";
32618
32949
  var DEFAULT_LINES = 200;
32619
32950
  async function runLogs(opts) {
32620
32951
  const home = resolveSlockHome();
32621
- const file = opts.service ? serviceLogPath(home) : serverDaemonLogPath(home, await resolveTargetServerId({ server: opts.server }));
32952
+ const file = opts.service ? serviceLogPath(home) : serverRunnerLogPath(home, await resolveTargetServerId({ server: opts.server }));
32622
32953
  let content;
32623
32954
  try {
32624
32955
  content = await readFile10(file, "utf8");
32625
32956
  } catch {
32626
32957
  fail(
32627
32958
  "NO_DAEMON_LOG",
32628
- opts.service ? `No service log at ${file}. Start the service first (\`slock-computer start\`).` : `No daemon log at ${file}. Start its daemon first (\`slock-computer start\`).`
32959
+ opts.service ? `No service log at ${file}. Start the service first (\`slock-computer start\`).` : `No server runner log at ${file}. Start its server runner first (\`slock-computer start\`).`
32629
32960
  );
32630
32961
  }
32631
32962
  const n = Number.isInteger(opts.lines) && opts.lines > 0 ? opts.lines : DEFAULT_LINES;
@@ -33035,7 +33366,7 @@ function errMsg(e) {
33035
33366
  return e instanceof Error ? e.message : String(e);
33036
33367
  }
33037
33368
  async function defaultReadTarballPackageJson(tarballPath) {
33038
- const raw = await new Promise((resolve, reject) => {
33369
+ const raw = await new Promise((resolve2, reject) => {
33039
33370
  const child = spawn3("tar", ["-xzOf", tarballPath, "package/package.json"], {
33040
33371
  stdio: ["ignore", "pipe", "pipe"]
33041
33372
  });
@@ -33053,7 +33384,7 @@ async function defaultReadTarballPackageJson(tarballPath) {
33053
33384
  reject(new Error(`tar -xzOf exited ${code}: ${stderrBuf.slice(0, 300)}`));
33054
33385
  return;
33055
33386
  }
33056
- resolve(stdoutBuf);
33387
+ resolve2(stdoutBuf);
33057
33388
  });
33058
33389
  });
33059
33390
  return JSON.parse(raw);
@@ -33181,7 +33512,7 @@ async function stagePhase(slockHome, version, deps = {}) {
33181
33512
  return { stagedPath, version, tarballSha1 };
33182
33513
  }
33183
33514
  function defaultNpmPack(cwd, packageRef) {
33184
- return new Promise((resolve) => {
33515
+ return new Promise((resolve2) => {
33185
33516
  const child = spawn4("npm", ["pack", packageRef, "--json"], {
33186
33517
  cwd,
33187
33518
  stdio: ["ignore", "pipe", "pipe"]
@@ -33195,23 +33526,23 @@ function defaultNpmPack(cwd, packageRef) {
33195
33526
  stderrBuf += String(chunk);
33196
33527
  });
33197
33528
  child.on("error", (err) => {
33198
- resolve({ tarballPath: "", exitCode: 1, stderr: String(err) });
33529
+ resolve2({ tarballPath: "", exitCode: 1, stderr: String(err) });
33199
33530
  });
33200
33531
  child.on("close", (code) => {
33201
33532
  if (code !== 0) {
33202
- resolve({ tarballPath: "", exitCode: code ?? 1, stderr: stderrBuf });
33533
+ resolve2({ tarballPath: "", exitCode: code ?? 1, stderr: stderrBuf });
33203
33534
  return;
33204
33535
  }
33205
33536
  try {
33206
33537
  const parsed = JSON.parse(stdoutBuf);
33207
33538
  const filename = parsed[0]?.filename;
33208
33539
  if (!filename) {
33209
- resolve({ tarballPath: "", exitCode: 1, stderr: "npm pack returned no filename" });
33540
+ resolve2({ tarballPath: "", exitCode: 1, stderr: "npm pack returned no filename" });
33210
33541
  return;
33211
33542
  }
33212
- resolve({ tarballPath: join6(cwd, filename), exitCode: 0, stderr: stderrBuf });
33543
+ resolve2({ tarballPath: join6(cwd, filename), exitCode: 0, stderr: stderrBuf });
33213
33544
  } catch (e) {
33214
- resolve({ tarballPath: "", exitCode: 1, stderr: `parse npm pack output: ${e}` });
33545
+ resolve2({ tarballPath: "", exitCode: 1, stderr: `parse npm pack output: ${e}` });
33215
33546
  }
33216
33547
  });
33217
33548
  });
@@ -33291,7 +33622,7 @@ async function extractTarball(tarballPath, destDir, deps = {}) {
33291
33622
  return { extractedPackageDir: join6(destDir, "package") };
33292
33623
  }
33293
33624
  function defaultTarSpawn(tarballPath, destDir) {
33294
- return new Promise((resolve) => {
33625
+ return new Promise((resolve2) => {
33295
33626
  const child = spawn4("tar", ["-xzf", tarballPath, "-C", destDir], {
33296
33627
  stdio: ["ignore", "ignore", "pipe"]
33297
33628
  });
@@ -33300,10 +33631,10 @@ function defaultTarSpawn(tarballPath, destDir) {
33300
33631
  stderrBuf += String(chunk);
33301
33632
  });
33302
33633
  child.on("error", (err) => {
33303
- resolve({ exitCode: 1, stderr: String(err) });
33634
+ resolve2({ exitCode: 1, stderr: String(err) });
33304
33635
  });
33305
33636
  child.on("close", (code) => {
33306
- resolve({ exitCode: code ?? 1, stderr: stderrBuf });
33637
+ resolve2({ exitCode: code ?? 1, stderr: stderrBuf });
33307
33638
  });
33308
33639
  });
33309
33640
  }
@@ -33319,7 +33650,7 @@ async function hydrateStagedDependencies(extractedPackageDir, deps = {}) {
33319
33650
  }
33320
33651
  }
33321
33652
  function defaultNpmInstallProductionDeps(cwd) {
33322
- return new Promise((resolve) => {
33653
+ return new Promise((resolve2) => {
33323
33654
  const child = spawn4(
33324
33655
  "npm",
33325
33656
  [
@@ -33340,10 +33671,10 @@ function defaultNpmInstallProductionDeps(cwd) {
33340
33671
  stderrBuf += String(chunk);
33341
33672
  });
33342
33673
  child.on("error", (err) => {
33343
- resolve({ exitCode: 1, stderr: String(err) });
33674
+ resolve2({ exitCode: 1, stderr: String(err) });
33344
33675
  });
33345
33676
  child.on("close", (code) => {
33346
- resolve({ exitCode: code ?? 1, stderr: stderrBuf });
33677
+ resolve2({ exitCode: code ?? 1, stderr: stderrBuf });
33347
33678
  });
33348
33679
  });
33349
33680
  }
@@ -33537,13 +33868,8 @@ async function restartPhase(slockHome, deps = {}) {
33537
33868
  return { ok: false, reason: "health_check_timeout" };
33538
33869
  }
33539
33870
  async function defaultReadServicePid(slockHome) {
33540
- try {
33541
- const raw = (await readFile14(servicePidPath(slockHome), "utf8")).trim();
33542
- const pid = Number.parseInt(raw, 10);
33543
- return Number.isInteger(pid) && pid > 0 ? pid : null;
33544
- } catch {
33545
- return null;
33546
- }
33871
+ const { pid } = await findLiveServicePid(slockHome);
33872
+ return pid;
33547
33873
  }
33548
33874
  async function defaultKillService(pid) {
33549
33875
  try {
@@ -33861,7 +34187,7 @@ async function rollingDaemonHealthCheck(slockHome, deps = {}) {
33861
34187
  }
33862
34188
  async function defaultReadDaemonPid(slockHome, serverId) {
33863
34189
  try {
33864
- const raw = (await readFile14(serverDaemonPidPath(slockHome, serverId), "utf8")).trim();
34190
+ const raw = (await readFile14(serverRunnerPidPath(slockHome, serverId), "utf8")).trim();
33865
34191
  const pid = Number.parseInt(raw, 10);
33866
34192
  return Number.isInteger(pid) && pid > 0 ? pid : null;
33867
34193
  } catch {
@@ -34067,6 +34393,8 @@ async function runUpgradeCli(slockHome, opts, deps = {}) {
34067
34393
  if (drainMode !== void 0 && drainMode !== "drain") {
34068
34394
  info(`Drain mode: ${drainMode}${drainMode === "force" ? " (in-flight turns will be dropped)" : ""}.`);
34069
34395
  }
34396
+ const readBundledFn = deps.readBundledDaemonVersion ?? readBundledDaemonVersion;
34397
+ const fromBundledDaemonVersion = await readBundledFn(currentBinaryDir);
34070
34398
  const runUpgradeFn = deps.runUpgradeFn ?? runUpgrade;
34071
34399
  const spawnFreshService = deps.spawnFreshService ?? defaultSpawnFreshService;
34072
34400
  const outcome = await runUpgradeFn(slockHome, {
@@ -34084,17 +34412,18 @@ async function runUpgradeCli(slockHome, opts, deps = {}) {
34084
34412
  spawnFreshService: () => spawnFreshService(slockHome)
34085
34413
  }
34086
34414
  });
34087
- const readBundledFn = deps.readBundledDaemonVersion ?? readBundledDaemonVersion;
34088
- const bundledDaemonVersion = await readBundledFn(currentBinaryDir);
34089
- const bundle = (version) => bundledDaemonVersion !== null ? { computerVersion: version, bundledDaemonVersion } : { computerVersion: version };
34415
+ const toBundledDaemonVersion = await readBundledFn(currentBinaryDir);
34416
+ const bundle = (version, bundledDaemonVersion) => bundledDaemonVersion !== null ? { computerVersion: version, bundledDaemonVersion } : { computerVersion: version };
34417
+ const fromBundle = bundle(fromVersion, fromBundledDaemonVersion);
34418
+ const toBundle = bundle(targetVersion, toBundledDaemonVersion);
34090
34419
  const logTrigger = opts.trigger ?? "cli";
34091
34420
  if (outcome.ok) {
34092
34421
  info(
34093
34422
  `Upgrade ${fromVersion} \u2192 ${targetVersion} succeeded (health-OK in ${outcome.restart?.healthAfterMs ?? 0}ms).`
34094
34423
  );
34095
34424
  await appendUpgradeLogEntry(slockHome, {
34096
- fromBundle: bundle(fromVersion),
34097
- toBundle: bundle(targetVersion),
34425
+ fromBundle,
34426
+ toBundle,
34098
34427
  channel: channel2,
34099
34428
  trigger: logTrigger,
34100
34429
  outcome: "ok"
@@ -34114,8 +34443,8 @@ async function runUpgradeCli(slockHome, opts, deps = {}) {
34114
34443
  `Upgrade ${fromVersion} \u2192 ${targetVersion} succeeded; cleanup phase reported a non-fatal issue: ${outcome.reason ?? "unknown"}. Active layout + service are healthy.`
34115
34444
  );
34116
34445
  await appendUpgradeLogEntry(slockHome, {
34117
- fromBundle: bundle(fromVersion),
34118
- toBundle: bundle(targetVersion),
34446
+ fromBundle,
34447
+ toBundle,
34119
34448
  channel: channel2,
34120
34449
  trigger: logTrigger,
34121
34450
  outcome: "ok"
@@ -34125,8 +34454,8 @@ async function runUpgradeCli(slockHome, opts, deps = {}) {
34125
34454
  }
34126
34455
  const code = mapFailurePhaseToCode(outcome);
34127
34456
  await appendUpgradeLogEntry(slockHome, {
34128
- fromBundle: bundle(fromVersion),
34129
- toBundle: bundle(targetVersion),
34457
+ fromBundle,
34458
+ toBundle,
34130
34459
  channel: channel2,
34131
34460
  trigger: logTrigger,
34132
34461
  outcome: "err",
@@ -34205,7 +34534,7 @@ async function defaultCurrentVersion() {
34205
34534
 
34206
34535
  // src/upgradeTestHarness.ts
34207
34536
  init_esm_shims();
34208
- import { mkdir as mkdir15, readdir as readdir4, stat as stat3, writeFile as writeFile12 } from "fs/promises";
34537
+ import { mkdir as mkdir15, readdir as readdir4, stat as stat4, writeFile as writeFile12 } from "fs/promises";
34209
34538
  import { join as join8 } from "path";
34210
34539
  import { createHash as createHash4 } from "crypto";
34211
34540
  var PHASES = /* @__PURE__ */ new Set([
@@ -34342,7 +34671,7 @@ async function arrangeSnapshotFailure(slockHome) {
34342
34671
  }
34343
34672
  async function pathInfo(path3) {
34344
34673
  try {
34345
- const s = await stat3(path3);
34674
+ const s = await stat4(path3);
34346
34675
  if (s.isDirectory()) return { kind: "dir", size: 0 };
34347
34676
  if (s.isFile()) return { kind: "file", size: s.size };
34348
34677
  return null;
@@ -34499,10 +34828,10 @@ async function runUpgradeInstallSmoke(slockHome, opts, deps = {}) {
34499
34828
  const activeVersion = await readServiceVersionEvidence(slockHome);
34500
34829
  return { outcome, activeVersion };
34501
34830
  }
34502
- async function runUpgradeInstallSmokeCli(slockHome, opts, writer = (s) => process.stdout.write(s)) {
34831
+ async function runUpgradeInstallSmokeCli(slockHome, opts, writer = (s) => process.stdout.write(s), deps = {}) {
34503
34832
  let report;
34504
34833
  try {
34505
- report = await runUpgradeInstallSmoke(slockHome, opts);
34834
+ report = await runUpgradeInstallSmoke(slockHome, opts, deps);
34506
34835
  } catch (e) {
34507
34836
  const msg = e instanceof Error ? e.message : String(e);
34508
34837
  writer(
@@ -34589,10 +34918,10 @@ program2.command("setup").argument("<serverSlug>", "target Slock server slug (ca
34589
34918
  program2.command("detach").argument("<serverSlug>", "server slug to detach from this Computer (canonical form `/myserver`)").description("Remove ONE server's local attachment; never touches user-session or other servers.").action(withCliExit(async (serverSlug) => {
34590
34919
  await withMutationLock(async () => runDetach(await resolveTargetServerId({ server: serverSlug }), serverSlug));
34591
34920
  }));
34592
- program2.command("status").description("Show this Computer's aggregate state (login + service + per-server daemons).").option("--json", "emit the machine-readable report").action(withCliExit(async (opts) => {
34921
+ program2.command("status").description("Show this Computer's aggregate state (login + service + per-server server-runners).").option("--json", "emit the machine-readable report").action(withCliExit(async (opts) => {
34593
34922
  await runStatus({ json: opts.json });
34594
34923
  }));
34595
- program2.command("start").argument("[serverSlug]", "optional: verify this server is attached and ensure its daemon is reconciled (default: ensure all attached)").description("Start/ensure the Computer service (manages all per-server daemons).").option("--foreground", "stay in this terminal instead of detaching").action(withCliExit(async (serverSlug, opts) => {
34924
+ program2.command("start").argument("[serverSlug]", "optional: verify this server is attached and ensure its server-runner is reconciled (default: ensure all attached)").description("Start/ensure the Computer service (manages all per-server server-runners).").option("--foreground", "stay in this terminal instead of detaching").action(withCliExit(async (serverSlug, opts) => {
34596
34925
  await withMutationLock(
34597
34926
  async () => runStart({
34598
34927
  foreground: opts.foreground,
@@ -34601,7 +34930,7 @@ program2.command("start").argument("[serverSlug]", "optional: verify this server
34601
34930
  })
34602
34931
  );
34603
34932
  }));
34604
- program2.command("stop").description("Stop the Computer service (and all managed per-server daemons).").action(withCliExit(async () => {
34933
+ program2.command("stop").description("Stop the Computer service (and all managed per-server server-runners).").action(withCliExit(async () => {
34605
34934
  await withMutationLock(() => runStop());
34606
34935
  }));
34607
34936
  program2.command("doctor").argument("[serverSlug]", "optional: scope detail (recent crashes) to one server").description("Diagnose login + per-server attachments + per-server preflight (no secrets).").option("--json", "emit the machine-readable report").option("--cleanup", "after diagnosis, run the local residue cleanup pass").option("--fix", "alias for --cleanup (same behavior)").option("--reset-health", "clear <serverSlug>'s crash history so service resumes auto-restart").action(
@@ -34631,7 +34960,7 @@ program2.command("reset").description("Clear a degraded state and resume the ser
34631
34960
  );
34632
34961
  })
34633
34962
  );
34634
- program2.command("logs").description("Tail one server's daemon log (or the service log); secrets redacted.").option("--lines <n>", "trailing lines to show (default 200)", (v) => Number.parseInt(v, 10)).option("--server <slug>", "select target server slug (required when \u22652 attached)").option("--service", "tail the global service log instead of a per-server daemon log").action(withCliExit(async (opts) => {
34963
+ program2.command("logs").description("Tail one server's server-runner log (or the service log); secrets redacted.").option("--lines <n>", "trailing lines to show (default 200)", (v) => Number.parseInt(v, 10)).option("--server <slug>", "select target server slug (required when \u22652 attached)").option("--service", "tail the global service log instead of a per-server server-runner log").action(withCliExit(async (opts) => {
34635
34964
  await runLogs({ lines: opts.lines, server: opts.server ?? null, service: !!opts.service });
34636
34965
  }));
34637
34966
  var runners = program2.command("runners").description("Computer runner control plane (per-server scoped; \xA712 whitelist server-side).");