@slock-ai/computer 0.0.13 → 0.0.14

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
@@ -6917,13 +6917,13 @@ var require_infra = __commonJS({
6917
6917
  }
6918
6918
  function collectASequenceOfCodePointsFast(char, input, position) {
6919
6919
  const idx = input.indexOf(char, position.position);
6920
- const start = position.position;
6920
+ const start2 = position.position;
6921
6921
  if (idx === -1) {
6922
6922
  position.position = input.length;
6923
- return input.slice(start);
6923
+ return input.slice(start2);
6924
6924
  }
6925
6925
  position.position = idx;
6926
- return input.slice(start, position.position);
6926
+ return input.slice(start2, position.position);
6927
6927
  }
6928
6928
  var ASCII_WHITESPACE_REPLACE_REGEX = /[\u0009\u000A\u000C\u000D\u0020]/g;
6929
6929
  function forgivingBase64(data) {
@@ -9193,11 +9193,11 @@ var require_formdata_parser = __commonJS({
9193
9193
  }
9194
9194
  }
9195
9195
  function collectASequenceOfBytes(condition, input, position) {
9196
- let start = position.position;
9197
- while (start < input.length && condition(input[start])) {
9198
- ++start;
9196
+ let start2 = position.position;
9197
+ while (start2 < input.length && condition(input[start2])) {
9198
+ ++start2;
9199
9199
  }
9200
- return input.subarray(position.position, position.position = start);
9200
+ return input.subarray(position.position, position.position = start2);
9201
9201
  }
9202
9202
  function removeChars(buf, leading, trailing, predicate) {
9203
9203
  let lead = 0;
@@ -9210,12 +9210,12 @@ var require_formdata_parser = __commonJS({
9210
9210
  }
9211
9211
  return lead === 0 && trail === buf.length - 1 ? buf : buf.subarray(lead, trail + 1);
9212
9212
  }
9213
- function bufferStartsWith(buffer, start, position) {
9214
- if (buffer.length < start.length) {
9213
+ function bufferStartsWith(buffer, start2, position) {
9214
+ if (buffer.length < start2.length) {
9215
9215
  return false;
9216
9216
  }
9217
- for (let i = 0; i < start.length; i++) {
9218
- if (start[i] !== buffer[position.position + i]) {
9217
+ for (let i = 0; i < start2.length; i++) {
9218
+ if (start2[i] !== buffer[position.position + i]) {
9219
9219
  return false;
9220
9220
  }
9221
9221
  }
@@ -9657,8 +9657,8 @@ var require_client_h1 = __commonJS({
9657
9657
  */
9658
9658
  wasm_on_status: (p, at, len) => {
9659
9659
  assert(currentParser.ptr === p);
9660
- const start = at - currentBufferPtr + currentBufferRef.byteOffset;
9661
- return currentParser.onStatus(new FastBuffer(currentBufferRef.buffer, start, len));
9660
+ const start2 = at - currentBufferPtr + currentBufferRef.byteOffset;
9661
+ return currentParser.onStatus(new FastBuffer(currentBufferRef.buffer, start2, len));
9662
9662
  },
9663
9663
  /**
9664
9664
  * @param {number} p
@@ -9676,8 +9676,8 @@ var require_client_h1 = __commonJS({
9676
9676
  */
9677
9677
  wasm_on_header_field: (p, at, len) => {
9678
9678
  assert(currentParser.ptr === p);
9679
- const start = at - currentBufferPtr + currentBufferRef.byteOffset;
9680
- return currentParser.onHeaderField(new FastBuffer(currentBufferRef.buffer, start, len));
9679
+ const start2 = at - currentBufferPtr + currentBufferRef.byteOffset;
9680
+ return currentParser.onHeaderField(new FastBuffer(currentBufferRef.buffer, start2, len));
9681
9681
  },
9682
9682
  /**
9683
9683
  * @param {number} p
@@ -9687,8 +9687,8 @@ var require_client_h1 = __commonJS({
9687
9687
  */
9688
9688
  wasm_on_header_value: (p, at, len) => {
9689
9689
  assert(currentParser.ptr === p);
9690
- const start = at - currentBufferPtr + currentBufferRef.byteOffset;
9691
- return currentParser.onHeaderValue(new FastBuffer(currentBufferRef.buffer, start, len));
9690
+ const start2 = at - currentBufferPtr + currentBufferRef.byteOffset;
9691
+ return currentParser.onHeaderValue(new FastBuffer(currentBufferRef.buffer, start2, len));
9692
9692
  },
9693
9693
  /**
9694
9694
  * @param {number} p
@@ -9709,8 +9709,8 @@ var require_client_h1 = __commonJS({
9709
9709
  */
9710
9710
  wasm_on_body: (p, at, len) => {
9711
9711
  assert(currentParser.ptr === p);
9712
- const start = at - currentBufferPtr + currentBufferRef.byteOffset;
9713
- return currentParser.onBody(new FastBuffer(currentBufferRef.buffer, start, len));
9712
+ const start2 = at - currentBufferPtr + currentBufferRef.byteOffset;
9713
+ return currentParser.onBody(new FastBuffer(currentBufferRef.buffer, start2, len));
9714
9714
  },
9715
9715
  /**
9716
9716
  * @param {number} p
@@ -13962,8 +13962,8 @@ var require_retry_handler = __commonJS({
13962
13962
  data: { count: this.retryCount }
13963
13963
  });
13964
13964
  }
13965
- const { start, size, end = size ? size - 1 : null } = contentRange;
13966
- assert(this.start === start, "content-range mismatch");
13965
+ const { start: start2, size, end = size ? size - 1 : null } = contentRange;
13966
+ assert(this.start === start2, "content-range mismatch");
13967
13967
  assert(this.end == null || this.end === end, "content-range mismatch");
13968
13968
  return;
13969
13969
  }
@@ -13980,13 +13980,13 @@ var require_retry_handler = __commonJS({
13980
13980
  );
13981
13981
  return;
13982
13982
  }
13983
- const { start, size, end = size ? size - 1 : null } = range;
13983
+ const { start: start2, size, end = size ? size - 1 : null } = range;
13984
13984
  assert(
13985
- start != null && Number.isFinite(start),
13985
+ start2 != null && Number.isFinite(start2),
13986
13986
  "content-range mismatch"
13987
13987
  );
13988
13988
  assert(end != null && Number.isFinite(end), "invalid content-length");
13989
- this.start = start;
13989
+ this.start = start2;
13990
13990
  this.end = end;
13991
13991
  }
13992
13992
  if (this.end == null) {
@@ -14473,9 +14473,9 @@ var require_readable = __commonJS({
14473
14473
  }
14474
14474
  const { _readableState: state } = consume2.stream;
14475
14475
  if (state.bufferIndex) {
14476
- const start = state.bufferIndex;
14476
+ const start2 = state.bufferIndex;
14477
14477
  const end = state.buffer.length;
14478
- for (let n = start; n < end; n++) {
14478
+ for (let n = start2; n < end; n++) {
14479
14479
  consumePush(consume2, state.buffer[n]);
14480
14480
  }
14481
14481
  } else {
@@ -14500,11 +14500,11 @@ var require_readable = __commonJS({
14500
14500
  }
14501
14501
  const buffer = chunks.length === 1 ? chunks[0] : Buffer.concat(chunks, length);
14502
14502
  const bufferLength = buffer.length;
14503
- const start = bufferLength > 2 && buffer[0] === 239 && buffer[1] === 187 && buffer[2] === 191 ? 3 : 0;
14503
+ const start2 = bufferLength > 2 && buffer[0] === 239 && buffer[1] === 187 && buffer[2] === 191 ? 3 : 0;
14504
14504
  if (!encoding || encoding === "utf8" || encoding === "utf-8") {
14505
- return buffer.utf8Slice(start, bufferLength);
14505
+ return buffer.utf8Slice(start2, bufferLength);
14506
14506
  } else {
14507
- return buffer.subarray(start, bufferLength).toString(encoding);
14507
+ return buffer.subarray(start2, bufferLength).toString(encoding);
14508
14508
  }
14509
14509
  }
14510
14510
  function chunksConcat(chunks, length) {
@@ -16617,8 +16617,8 @@ var require_snapshot_recorder = __commonJS({
16617
16617
  "../../node_modules/.pnpm/undici@7.24.8/node_modules/undici/lib/mock/snapshot-recorder.js"(exports, module) {
16618
16618
  "use strict";
16619
16619
  init_esm_shims();
16620
- var { writeFile: writeFile11, readFile: readFile14, mkdir: mkdir15 } = __require("fs/promises");
16621
- var { dirname: dirname12, resolve } = __require("path");
16620
+ var { writeFile: writeFile12, readFile: readFile16, mkdir: mkdir16 } = __require("fs/promises");
16621
+ var { dirname: dirname13, resolve } = __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 readFile14(resolve(path3), "utf8");
16822
+ const data = await readFile16(resolve(path3), "utf8");
16823
16823
  const parsed = JSON.parse(data);
16824
16824
  if (Array.isArray(parsed)) {
16825
16825
  this.#snapshots.clear();
@@ -16849,12 +16849,12 @@ var require_snapshot_recorder = __commonJS({
16849
16849
  throw new InvalidArgumentError2("Snapshot path is required");
16850
16850
  }
16851
16851
  const resolvedPath = resolve(path3);
16852
- await mkdir15(dirname12(resolvedPath), { recursive: true });
16852
+ await mkdir16(dirname13(resolvedPath), { recursive: true });
16853
16853
  const data = Array.from(this.#snapshots.entries()).map(([hash, snapshot]) => ({
16854
16854
  hash,
16855
16855
  snapshot
16856
16856
  }));
16857
- await writeFile11(resolvedPath, JSON.stringify(data, null, 2), { flush: true });
16857
+ await writeFile12(resolvedPath, JSON.stringify(data, null, 2), { flush: true });
16858
16858
  }
16859
16859
  /**
16860
16860
  * Clears all recorded snapshots
@@ -22862,7 +22862,7 @@ var require_fetch = __commonJS({
22862
22862
  function handleFetchDone(response) {
22863
22863
  finalizeAndReportTiming(response, "fetch");
22864
22864
  }
22865
- function fetch4(input, init = void 0) {
22865
+ function fetch5(input, init = void 0) {
22866
22866
  webidl.argumentLengthCheck(arguments, 1, "globalThis.fetch");
22867
22867
  let p = createDeferredPromise();
22868
22868
  let requestObject;
@@ -23892,7 +23892,7 @@ var require_fetch = __commonJS({
23892
23892
  }
23893
23893
  }
23894
23894
  module.exports = {
23895
- fetch: fetch4,
23895
+ fetch: fetch5,
23896
23896
  Fetch,
23897
23897
  fetching,
23898
23898
  finalizeAndReportTiming
@@ -27902,7 +27902,7 @@ var require_undici = __commonJS({
27902
27902
  err.stack = stack ? `${stack}
27903
27903
  ${captureLines}` : capture.stack;
27904
27904
  }
27905
- module.exports.fetch = function fetch4(init, options = void 0) {
27905
+ module.exports.fetch = function fetch5(init, options = void 0) {
27906
27906
  return fetchImpl(init, options).catch((err) => {
27907
27907
  if (currentFilename) {
27908
27908
  appendFetchStackTrace(err, currentFilename);
@@ -28040,10 +28040,10 @@ var require_polyfills = __commonJS({
28040
28040
  if (platform === "win32") {
28041
28041
  fs.rename = typeof fs.rename !== "function" ? fs.rename : (function(fs$rename) {
28042
28042
  function rename5(from, to, cb) {
28043
- var start = Date.now();
28043
+ var start2 = Date.now();
28044
28044
  var backoff = 0;
28045
28045
  fs$rename(from, to, function CB(er) {
28046
- if (er && (er.code === "EACCES" || er.code === "EPERM" || er.code === "EBUSY") && Date.now() - start < 6e4) {
28046
+ if (er && (er.code === "EACCES" || er.code === "EPERM" || er.code === "EBUSY") && Date.now() - start2 < 6e4) {
28047
28047
  setTimeout(function() {
28048
28048
  fs.stat(to, function(stater, st) {
28049
28049
  if (stater && stater.code === "ENOENT")
@@ -28469,8 +28469,8 @@ var require_graceful_fs = __commonJS({
28469
28469
  fs2.createReadStream = createReadStream;
28470
28470
  fs2.createWriteStream = createWriteStream;
28471
28471
  var fs$readFile = fs2.readFile;
28472
- fs2.readFile = readFile14;
28473
- function readFile14(path3, options, cb) {
28472
+ fs2.readFile = readFile16;
28473
+ function readFile16(path3, options, cb) {
28474
28474
  if (typeof options === "function")
28475
28475
  cb = options, options = null;
28476
28476
  return go$readFile(path3, options, cb);
@@ -28486,8 +28486,8 @@ var require_graceful_fs = __commonJS({
28486
28486
  }
28487
28487
  }
28488
28488
  var fs$writeFile = fs2.writeFile;
28489
- fs2.writeFile = writeFile11;
28490
- function writeFile11(path3, data, options, cb) {
28489
+ fs2.writeFile = writeFile12;
28490
+ function writeFile12(path3, data, options, cb) {
28491
28491
  if (typeof options === "function")
28492
28492
  cb = options, options = null;
28493
28493
  return go$writeFile(path3, data, options, cb);
@@ -29094,7 +29094,7 @@ var require_signal_exit = __commonJS({
29094
29094
  emitter.count -= 1;
29095
29095
  };
29096
29096
  module.exports.unload = unload;
29097
- emit = function emit2(event, code, signal) {
29097
+ emit7 = function emit8(event, code, signal) {
29098
29098
  if (emitter.emitted[event]) {
29099
29099
  return;
29100
29100
  }
@@ -29110,8 +29110,8 @@ var require_signal_exit = __commonJS({
29110
29110
  var listeners = process2.listeners(sig);
29111
29111
  if (listeners.length === emitter.count) {
29112
29112
  unload();
29113
- emit("exit", null, sig);
29114
- emit("afterexit", null, sig);
29113
+ emit7("exit", null, sig);
29114
+ emit7("afterexit", null, sig);
29115
29115
  if (isWin && sig === "SIGHUP") {
29116
29116
  sig = "SIGINT";
29117
29117
  }
@@ -29148,8 +29148,8 @@ var require_signal_exit = __commonJS({
29148
29148
  }
29149
29149
  process2.exitCode = code || /* istanbul ignore next */
29150
29150
  0;
29151
- emit("exit", process2.exitCode, null);
29152
- emit("afterexit", process2.exitCode, null);
29151
+ emit7("exit", process2.exitCode, null);
29152
+ emit7("afterexit", process2.exitCode, null);
29153
29153
  originalProcessReallyExit.call(process2, process2.exitCode);
29154
29154
  };
29155
29155
  originalProcessEmit = process2.emit;
@@ -29159,8 +29159,8 @@ var require_signal_exit = __commonJS({
29159
29159
  process2.exitCode = arg;
29160
29160
  }
29161
29161
  var ret = originalProcessEmit.apply(this, arguments);
29162
- emit("exit", process2.exitCode, null);
29163
- emit("afterexit", process2.exitCode, null);
29162
+ emit7("exit", process2.exitCode, null);
29163
+ emit7("afterexit", process2.exitCode, null);
29164
29164
  return ret;
29165
29165
  } else {
29166
29166
  return originalProcessEmit.apply(this, arguments);
@@ -29173,7 +29173,7 @@ var require_signal_exit = __commonJS({
29173
29173
  var EE;
29174
29174
  var emitter;
29175
29175
  var unload;
29176
- var emit;
29176
+ var emit7;
29177
29177
  var sigListeners;
29178
29178
  var loaded;
29179
29179
  var load;
@@ -29591,6 +29591,48 @@ var {
29591
29591
 
29592
29592
  // src/login.ts
29593
29593
  init_esm_shims();
29594
+
29595
+ // src/output.ts
29596
+ init_esm_shims();
29597
+ var CliExit = class extends Error {
29598
+ /**
29599
+ * v8.3.3 PR-2c — carry the closed-set / stderr token through the thrown
29600
+ * error so callers can pattern-match without parsing stderr. Optional
29601
+ * because some callsites throw `new CliExit(code)` directly without a
29602
+ * named token (e.g. the harness EX_CONFIG paths).
29603
+ */
29604
+ constructor(exitCode, code) {
29605
+ super(`CliExit(${exitCode}${code ? ` ${code}` : ""})`);
29606
+ this.exitCode = exitCode;
29607
+ this.code = code;
29608
+ this.name = "CliExit";
29609
+ }
29610
+ };
29611
+ function info(line) {
29612
+ process.stdout.write(`${line}
29613
+ `);
29614
+ }
29615
+ function fail(code, message, exitCode = 1) {
29616
+ process.stderr.write(`${JSON.stringify({ ok: false, code, message })}
29617
+ `);
29618
+ throw new CliExit(exitCode, code);
29619
+ }
29620
+
29621
+ // src/services/errors.ts
29622
+ init_esm_shims();
29623
+ var ComputerServiceError = class extends Error {
29624
+ code;
29625
+ cause;
29626
+ constructor(code, message, cause) {
29627
+ super(message);
29628
+ this.name = "ComputerServiceError";
29629
+ this.code = code;
29630
+ if (cause !== void 0) this.cause = cause;
29631
+ }
29632
+ };
29633
+
29634
+ // src/services/login.ts
29635
+ init_esm_shims();
29594
29636
  import { mkdir, writeFile } from "fs/promises";
29595
29637
  import { dirname } from "path";
29596
29638
 
@@ -29901,32 +29943,6 @@ function formatUpgradeLogTimestamp(date = /* @__PURE__ */ new Date()) {
29901
29943
  return iso.replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
29902
29944
  }
29903
29945
 
29904
- // src/output.ts
29905
- init_esm_shims();
29906
- var CliExit = class extends Error {
29907
- /**
29908
- * v8.3.3 PR-2c — carry the closed-set / stderr token through the thrown
29909
- * error so callers can pattern-match without parsing stderr. Optional
29910
- * because some callsites throw `new CliExit(code)` directly without a
29911
- * named token (e.g. the harness EX_CONFIG paths).
29912
- */
29913
- constructor(exitCode, code) {
29914
- super(`CliExit(${exitCode}${code ? ` ${code}` : ""})`);
29915
- this.exitCode = exitCode;
29916
- this.code = code;
29917
- this.name = "CliExit";
29918
- }
29919
- };
29920
- function info(line) {
29921
- process.stdout.write(`${line}
29922
- `);
29923
- }
29924
- function fail(code, message, exitCode = 1) {
29925
- process.stderr.write(`${JSON.stringify({ ok: false, code, message })}
29926
- `);
29927
- throw new CliExit(exitCode, code);
29928
- }
29929
-
29930
29946
  // src/serverUrl.ts
29931
29947
  init_esm_shims();
29932
29948
  var DEFAULT_SLOCK_SERVER_URL = "https://api.slock.ai";
@@ -29938,62 +29954,146 @@ function resolveServerUrl(...candidates) {
29938
29954
  return DEFAULT_SLOCK_SERVER_URL;
29939
29955
  }
29940
29956
 
29941
- // src/login.ts
29942
- function sleep(ms) {
29943
- return new Promise((r) => setTimeout(r, ms));
29957
+ // src/services/login.ts
29958
+ function sleep(ms, signal) {
29959
+ return new Promise((resolve, reject) => {
29960
+ if (signal?.aborted) {
29961
+ reject(abortError(signal));
29962
+ return;
29963
+ }
29964
+ const t = setTimeout(() => {
29965
+ signal?.removeEventListener("abort", onAbort);
29966
+ resolve();
29967
+ }, ms);
29968
+ const onAbort = () => {
29969
+ clearTimeout(t);
29970
+ reject(abortError(signal));
29971
+ };
29972
+ signal?.addEventListener("abort", onAbort, { once: true });
29973
+ });
29944
29974
  }
29945
- async function runLogin(opts) {
29946
- const baseUrl = resolveServerUrl(opts.serverUrl, process.env.SLOCK_SERVER_URL);
29975
+ function abortError(signal) {
29976
+ const reason = signal.reason;
29977
+ if (reason instanceof Error) return reason;
29978
+ const err = new Error("aborted");
29979
+ err.name = "AbortError";
29980
+ return err;
29981
+ }
29982
+ function emit(opts, event) {
29983
+ const cb = opts?.onEvent;
29984
+ if (!cb) return;
29985
+ try {
29986
+ cb(event);
29987
+ } catch {
29988
+ }
29989
+ }
29990
+ async function login(input, options = {}) {
29991
+ const baseUrl = resolveServerUrl(input.serverUrl, process.env.SLOCK_SERVER_URL);
29992
+ options.signal?.throwIfAborted?.();
29947
29993
  const client = new DeviceAuthClient(baseUrl);
29948
29994
  let grant;
29949
29995
  try {
29950
29996
  grant = await client.authorize("slock-computer");
29951
29997
  } catch (err) {
29952
29998
  const reason = err instanceof Error ? err.message : String(err);
29953
- fail(
29999
+ throw new ComputerServiceError(
29954
30000
  "DEVICE_AUTHORIZE_FAILED",
29955
- `Could not start device login at ${baseUrl}: ${reason}. Check that the server URL is correct and reachable.`
30001
+ `Could not start device login at ${baseUrl}: ${reason}. Check that the server URL is correct and reachable.`,
30002
+ err
29956
30003
  );
29957
30004
  }
29958
30005
  const verificationUri = grant.verificationUriComplete || grant.verificationUri;
29959
30006
  const verifyUrl = new URL(verificationUri, baseUrl).toString();
29960
- info(`To finish login, open: ${verifyUrl}`);
29961
- info(`and enter the code: ${grant.userCode}`);
29962
- info(`Waiting for approval (expires in ${grant.expiresIn}s)\u2026`);
30007
+ const expiresAt = new Date(Date.now() + grant.expiresIn * 1e3).toISOString();
30008
+ emit(options, {
30009
+ type: "device-code",
30010
+ verifyUrl,
30011
+ userCode: grant.userCode,
30012
+ expiresAt,
30013
+ expiresInSeconds: grant.expiresIn
30014
+ });
29963
30015
  const intervalMs = Math.max(1, grant.interval) * 1e3;
29964
30016
  const deadline = Date.now() + grant.expiresIn * 1e3;
29965
30017
  while (Date.now() < deadline) {
29966
- await sleep(intervalMs);
30018
+ options.signal?.throwIfAborted?.();
30019
+ await sleep(intervalMs, options.signal);
30020
+ emit(options, { type: "polling" });
29967
30021
  const r = await client.token(grant.deviceCode);
29968
30022
  if (r.status === "pending") continue;
29969
- if (r.status === "denied") fail("LOGIN_DENIED", "Login was denied in the approval page.");
29970
- if (r.status === "expired") fail("LOGIN_EXPIRED", "Login request expired before approval. Re-run `slock-computer login`.");
29971
- if (r.status === "error") fail("LOGIN_FAILED", `Login failed (${r.code}). Re-run \`slock-computer login\`.`);
30023
+ if (r.status === "denied") {
30024
+ throw new ComputerServiceError("LOGIN_DENIED", "Login was denied in the approval page.");
30025
+ }
30026
+ if (r.status === "expired") {
30027
+ throw new ComputerServiceError(
30028
+ "LOGIN_EXPIRED",
30029
+ "Login request expired before approval. Re-run `slock-computer login`."
30030
+ );
30031
+ }
30032
+ if (r.status === "error") {
30033
+ throw new ComputerServiceError(
30034
+ "LOGIN_FAILED",
30035
+ `Login failed (${r.code}). Re-run \`slock-computer login\`.`
30036
+ );
30037
+ }
29972
30038
  const slockHome = resolveSlockHome();
29973
30039
  const file = userSessionPath(slockHome);
29974
30040
  await mkdir(dirname(file), { recursive: true });
29975
30041
  await writeFile(
29976
30042
  file,
29977
30043
  JSON.stringify(
29978
- { kind: "user-session", userId: r.userId, accessToken: r.accessToken, refreshToken: r.refreshToken, serverUrl: baseUrl, createdAt: (/* @__PURE__ */ new Date()).toISOString() },
30044
+ {
30045
+ kind: "user-session",
30046
+ userId: r.userId,
30047
+ accessToken: r.accessToken,
30048
+ refreshToken: r.refreshToken,
30049
+ serverUrl: baseUrl,
30050
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
30051
+ },
29979
30052
  null,
29980
30053
  2
29981
30054
  ),
29982
30055
  { mode: 384 }
29983
30056
  );
29984
- info(`Logged in. User session written to ${file}`);
29985
- if (!opts.orchestrated) {
29986
- info(`Next: run \`slock-computer attach <serverSlug>\` to attach this machine.`);
30057
+ emit(options, { type: "approved", userId: r.userId, sessionPath: file });
30058
+ return { userId: r.userId, sessionPath: file, serverUrl: baseUrl };
30059
+ }
30060
+ throw new ComputerServiceError(
30061
+ "LOGIN_EXPIRED",
30062
+ "Login request expired before approval. Re-run `slock-computer login`."
30063
+ );
30064
+ }
30065
+
30066
+ // src/login.ts
30067
+ async function runLogin(opts) {
30068
+ try {
30069
+ await login(
30070
+ { serverUrl: opts.serverUrl },
30071
+ {
30072
+ onEvent: (event) => {
30073
+ if (event.type === "device-code") {
30074
+ info(`To finish login, open: ${event.verifyUrl}`);
30075
+ info(`and enter the code: ${event.userCode}`);
30076
+ info(`Waiting for approval (expires in ${event.expiresInSeconds}s)\u2026`);
30077
+ } else if (event.type === "approved") {
30078
+ info(`Logged in. User session written to ${event.sessionPath}`);
30079
+ if (!opts.orchestrated) {
30080
+ info(`Next: run \`slock-computer attach <serverSlug>\` to attach this machine.`);
30081
+ }
30082
+ }
30083
+ }
30084
+ }
30085
+ );
30086
+ } catch (err) {
30087
+ if (err instanceof CliExit) throw err;
30088
+ if (err instanceof ComputerServiceError) {
30089
+ fail(err.code, err.message);
29987
30090
  }
29988
- return;
30091
+ throw err;
29989
30092
  }
29990
- fail("LOGIN_EXPIRED", "Login request expired before approval. Re-run `slock-computer login`.");
29991
30093
  }
29992
30094
 
29993
30095
  // src/attach.ts
29994
30096
  init_esm_shims();
29995
- import { chmod as chmod2, mkdir as mkdir3, readFile as readFile2, writeFile as writeFile3 } from "fs/promises";
29996
- import { dirname as dirname3 } from "path";
29997
30097
 
29998
30098
  // src/serverState.ts
29999
30099
  init_esm_shims();
@@ -30102,77 +30202,95 @@ async function listManagedServerIds(slockHome) {
30102
30202
  return out;
30103
30203
  }
30104
30204
 
30105
- // src/attach.ts
30106
- async function runAttach(opts) {
30205
+ // src/services/attach.ts
30206
+ init_esm_shims();
30207
+ import { chmod as chmod2, mkdir as mkdir3, readFile as readFile2, writeFile as writeFile3 } from "fs/promises";
30208
+ import { dirname as dirname3 } from "path";
30209
+ function emit2(opts, event) {
30210
+ const cb = opts?.onEvent;
30211
+ if (!cb) return;
30212
+ try {
30213
+ cb(event);
30214
+ } catch {
30215
+ }
30216
+ }
30217
+ async function attach(input, options = {}) {
30218
+ options.signal?.throwIfAborted?.();
30107
30219
  const slockHome = resolveSlockHome();
30108
30220
  const sessionFile = userSessionPath(slockHome);
30109
30221
  let session;
30110
30222
  try {
30111
30223
  session = JSON.parse(await readFile2(sessionFile, "utf8"));
30112
- } catch {
30113
- fail(
30224
+ } catch (err) {
30225
+ throw new ComputerServiceError(
30114
30226
  "NO_USER_SESSION",
30115
- `No user session at ${sessionFile}. Run \`slock-computer login\` first.`
30227
+ `No user session at ${sessionFile}. Run \`slock-computer login\` first.`,
30228
+ err
30116
30229
  );
30117
30230
  }
30118
30231
  if (session.kind !== "user-session" || typeof session.accessToken !== "string" || !session.accessToken) {
30119
- fail(
30232
+ throw new ComputerServiceError(
30120
30233
  "INVALID_USER_SESSION",
30121
30234
  `User session at ${sessionFile} is invalid. Re-run \`slock-computer login\`.`
30122
30235
  );
30123
30236
  }
30124
- const baseUrl = resolveServerUrl(opts.serverUrl, session.serverUrl, process.env.SLOCK_SERVER_URL);
30125
- const client = new ComputerAttachClient(baseUrl, session.accessToken);
30126
- const slugForServer = normalizeServerSlug(opts.serverSlug);
30237
+ const baseUrl = resolveServerUrl(input.serverUrl, session.serverUrl, process.env.SLOCK_SERVER_URL);
30238
+ const slugForServer = normalizeServerSlug(input.serverSlug);
30127
30239
  if (!slugForServer) {
30128
- fail("ATTACH_NOT_AUTHORIZED", "Server slug must not be empty.");
30240
+ throw new ComputerServiceError("ATTACH_NOT_AUTHORIZED", "Server slug must not be empty.");
30129
30241
  }
30130
- const computerName = opts.name?.trim() || deriveDefaultComputerName();
30131
- info(`Attaching this machine to server ${formatServerSlugDisplay(slugForServer)}\u2026`);
30242
+ const computerName = input.name?.trim() || deriveDefaultComputerName();
30243
+ options.signal?.throwIfAborted?.();
30244
+ emit2(options, { type: "attaching", serverSlug: slugForServer });
30245
+ const client = new ComputerAttachClient(baseUrl, session.accessToken);
30132
30246
  const attached = await client.attach(slugForServer, computerName);
30133
30247
  if (attached.status === "disabled") {
30134
- fail(
30248
+ throw new ComputerServiceError(
30135
30249
  "ATTACH_DISABLED",
30136
30250
  "Computer attach is not enabled on this server (ask an admin to unset SLOCK_DEVICE_LOGIN_ENABLED or set it back to a non-false value; this surface is on by default since PR-G \u2014 upgrade the server if you are on an older build)."
30137
30251
  );
30138
30252
  }
30139
30253
  if (attached.status === "not_authorized") {
30140
- fail(
30254
+ throw new ComputerServiceError(
30141
30255
  "ATTACH_NOT_AUTHORIZED",
30142
30256
  "Not authorized to attach to that server. Check the server slug and that you're a member."
30143
30257
  );
30144
30258
  }
30145
30259
  if (attached.status === "server_not_found") {
30146
- fail(
30260
+ throw new ComputerServiceError(
30147
30261
  "ATTACH_SERVER_NOT_FOUND",
30148
30262
  `Server ${formatServerSlugDisplay(slugForServer)} was not found on ${baseUrl}. Check the slug spelling and --server-url, then retry.`
30149
30263
  );
30150
30264
  }
30151
30265
  if (attached.status === "error") {
30152
30266
  if (attached.code === "session_invalid") {
30153
- fail(
30267
+ throw new ComputerServiceError(
30154
30268
  "USER_SESSION_EXPIRED",
30155
30269
  "Your user session is no longer valid. Re-run `slock-computer login`."
30156
30270
  );
30157
30271
  }
30158
30272
  if (attached.code === "request_failed") {
30159
- fail(
30273
+ throw new ComputerServiceError(
30160
30274
  "ATTACH_REQUEST_FAILED",
30161
30275
  `Could not reach ${baseUrl} while attaching to ${formatServerSlugDisplay(slugForServer)}. Check --server-url / network connectivity, then retry.`
30162
30276
  );
30163
30277
  }
30164
30278
  if (attached.code === "COMPUTER_NAME_COLLISION") {
30165
- fail(
30279
+ throw new ComputerServiceError(
30166
30280
  "COMPUTER_NAME_COLLISION",
30167
30281
  `A Computer named ${JSON.stringify(computerName)} already exists on that server. Re-run with --name <name>.`
30168
30282
  );
30169
30283
  }
30170
- fail("ATTACH_FAILED", `Attach failed (${attached.code}). Re-run after checking --server-url / server version.`);
30284
+ throw new ComputerServiceError(
30285
+ "ATTACH_FAILED",
30286
+ `Attach failed (${attached.code}). Re-run after checking --server-url / server version.`
30287
+ );
30171
30288
  }
30172
- info(attached.resumed ? "Resumed existing attachment; running preflight\u2026" : "Attachment issued; running preflight\u2026");
30289
+ emit2(options, { type: "preflight", resumed: attached.resumed });
30290
+ options.signal?.throwIfAborted?.();
30173
30291
  const pre = await client.preflight(attached.apiKey);
30174
30292
  if (!pre.ok) {
30175
- fail(
30293
+ throw new ComputerServiceError(
30176
30294
  "PREFLIGHT_FAILED",
30177
30295
  `Server preflight failed (${pre.code}). The server's Computer surface is not aligned; nothing was written locally. Upgrade the server or retry.`
30178
30296
  );
@@ -30197,9 +30315,57 @@ async function runAttach(opts) {
30197
30315
  { mode: 384 }
30198
30316
  );
30199
30317
  await chmod2(file, 384);
30200
- info(`Attached. Computer state written to ${file}`);
30201
- info(` server: ${formatServerSlugDisplay(attached.serverSlug)}`);
30202
- info(` serverMachine: ${attached.serverMachineId}`);
30318
+ const apiKeyRedactedPrefix = attached.apiKey.slice(0, 8);
30319
+ emit2(options, {
30320
+ type: "attached",
30321
+ serverId: attached.serverId,
30322
+ serverMachineId: attached.serverMachineId,
30323
+ serverSlug: attached.serverSlug,
30324
+ attachmentPath: file,
30325
+ resumed: attached.resumed,
30326
+ apiKeyRedactedPrefix
30327
+ });
30328
+ return {
30329
+ serverId: attached.serverId,
30330
+ serverMachineId: attached.serverMachineId,
30331
+ serverSlug: attached.serverSlug,
30332
+ serverUrl: baseUrl,
30333
+ attachmentPath: file,
30334
+ resumed: attached.resumed,
30335
+ apiKeyRedactedPrefix
30336
+ };
30337
+ }
30338
+
30339
+ // src/attach.ts
30340
+ async function runAttach(opts) {
30341
+ try {
30342
+ await attach(
30343
+ {
30344
+ serverSlug: opts.serverSlug,
30345
+ serverUrl: opts.serverUrl,
30346
+ name: opts.name
30347
+ },
30348
+ {
30349
+ onEvent: (event) => {
30350
+ if (event.type === "attaching") {
30351
+ info(`Attaching this machine to server ${formatServerSlugDisplay(event.serverSlug)}\u2026`);
30352
+ } else if (event.type === "preflight") {
30353
+ info(event.resumed ? "Resumed existing attachment; running preflight\u2026" : "Attachment issued; running preflight\u2026");
30354
+ } else if (event.type === "attached") {
30355
+ info(`Attached. Computer state written to ${event.attachmentPath}`);
30356
+ info(` server: ${formatServerSlugDisplay(event.serverSlug)}`);
30357
+ info(` serverMachine: ${event.serverMachineId}`);
30358
+ }
30359
+ }
30360
+ }
30361
+ );
30362
+ } catch (err) {
30363
+ if (err instanceof CliExit) throw err;
30364
+ if (err instanceof ComputerServiceError) {
30365
+ fail(err.code, err.message);
30366
+ }
30367
+ throw err;
30368
+ }
30203
30369
  if (opts.orchestrated) {
30204
30370
  return;
30205
30371
  }
@@ -30212,77 +30378,21 @@ async function runAttach(opts) {
30212
30378
 
30213
30379
  // src/adopt.ts
30214
30380
  init_esm_shims();
30381
+ import { readFile as readFile4 } from "fs/promises";
30382
+
30383
+ // src/services/adoptLegacy.ts
30384
+ init_esm_shims();
30215
30385
  import { chmod as chmod3, mkdir as mkdir4, readFile as readFile3, writeFile as writeFile4, appendFile, stat } from "fs/promises";
30216
30386
  import { createHash as createHash2 } from "crypto";
30217
30387
  import { dirname as dirname4, join } from "path";
30218
30388
  import { setTimeout as delay } from "timers/promises";
30219
- async function resolveLegacyKey(inputs, env) {
30220
- const sources = [];
30221
- if (typeof inputs.legacyApiKey === "string" && inputs.legacyApiKey.length > 0) {
30222
- sources.push({
30223
- mode: "legacy_key_argv",
30224
- load: async () => inputs.legacyApiKey.trim()
30225
- });
30226
- }
30227
- if (typeof inputs.legacyApiKeyFile === "string" && inputs.legacyApiKeyFile.length > 0) {
30228
- sources.push({
30229
- mode: "legacy_key_file",
30230
- load: async () => {
30231
- const contents = await readFile3(inputs.legacyApiKeyFile, "utf8");
30232
- return contents.trim();
30233
- }
30234
- });
30235
- }
30236
- if (inputs.legacyApiKeyStdin) {
30237
- sources.push({
30238
- mode: "legacy_key_stdin",
30239
- load: async () => readAllStdin()
30240
- });
30241
- }
30242
- const envKey = env.SLOCK_LEGACY_API_KEY;
30243
- if (typeof envKey === "string" && envKey.trim().length > 0) {
30244
- sources.push({
30245
- mode: "legacy_key_env",
30246
- load: async () => envKey.trim()
30247
- });
30248
- }
30249
- if (sources.length === 0) {
30250
- fail(
30251
- "LEGACY_KEY_REQUIRED",
30252
- "No legacy api key provided. Pass exactly one of: --legacy-api-key <key>, --legacy-api-key-file <path>, --legacy-api-key-stdin, or SLOCK_LEGACY_API_KEY."
30253
- );
30254
- }
30255
- if (sources.length > 1) {
30256
- const modes = sources.map((s) => s.mode).join(", ");
30257
- fail(
30258
- "LEGACY_KEY_MULTIPLE_SOURCES",
30259
- `Multiple legacy api key sources provided (${modes}). Choose exactly one.`
30260
- );
30261
- }
30262
- const chosen = sources[0];
30263
- const rawKey = await chosen.load();
30264
- if (chosen.mode === "legacy_key_env") {
30265
- delete env.SLOCK_LEGACY_API_KEY;
30266
- }
30267
- if (!rawKey.startsWith("sk_machine_") && !rawKey.startsWith("sk_daemon_")) {
30268
- fail(
30269
- "LEGACY_KEY_INVALID",
30270
- "Provided key does not look like a legacy machine key (expected `sk_machine_*` or `sk_daemon_*`)."
30271
- );
30389
+ function emit3(opts, event) {
30390
+ const cb = opts?.onEvent;
30391
+ if (!cb) return;
30392
+ try {
30393
+ cb(event);
30394
+ } catch {
30272
30395
  }
30273
- return {
30274
- rawKey,
30275
- mode: chosen.mode,
30276
- redactedPrefix: rawKey.slice(0, 8)
30277
- };
30278
- }
30279
- async function readAllStdin() {
30280
- return new Promise((resolve, reject) => {
30281
- const chunks = [];
30282
- process.stdin.on("data", (chunk) => chunks.push(chunk));
30283
- process.stdin.on("end", () => resolve(Buffer.concat(chunks).toString("utf8").trim()));
30284
- process.stdin.on("error", reject);
30285
- });
30286
30396
  }
30287
30397
  var LEGACY_STOP_WAIT_MS = 1e4;
30288
30398
  var LEGACY_STOP_POLL_MS = 200;
@@ -30371,162 +30481,164 @@ function formatLegacyOwnerEvidence(slockHome, evidence) {
30371
30481
  if (evidence.reason) fields.push(`ownerStatus=${evidence.reason}`);
30372
30482
  return fields.join(" ");
30373
30483
  }
30374
- async function runAdoptLegacy(inputs) {
30484
+ async function adoptLegacy(input, options = {}) {
30485
+ options.signal?.throwIfAborted?.();
30375
30486
  const slockHome = resolveSlockHome();
30376
30487
  const sessionFile = userSessionPath(slockHome);
30377
30488
  let session;
30378
30489
  try {
30379
30490
  session = JSON.parse(await readFile3(sessionFile, "utf8"));
30380
- } catch {
30381
- fail(
30491
+ } catch (err) {
30492
+ throw new ComputerServiceError(
30382
30493
  "NO_USER_SESSION",
30383
- `No user session at ${sessionFile}. Run \`slock-computer login\` first.`
30494
+ `No user session at ${sessionFile}. Run \`slock-computer login\` first.`,
30495
+ err
30384
30496
  );
30385
30497
  }
30386
30498
  if (session.kind !== "user-session" || typeof session.accessToken !== "string" || !session.accessToken) {
30387
- fail(
30499
+ throw new ComputerServiceError(
30388
30500
  "INVALID_USER_SESSION",
30389
30501
  `User session at ${sessionFile} is invalid. Re-run \`slock-computer login\`.`
30390
30502
  );
30391
30503
  }
30392
- const baseUrl = resolveServerUrl(inputs.serverUrl, session.serverUrl, process.env.SLOCK_SERVER_URL);
30393
- const slugForServer = normalizeServerSlug(inputs.serverSlug);
30504
+ const baseUrl = resolveServerUrl(input.serverUrl, session.serverUrl, process.env.SLOCK_SERVER_URL);
30505
+ const slugForServer = normalizeServerSlug(input.serverSlug);
30394
30506
  if (!slugForServer) {
30395
- fail("ADOPT_NOT_AUTHORIZED", "Server slug must not be empty.");
30507
+ throw new ComputerServiceError("ADOPT_NOT_AUTHORIZED", "Server slug must not be empty.");
30396
30508
  }
30397
- const legacy = await resolveLegacyKey(inputs, process.env);
30398
- info(
30399
- `Adopting legacy daemon for ${formatServerSlugDisplay(slugForServer)} via ${legacy.mode}\u2026`
30400
- );
30509
+ options.signal?.throwIfAborted?.();
30510
+ emit3(options, { type: "adopting", serverSlug: slugForServer, mode: input.mode });
30401
30511
  const client = new ComputerAttachClient(baseUrl, session.accessToken);
30402
30512
  const adoptStartedAt = /* @__PURE__ */ new Date();
30403
- const legacyOwnerFile = legacyLockOwnerPath(slockHome, legacy.rawKey);
30513
+ const legacyOwnerFile = legacyLockOwnerPath(slockHome, input.rawKey);
30404
30514
  const ownerEvidence = await readLegacyOwnerEvidence(legacyOwnerFile);
30405
- const result = await client.adoptLegacy(legacy.rawKey, inputs.name);
30406
- legacy.rawKey = void 0;
30515
+ const result = await client.adoptLegacy(input.rawKey, input.name);
30516
+ input.rawKey = void 0;
30407
30517
  if (result.status === "disabled") {
30408
30518
  await appendAdoptionLog(slockHome, {
30409
- mode: legacy.mode,
30410
- redactedPrefix: legacy.redactedPrefix,
30519
+ mode: input.mode,
30520
+ redactedPrefix: input.redactedPrefix,
30411
30521
  startedAt: adoptStartedAt,
30412
30522
  outcome: "failed",
30413
30523
  failureReason: "computer_adopt_disabled"
30414
30524
  });
30415
- fail(
30525
+ throw new ComputerServiceError(
30416
30526
  "ADOPT_DISABLED",
30417
30527
  "Computer legacy adoption is not enabled on this server. Upgrade the server or set SLOCK_DEVICE_LOGIN_ENABLED."
30418
30528
  );
30419
30529
  }
30420
30530
  if (result.status === "legacy_key_invalid") {
30421
30531
  await appendAdoptionLog(slockHome, {
30422
- mode: legacy.mode,
30423
- redactedPrefix: legacy.redactedPrefix,
30532
+ mode: input.mode,
30533
+ redactedPrefix: input.redactedPrefix,
30424
30534
  startedAt: adoptStartedAt,
30425
30535
  outcome: "failed",
30426
30536
  failureReason: "legacy_key_invalid"
30427
30537
  });
30428
- fail(
30538
+ throw new ComputerServiceError(
30429
30539
  "LEGACY_KEY_INVALID",
30430
30540
  `Server rejected the legacy api key (unknown / wrong server / malformed). Local legacy owner evidence for this key: ${formatLegacyOwnerEvidence(slockHome, ownerEvidence)}. Verify the key came from this SLOCK_HOME and server, or use a fresh isolated SLOCK_HOME for a clean Computer setup.`
30431
30541
  );
30432
30542
  }
30433
30543
  if (result.status === "legacy_machine_key_migrated") {
30434
30544
  await appendAdoptionLog(slockHome, {
30435
- mode: legacy.mode,
30436
- redactedPrefix: legacy.redactedPrefix,
30545
+ mode: input.mode,
30546
+ redactedPrefix: input.redactedPrefix,
30437
30547
  startedAt: adoptStartedAt,
30438
30548
  outcome: "failed",
30439
30549
  failureReason: "legacy_machine_key_migrated"
30440
30550
  });
30441
- fail(
30551
+ throw new ComputerServiceError(
30442
30552
  "LEGACY_MACHINE_KEY_MIGRATED",
30443
30553
  `This machine has already been adopted; the legacy key is no longer accepted. Local legacy owner evidence for this key: ${formatLegacyOwnerEvidence(slockHome, ownerEvidence)}. Use \`slock-computer attach\` to add another Computer attachment, or use a fresh isolated SLOCK_HOME for a clean setup.`
30444
30554
  );
30445
30555
  }
30446
30556
  if (result.status === "auth_required") {
30447
30557
  await appendAdoptionLog(slockHome, {
30448
- mode: legacy.mode,
30449
- redactedPrefix: legacy.redactedPrefix,
30558
+ mode: input.mode,
30559
+ redactedPrefix: input.redactedPrefix,
30450
30560
  startedAt: adoptStartedAt,
30451
30561
  outcome: "failed",
30452
30562
  failureReason: "auth_required"
30453
30563
  });
30454
- fail(
30564
+ throw new ComputerServiceError(
30455
30565
  "ADOPT_AUTH_REQUIRED",
30456
30566
  `Your Computer user session was rejected by the server before legacy adoption. Local legacy owner evidence for this key: ${formatLegacyOwnerEvidence(slockHome, ownerEvidence)}. Re-run \`slock-computer login\` for this server (use the same \`--server-url\` if not on production), then retry the adopt command. No local Computer state was written.`
30457
30567
  );
30458
30568
  }
30459
30569
  if (result.status === "not_authorized") {
30460
30570
  await appendAdoptionLog(slockHome, {
30461
- mode: legacy.mode,
30462
- redactedPrefix: legacy.redactedPrefix,
30571
+ mode: input.mode,
30572
+ redactedPrefix: input.redactedPrefix,
30463
30573
  startedAt: adoptStartedAt,
30464
30574
  outcome: "failed",
30465
30575
  failureReason: "not_authorized"
30466
30576
  });
30467
- fail(
30577
+ throw new ComputerServiceError(
30468
30578
  "ADOPT_NOT_AUTHORIZED",
30469
30579
  "Not authorized to adopt this machine on this server. Check that you are a current member."
30470
30580
  );
30471
30581
  }
30472
30582
  if (result.status === "unexpected_response") {
30473
30583
  await appendAdoptionLog(slockHome, {
30474
- mode: legacy.mode,
30475
- redactedPrefix: legacy.redactedPrefix,
30584
+ mode: input.mode,
30585
+ redactedPrefix: input.redactedPrefix,
30476
30586
  startedAt: adoptStartedAt,
30477
30587
  outcome: "failed",
30478
30588
  failureReason: result.code ? `unexpected_response_${result.code}` : "unexpected_response_missing_code"
30479
30589
  });
30480
30590
  const responseDetail = result.code ? `status ${result.httpStatus}, code ${result.code}` : `status ${result.httpStatus}, missing error code`;
30481
30591
  const authHint = result.httpStatus === 401 ? " If your server may be on an older release that does not emit `code: auth_required`, re-run `slock-computer login` first to refresh your user session, then retry. If the issue persists, report it as server contract drift." : "";
30482
- fail(
30592
+ throw new ComputerServiceError(
30483
30593
  "ADOPT_UNEXPECTED_RESPONSE",
30484
30594
  `Server returned an unexpected legacy adoption response (${responseDetail}).${authHint} Local legacy owner evidence for this key: ${formatLegacyOwnerEvidence(slockHome, ownerEvidence)}. Please report this with the command, server URL, and SLOCK_HOME; no local Computer state was written.`
30485
30595
  );
30486
30596
  }
30487
30597
  if (result.status === "error") {
30488
30598
  await appendAdoptionLog(slockHome, {
30489
- mode: legacy.mode,
30490
- redactedPrefix: legacy.redactedPrefix,
30599
+ mode: input.mode,
30600
+ redactedPrefix: input.redactedPrefix,
30491
30601
  startedAt: adoptStartedAt,
30492
30602
  outcome: "failed",
30493
30603
  failureReason: result.code
30494
30604
  });
30495
- fail(
30605
+ throw new ComputerServiceError(
30496
30606
  "ADOPT_FAILED",
30497
30607
  `Adoption failed at server exchange (${result.code}). Local legacy owner evidence for this key: ${formatLegacyOwnerEvidence(slockHome, ownerEvidence)}. Confirm the user session is valid, the legacy key belongs to this server/SLOCK_HOME, and the server URL is correct. No local Computer state was written.`
30498
30608
  );
30499
30609
  }
30500
- info(result.resumed ? "Adopted (resumed prior attachment); running preflight\u2026" : "Adopted; running preflight\u2026");
30610
+ emit3(options, { type: "preflight", resumed: result.resumed });
30611
+ options.signal?.throwIfAborted?.();
30501
30612
  const pre = await client.preflight(result.apiKey);
30502
30613
  if (!pre.ok) {
30503
30614
  await appendAdoptionLog(slockHome, {
30504
- mode: legacy.mode,
30505
- redactedPrefix: legacy.redactedPrefix,
30615
+ mode: input.mode,
30616
+ redactedPrefix: input.redactedPrefix,
30506
30617
  startedAt: adoptStartedAt,
30507
30618
  outcome: "failed",
30508
30619
  failureReason: `preflight_${pre.code}`
30509
30620
  });
30510
- fail(
30621
+ throw new ComputerServiceError(
30511
30622
  "PREFLIGHT_FAILED",
30512
30623
  `Server preflight failed (${pre.code}); local state not written. Upgrade the server or retry.`
30513
30624
  );
30514
30625
  }
30515
- const stop = await stopLegacyDaemonByOwnerFile(legacyOwnerFile);
30516
- if (stop.outcome === "timed_out" || stop.outcome === "denied" || stop.outcome === "error") {
30626
+ options.signal?.throwIfAborted?.();
30627
+ const stop2 = await stopLegacyDaemonByOwnerFile(legacyOwnerFile);
30628
+ if (stop2.outcome === "timed_out" || stop2.outcome === "denied" || stop2.outcome === "error") {
30517
30629
  await appendAdoptionLog(slockHome, {
30518
- mode: legacy.mode,
30519
- redactedPrefix: legacy.redactedPrefix,
30630
+ mode: input.mode,
30631
+ redactedPrefix: input.redactedPrefix,
30520
30632
  startedAt: adoptStartedAt,
30521
30633
  outcome: "failed",
30522
- failureReason: `legacy_stop_${stop.outcome}`,
30634
+ failureReason: `legacy_stop_${stop2.outcome}`,
30523
30635
  computerId: result.computerId,
30524
30636
  machineId: result.machineId,
30525
30637
  serverId: result.serverId,
30526
- legacyStop: stop
30638
+ legacyStop: stop2
30527
30639
  });
30528
- const detail = stop.outcome === "timed_out" ? `pid ${stop.pid} did not exit within ${LEGACY_STOP_WAIT_MS}ms` : stop.outcome === "denied" ? `pid ${stop.pid} cannot be stopped (permission denied)` : `stop attempt error (${stop.reason ?? "unknown"})`;
30529
- fail(
30640
+ const detail = stop2.outcome === "timed_out" ? `pid ${stop2.pid} did not exit within ${LEGACY_STOP_WAIT_MS}ms` : stop2.outcome === "denied" ? `pid ${stop2.pid} cannot be stopped (permission denied)` : `stop attempt error (${stop2.reason ?? "unknown"})`;
30641
+ throw new ComputerServiceError(
30530
30642
  "LEGACY_DAEMON_STOP_FAILED",
30531
30643
  `Adoption succeeded server-side but the legacy daemon could not be stopped: ${detail}. Stop it manually and re-run \`slock-computer adopt-legacy\`, or run \`slock-computer attach\` after confirming the legacy process is gone. No local Computer state was written.`
30532
30644
  );
@@ -30554,33 +30666,38 @@ async function runAdoptLegacy(inputs) {
30554
30666
  );
30555
30667
  await chmod3(file, 384);
30556
30668
  await appendAdoptionLog(slockHome, {
30557
- mode: legacy.mode,
30558
- redactedPrefix: legacy.redactedPrefix,
30669
+ mode: input.mode,
30670
+ redactedPrefix: input.redactedPrefix,
30559
30671
  startedAt: adoptStartedAt,
30560
30672
  outcome: "succeeded",
30561
30673
  computerId: result.computerId,
30562
30674
  machineId: result.machineId,
30563
30675
  serverId: result.serverId,
30564
- legacyStop: stop
30676
+ legacyStop: stop2
30565
30677
  });
30566
- info(`Adopted. Computer state written to ${file}`);
30567
- info(` server: ${formatServerSlugDisplay(slugForServer)}`);
30568
- info(` serverMachine: ${result.computerId}`);
30569
- info(` legacyMachine: ${result.machineId}`);
30570
- switch (stop.outcome) {
30571
- case "absent":
30572
- info(" legacy daemon: not detected on this Computer (no local lock file)");
30573
- break;
30574
- case "already_dead":
30575
- info(` legacy daemon: already stopped (pid ${stop.pid} not running)`);
30576
- break;
30577
- case "stopped":
30578
- info(` legacy daemon: stopped (pid ${stop.pid}, SIGTERM)`);
30579
- break;
30580
- }
30581
- if (!inputs.orchestrated) {
30582
- info(`Next: run \`slock-computer start\` to bring this server online under the Computer supervisor.`);
30583
- }
30678
+ const apiKeyRedactedPrefix = result.apiKey.slice(0, 8);
30679
+ emit3(options, {
30680
+ type: "adopted",
30681
+ serverId: result.serverId,
30682
+ serverMachineId: result.computerId,
30683
+ legacyMachineId: result.machineId,
30684
+ serverSlug: slugForServer,
30685
+ attachmentPath: file,
30686
+ resumed: result.resumed,
30687
+ apiKeyRedactedPrefix,
30688
+ legacyStop: stop2
30689
+ });
30690
+ return {
30691
+ serverId: result.serverId,
30692
+ serverMachineId: result.computerId,
30693
+ legacyMachineId: result.machineId,
30694
+ serverSlug: slugForServer,
30695
+ serverUrl: baseUrl,
30696
+ attachmentPath: file,
30697
+ resumed: result.resumed,
30698
+ apiKeyRedactedPrefix,
30699
+ legacyStop: stop2
30700
+ };
30584
30701
  }
30585
30702
  async function appendAdoptionLog(slockHome, line) {
30586
30703
  try {
@@ -30616,22 +30733,178 @@ async function appendAdoptionLog(slockHome, line) {
30616
30733
  }
30617
30734
  }
30618
30735
 
30736
+ // src/adopt.ts
30737
+ async function resolveLegacyKey(inputs, env) {
30738
+ const sources = [];
30739
+ if (typeof inputs.legacyApiKey === "string" && inputs.legacyApiKey.length > 0) {
30740
+ sources.push({
30741
+ mode: "legacy_key_argv",
30742
+ load: async () => inputs.legacyApiKey.trim()
30743
+ });
30744
+ }
30745
+ if (typeof inputs.legacyApiKeyFile === "string" && inputs.legacyApiKeyFile.length > 0) {
30746
+ sources.push({
30747
+ mode: "legacy_key_file",
30748
+ load: async () => {
30749
+ const contents = await readFile4(inputs.legacyApiKeyFile, "utf8");
30750
+ return contents.trim();
30751
+ }
30752
+ });
30753
+ }
30754
+ if (inputs.legacyApiKeyStdin) {
30755
+ sources.push({
30756
+ mode: "legacy_key_stdin",
30757
+ load: async () => readAllStdin()
30758
+ });
30759
+ }
30760
+ const envKey = env.SLOCK_LEGACY_API_KEY;
30761
+ if (typeof envKey === "string" && envKey.trim().length > 0) {
30762
+ sources.push({
30763
+ mode: "legacy_key_env",
30764
+ load: async () => envKey.trim()
30765
+ });
30766
+ }
30767
+ if (sources.length === 0) {
30768
+ fail(
30769
+ "LEGACY_KEY_REQUIRED",
30770
+ "No legacy api key provided. Pass exactly one of: --legacy-api-key <key>, --legacy-api-key-file <path>, --legacy-api-key-stdin, or SLOCK_LEGACY_API_KEY."
30771
+ );
30772
+ }
30773
+ if (sources.length > 1) {
30774
+ const modes = sources.map((s) => s.mode).join(", ");
30775
+ fail(
30776
+ "LEGACY_KEY_MULTIPLE_SOURCES",
30777
+ `Multiple legacy api key sources provided (${modes}). Choose exactly one.`
30778
+ );
30779
+ }
30780
+ const chosen = sources[0];
30781
+ const rawKey = await chosen.load();
30782
+ if (chosen.mode === "legacy_key_env") {
30783
+ delete env.SLOCK_LEGACY_API_KEY;
30784
+ }
30785
+ if (!rawKey.startsWith("sk_machine_") && !rawKey.startsWith("sk_daemon_")) {
30786
+ fail(
30787
+ "LEGACY_KEY_INVALID",
30788
+ "Provided key does not look like a legacy machine key (expected `sk_machine_*` or `sk_daemon_*`)."
30789
+ );
30790
+ }
30791
+ return {
30792
+ rawKey,
30793
+ mode: chosen.mode,
30794
+ redactedPrefix: rawKey.slice(0, 8)
30795
+ };
30796
+ }
30797
+ async function readAllStdin() {
30798
+ return new Promise((resolve, reject) => {
30799
+ const chunks = [];
30800
+ process.stdin.on("data", (chunk) => chunks.push(chunk));
30801
+ process.stdin.on("end", () => resolve(Buffer.concat(chunks).toString("utf8").trim()));
30802
+ process.stdin.on("error", reject);
30803
+ });
30804
+ }
30805
+ async function runAdoptLegacy(inputs) {
30806
+ const legacy = await resolveLegacyKey(inputs, process.env);
30807
+ try {
30808
+ await adoptLegacy(
30809
+ {
30810
+ serverSlug: inputs.serverSlug,
30811
+ serverUrl: inputs.serverUrl,
30812
+ name: inputs.name,
30813
+ rawKey: legacy.rawKey,
30814
+ mode: legacy.mode,
30815
+ redactedPrefix: legacy.redactedPrefix
30816
+ },
30817
+ {
30818
+ onEvent: (event) => {
30819
+ if (event.type === "adopting") {
30820
+ info(
30821
+ `Adopting legacy daemon for ${formatServerSlugDisplay(event.serverSlug)} via ${event.mode}\u2026`
30822
+ );
30823
+ } else if (event.type === "preflight") {
30824
+ info(event.resumed ? "Adopted (resumed prior attachment); running preflight\u2026" : "Adopted; running preflight\u2026");
30825
+ } else if (event.type === "adopted") {
30826
+ info(`Adopted. Computer state written to ${event.attachmentPath}`);
30827
+ info(` server: ${formatServerSlugDisplay(event.serverSlug)}`);
30828
+ info(` serverMachine: ${event.serverMachineId}`);
30829
+ info(` legacyMachine: ${event.legacyMachineId}`);
30830
+ switch (event.legacyStop.outcome) {
30831
+ case "absent":
30832
+ info(" legacy daemon: not detected on this Computer (no local lock file)");
30833
+ break;
30834
+ case "already_dead":
30835
+ info(` legacy daemon: already stopped (pid ${event.legacyStop.pid} not running)`);
30836
+ break;
30837
+ case "stopped":
30838
+ info(` legacy daemon: stopped (pid ${event.legacyStop.pid}, SIGTERM)`);
30839
+ break;
30840
+ }
30841
+ }
30842
+ }
30843
+ }
30844
+ );
30845
+ } catch (err) {
30846
+ if (err instanceof CliExit) throw err;
30847
+ if (err instanceof ComputerServiceError) {
30848
+ fail(err.code, err.message);
30849
+ }
30850
+ throw err;
30851
+ } finally {
30852
+ legacy.rawKey = void 0;
30853
+ }
30854
+ if (!inputs.orchestrated) {
30855
+ info(`Next: run \`slock-computer start\` to bring this server online under the Computer supervisor.`);
30856
+ }
30857
+ }
30858
+
30619
30859
  // src/setup.ts
30620
30860
  init_esm_shims();
30621
- var import_undici2 = __toESM(require_undici(), 1);
30622
- import { chmod as chmod4, mkdir as mkdir8, readdir as readdir3, readFile as readFile6, rename as rename3, rm as rm2, writeFile as writeFile7 } from "fs/promises";
30623
- import { dirname as dirname8, join as join3 } from "path";
30861
+ var import_undici3 = __toESM(require_undici(), 1);
30862
+ import { chmod as chmod4, mkdir as mkdir9, readdir as readdir3, readFile as readFile8, rename as rename3, rm as rm2, writeFile as writeFile8 } from "fs/promises";
30863
+ import { dirname as dirname9, join as join3 } from "path";
30864
+
30865
+ // src/internal/process-primitives.ts
30866
+ init_esm_shims();
30867
+ import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile5, unlink as unlink2 } from "fs/promises";
30868
+ import { dirname as dirname5 } from "path";
30869
+ async function readPidfileAt(pidfilePath) {
30870
+ try {
30871
+ const raw = (await readFile5(pidfilePath, "utf8")).trim();
30872
+ const pid = Number.parseInt(raw, 10);
30873
+ return Number.isInteger(pid) && pid > 0 ? pid : null;
30874
+ } catch {
30875
+ return null;
30876
+ }
30877
+ }
30878
+ function isProcessAlive2(pid) {
30879
+ if (!Number.isInteger(pid) || pid <= 0) return false;
30880
+ try {
30881
+ process.kill(pid, 0);
30882
+ return true;
30883
+ } catch (err) {
30884
+ return err.code === "EPERM";
30885
+ }
30886
+ }
30887
+ async function writePidfileAt(pidfilePath, pid) {
30888
+ await mkdir5(dirname5(pidfilePath), { recursive: true });
30889
+ await writeFile5(pidfilePath, String(pid), { mode: 384 });
30890
+ }
30891
+ async function clearPidfileAt(pidfilePath) {
30892
+ try {
30893
+ await unlink2(pidfilePath);
30894
+ } catch {
30895
+ }
30896
+ }
30624
30897
 
30625
30898
  // src/supervisor.ts
30626
30899
  init_esm_shims();
30627
30900
  import { spawn as spawn2 } from "child_process";
30628
- import { mkdir as mkdir7, readFile as readFile5, writeFile as writeFile6, open, unlink as unlink4, rename as rename2 } from "fs/promises";
30629
- import { dirname as dirname7, join as joinPath } from "path";
30901
+ import { mkdir as mkdir8, readFile as readFile7, writeFile as writeFile7, open, rename as rename2 } from "fs/promises";
30902
+ import { dirname as dirname8, join as joinPath } from "path";
30630
30903
  import { fileURLToPath as fileURLToPath2 } from "url";
30631
30904
 
30632
30905
  // src/cleanup.ts
30633
30906
  init_esm_shims();
30634
- import { readdir as readdir2, stat as stat2, unlink as unlink2, rm, rmdir, rename, mkdir as mkdir5 } from "fs/promises";
30907
+ import { readdir as readdir2, stat as stat2, unlink as unlink3, rm, rmdir, rename, mkdir as mkdir6 } from "fs/promises";
30635
30908
  import { spawn } from "child_process";
30636
30909
  import { join as join2 } from "path";
30637
30910
  function emptyCleanupReport() {
@@ -30649,7 +30922,7 @@ async function cleanupStalePidfile(pidfilePath) {
30649
30922
  if (pid === null) return false;
30650
30923
  if (isProcessAlive2(pid)) return false;
30651
30924
  try {
30652
- await unlink2(pidfilePath);
30925
+ await unlink3(pidfilePath);
30653
30926
  return true;
30654
30927
  } catch {
30655
30928
  return false;
@@ -30734,11 +31007,11 @@ async function quarantineServerSubtree(slockHome, serverId) {
30734
31007
  const src = join2(serversDir(slockHome), serverId);
30735
31008
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
30736
31009
  const dest = join2(quarantineDir(slockHome), `${stamp}-${serverId}`);
30737
- await mkdir5(dirname5(dest), { recursive: true });
31010
+ await mkdir6(dirname6(dest), { recursive: true });
30738
31011
  await rename(src, dest);
30739
31012
  return dest;
30740
31013
  }
30741
- function dirname5(p) {
31014
+ function dirname6(p) {
30742
31015
  const idx = p.lastIndexOf("/");
30743
31016
  return idx > 0 ? p.slice(0, idx) : "/";
30744
31017
  }
@@ -30797,7 +31070,7 @@ async function cleanupTmpFiles(slockHome) {
30797
31070
  try {
30798
31071
  const s = await stat2(snap);
30799
31072
  if (Date.now() - s.mtimeMs > TMP_MAX_AGE_MS) {
30800
- await unlink2(snap);
31073
+ await unlink3(snap);
30801
31074
  removed.push(snap);
30802
31075
  }
30803
31076
  } catch {
@@ -30841,14 +31114,14 @@ async function runFullCleanup(slockHome, options = {}) {
30841
31114
 
30842
31115
  // src/health.ts
30843
31116
  init_esm_shims();
30844
- import { readFile as readFile4, writeFile as writeFile5, unlink as unlink3, mkdir as mkdir6 } from "fs/promises";
30845
- import { dirname as dirname6 } from "path";
31117
+ import { readFile as readFile6, writeFile as writeFile6, unlink as unlink4, mkdir as mkdir7 } from "fs/promises";
31118
+ import { dirname as dirname7 } from "path";
30846
31119
  var CRASH_WINDOW_MS = 6e4;
30847
31120
  var DEGRADED_THRESHOLD = 3;
30848
31121
  async function readHealthFile(slockHome, serverId) {
30849
31122
  if (!isValidServerId(serverId)) return { crashes: [] };
30850
31123
  try {
30851
- const raw = await readFile4(serverHealthPath(slockHome, serverId), "utf8");
31124
+ const raw = await readFile6(serverHealthPath(slockHome, serverId), "utf8");
30852
31125
  const parsed = JSON.parse(raw);
30853
31126
  if (parsed && typeof parsed === "object" && Array.isArray(parsed.crashes)) {
30854
31127
  return parsed;
@@ -30859,8 +31132,8 @@ async function readHealthFile(slockHome, serverId) {
30859
31132
  }
30860
31133
  async function writeHealthFile(slockHome, serverId, file) {
30861
31134
  const path3 = serverHealthPath(slockHome, serverId);
30862
- await mkdir6(dirname6(path3), { recursive: true });
30863
- await writeFile5(path3, JSON.stringify(file), { mode: 384 });
31135
+ await mkdir7(dirname7(path3), { recursive: true });
31136
+ await writeFile6(path3, JSON.stringify(file), { mode: 384 });
30864
31137
  }
30865
31138
  async function recordCrash(slockHome, serverId, exitCode, signal, nowMs = Date.now()) {
30866
31139
  if (!isValidServerId(serverId)) return;
@@ -30893,63 +31166,373 @@ async function isDegraded(slockHome, serverId, nowMs = Date.now()) {
30893
31166
  const t = new Date(c.at).getTime();
30894
31167
  return Number.isFinite(t) && t >= cutoffMs;
30895
31168
  });
30896
- return recent.length >= DEGRADED_THRESHOLD;
30897
- }
30898
- async function markFatalConfig(slockHome, serverId, exitCode, signal, nowMs = Date.now()) {
30899
- if (!isValidServerId(serverId)) return;
30900
- const file = await readHealthFile(slockHome, serverId);
30901
- file.fatalConfig = {
30902
- at: new Date(nowMs).toISOString(),
30903
- exitCode,
30904
- signal,
30905
- reason: "ex_config"
31169
+ return recent.length >= DEGRADED_THRESHOLD;
31170
+ }
31171
+ async function markFatalConfig(slockHome, serverId, exitCode, signal, nowMs = Date.now()) {
31172
+ if (!isValidServerId(serverId)) return;
31173
+ const file = await readHealthFile(slockHome, serverId);
31174
+ file.fatalConfig = {
31175
+ at: new Date(nowMs).toISOString(),
31176
+ exitCode,
31177
+ signal,
31178
+ reason: "ex_config"
31179
+ };
31180
+ await writeHealthFile(slockHome, serverId, file);
31181
+ }
31182
+ async function resetHealth(slockHome, serverId) {
31183
+ if (!isValidServerId(serverId)) return;
31184
+ try {
31185
+ await unlink4(serverHealthPath(slockHome, serverId));
31186
+ } catch {
31187
+ }
31188
+ }
31189
+
31190
+ // src/services/start.ts
31191
+ init_esm_shims();
31192
+ var START_ENSURE_TIMEOUT_MS = 15e3;
31193
+ var START_ENSURE_POLL_INTERVAL_MS = 100;
31194
+ function emit4(opts, event) {
31195
+ const cb = opts?.onEvent;
31196
+ if (!cb) return;
31197
+ try {
31198
+ cb(event);
31199
+ } catch {
31200
+ }
31201
+ }
31202
+ async function waitForManagedDaemonPids(slockHome, serverIds, opts) {
31203
+ const readPidfile = opts.readPidfile ?? readPidfileAt;
31204
+ const isAlive = opts.isProcessAlive ?? isProcessAlive2;
31205
+ const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
31206
+ const timeoutMs = opts.ensureTimeoutMs ?? START_ENSURE_TIMEOUT_MS;
31207
+ const pollIntervalMs = opts.ensurePollIntervalMs ?? START_ENSURE_POLL_INTERVAL_MS;
31208
+ const deadline = Date.now() + timeoutMs;
31209
+ const ready = /* @__PURE__ */ new Map();
31210
+ while (ready.size < serverIds.length) {
31211
+ for (const serverId of serverIds) {
31212
+ if (ready.has(serverId)) continue;
31213
+ const pid = await readPidfile(serverDaemonPidPath(slockHome, serverId));
31214
+ if (pid && isAlive(pid)) ready.set(serverId, pid);
31215
+ }
31216
+ if (ready.size === serverIds.length) return ready;
31217
+ const remaining = deadline - Date.now();
31218
+ if (remaining <= 0) return ready;
31219
+ await sleep2(Math.min(pollIntervalMs, remaining));
31220
+ }
31221
+ return ready;
31222
+ }
31223
+ function buildTimeoutMessage(slockHome, serverIds, ready, input) {
31224
+ const missing = serverIds.filter((id) => !ready.has(id));
31225
+ const target = input.serverId && missing.length === 1 ? `${input.serverLabel ?? input.serverId}` : `${missing.length} daemon(s): ${missing.join(", ")}`;
31226
+ return `Timed out waiting for ${target} to start. Run \`slock-computer status\` and inspect ${supervisorLogPath(slockHome)} plus per-server daemon logs under ~/.slock/computer/servers/<serverId>/daemon.log.`;
31227
+ }
31228
+ async function start(input, options = {}) {
31229
+ options.signal?.throwIfAborted?.();
31230
+ const slockHome = resolveSlockHome();
31231
+ const attached = await listAttachedServerIds(slockHome);
31232
+ if (attached.length === 0) {
31233
+ throw new ComputerServiceError(
31234
+ "NO_ATTACHMENT",
31235
+ "No server attachments yet. Run `slock-computer attach <serverId>` first."
31236
+ );
31237
+ }
31238
+ if (input.serverId && !attached.includes(input.serverId)) {
31239
+ throw new ComputerServiceError(
31240
+ "NOT_ATTACHED",
31241
+ `Not attached to server ${input.serverId}. Run \`slock-computer attach ${input.serverId}\` first or omit the argument.`
31242
+ );
31243
+ }
31244
+ const managedTargets = input.serverId ? [input.serverId] : attached;
31245
+ emit4(options, {
31246
+ type: "starting",
31247
+ managedTargets,
31248
+ attachedCount: attached.length,
31249
+ foreground: !!input.foreground
31250
+ });
31251
+ for (const id of managedTargets) {
31252
+ await setServerManaged(slockHome, id);
31253
+ }
31254
+ const existing = await readPidfileAt(supervisorPidPath(slockHome));
31255
+ if (existing && isProcessAlive2(existing)) {
31256
+ emit4(options, {
31257
+ type: "already_running",
31258
+ supervisorPid: existing,
31259
+ managedTargets,
31260
+ attachedCount: attached.length
31261
+ });
31262
+ const ready2 = await waitForManagedDaemonPids(slockHome, managedTargets, options);
31263
+ if (ready2.size !== managedTargets.length) {
31264
+ throw new ComputerServiceError(
31265
+ "START_DAEMON_TIMEOUT",
31266
+ buildTimeoutMessage(slockHome, managedTargets, ready2, input)
31267
+ );
31268
+ }
31269
+ emit4(options, { type: "ready", ready: ready2, managedTargets });
31270
+ return {
31271
+ status: "already_running",
31272
+ managedTargets,
31273
+ attachedCount: attached.length,
31274
+ ready: ready2,
31275
+ supervisorPid: existing,
31276
+ supervisorLogPath: supervisorLogPath(slockHome)
31277
+ };
31278
+ }
31279
+ if (input.foreground) {
31280
+ emit4(options, {
31281
+ type: "running",
31282
+ managedTargets,
31283
+ attachedCount: attached.length
31284
+ });
31285
+ const supervise = options.runSupervise ?? runSupervise;
31286
+ await supervise();
31287
+ return {
31288
+ status: "running",
31289
+ managedTargets,
31290
+ attachedCount: attached.length,
31291
+ ready: /* @__PURE__ */ new Map(),
31292
+ supervisorPid: null,
31293
+ supervisorLogPath: supervisorLogPath(slockHome)
31294
+ };
31295
+ }
31296
+ options.signal?.throwIfAborted?.();
31297
+ let pid;
31298
+ try {
31299
+ pid = await (options.spawnDetachedSupervisor ?? spawnDetachedSupervisor)(slockHome);
31300
+ } catch (err) {
31301
+ const msg = err instanceof Error ? err.message : String(err);
31302
+ throw new ComputerServiceError("SUPERVISOR_SPAWN_FAILED", msg, err);
31303
+ }
31304
+ emit4(options, {
31305
+ type: "spawned",
31306
+ supervisorPid: pid,
31307
+ managedTargets,
31308
+ attachedCount: attached.length
31309
+ });
31310
+ if (options.signal?.aborted) {
31311
+ const ready2 = await pollReadyOnce(slockHome, managedTargets, options);
31312
+ emit4(options, { type: "aborted", supervisorPid: pid, managedTargets, ready: ready2 });
31313
+ return {
31314
+ status: "aborted",
31315
+ managedTargets,
31316
+ attachedCount: attached.length,
31317
+ ready: ready2,
31318
+ supervisorPid: pid,
31319
+ supervisorLogPath: supervisorLogPath(slockHome)
31320
+ };
31321
+ }
31322
+ const ready = await waitForManagedDaemonPids(slockHome, managedTargets, options);
31323
+ if (ready.size !== managedTargets.length) {
31324
+ throw new ComputerServiceError(
31325
+ "START_DAEMON_TIMEOUT",
31326
+ buildTimeoutMessage(slockHome, managedTargets, ready, input)
31327
+ );
31328
+ }
31329
+ emit4(options, { type: "ready", ready, managedTargets });
31330
+ return {
31331
+ status: "spawned",
31332
+ managedTargets,
31333
+ attachedCount: attached.length,
31334
+ ready,
31335
+ supervisorPid: pid,
31336
+ supervisorLogPath: supervisorLogPath(slockHome)
30906
31337
  };
30907
- await writeHealthFile(slockHome, serverId, file);
30908
31338
  }
30909
- async function resetHealth(slockHome, serverId) {
30910
- if (!isValidServerId(serverId)) return;
30911
- try {
30912
- await unlink3(serverHealthPath(slockHome, serverId));
30913
- } catch {
31339
+ async function pollReadyOnce(slockHome, serverIds, opts) {
31340
+ const readPidfile = opts.readPidfile ?? readPidfileAt;
31341
+ const isAlive = opts.isProcessAlive ?? isProcessAlive2;
31342
+ const ready = /* @__PURE__ */ new Map();
31343
+ for (const serverId of serverIds) {
31344
+ const pid = await readPidfile(serverDaemonPidPath(slockHome, serverId));
31345
+ if (pid && isAlive(pid)) ready.set(serverId, pid);
30914
31346
  }
31347
+ return ready;
30915
31348
  }
30916
31349
 
30917
- // src/supervisor.ts
30918
- async function readPidfileAt(pidfilePath) {
31350
+ // src/services/stop.ts
31351
+ init_esm_shims();
31352
+ var STOP_POLL_INTERVAL_MS = 200;
31353
+ var STOP_TIMEOUT_MS = 5e3;
31354
+ function emit5(opts, event) {
31355
+ const cb = opts?.onEvent;
31356
+ if (!cb) return;
30919
31357
  try {
30920
- const raw = (await readFile5(pidfilePath, "utf8")).trim();
30921
- const pid = Number.parseInt(raw, 10);
30922
- return Number.isInteger(pid) && pid > 0 ? pid : null;
31358
+ cb(event);
30923
31359
  } catch {
30924
- return null;
30925
31360
  }
30926
31361
  }
30927
- function isProcessAlive2(pid) {
30928
- if (!Number.isInteger(pid) || pid <= 0) return false;
31362
+ async function stop(input = {}, options = {}) {
31363
+ void input;
31364
+ options.signal?.throwIfAborted?.();
31365
+ const slockHome = resolveSlockHome();
31366
+ const readPidfile = options.readPidfile ?? readPidfileAt;
31367
+ const isAlive = options.isProcessAlive ?? isProcessAlive2;
31368
+ const killer = options.killSupervisor ?? ((pid2) => {
31369
+ process.kill(pid2, "SIGTERM");
31370
+ });
31371
+ const sleep2 = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
31372
+ const pollIntervalMs = options.pollIntervalMs ?? STOP_POLL_INTERVAL_MS;
31373
+ const timeoutMs = options.timeoutMs ?? STOP_TIMEOUT_MS;
31374
+ const pidfilePath = supervisorPidPath(slockHome);
31375
+ const pid = await readPidfile(pidfilePath);
31376
+ emit5(options, { type: "stopping", pid });
31377
+ if (pid === null) {
31378
+ emit5(options, { type: "not_running" });
31379
+ return { status: "not_running", pid: null, pidfilePath };
31380
+ }
31381
+ if (!isAlive(pid)) {
31382
+ await clearPidfileAt(pidfilePath);
31383
+ emit5(options, { type: "stale_pidfile_cleared", pid });
31384
+ return { status: "stale_pidfile_cleared", pid, pidfilePath };
31385
+ }
31386
+ options.signal?.throwIfAborted?.();
30929
31387
  try {
30930
- process.kill(pid, 0);
30931
- return true;
31388
+ killer(pid);
30932
31389
  } catch (err) {
30933
- return err.code === "EPERM";
31390
+ const cause = err instanceof Error ? err : new Error(String(err));
31391
+ throw new ComputerServiceError(
31392
+ "STOP_SIGNAL_FAILED",
31393
+ `Failed to send SIGTERM to supervisor (pid ${pid}): ${cause.message}. Check process permissions or run: kill ${pid}`,
31394
+ err
31395
+ );
30934
31396
  }
31397
+ emit5(options, { type: "signaled", pid });
31398
+ const deadline = Date.now() + timeoutMs;
31399
+ while (Date.now() < deadline) {
31400
+ options.signal?.throwIfAborted?.();
31401
+ if (!isAlive(pid)) {
31402
+ await clearPidfileAt(pidfilePath);
31403
+ emit5(options, { type: "stopped", pid });
31404
+ return { status: "stopped", pid, pidfilePath };
31405
+ }
31406
+ const remaining = deadline - Date.now();
31407
+ if (remaining <= 0) break;
31408
+ await sleep2(Math.min(pollIntervalMs, remaining));
31409
+ }
31410
+ throw new ComputerServiceError(
31411
+ "STOP_TIMEOUT",
31412
+ `Supervisor (pid ${pid}) did not exit within ${timeoutMs}ms after SIGTERM. Force-kill with: kill -9 ${pid}`
31413
+ );
30935
31414
  }
30936
- async function writePidfileAt(pidfilePath, pid) {
30937
- await mkdir7(dirname7(pidfilePath), { recursive: true });
30938
- await writeFile6(pidfilePath, String(pid), { mode: 384 });
30939
- }
30940
- async function clearPidfileAt(pidfilePath) {
31415
+
31416
+ // src/services/detach.ts
31417
+ init_esm_shims();
31418
+ var import_undici2 = __toESM(require_undici(), 1);
31419
+ var REVOKE_TIMEOUT_MS = 3e3;
31420
+ function emit6(opts, event) {
31421
+ const cb = opts?.onEvent;
31422
+ if (!cb) return;
30941
31423
  try {
30942
- await unlink4(pidfilePath);
31424
+ cb(event);
30943
31425
  } catch {
30944
31426
  }
30945
31427
  }
31428
+ async function bestEffortServerRevoke(fetchImpl, serverUrl, apiKey, serverId, timeoutMs) {
31429
+ const url = `${serverUrl.replace(/\/$/, "")}/internal/computer/attachment/revoke`;
31430
+ const controller = new AbortController();
31431
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
31432
+ try {
31433
+ const res = await fetchImpl(url, {
31434
+ method: "POST",
31435
+ headers: {
31436
+ "content-type": "application/json",
31437
+ authorization: `Bearer ${apiKey}`
31438
+ },
31439
+ body: JSON.stringify({ reason: "computer_detach" }),
31440
+ signal: controller.signal
31441
+ });
31442
+ if (!res.ok) {
31443
+ return {
31444
+ kind: "http_error",
31445
+ status: res.status,
31446
+ message: `server-side revoke for server ${serverId} returned HTTP ${res.status}; local detach proceeds, but the credential may remain valid until it expires.`
31447
+ };
31448
+ }
31449
+ return { kind: "success" };
31450
+ } catch (err) {
31451
+ const m = err instanceof Error ? err.message : String(err);
31452
+ return {
31453
+ kind: "network_error",
31454
+ message: `server-side revoke for server ${serverId} failed: ${m}. Local detach proceeds, but the credential may remain valid until it expires.`
31455
+ };
31456
+ } finally {
31457
+ clearTimeout(timeout);
31458
+ }
31459
+ }
31460
+ async function detach(input, options = {}) {
31461
+ options.signal?.throwIfAborted?.();
31462
+ const serverId = assertValidServerId(input.serverId);
31463
+ const serverLabel = input.serverLabel ?? serverId;
31464
+ const slockHome = resolveSlockHome();
31465
+ const attachment = await readServerAttachment(slockHome, serverId);
31466
+ if (!attachment) {
31467
+ throw new ComputerServiceError(
31468
+ "NOT_ATTACHED",
31469
+ `Not attached to server ${serverLabel} (nothing to detach).`
31470
+ );
31471
+ }
31472
+ emit6(options, { type: "detaching", serverId, serverLabel });
31473
+ options.signal?.throwIfAborted?.();
31474
+ const fetchImpl = options.fetch ?? import_undici2.fetch;
31475
+ const timeoutMs = options.revokeTimeoutMs ?? REVOKE_TIMEOUT_MS;
31476
+ const outcome = await bestEffortServerRevoke(
31477
+ fetchImpl,
31478
+ attachment.serverUrl,
31479
+ attachment.apiKey,
31480
+ serverId,
31481
+ timeoutMs
31482
+ );
31483
+ let revokeOutcome;
31484
+ let revokeHttpStatus;
31485
+ if (outcome.kind === "success") {
31486
+ revokeOutcome = "success";
31487
+ emit6(options, { type: "revoke_succeeded", serverId, serverLabel });
31488
+ } else if (outcome.kind === "http_error") {
31489
+ revokeOutcome = "http_error";
31490
+ revokeHttpStatus = outcome.status;
31491
+ emit6(options, {
31492
+ type: "revoke_failed",
31493
+ serverId,
31494
+ serverLabel,
31495
+ reason: "http_error",
31496
+ httpStatus: outcome.status,
31497
+ message: outcome.message
31498
+ });
31499
+ } else {
31500
+ revokeOutcome = "network_error";
31501
+ emit6(options, {
31502
+ type: "revoke_failed",
31503
+ serverId,
31504
+ serverLabel,
31505
+ reason: "network_error",
31506
+ message: outcome.message
31507
+ });
31508
+ }
31509
+ options.signal?.throwIfAborted?.();
31510
+ await clearServerManaged(slockHome, serverId);
31511
+ const subtree = [
31512
+ serverAttachmentPath(slockHome, serverId),
31513
+ serverDaemonPidPath(slockHome, serverId),
31514
+ serverDaemonLogPath(slockHome, serverId)
31515
+ ];
31516
+ for (const p of subtree) await clearPidfileAt(p);
31517
+ emit6(options, { type: "subtree_cleared", serverId, serverLabel });
31518
+ emit6(options, { type: "detached", serverId, serverLabel });
31519
+ return {
31520
+ status: "detached",
31521
+ serverId,
31522
+ serverLabel,
31523
+ revokeOutcome,
31524
+ revokeHttpStatus
31525
+ };
31526
+ }
31527
+
31528
+ // src/supervisor.ts
30946
31529
  function buildResidentSpawn(mode, serverId, selfEntry = process.argv[1] ?? "", execArgv = process.execArgv) {
30947
31530
  const tail = serverId ? [mode, serverId] : [mode];
30948
31531
  return { command: process.execPath, args: [...execArgv, selfEntry, ...tail] };
30949
31532
  }
30950
31533
  var PARENT_LOCK_HELD_ENV_VAR = "SLOCK_COMPUTER_PARENT_MUTATION_LOCK_HELD";
30951
31534
  async function spawnDetachedSupervisor(slockHome) {
30952
- await mkdir7(computerDir(slockHome), { recursive: true });
31535
+ await mkdir8(computerDir(slockHome), { recursive: true });
30953
31536
  const supLogFd = await open(supervisorLogPath(slockHome), "a");
30954
31537
  const { command, args } = buildResidentSpawn("__supervise", null);
30955
31538
  const child = spawn2(command, args, {
@@ -31034,29 +31617,6 @@ async function runResident(serverId, deps = {}) {
31034
31617
  }
31035
31618
  var RECONCILE_INTERVAL_MS = 5e3;
31036
31619
  var CHILD_RESTART_BACKOFF_MS = 2e3;
31037
- var START_ENSURE_TIMEOUT_MS = 15e3;
31038
- var START_ENSURE_POLL_INTERVAL_MS = 100;
31039
- async function waitForManagedDaemonPids(slockHome, serverIds, deps) {
31040
- const readPidfile = deps.readPidfile ?? readPidfileAt;
31041
- const isAlive = deps.isProcessAlive ?? isProcessAlive2;
31042
- const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
31043
- const timeoutMs = deps.ensureTimeoutMs ?? START_ENSURE_TIMEOUT_MS;
31044
- const pollIntervalMs = deps.ensurePollIntervalMs ?? START_ENSURE_POLL_INTERVAL_MS;
31045
- const deadline = Date.now() + timeoutMs;
31046
- const ready = /* @__PURE__ */ new Map();
31047
- while (ready.size < serverIds.length) {
31048
- for (const serverId of serverIds) {
31049
- if (ready.has(serverId)) continue;
31050
- const pid = await readPidfile(serverDaemonPidPath(slockHome, serverId));
31051
- if (pid && isAlive(pid)) ready.set(serverId, pid);
31052
- }
31053
- if (ready.size === serverIds.length) return ready;
31054
- const remaining = deadline - Date.now();
31055
- if (remaining <= 0) return ready;
31056
- await sleep2(Math.min(pollIntervalMs, remaining));
31057
- }
31058
- return ready;
31059
- }
31060
31620
  function formatReadySummary(ready, serverIds, opts) {
31061
31621
  if (opts.serverId && serverIds.length === 1) {
31062
31622
  const pid = ready.get(opts.serverId);
@@ -31064,14 +31624,6 @@ function formatReadySummary(ready, serverIds, opts) {
31064
31624
  }
31065
31625
  return `Daemons for ${serverIds.length} managed server(s) are running.`;
31066
31626
  }
31067
- function failStartEnsureTimeout(slockHome, serverIds, ready, opts) {
31068
- const missing = serverIds.filter((id) => !ready.has(id));
31069
- const target = opts.serverId && missing.length === 1 ? `${opts.serverLabel ?? opts.serverId}` : `${missing.length} daemon(s): ${missing.join(", ")}`;
31070
- fail(
31071
- "START_DAEMON_TIMEOUT",
31072
- `Timed out waiting for ${target} to start. Run \`slock-computer status\` and inspect ${supervisorLogPath(slockHome)} plus per-server daemon logs under ~/.slock/computer/servers/<serverId>/daemon.log.`
31073
- );
31074
- }
31075
31627
  async function runSupervisorStartupRecovery(slockHome) {
31076
31628
  const parentHoldsLock = process.env[PARENT_LOCK_HELD_ENV_VAR] === "1";
31077
31629
  try {
@@ -31105,10 +31657,10 @@ async function runSupervisorStartupRecovery(slockHome) {
31105
31657
  }
31106
31658
  async function resolveSupervisorIdentity() {
31107
31659
  const here = fileURLToPath2(import.meta.url);
31108
- const installRoot = dirname7(dirname7(here));
31660
+ const installRoot = dirname8(dirname8(here));
31109
31661
  let version = null;
31110
31662
  try {
31111
- const raw = await readFile5(joinPath(installRoot, "package.json"), "utf8");
31663
+ const raw = await readFile7(joinPath(installRoot, "package.json"), "utf8");
31112
31664
  const parsed = JSON.parse(raw);
31113
31665
  if (typeof parsed.version === "string" && parsed.version.length > 0) {
31114
31666
  version = parsed.version;
@@ -31128,7 +31680,7 @@ async function writeSupervisorVersionEvidence(slockHome) {
31128
31680
  };
31129
31681
  const dest = supervisorVersionPath(slockHome);
31130
31682
  const tmp = `${dest}.tmp`;
31131
- await writeFile6(tmp, JSON.stringify(payload) + "\n", { mode: 384 });
31683
+ await writeFile7(tmp, JSON.stringify(payload) + "\n", { mode: 384 });
31132
31684
  await rename2(tmp, dest);
31133
31685
  } catch (err) {
31134
31686
  const msg = err instanceof Error ? err.message : String(err);
@@ -31140,7 +31692,7 @@ async function writeSupervisorVersionEvidence(slockHome) {
31140
31692
  }
31141
31693
  async function readSupervisorVersionEvidence(slockHome) {
31142
31694
  try {
31143
- const raw = await readFile5(supervisorVersionPath(slockHome), "utf8");
31695
+ const raw = await readFile7(supervisorVersionPath(slockHome), "utf8");
31144
31696
  const parsed = JSON.parse(raw);
31145
31697
  if (typeof parsed.installRoot !== "string" || typeof parsed.pid !== "number" || typeof parsed.writtenAt !== "string" || parsed.version !== null && typeof parsed.version !== "string") {
31146
31698
  return null;
@@ -31157,7 +31709,7 @@ async function readSupervisorVersionEvidence(slockHome) {
31157
31709
  }
31158
31710
  async function runSupervise() {
31159
31711
  const slockHome = resolveSlockHome();
31160
- await mkdir7(computerDir(slockHome), { recursive: true });
31712
+ await mkdir8(computerDir(slockHome), { recursive: true });
31161
31713
  await runSupervisorStartupRecovery(slockHome);
31162
31714
  await writePidfileAt(supervisorPidPath(slockHome), process.pid);
31163
31715
  await writeSupervisorVersionEvidence(slockHome);
@@ -31165,7 +31717,7 @@ async function runSupervise() {
31165
31717
  const { [PARENT_LOCK_HELD_ENV_VAR]: _parentLockMarker, ...childEnv } = process.env;
31166
31718
  const spawnChild = async (serverId) => {
31167
31719
  const logPath = serverDaemonLogPath(slockHome, serverId);
31168
- await mkdir7(dirname7(logPath), { recursive: true });
31720
+ await mkdir8(dirname8(logPath), { recursive: true });
31169
31721
  const logFd = await open(logPath, "a");
31170
31722
  const { command, args } = buildResidentSpawn("__run", serverId);
31171
31723
  const child = spawn2(command, args, {
@@ -31254,149 +31806,111 @@ async function runSupervise() {
31254
31806
  });
31255
31807
  }
31256
31808
  async function runStart(opts = {}, deps = {}) {
31257
- const slockHome = resolveSlockHome();
31258
- const attached = await listAttachedServerIds(slockHome);
31259
- if (attached.length === 0) {
31260
- fail(
31261
- "NO_ATTACHMENT",
31262
- "No server attachments yet. Run `slock-computer attach <serverId>` first."
31263
- );
31264
- }
31265
- if (opts.serverId && !attached.includes(opts.serverId)) {
31266
- fail(
31267
- "NOT_ATTACHED",
31268
- `Not attached to server ${opts.serverId}. Run \`slock-computer attach ${opts.serverId}\` first or omit the argument.`
31809
+ let spawnedBackground = null;
31810
+ try {
31811
+ await start(
31812
+ {
31813
+ foreground: opts.foreground,
31814
+ serverId: opts.serverId ?? null,
31815
+ serverLabel: opts.serverLabel ?? null
31816
+ },
31817
+ {
31818
+ spawnDetachedSupervisor: deps.spawnDetachedSupervisor,
31819
+ readPidfile: deps.readPidfile,
31820
+ isProcessAlive: deps.isProcessAlive,
31821
+ sleep: deps.sleep,
31822
+ ensureTimeoutMs: deps.ensureTimeoutMs,
31823
+ ensurePollIntervalMs: deps.ensurePollIntervalMs,
31824
+ onEvent: (event) => {
31825
+ if (event.type === "already_running") {
31826
+ info(`Supervisor already running (pid ${event.supervisorPid}).`);
31827
+ } else if (event.type === "running") {
31828
+ info(
31829
+ `Running supervisor in the foreground (managing ${event.managedTargets.length} of ${event.attachedCount} attached server(s)). Ctrl-C to stop.`
31830
+ );
31831
+ } else if (event.type === "spawned") {
31832
+ info(`Supervisor started (pid ${event.supervisorPid}); keeps running after this terminal closes.`);
31833
+ spawnedBackground = {
31834
+ managedCount: event.managedTargets.length,
31835
+ attachedCount: event.attachedCount,
31836
+ logPath: supervisorLogPath(resolveSlockHome())
31837
+ };
31838
+ } else if (event.type === "ready") {
31839
+ info(formatReadySummary(event.ready, event.managedTargets, opts));
31840
+ }
31841
+ }
31842
+ }
31269
31843
  );
31270
- }
31271
- const managedTargets = opts.serverId ? [opts.serverId] : attached;
31272
- for (const id of managedTargets) {
31273
- await setServerManaged(slockHome, id);
31274
- }
31275
- const existing = await readPidfileAt(supervisorPidPath(slockHome));
31276
- if (existing && isProcessAlive2(existing)) {
31277
- info(`Supervisor already running (pid ${existing}).`);
31278
- const ready2 = await waitForManagedDaemonPids(slockHome, managedTargets, deps);
31279
- if (ready2.size !== managedTargets.length) {
31280
- failStartEnsureTimeout(slockHome, managedTargets, ready2, opts);
31844
+ } catch (err) {
31845
+ if (err instanceof CliExit) throw err;
31846
+ if (err instanceof ComputerServiceError) {
31847
+ fail(err.code, err.message);
31281
31848
  }
31282
- info(formatReadySummary(ready2, managedTargets, opts));
31283
- return;
31849
+ throw err;
31284
31850
  }
31285
- if (opts.foreground) {
31851
+ if (spawnedBackground) {
31852
+ const sb = spawnedBackground;
31286
31853
  info(
31287
- `Running supervisor in the foreground (managing ${managedTargets.length} of ${attached.length} attached server(s)). Ctrl-C to stop.`
31854
+ `Managing ${sb.managedCount} of ${sb.attachedCount} attached server(s). Logs: ${sb.logPath}`
31288
31855
  );
31289
- await runSupervise();
31290
- return;
31291
- }
31292
- let pid;
31293
- try {
31294
- pid = await (deps.spawnDetachedSupervisor ?? spawnDetachedSupervisor)(slockHome);
31295
- } catch (err) {
31296
- const msg = err instanceof Error ? err.message : String(err);
31297
- fail("SUPERVISOR_SPAWN_FAILED", msg);
31298
- }
31299
- info(`Supervisor started (pid ${pid}); keeps running after this terminal closes.`);
31300
- const ready = await waitForManagedDaemonPids(slockHome, managedTargets, deps);
31301
- if (ready.size !== managedTargets.length) {
31302
- failStartEnsureTimeout(slockHome, managedTargets, ready, opts);
31856
+ info(`Per-server daemon logs: ~/.slock/computer/servers/<serverId>/daemon.log`);
31857
+ info(`Check state with \`slock-computer status\`.`);
31303
31858
  }
31304
- info(formatReadySummary(ready, managedTargets, opts));
31305
- info(
31306
- `Managing ${managedTargets.length} of ${attached.length} attached server(s). Logs: ${supervisorLogPath(slockHome)}`
31307
- );
31308
- info(`Per-server daemon logs: ~/.slock/computer/servers/<serverId>/daemon.log`);
31309
- info(`Check state with \`slock-computer status\`.`);
31310
31859
  }
31311
31860
  async function runStop(deps = {}) {
31312
- const slockHome = resolveSlockHome();
31313
- const readPidfile = deps.readPidfile ?? readPidfileAt;
31314
- const isAlive = deps.isProcessAlive ?? isProcessAlive2;
31315
- const killer = deps.killSupervisor ?? ((pid2) => {
31316
- process.kill(pid2, "SIGTERM");
31317
- });
31318
- const sleep2 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
31319
- const pollIntervalMs = deps.pollIntervalMs ?? 200;
31320
- const timeoutMs = deps.timeoutMs ?? 5e3;
31321
- const pidfilePath = supervisorPidPath(slockHome);
31322
- const pid = await readPidfile(pidfilePath);
31323
- if (pid === null) {
31324
- info("Supervisor not running.");
31325
- return;
31326
- }
31327
- if (!isAlive(pid)) {
31328
- await clearPidfileAt(pidfilePath);
31329
- info(`Supervisor not running (cleared stale pidfile for pid ${pid}).`);
31330
- return;
31331
- }
31332
31861
  try {
31333
- killer(pid);
31334
- } catch (err) {
31335
- fail(
31336
- "STOP_SIGNAL_FAILED",
31337
- `Failed to send SIGTERM to supervisor (pid ${pid}): ${err instanceof Error ? err.message : String(err)}. Check process permissions or run: kill ${pid}`
31862
+ await stop(
31863
+ {},
31864
+ {
31865
+ readPidfile: deps.readPidfile,
31866
+ isProcessAlive: deps.isProcessAlive,
31867
+ killSupervisor: deps.killSupervisor,
31868
+ sleep: deps.sleep,
31869
+ pollIntervalMs: deps.pollIntervalMs,
31870
+ timeoutMs: deps.timeoutMs,
31871
+ onEvent: (event) => {
31872
+ if (event.type === "not_running") {
31873
+ info("Supervisor not running.");
31874
+ } else if (event.type === "stale_pidfile_cleared") {
31875
+ info(`Supervisor not running (cleared stale pidfile for pid ${event.pid}).`);
31876
+ } else if (event.type === "stopped") {
31877
+ info(`Stopped supervisor (pid ${event.pid}).`);
31878
+ }
31879
+ }
31880
+ }
31338
31881
  );
31339
- }
31340
- const deadline = Date.now() + timeoutMs;
31341
- while (Date.now() < deadline) {
31342
- if (!isAlive(pid)) {
31343
- await clearPidfileAt(pidfilePath);
31344
- info(`Stopped supervisor (pid ${pid}).`);
31345
- return;
31882
+ } catch (err) {
31883
+ if (err instanceof CliExit) throw err;
31884
+ if (err instanceof ComputerServiceError) {
31885
+ fail(err.code, err.message);
31346
31886
  }
31347
- await sleep2(pollIntervalMs);
31887
+ throw err;
31348
31888
  }
31349
- fail(
31350
- "STOP_TIMEOUT",
31351
- `Supervisor (pid ${pid}) did not exit within ${timeoutMs}ms after SIGTERM. Force-kill with: kill -9 ${pid}`
31352
- );
31353
31889
  }
31354
31890
  async function runDetach(serverId, serverLabel = serverId) {
31355
- assertValidServerId(serverId);
31356
- const slockHome = resolveSlockHome();
31357
- const a = await readServerAttachment(slockHome, serverId);
31358
- if (!a) {
31359
- fail("NOT_ATTACHED", `Not attached to server ${serverLabel} (nothing to detach).`);
31360
- }
31361
- await bestEffortServerRevoke(a.serverUrl, a.apiKey, serverId);
31362
- await clearServerManaged(slockHome, serverId);
31363
- const subtree = [
31364
- serverAttachmentPath(slockHome, serverId),
31365
- serverDaemonPidPath(slockHome, serverId),
31366
- serverDaemonLogPath(slockHome, serverId)
31367
- ];
31368
- for (const p of subtree) await clearPidfileAt(p);
31369
- info(`Detached from server ${serverLabel}.`);
31370
- info(`The supervisor (if running) will stop that server's daemon on its next reconcile tick.`);
31371
- }
31372
- async function bestEffortServerRevoke(serverUrl, apiKey, serverId) {
31373
- const url = `${serverUrl.replace(/\/$/, "")}/internal/computer/attachment/revoke`;
31374
- const controller = new AbortController();
31375
- const timeout = setTimeout(() => controller.abort(), 3e3);
31376
31891
  try {
31377
- const res = await fetch(url, {
31378
- method: "POST",
31379
- headers: {
31380
- "content-type": "application/json",
31381
- authorization: `Bearer ${apiKey}`
31382
- },
31383
- body: JSON.stringify({ reason: "computer_detach" }),
31384
- signal: controller.signal
31385
- });
31386
- if (!res.ok) {
31387
- process.stderr.write(
31388
- `Warning: server-side revoke for server ${serverId} returned HTTP ${res.status}; local detach proceeds, but the credential may remain valid until it expires.
31389
- `
31390
- );
31391
- }
31392
- } catch (err) {
31393
- const msg = err instanceof Error ? err.message : String(err);
31394
- process.stderr.write(
31395
- `Warning: server-side revoke for server ${serverId} failed: ${msg}. Local detach proceeds, but the credential may remain valid until it expires.
31396
- `
31892
+ await detach(
31893
+ { serverId, serverLabel },
31894
+ {
31895
+ onEvent: (event) => {
31896
+ if (event.type === "revoke_failed") {
31897
+ process.stderr.write(`Warning: ${event.message}
31898
+ `);
31899
+ } else if (event.type === "detached") {
31900
+ info(`Detached from server ${event.serverLabel}.`);
31901
+ info(
31902
+ `The supervisor (if running) will stop that server's daemon on its next reconcile tick.`
31903
+ );
31904
+ }
31905
+ }
31906
+ }
31397
31907
  );
31398
- } finally {
31399
- clearTimeout(timeout);
31908
+ } catch (err) {
31909
+ if (err instanceof CliExit) throw err;
31910
+ if (err instanceof ComputerServiceError) {
31911
+ fail(err.code, err.message);
31912
+ }
31913
+ throw err;
31400
31914
  }
31401
31915
  }
31402
31916
 
@@ -31404,7 +31918,7 @@ async function bestEffortServerRevoke(serverUrl, apiKey, serverId) {
31404
31918
  var USER_SESSION_EXPIRY_LEEWAY_MS = 3e4;
31405
31919
  async function hasValidUserSession(slockHome) {
31406
31920
  try {
31407
- const parsed = JSON.parse(await readFile6(userSessionPath(slockHome), "utf8"));
31921
+ const parsed = JSON.parse(await readFile8(userSessionPath(slockHome), "utf8"));
31408
31922
  return parsed.kind === "user-session" && typeof parsed.accessToken === "string" && parsed.accessToken.length > 0 && !isJwtExpired(parsed.accessToken);
31409
31923
  } catch {
31410
31924
  return false;
@@ -31424,7 +31938,7 @@ async function refreshUserSession(slockHome, serverUrl) {
31424
31938
  const file = userSessionPath(slockHome);
31425
31939
  let session;
31426
31940
  try {
31427
- session = JSON.parse(await readFile6(file, "utf8"));
31941
+ session = JSON.parse(await readFile8(file, "utf8"));
31428
31942
  } catch {
31429
31943
  return false;
31430
31944
  }
@@ -31434,7 +31948,7 @@ async function refreshUserSession(slockHome, serverUrl) {
31434
31948
  const baseUrl = resolveServerUrl(serverUrl, session.serverUrl, process.env.SLOCK_SERVER_URL);
31435
31949
  const tmpFile = `${file}.${process.pid}.${Date.now()}.tmp`;
31436
31950
  try {
31437
- const res = await (0, import_undici2.fetch)(new URL("/api/auth/refresh", baseUrl).toString(), {
31951
+ const res = await (0, import_undici3.fetch)(new URL("/api/auth/refresh", baseUrl).toString(), {
31438
31952
  method: "POST",
31439
31953
  headers: { "Content-Type": "application/json" },
31440
31954
  body: JSON.stringify({ refreshToken: session.refreshToken })
@@ -31443,8 +31957,8 @@ async function refreshUserSession(slockHome, serverUrl) {
31443
31957
  if (res.status !== 200 || typeof body?.accessToken !== "string" || typeof body.refreshToken !== "string") {
31444
31958
  return false;
31445
31959
  }
31446
- await mkdir8(dirname8(file), { recursive: true });
31447
- await writeFile7(
31960
+ await mkdir9(dirname9(file), { recursive: true });
31961
+ await writeFile8(
31448
31962
  tmpFile,
31449
31963
  JSON.stringify(
31450
31964
  {
@@ -31479,7 +31993,7 @@ async function hasLiveLegacyDaemon(slockHome) {
31479
31993
  for (const name of machineDirs) {
31480
31994
  if (!name.startsWith("machine-")) continue;
31481
31995
  try {
31482
- const raw = await readFile6(join3(slockHome, "machines", name, "daemon.lock", "owner.json"), "utf8");
31996
+ const raw = await readFile8(join3(slockHome, "machines", name, "daemon.lock", "owner.json"), "utf8");
31483
31997
  const owner = JSON.parse(raw);
31484
31998
  if (typeof owner.pid === "number" && isProcessAlive2(owner.pid)) return true;
31485
31999
  } catch {
@@ -31496,10 +32010,10 @@ function hasAmbientLegacyKeyInput() {
31496
32010
  async function runSetup(opts, deps = {}) {
31497
32011
  const slockHome = resolveSlockHome();
31498
32012
  const isTty = deps.isTty ?? Boolean(process.stdin.isTTY && process.stdout.isTTY);
31499
- const login = deps.runLogin ?? runLogin;
31500
- const attach = deps.runAttach ?? runAttach;
32013
+ const login2 = deps.runLogin ?? runLogin;
32014
+ const attach2 = deps.runAttach ?? runAttach;
31501
32015
  const adopt = deps.runAdoptLegacy ?? runAdoptLegacy;
31502
- const start = deps.runStart ?? runStart;
32016
+ const start2 = deps.runStart ?? runStart;
31503
32017
  const refreshSession = deps.refreshUserSession ?? refreshUserSession;
31504
32018
  const legacyDaemonCheck = deps.hasLiveLegacyDaemon ?? hasLiveLegacyDaemon;
31505
32019
  if (!isTty && !opts.yes) {
@@ -31527,7 +32041,7 @@ async function runSetup(opts, deps = {}) {
31527
32041
  );
31528
32042
  }
31529
32043
  info("User session: missing or expired; starting login.");
31530
- await login({ serverUrl: opts.serverUrl, orchestrated: true });
32044
+ await login2({ serverUrl: opts.serverUrl, orchestrated: true });
31531
32045
  }
31532
32046
  } else {
31533
32047
  info("User session: already logged in.");
@@ -31564,7 +32078,7 @@ async function runSetup(opts, deps = {}) {
31564
32078
  if (opts.adoptLegacy) {
31565
32079
  info("Legacy key: not provided; falling back to fresh device-login attach.");
31566
32080
  }
31567
- await attach({
32081
+ await attach2({
31568
32082
  serverSlug: opts.serverSlug,
31569
32083
  serverUrl: opts.serverUrl,
31570
32084
  name: opts.name,
@@ -31584,7 +32098,7 @@ async function runSetup(opts, deps = {}) {
31584
32098
  info(`Start: skipped (--no-start). Run \`slock-computer start ${label}\` when ready.`);
31585
32099
  return;
31586
32100
  }
31587
- await start({
32101
+ await start2({
31588
32102
  serverId: attachment.serverId,
31589
32103
  serverLabel: opts.serverSlug,
31590
32104
  foreground: opts.foreground
@@ -31593,10 +32107,10 @@ async function runSetup(opts, deps = {}) {
31593
32107
 
31594
32108
  // src/status.ts
31595
32109
  init_esm_shims();
31596
- import { readFile as readFile7 } from "fs/promises";
32110
+ import { readFile as readFile9 } from "fs/promises";
31597
32111
  async function readUserSession(path3) {
31598
32112
  try {
31599
- const parsed = JSON.parse(await readFile7(path3, "utf8"));
32113
+ const parsed = JSON.parse(await readFile9(path3, "utf8"));
31600
32114
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
31601
32115
  return { state: "present", session: parsed, error: null };
31602
32116
  }
@@ -31976,14 +32490,14 @@ async function runDoctor(opts) {
31976
32490
 
31977
32491
  // src/logs.ts
31978
32492
  init_esm_shims();
31979
- import { readFile as readFile8 } from "fs/promises";
32493
+ import { readFile as readFile10 } from "fs/promises";
31980
32494
  var DEFAULT_LINES = 200;
31981
32495
  async function runLogs(opts) {
31982
32496
  const home = resolveSlockHome();
31983
32497
  const file = opts.supervisor ? supervisorLogPath(home) : serverDaemonLogPath(home, await resolveTargetServerId({ server: opts.server }));
31984
32498
  let content;
31985
32499
  try {
31986
- content = await readFile8(file, "utf8");
32500
+ content = await readFile10(file, "utf8");
31987
32501
  } catch {
31988
32502
  fail(
31989
32503
  "NO_DAEMON_LOG",
@@ -32000,13 +32514,13 @@ async function runLogs(opts) {
32000
32514
  // src/concurrency.ts
32001
32515
  init_esm_shims();
32002
32516
  var import_proper_lockfile = __toESM(require_proper_lockfile(), 1);
32003
- import { mkdir as mkdir9 } from "fs/promises";
32517
+ import { mkdir as mkdir10 } from "fs/promises";
32004
32518
  import { join as join4 } from "path";
32005
32519
  var STALE_LOCK_THRESHOLD_MS = 6e4;
32006
32520
  async function withMutationLock(fn) {
32007
32521
  const slockHome = resolveSlockHome();
32008
32522
  const lockTarget = computerDir(slockHome);
32009
- await mkdir9(lockTarget, { recursive: true });
32523
+ await mkdir10(lockTarget, { recursive: true });
32010
32524
  const lockfilePath = join4(lockTarget, ".lock");
32011
32525
  let release = null;
32012
32526
  try {
@@ -32045,8 +32559,8 @@ async function withMutationLock(fn) {
32045
32559
 
32046
32560
  // src/channel.ts
32047
32561
  init_esm_shims();
32048
- import { readFile as readFile9, writeFile as writeFile8, mkdir as mkdir10 } from "fs/promises";
32049
- import { dirname as dirname9 } from "path";
32562
+ import { readFile as readFile11, writeFile as writeFile9, mkdir as mkdir11 } from "fs/promises";
32563
+ import { dirname as dirname10 } from "path";
32050
32564
  var DEFAULT_CHANNEL = "latest";
32051
32565
  var SEMVER_RE = /^\d+\.\d+\.\d+(-[\w.]+)?$/;
32052
32566
  function parseChannel(raw) {
@@ -32061,7 +32575,7 @@ function parseChannel(raw) {
32061
32575
  }
32062
32576
  async function readChannel(slockHome) {
32063
32577
  try {
32064
- const raw = await readFile9(channelPath(slockHome), "utf8");
32578
+ const raw = await readFile11(channelPath(slockHome), "utf8");
32065
32579
  const parsed = parseChannel(raw);
32066
32580
  if (parsed !== null) return parsed;
32067
32581
  } catch {
@@ -32070,8 +32584,8 @@ async function readChannel(slockHome) {
32070
32584
  }
32071
32585
  async function writeChannel(slockHome, channel2) {
32072
32586
  const p = channelPath(slockHome);
32073
- await mkdir10(dirname9(p), { recursive: true });
32074
- await writeFile8(p, `${channel2}
32587
+ await mkdir11(dirname10(p), { recursive: true });
32588
+ await writeFile9(p, `${channel2}
32075
32589
  `, { mode: 384 });
32076
32590
  }
32077
32591
  async function runChannelShow(slockHome) {
@@ -32094,20 +32608,20 @@ async function runChannelSet(slockHome, raw) {
32094
32608
 
32095
32609
  // src/upgradeCli.ts
32096
32610
  init_esm_shims();
32097
- import { readFile as readFile12 } from "fs/promises";
32611
+ import { readFile as readFile14 } from "fs/promises";
32098
32612
  import { fileURLToPath as fileURLToPath3 } from "url";
32099
- import { dirname as dirname10, join as join7 } from "path";
32613
+ import { dirname as dirname11, join as join7 } from "path";
32100
32614
 
32101
32615
  // src/upgrade.ts
32102
32616
  init_esm_shims();
32103
32617
  import { spawn as spawn4 } from "child_process";
32104
- import { mkdir as mkdir11, readFile as readFile11, writeFile as writeFile9, rm as rm3, rename as rename4 } from "fs/promises";
32618
+ import { mkdir as mkdir12, readFile as readFile13, writeFile as writeFile10, rm as rm3, rename as rename4 } from "fs/promises";
32105
32619
  import { join as join6 } from "path";
32106
32620
  import { createHash as createHash3 } from "crypto";
32107
32621
 
32108
32622
  // src/preflightDepDrift.ts
32109
32623
  init_esm_shims();
32110
- import { readFile as readFile10 } from "fs/promises";
32624
+ import { readFile as readFile12 } from "fs/promises";
32111
32625
  import { spawn as spawn3 } from "child_process";
32112
32626
  import { createRequire } from "module";
32113
32627
  import { join as join5 } from "path";
@@ -32278,7 +32792,7 @@ async function defaultReadTarballPackageJson(tarballPath) {
32278
32792
  return JSON.parse(raw);
32279
32793
  }
32280
32794
  async function defaultReadCurrentPackageJson(currentBinaryDir) {
32281
- const raw = await readFile10(join5(currentBinaryDir, "package.json"), "utf8");
32795
+ const raw = await readFile12(join5(currentBinaryDir, "package.json"), "utf8");
32282
32796
  return JSON.parse(raw);
32283
32797
  }
32284
32798
  async function defaultReadInstalledDaemonVersion(currentBinaryDir) {
@@ -32295,7 +32809,7 @@ async function defaultReadInstalledDaemonVersion(currentBinaryDir) {
32295
32809
  for (const base of searchPaths) {
32296
32810
  const candidate = join5(base, ...subPath, "package.json");
32297
32811
  try {
32298
- const raw = await readFile10(candidate, "utf8");
32812
+ const raw = await readFile12(candidate, "utf8");
32299
32813
  const parsed = JSON.parse(raw);
32300
32814
  if (parsed.name !== DAEMON_PACKAGE_NAME) continue;
32301
32815
  if (typeof parsed.version === "string" && parsed.version.length > 0) {
@@ -32375,9 +32889,9 @@ function satisfiesTilde(ver, base) {
32375
32889
  // src/upgrade.ts
32376
32890
  async function stagePhase(slockHome, version, deps = {}) {
32377
32891
  const npmPack = deps.npmPack ?? defaultNpmPack;
32378
- const fsReadFile = deps.fsReadFile ?? readFile11;
32892
+ const fsReadFile = deps.fsReadFile ?? readFile13;
32379
32893
  const stagedPath = upgradeStagingDir(slockHome, version);
32380
- await mkdir11(stagedPath, { recursive: true });
32894
+ await mkdir12(stagedPath, { recursive: true });
32381
32895
  const packageRef = `@slock-ai/computer@${version}`;
32382
32896
  const result = await npmPack(stagedPath, packageRef);
32383
32897
  if (result.exitCode !== 0) {
@@ -32485,8 +32999,8 @@ async function cleanupStaged(slockHome, version) {
32485
32999
  async function snapshotPhase(slockHome, snap) {
32486
33000
  const path3 = upgradeSnapshotPath(slockHome);
32487
33001
  const tmp = `${path3}.tmp`;
32488
- await mkdir11(computerDir(slockHome), { recursive: true });
32489
- await writeFile9(tmp, JSON.stringify(snap, null, 2), { mode: 384 });
33002
+ await mkdir12(computerDir(slockHome), { recursive: true });
33003
+ await writeFile10(tmp, JSON.stringify(snap, null, 2), { mode: 384 });
32490
33004
  await rename4(tmp, path3);
32491
33005
  }
32492
33006
  async function clearUpgradeSnapshot(slockHome) {
@@ -32498,7 +33012,7 @@ async function clearUpgradeSnapshot(slockHome) {
32498
33012
  }
32499
33013
  async function extractTarball(tarballPath, destDir, deps = {}) {
32500
33014
  const tarSpawn = deps.tarSpawn ?? defaultTarSpawn;
32501
- await mkdir11(destDir, { recursive: true });
33015
+ await mkdir12(destDir, { recursive: true });
32502
33016
  const result = await tarSpawn(tarballPath, destDir);
32503
33017
  if (result.exitCode !== 0) {
32504
33018
  const err = new Error(
@@ -32603,7 +33117,7 @@ async function rollbackSwap(currentBinaryDir, deps = {}) {
32603
33117
  }
32604
33118
  async function rollbackRestart(slockHome, currentBinaryDir, deps = {}) {
32605
33119
  const readSupervisorPid = deps.readSupervisorPid ?? (() => defaultReadSupervisorPid(slockHome));
32606
- const isProcessAlive3 = deps.isProcessAlive ?? defaultIsProcessAlive;
33120
+ const isProcessAlive4 = deps.isProcessAlive ?? defaultIsProcessAlive;
32607
33121
  const killSupervisor = deps.killSupervisor ?? defaultKillSupervisor;
32608
33122
  const forceKillSupervisor = deps.forceKillSupervisor ?? defaultForceKillSupervisor;
32609
33123
  const waitForExit = deps.waitForExit ?? defaultWaitForExit;
@@ -32614,7 +33128,7 @@ async function rollbackRestart(slockHome, currentBinaryDir, deps = {}) {
32614
33128
  let supervisorStopped = false;
32615
33129
  try {
32616
33130
  const pid = await readSupervisorPid();
32617
- if (pid === null || !isProcessAlive3(pid)) {
33131
+ if (pid === null || !isProcessAlive4(pid)) {
32618
33132
  supervisorStopped = true;
32619
33133
  } else {
32620
33134
  await killSupervisor(pid);
@@ -32683,9 +33197,9 @@ async function rollbackRestart(slockHome, currentBinaryDir, deps = {}) {
32683
33197
  reason: e instanceof Error ? e.message : String(e)
32684
33198
  };
32685
33199
  }
32686
- const start = Date.now();
33200
+ const start2 = Date.now();
32687
33201
  let healthy = false;
32688
- while (Date.now() - start < healthTimeoutMs) {
33202
+ while (Date.now() - start2 < healthTimeoutMs) {
32689
33203
  if (await healthCheck()) {
32690
33204
  healthy = true;
32691
33205
  break;
@@ -32746,10 +33260,10 @@ async function restartPhase(slockHome, deps = {}) {
32746
33260
  return { ok: false, reason: "spawn_failed" };
32747
33261
  }
32748
33262
  }
32749
- const start = Date.now();
32750
- while (Date.now() - start < healthTimeoutMs) {
33263
+ const start2 = Date.now();
33264
+ while (Date.now() - start2 < healthTimeoutMs) {
32751
33265
  if (await healthCheck()) {
32752
- return { ok: true, healthAfterMs: Date.now() - start };
33266
+ return { ok: true, healthAfterMs: Date.now() - start2 };
32753
33267
  }
32754
33268
  await new Promise((r) => setTimeout(r, healthPollIntervalMs));
32755
33269
  }
@@ -32757,7 +33271,7 @@ async function restartPhase(slockHome, deps = {}) {
32757
33271
  }
32758
33272
  async function defaultReadSupervisorPid(slockHome) {
32759
33273
  try {
32760
- const raw = (await readFile11(supervisorPidPath(slockHome), "utf8")).trim();
33274
+ const raw = (await readFile13(supervisorPidPath(slockHome), "utf8")).trim();
32761
33275
  const pid = Number.parseInt(raw, 10);
32762
33276
  return Number.isInteger(pid) && pid > 0 ? pid : null;
32763
33277
  } catch {
@@ -32784,8 +33298,8 @@ async function defaultIsDaemonBusy(_slockHome) {
32784
33298
  return false;
32785
33299
  }
32786
33300
  async function defaultWaitForExit(pid, timeoutMs) {
32787
- const start = Date.now();
32788
- while (Date.now() - start < timeoutMs) {
33301
+ const start2 = Date.now();
33302
+ while (Date.now() - start2 < timeoutMs) {
32789
33303
  try {
32790
33304
  process.kill(pid, 0);
32791
33305
  } catch (err) {
@@ -33019,20 +33533,20 @@ async function runUpgrade(slockHome, opts) {
33019
33533
  async function rollingDaemonHealthCheck(slockHome, deps = {}) {
33020
33534
  const list = deps.listManagedServerIds ?? listManagedServerIds;
33021
33535
  const readDaemonPid = deps.readDaemonPid ?? defaultReadDaemonPid;
33022
- const isProcessAlive3 = deps.isProcessAlive ?? defaultIsProcessAlive;
33536
+ const isProcessAlive4 = deps.isProcessAlive ?? defaultIsProcessAlive;
33023
33537
  const perDaemonTimeoutMs = deps.perDaemonTimeoutMs ?? 3e4;
33024
33538
  const pollIntervalMs = deps.pollIntervalMs ?? 500;
33025
33539
  const managed = await list(slockHome);
33026
33540
  const daemons = [];
33027
33541
  for (const serverId of managed) {
33028
- const start = Date.now();
33542
+ const start2 = Date.now();
33029
33543
  let lastReason = "timeout";
33030
33544
  let healthy = false;
33031
- while (Date.now() - start < perDaemonTimeoutMs) {
33545
+ while (Date.now() - start2 < perDaemonTimeoutMs) {
33032
33546
  const pid = await readDaemonPid(slockHome, serverId);
33033
33547
  if (pid === null) {
33034
33548
  lastReason = "pidfile_missing";
33035
- } else if (!isProcessAlive3(pid)) {
33549
+ } else if (!isProcessAlive4(pid)) {
33036
33550
  lastReason = "process_dead";
33037
33551
  } else {
33038
33552
  healthy = true;
@@ -33040,7 +33554,7 @@ async function rollingDaemonHealthCheck(slockHome, deps = {}) {
33040
33554
  }
33041
33555
  await new Promise((r) => setTimeout(r, pollIntervalMs));
33042
33556
  }
33043
- const entry = healthy ? { serverId, ok: true, healthAfterMs: Date.now() - start } : { serverId, ok: false, reason: lastReason };
33557
+ const entry = healthy ? { serverId, ok: true, healthAfterMs: Date.now() - start2 } : { serverId, ok: false, reason: lastReason };
33044
33558
  daemons.push(entry);
33045
33559
  if (!healthy) {
33046
33560
  return { ok: false, daemons };
@@ -33050,7 +33564,7 @@ async function rollingDaemonHealthCheck(slockHome, deps = {}) {
33050
33564
  }
33051
33565
  async function defaultReadDaemonPid(slockHome, serverId) {
33052
33566
  try {
33053
- const raw = (await readFile11(serverDaemonPidPath(slockHome, serverId), "utf8")).trim();
33567
+ const raw = (await readFile13(serverDaemonPidPath(slockHome, serverId), "utf8")).trim();
33054
33568
  const pid = Number.parseInt(raw, 10);
33055
33569
  return Number.isInteger(pid) && pid > 0 ? pid : null;
33056
33570
  } catch {
@@ -33078,7 +33592,7 @@ async function locateStagedTarball(stagedPath) {
33078
33592
 
33079
33593
  // src/upgradeLog.ts
33080
33594
  init_esm_shims();
33081
- import { chmod as chmod5, mkdir as mkdir12, open as open2 } from "fs/promises";
33595
+ import { chmod as chmod5, mkdir as mkdir13, open as open2 } from "fs/promises";
33082
33596
  var FILE_MODE = 384;
33083
33597
  var UPGRADE_ERROR_CODES = [
33084
33598
  "UPGRADE_DEPS_CHANGED",
@@ -33123,7 +33637,7 @@ function assertUpgradeLogEntry(entry) {
33123
33637
  }
33124
33638
  async function appendUpgradeLogEntry(slockHome, entry) {
33125
33639
  assertUpgradeLogEntry(entry);
33126
- await mkdir12(computerDir(slockHome), { recursive: true });
33640
+ await mkdir13(computerDir(slockHome), { recursive: true });
33127
33641
  const path3 = upgradeLogPath(slockHome);
33128
33642
  const at = entry.at ?? formatUpgradeLogTimestamp();
33129
33643
  const fullEntry = { ...entry, at };
@@ -33146,7 +33660,7 @@ function isEphemeralNpxContext(binaryDir) {
33146
33660
  async function readBundledDaemonVersion(binaryDir) {
33147
33661
  try {
33148
33662
  const pkgPath = join7(binaryDir, "package.json");
33149
- const raw = await readFile12(pkgPath, "utf8");
33663
+ const raw = await readFile14(pkgPath, "utf8");
33150
33664
  const parsed = JSON.parse(raw);
33151
33665
  const pinned = parsed.dependencies?.["@slock-ai/daemon"];
33152
33666
  if (typeof pinned !== "string" || pinned.length === 0) return null;
@@ -33374,12 +33888,12 @@ async function defaultFetchDistTags() {
33374
33888
  }
33375
33889
  function defaultCurrentBinaryDir() {
33376
33890
  const here = fileURLToPath3(import.meta.url);
33377
- return dirname10(dirname10(here));
33891
+ return dirname11(dirname11(here));
33378
33892
  }
33379
33893
  async function defaultCurrentVersion() {
33380
33894
  const pkgPath = join7(defaultCurrentBinaryDir(), "package.json");
33381
33895
  try {
33382
- const raw = await readFile12(pkgPath, "utf8");
33896
+ const raw = await readFile14(pkgPath, "utf8");
33383
33897
  const parsed = JSON.parse(raw);
33384
33898
  if (typeof parsed.version === "string" && parsed.version.length > 0) {
33385
33899
  return parsed.version;
@@ -33391,7 +33905,7 @@ async function defaultCurrentVersion() {
33391
33905
 
33392
33906
  // src/upgradeTestHarness.ts
33393
33907
  init_esm_shims();
33394
- import { mkdir as mkdir13, readdir as readdir4, stat as stat3, writeFile as writeFile10 } from "fs/promises";
33908
+ import { mkdir as mkdir14, readdir as readdir4, stat as stat3, writeFile as writeFile11 } from "fs/promises";
33395
33909
  import { join as join8 } from "path";
33396
33910
  import { createHash as createHash4 } from "crypto";
33397
33911
  var PHASES = /* @__PURE__ */ new Set([
@@ -33450,7 +33964,7 @@ function buildSimulatedDeps(slockHome, opts) {
33450
33964
  return { tarballPath: "", exitCode: 1, stderr: "simulated stage failure" };
33451
33965
  }
33452
33966
  const filename = `slock-ai-computer-${targetVersion}.tgz`;
33453
- await writeFile10(join8(cwd, filename), tarballBytes);
33967
+ await writeFile11(join8(cwd, filename), tarballBytes);
33454
33968
  return { tarballPath: join8(cwd, filename), exitCode: 0, stderr: "" };
33455
33969
  },
33456
33970
  fetchAdvertisedHash: async () => {
@@ -33462,8 +33976,8 @@ function buildSimulatedDeps(slockHome, opts) {
33462
33976
  if (opts.simulateFail === "extract") {
33463
33977
  return { exitCode: 1, stderr: "simulated extract failure" };
33464
33978
  }
33465
- await mkdir13(join8(destDir, "package"), { recursive: true });
33466
- await writeFile10(join8(destDir, "package", "marker.txt"), `NEW@${targetVersion}`);
33979
+ await mkdir14(join8(destDir, "package"), { recursive: true });
33980
+ await writeFile11(join8(destDir, "package", "marker.txt"), `NEW@${targetVersion}`);
33467
33981
  return { exitCode: 0, stderr: "" };
33468
33982
  },
33469
33983
  npmInstall: async () => ({ exitCode: 0, stderr: "" }),
@@ -33524,7 +34038,7 @@ function buildSimulatedDeps(slockHome, opts) {
33524
34038
  }
33525
34039
  async function arrangeSnapshotFailure(slockHome) {
33526
34040
  const snapshotPath = join8(slockHome, "computer", "upgrade-snapshot.json");
33527
- await mkdir13(snapshotPath, { recursive: true });
34041
+ await mkdir14(snapshotPath, { recursive: true });
33528
34042
  }
33529
34043
  async function pathInfo(path3) {
33530
34044
  try {
@@ -33579,12 +34093,12 @@ async function runUpgradeTestHarness(slockHome, opts, writer = (s) => process.st
33579
34093
  process.exitCode = 1;
33580
34094
  return;
33581
34095
  }
33582
- await mkdir13(slockHome, { recursive: true });
34096
+ await mkdir14(slockHome, { recursive: true });
33583
34097
  if (opts.simulateFail === "snapshot") {
33584
34098
  await arrangeSnapshotFailure(slockHome);
33585
34099
  }
33586
34100
  const { opts: upgradeOpts } = buildSimulatedDeps(slockHome, opts);
33587
- await mkdir13(upgradeOpts.currentBinaryDir, { recursive: true });
34101
+ await mkdir14(upgradeOpts.currentBinaryDir, { recursive: true });
33588
34102
  let outcome;
33589
34103
  try {
33590
34104
  outcome = await runUpgrade(slockHome, upgradeOpts);
@@ -33622,9 +34136,9 @@ async function runUpgradeTestHarness(slockHome, opts, writer = (s) => process.st
33622
34136
 
33623
34137
  // src/upgradeInstallSmoke.ts
33624
34138
  init_esm_shims();
33625
- import { copyFile, mkdir as mkdir14, readFile as readFile13 } from "fs/promises";
34139
+ import { copyFile, mkdir as mkdir15, readFile as readFile15 } from "fs/promises";
33626
34140
  import { createHash as createHash5 } from "crypto";
33627
- import { dirname as dirname11, isAbsolute, join as join9, resolve as pathResolve } from "path";
34141
+ import { dirname as dirname12, isAbsolute, join as join9, resolve as pathResolve } from "path";
33628
34142
  import { fileURLToPath as fileURLToPath4 } from "url";
33629
34143
  async function runUpgradeInstallSmoke(slockHome, opts, deps = {}) {
33630
34144
  if (typeof opts.packageTarball !== "string" || opts.packageTarball.trim().length === 0) {
@@ -33633,7 +34147,7 @@ async function runUpgradeInstallSmoke(slockHome, opts, deps = {}) {
33633
34147
  const tarballPath = isAbsolute(opts.packageTarball) ? opts.packageTarball : pathResolve(opts.packageTarball);
33634
34148
  let tarballBytes;
33635
34149
  try {
33636
- tarballBytes = await readFile13(tarballPath);
34150
+ tarballBytes = await readFile15(tarballPath);
33637
34151
  } catch (e) {
33638
34152
  const msg = e instanceof Error ? e.message : String(e);
33639
34153
  throw new Error(`__upgrade-install-smoke: cannot read --package-tarball ${tarballPath}: ${msg}`);
@@ -33645,7 +34159,7 @@ async function runUpgradeInstallSmoke(slockHome, opts, deps = {}) {
33645
34159
  const spawnFreshSupervisor = deps.spawnFreshSupervisor ?? (async (h) => {
33646
34160
  await spawnDetachedSupervisor(h);
33647
34161
  });
33648
- await mkdir14(slockHome, { recursive: true });
34162
+ await mkdir15(slockHome, { recursive: true });
33649
34163
  const outcome = await runUpgrade(slockHome, {
33650
34164
  targetVersion: opts.targetVersion,
33651
34165
  fromVersion,
@@ -33703,12 +34217,12 @@ async function runUpgradeInstallSmokeCli(slockHome, opts, writer = (s) => proces
33703
34217
  }
33704
34218
  function defaultCurrentBinaryDirLocal() {
33705
34219
  const here = fileURLToPath4(import.meta.url);
33706
- return dirname11(dirname11(here));
34220
+ return dirname12(dirname12(here));
33707
34221
  }
33708
34222
  async function defaultCurrentVersionLocal() {
33709
34223
  const pkgPath = join9(defaultCurrentBinaryDirLocal(), "package.json");
33710
34224
  try {
33711
- const raw = await readFile13(pkgPath, "utf8");
34225
+ const raw = await readFile15(pkgPath, "utf8");
33712
34226
  const parsed = JSON.parse(raw);
33713
34227
  if (typeof parsed.version === "string" && parsed.version.length > 0) {
33714
34228
  return parsed.version;