@botiverse/raft-computer 0.0.60 → 0.0.61

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
@@ -29592,6 +29592,7 @@ var {
29592
29592
 
29593
29593
  // src/login.ts
29594
29594
  init_esm_shims();
29595
+ import { unlink } from "fs/promises";
29595
29596
 
29596
29597
  // src/output.ts
29597
29598
  init_esm_shims();
@@ -29619,6 +29620,124 @@ function fail(code, message, exitCode = 1) {
29619
29620
  throw new CliExit(exitCode, code);
29620
29621
  }
29621
29622
 
29623
+ // src/paths.ts
29624
+ init_esm_shims();
29625
+ import { createHash } from "crypto";
29626
+ import os from "os";
29627
+ import path2 from "path";
29628
+ var CURRENT_SCHEMA_VERSION = 1;
29629
+ function resolveSlockHome(env = process.env, homeDir = os.homedir()) {
29630
+ const configured = env.SLOCK_HOME?.trim();
29631
+ const raw = configured && configured.length > 0 ? configured : path2.join(homeDir, ".slock");
29632
+ return path2.resolve(expandHome(raw, homeDir));
29633
+ }
29634
+ function expandHome(input, homeDir) {
29635
+ if (input === "~") return homeDir;
29636
+ if (input.startsWith("~/")) return path2.join(homeDir, input.slice(2));
29637
+ return input;
29638
+ }
29639
+ function computerDir(slockHome) {
29640
+ return path2.join(slockHome, "computer");
29641
+ }
29642
+ function userSessionPath(slockHome) {
29643
+ return path2.join(computerDir(slockHome), "user-session.json");
29644
+ }
29645
+ var SERVER_ID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
29646
+ function isValidServerId(serverId) {
29647
+ return SERVER_ID_RE.test(serverId);
29648
+ }
29649
+ function assertValidServerId(serverId) {
29650
+ if (!isValidServerId(serverId)) {
29651
+ throw new Error(`invalid server id: ${JSON.stringify(serverId)} (expected a UUID)`);
29652
+ }
29653
+ return serverId;
29654
+ }
29655
+ function serversDir(slockHome) {
29656
+ return path2.join(computerDir(slockHome), "servers");
29657
+ }
29658
+ function serverDir(slockHome, serverId) {
29659
+ return path2.join(serversDir(slockHome), assertValidServerId(serverId));
29660
+ }
29661
+ function serverAttachmentPath(slockHome, serverId) {
29662
+ return path2.join(serverDir(slockHome, serverId), "runner.state.json");
29663
+ }
29664
+ function legacyServerAttachmentPath(slockHome, serverId) {
29665
+ return path2.join(serverDir(slockHome, serverId), "attachment.json");
29666
+ }
29667
+ function serverRunnerPidPath(slockHome, serverId) {
29668
+ return path2.join(serverDir(slockHome, serverId), "runner.pid");
29669
+ }
29670
+ function serverRunnerLogPath(slockHome, serverId) {
29671
+ return path2.join(serverDir(slockHome, serverId), "runner.log");
29672
+ }
29673
+ function serverManagedFlagPath(slockHome, serverId) {
29674
+ return path2.join(serverDir(slockHome, serverId), "managed.flag");
29675
+ }
29676
+ function serverHealthPath(slockHome, serverId) {
29677
+ return path2.join(serverDir(slockHome, serverId), "health.json");
29678
+ }
29679
+ function serviceRunDir(slockHome) {
29680
+ return path2.join(computerDir(slockHome), "run");
29681
+ }
29682
+ function serviceStatePath(slockHome) {
29683
+ return path2.join(serviceRunDir(slockHome), "service.state.json");
29684
+ }
29685
+ function servicePidPath(slockHome) {
29686
+ return path2.join(serviceRunDir(slockHome), "service.pid");
29687
+ }
29688
+ function servicePidReadFallback(slockHome) {
29689
+ return [servicePidPath(slockHome)];
29690
+ }
29691
+ function serviceLogPath(slockHome) {
29692
+ return path2.join(serviceRunDir(slockHome), "service.log");
29693
+ }
29694
+ function serviceSocketPath(slockHome) {
29695
+ return path2.join(computerDir(slockHome), "run", "service.sock");
29696
+ }
29697
+ function serviceWindowsPipeName(slockHome) {
29698
+ const hash = createHash("sha256").update(computerDir(slockHome)).digest("hex").slice(0, 16);
29699
+ return `\\\\.\\pipe\\slock-computer-${hash}`;
29700
+ }
29701
+ function serviceVersionPath(slockHome) {
29702
+ return path2.join(computerDir(slockHome), "service-version.json");
29703
+ }
29704
+ var HOSTNAME_SUFFIXES = [
29705
+ ".fritz.box",
29706
+ ".localdomain",
29707
+ ".local",
29708
+ ".lan",
29709
+ ".home"
29710
+ ];
29711
+ function shortHostnameHash(hostname) {
29712
+ return createHash("sha256").update(hostname).digest("hex").slice(0, 8);
29713
+ }
29714
+ function deriveDefaultComputerName(hostname = os.hostname()) {
29715
+ const original = hostname.trim();
29716
+ let name = original;
29717
+ let lower = name.toLowerCase();
29718
+ for (; ; ) {
29719
+ const suffix = HOSTNAME_SUFFIXES.find((candidate) => lower.endsWith(candidate));
29720
+ if (!suffix) break;
29721
+ name = name.slice(0, -suffix.length);
29722
+ lower = name.toLowerCase();
29723
+ }
29724
+ name = name.replace(/[\s'"]+/g, "-").replace(/-+$/g, "");
29725
+ return name.length > 0 ? name : `slock-computer-${shortHostnameHash(original)}`;
29726
+ }
29727
+ function adoptionLogPath(slockHome) {
29728
+ return path2.join(computerDir(slockHome), "adoption.log");
29729
+ }
29730
+ function channelPath(slockHome) {
29731
+ return path2.join(computerDir(slockHome), "channel");
29732
+ }
29733
+ function upgradeLogPath(slockHome) {
29734
+ return path2.join(computerDir(slockHome), "upgrade.log");
29735
+ }
29736
+ function formatUpgradeLogTimestamp(date = /* @__PURE__ */ new Date()) {
29737
+ const iso = date.toISOString();
29738
+ return iso.replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
29739
+ }
29740
+
29622
29741
  // src/services/errors.ts
29623
29742
  init_esm_shims();
29624
29743
  var ComputerServiceError = class extends Error {
@@ -29931,123 +30050,6 @@ var RunnersClient = class {
29931
30050
  }
29932
30051
  };
29933
30052
 
29934
- // src/paths.ts
29935
- init_esm_shims();
29936
- import { createHash } from "crypto";
29937
- import os from "os";
29938
- import path2 from "path";
29939
- function resolveSlockHome(env = process.env, homeDir = os.homedir()) {
29940
- const configured = env.SLOCK_HOME?.trim();
29941
- const raw = configured && configured.length > 0 ? configured : path2.join(homeDir, ".slock");
29942
- return path2.resolve(expandHome(raw, homeDir));
29943
- }
29944
- function expandHome(input, homeDir) {
29945
- if (input === "~") return homeDir;
29946
- if (input.startsWith("~/")) return path2.join(homeDir, input.slice(2));
29947
- return input;
29948
- }
29949
- function computerDir(slockHome) {
29950
- return path2.join(slockHome, "computer");
29951
- }
29952
- function userSessionPath(slockHome) {
29953
- return path2.join(computerDir(slockHome), "user-session.json");
29954
- }
29955
- var SERVER_ID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
29956
- function isValidServerId(serverId) {
29957
- return SERVER_ID_RE.test(serverId);
29958
- }
29959
- function assertValidServerId(serverId) {
29960
- if (!isValidServerId(serverId)) {
29961
- throw new Error(`invalid server id: ${JSON.stringify(serverId)} (expected a UUID)`);
29962
- }
29963
- return serverId;
29964
- }
29965
- function serversDir(slockHome) {
29966
- return path2.join(computerDir(slockHome), "servers");
29967
- }
29968
- function serverDir(slockHome, serverId) {
29969
- return path2.join(serversDir(slockHome), assertValidServerId(serverId));
29970
- }
29971
- function serverAttachmentPath(slockHome, serverId) {
29972
- return path2.join(serverDir(slockHome, serverId), "runner.state.json");
29973
- }
29974
- function legacyServerAttachmentPath(slockHome, serverId) {
29975
- return path2.join(serverDir(slockHome, serverId), "attachment.json");
29976
- }
29977
- function serverRunnerPidPath(slockHome, serverId) {
29978
- return path2.join(serverDir(slockHome, serverId), "server-runner.pid");
29979
- }
29980
- function serverRunnerLogPath(slockHome, serverId) {
29981
- return path2.join(serverDir(slockHome, serverId), "server-runner.log");
29982
- }
29983
- function serverManagedFlagPath(slockHome, serverId) {
29984
- return path2.join(serverDir(slockHome, serverId), "managed.flag");
29985
- }
29986
- function serverHealthPath(slockHome, serverId) {
29987
- return path2.join(serverDir(slockHome, serverId), "health.json");
29988
- }
29989
- function serviceRunDir(slockHome) {
29990
- return path2.join(computerDir(slockHome), "run");
29991
- }
29992
- function serviceStatePath(slockHome) {
29993
- return path2.join(serviceRunDir(slockHome), "service.state.json");
29994
- }
29995
- function servicePidPath(slockHome) {
29996
- return path2.join(serviceRunDir(slockHome), "service.pid");
29997
- }
29998
- function servicePidReadFallback(slockHome) {
29999
- return [servicePidPath(slockHome)];
30000
- }
30001
- function serviceLogPath(slockHome) {
30002
- return path2.join(serviceRunDir(slockHome), "service.log");
30003
- }
30004
- function serviceSocketPath(slockHome) {
30005
- return path2.join(computerDir(slockHome), "run", "service.sock");
30006
- }
30007
- function serviceWindowsPipeName(slockHome) {
30008
- const hash = createHash("sha256").update(computerDir(slockHome)).digest("hex").slice(0, 16);
30009
- return `\\\\.\\pipe\\slock-computer-${hash}`;
30010
- }
30011
- function serviceVersionPath(slockHome) {
30012
- return path2.join(computerDir(slockHome), "service-version.json");
30013
- }
30014
- var HOSTNAME_SUFFIXES = [
30015
- ".fritz.box",
30016
- ".localdomain",
30017
- ".local",
30018
- ".lan",
30019
- ".home"
30020
- ];
30021
- function shortHostnameHash(hostname) {
30022
- return createHash("sha256").update(hostname).digest("hex").slice(0, 8);
30023
- }
30024
- function deriveDefaultComputerName(hostname = os.hostname()) {
30025
- const original = hostname.trim();
30026
- let name = original;
30027
- let lower = name.toLowerCase();
30028
- for (; ; ) {
30029
- const suffix = HOSTNAME_SUFFIXES.find((candidate) => lower.endsWith(candidate));
30030
- if (!suffix) break;
30031
- name = name.slice(0, -suffix.length);
30032
- lower = name.toLowerCase();
30033
- }
30034
- name = name.replace(/[\s'"]+/g, "-").replace(/-+$/g, "");
30035
- return name.length > 0 ? name : `slock-computer-${shortHostnameHash(original)}`;
30036
- }
30037
- function adoptionLogPath(slockHome) {
30038
- return path2.join(computerDir(slockHome), "adoption.log");
30039
- }
30040
- function channelPath(slockHome) {
30041
- return path2.join(computerDir(slockHome), "channel");
30042
- }
30043
- function upgradeLogPath(slockHome) {
30044
- return path2.join(computerDir(slockHome), "upgrade.log");
30045
- }
30046
- function formatUpgradeLogTimestamp(date = /* @__PURE__ */ new Date()) {
30047
- const iso = date.toISOString();
30048
- return iso.replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
30049
- }
30050
-
30051
30053
  // src/serverUrl.ts
30052
30054
  init_esm_shims();
30053
30055
  var DEFAULT_SLOCK_SERVER_URL = "https://api.raft.build";
@@ -30148,6 +30150,8 @@ async function login(input, options = {}) {
30148
30150
  JSON.stringify(
30149
30151
  {
30150
30152
  kind: "user-session",
30153
+ // On-disk schema version; readers tolerate a missing value.
30154
+ schemaVersion: CURRENT_SCHEMA_VERSION,
30151
30155
  userId: r.userId,
30152
30156
  accessToken: r.accessToken,
30153
30157
  refreshToken: r.refreshToken,
@@ -30196,21 +30200,36 @@ async function runLogin(opts) {
30196
30200
  throw err;
30197
30201
  }
30198
30202
  }
30203
+ async function runLogout() {
30204
+ const sessionPath = userSessionPath(resolveSlockHome());
30205
+ try {
30206
+ await unlink(sessionPath);
30207
+ info(`Logged out. Cleared user session at ${sessionPath}.`);
30208
+ } catch (err) {
30209
+ if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") {
30210
+ info("Already logged out (no saved user session).");
30211
+ return;
30212
+ }
30213
+ throw err;
30214
+ }
30215
+ }
30199
30216
 
30200
30217
  // src/attach.ts
30201
30218
  init_esm_shims();
30202
30219
 
30203
30220
  // src/serverState.ts
30204
30221
  init_esm_shims();
30205
- import { readFile, readdir, writeFile as writeFile2, mkdir as mkdir2, unlink, access, chmod } from "fs/promises";
30222
+ import { readFile, readdir, writeFile as writeFile2, mkdir as mkdir2, unlink as unlink2, access, chmod } from "fs/promises";
30206
30223
  import { dirname as dirname2 } from "path";
30207
30224
  import { constants as fsConstants } from "fs";
30208
30225
  function parseAttachment(raw) {
30209
30226
  try {
30210
30227
  const a = JSON.parse(raw);
30211
30228
  if (a.kind === "computer-attachment" && typeof a.serverId === "string" && typeof a.serverMachineId === "string" && typeof a.apiKey === "string" && a.apiKey.length > 0 && typeof a.serverUrl === "string") {
30229
+ const schemaVersion = typeof a.schemaVersion === "number" ? a.schemaVersion : CURRENT_SCHEMA_VERSION;
30212
30230
  return {
30213
30231
  kind: "computer-attachment",
30232
+ schemaVersion,
30214
30233
  serverId: a.serverId,
30215
30234
  serverSlug: typeof a.serverSlug === "string" && a.serverSlug.length > 0 ? a.serverSlug : void 0,
30216
30235
  serverMachineId: a.serverMachineId,
@@ -30236,18 +30255,41 @@ async function readServerAttachment(slockHome, serverId) {
30236
30255
  if (!isValidServerId(serverId)) return null;
30237
30256
  const current = await readAttachmentAt(serverAttachmentPath(slockHome, serverId));
30238
30257
  const legacy = await readAttachmentAt(legacyServerAttachmentPath(slockHome, serverId));
30239
- if (!current) return legacy;
30240
- if (!legacy) return current;
30241
- if (legacy.adoptedFromLegacy === true && current.adoptedFromLegacy !== true && legacy.serverMachineId !== current.serverMachineId) {
30242
- return legacy;
30258
+ let effective;
30259
+ let decidedByLegacy;
30260
+ if (!current) {
30261
+ effective = legacy;
30262
+ decidedByLegacy = legacy !== null;
30263
+ } else if (!legacy) {
30264
+ effective = current;
30265
+ decidedByLegacy = false;
30266
+ } else if (
30267
+ // Adopted-machine-still-wins: when the legacy attachment is an adopted
30268
+ // (working) machine and the current one is a different, not-adopted machine
30269
+ // (e.g. a fresh attach that the server rejects), prefer the legacy creds.
30270
+ legacy.adoptedFromLegacy === true && current.adoptedFromLegacy !== true && legacy.serverMachineId !== current.serverMachineId
30271
+ ) {
30272
+ effective = legacy;
30273
+ decidedByLegacy = true;
30274
+ } else {
30275
+ effective = current;
30276
+ decidedByLegacy = false;
30277
+ }
30278
+ if (decidedByLegacy && effective) {
30279
+ try {
30280
+ await writeServerAttachment(slockHome, effective);
30281
+ await unlink2(legacyServerAttachmentPath(slockHome, serverId));
30282
+ } catch {
30283
+ }
30243
30284
  }
30244
- return current;
30285
+ return effective;
30245
30286
  }
30246
30287
  async function writeServerAttachment(slockHome, attachment) {
30247
30288
  if (!isValidServerId(attachment.serverId)) return;
30248
30289
  const path3 = serverAttachmentPath(slockHome, attachment.serverId);
30249
30290
  await mkdir2(dirname2(path3), { recursive: true });
30250
- await writeFile2(path3, JSON.stringify(attachment, null, 2), { mode: 384 });
30291
+ const stamped = { ...attachment, schemaVersion: CURRENT_SCHEMA_VERSION };
30292
+ await writeFile2(path3, JSON.stringify(stamped, null, 2), { mode: 384 });
30251
30293
  await chmod(path3, 384);
30252
30294
  }
30253
30295
  async function listAttachedServerIds(slockHome) {
@@ -30297,7 +30339,7 @@ async function setServerManaged(slockHome, serverId) {
30297
30339
  async function clearServerManaged(slockHome, serverId) {
30298
30340
  if (!isValidServerId(serverId)) return;
30299
30341
  try {
30300
- await unlink(serverManagedFlagPath(slockHome, serverId));
30342
+ await unlink2(serverManagedFlagPath(slockHome, serverId));
30301
30343
  } catch {
30302
30344
  }
30303
30345
  }
@@ -30492,7 +30534,7 @@ async function runAttach(opts) {
30492
30534
  if (opts.orchestrated) {
30493
30535
  return;
30494
30536
  }
30495
- if (opts.run === false) {
30537
+ if (opts.start === false) {
30496
30538
  info("Next: run `slock-computer start` to run it in the background.");
30497
30539
  } else {
30498
30540
  info("Next: run `slock-computer start` (background) \u2014 closing the terminal is fine.");
@@ -31035,13 +31077,13 @@ import { fileURLToPath as fileURLToPath2 } from "url";
31035
31077
 
31036
31078
  // src/cleanup.ts
31037
31079
  init_esm_shims();
31038
- import { readdir as readdir3, stat as stat3, unlink as unlink3, rm, rmdir, rename, mkdir as mkdir6 } from "fs/promises";
31080
+ import { readdir as readdir3, stat as stat3, unlink as unlink4, rm, rmdir, rename, mkdir as mkdir6 } from "fs/promises";
31039
31081
  import { spawn } from "child_process";
31040
31082
  import { join as join3 } from "path";
31041
31083
 
31042
31084
  // src/internal/process-primitives.ts
31043
31085
  init_esm_shims();
31044
- import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile5, unlink as unlink2 } from "fs/promises";
31086
+ import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile5, unlink as unlink3 } from "fs/promises";
31045
31087
  import { dirname as dirname5 } from "path";
31046
31088
  async function readPidfileAt(pidfilePath) {
31047
31089
  try {
@@ -31067,7 +31109,7 @@ async function writePidfileAt(pidfilePath, pid) {
31067
31109
  }
31068
31110
  async function clearPidfileAt(pidfilePath) {
31069
31111
  try {
31070
- await unlink2(pidfilePath);
31112
+ await unlink3(pidfilePath);
31071
31113
  } catch {
31072
31114
  }
31073
31115
  }
@@ -31088,7 +31130,7 @@ async function cleanupStalePidfile(pidfilePath) {
31088
31130
  if (pid === null) return false;
31089
31131
  if (isProcessAlive2(pid)) return false;
31090
31132
  try {
31091
- await unlink3(pidfilePath);
31133
+ await unlink4(pidfilePath);
31092
31134
  return true;
31093
31135
  } catch {
31094
31136
  return false;
@@ -31236,7 +31278,7 @@ async function cleanupTmpFiles(slockHome) {
31236
31278
  try {
31237
31279
  const s = await stat3(snap);
31238
31280
  if (Date.now() - s.mtimeMs > TMP_MAX_AGE_MS) {
31239
- await unlink3(snap);
31281
+ await unlink4(snap);
31240
31282
  removed.push(snap);
31241
31283
  }
31242
31284
  } catch {
@@ -31280,7 +31322,7 @@ async function runFullCleanup(slockHome, options = {}) {
31280
31322
 
31281
31323
  // src/health.ts
31282
31324
  init_esm_shims();
31283
- import { readFile as readFile6, writeFile as writeFile6, unlink as unlink4, mkdir as mkdir7, appendFile as appendFile2 } from "fs/promises";
31325
+ import { readFile as readFile6, writeFile as writeFile6, unlink as unlink5, mkdir as mkdir7, appendFile as appendFile2 } from "fs/promises";
31284
31326
  import { dirname as dirname7 } from "path";
31285
31327
  var CRASH_WINDOW_MS = 6e4;
31286
31328
  var DEGRADED_THRESHOLD = 3;
@@ -31290,7 +31332,9 @@ async function readHealthFile(slockHome, serverId) {
31290
31332
  const raw = await readFile6(serverHealthPath(slockHome, serverId), "utf8");
31291
31333
  const parsed = JSON.parse(raw);
31292
31334
  if (parsed && typeof parsed === "object" && Array.isArray(parsed.crashes)) {
31293
- return parsed;
31335
+ const file = parsed;
31336
+ if (typeof file.schemaVersion !== "number") file.schemaVersion = CURRENT_SCHEMA_VERSION;
31337
+ return file;
31294
31338
  }
31295
31339
  } catch {
31296
31340
  }
@@ -31299,7 +31343,8 @@ async function readHealthFile(slockHome, serverId) {
31299
31343
  async function writeHealthFile(slockHome, serverId, file) {
31300
31344
  const path3 = serverHealthPath(slockHome, serverId);
31301
31345
  await mkdir7(dirname7(path3), { recursive: true });
31302
- await writeFile6(path3, JSON.stringify(file), { mode: 384 });
31346
+ const stamped = { ...file, schemaVersion: CURRENT_SCHEMA_VERSION };
31347
+ await writeFile6(path3, JSON.stringify(stamped), { mode: 384 });
31303
31348
  }
31304
31349
  async function recordCrash(slockHome, serverId, exitCode, signal, nowMs = Date.now()) {
31305
31350
  if (!isValidServerId(serverId)) return;
@@ -31348,7 +31393,7 @@ async function markFatalConfig(slockHome, serverId, exitCode, signal, nowMs = Da
31348
31393
  async function resetHealth(slockHome, serverId) {
31349
31394
  if (!isValidServerId(serverId)) return;
31350
31395
  try {
31351
- await unlink4(serverHealthPath(slockHome, serverId));
31396
+ await unlink5(serverHealthPath(slockHome, serverId));
31352
31397
  } catch {
31353
31398
  }
31354
31399
  }
@@ -31517,7 +31562,7 @@ async function waitForManagedDaemonPids(slockHome, serverIds, opts) {
31517
31562
  function buildTimeoutMessage(slockHome, serverIds, ready, input) {
31518
31563
  const missing = serverIds.filter((id) => !ready.has(id));
31519
31564
  const target = input.serverId && missing.length === 1 ? `${input.serverLabel ?? input.serverId}` : `${missing.length} server runner(s): ${missing.join(", ")}`;
31520
- return `Timed out waiting for ${target} to start. Run \`slock-computer status\` and inspect ${serviceLogPath(slockHome)} plus per-server server-runner logs under ~/.slock/computer/servers/<serverId>/server-runner.log.`;
31565
+ return `Timed out waiting for ${target} to start. Run \`slock-computer status\` and inspect ${serviceLogPath(slockHome)} plus per-server runner logs under ~/.slock/computer/servers/<serverId>/runner.log.`;
31521
31566
  }
31522
31567
  async function start(input, options = {}) {
31523
31568
  options.signal?.throwIfAborted?.();
@@ -31837,7 +31882,7 @@ async function detach(input, options = {}) {
31837
31882
  // src/upgradeSea.ts
31838
31883
  init_esm_shims();
31839
31884
  import { createHash as createHash3 } from "crypto";
31840
- import { chmod as chmod4, mkdir as mkdir9, readFile as readFile8, rename as rename2, rm as rm2, writeFile as writeFile8 } from "fs/promises";
31885
+ import { access as access2, chmod as chmod4, mkdir as mkdir9, readFile as readFile8, rename as rename2, rm as rm2, writeFile as writeFile8 } from "fs/promises";
31841
31886
  import { join as join4 } from "path";
31842
31887
 
31843
31888
  // src/channel.ts
@@ -31993,6 +32038,24 @@ async function swapSeaPhase(currentBinaryPath, stagedBinaryPath) {
31993
32038
  }
31994
32039
  return { prevBinaryPath, currentBinaryPath };
31995
32040
  }
32041
+ async function rollbackSeaSwap(swap) {
32042
+ await rm2(swap.currentBinaryPath, { force: true });
32043
+ await rename2(swap.prevBinaryPath, swap.currentBinaryPath);
32044
+ await chmod4(swap.currentBinaryPath, 493);
32045
+ }
32046
+ function seaPrevBinaryPath(currentBinaryPath) {
32047
+ return `${currentBinaryPath}.prev`;
32048
+ }
32049
+ async function rollbackToPrev(currentBinaryPath) {
32050
+ const prevBinaryPath = seaPrevBinaryPath(currentBinaryPath);
32051
+ try {
32052
+ await access2(prevBinaryPath);
32053
+ } catch {
32054
+ throw new Error("UPGRADE_NO_ROLLBACK");
32055
+ }
32056
+ await rollbackSeaSwap({ prevBinaryPath, currentBinaryPath });
32057
+ return { restoredFrom: prevBinaryPath, currentBinaryPath };
32058
+ }
31996
32059
  function pendingUpgradeMarkerPath(slockHome) {
31997
32060
  return join4(slockHome, "upgrade-pending.json");
31998
32061
  }
@@ -32000,7 +32063,8 @@ async function writePendingUpgradeMarker(slockHome, marker) {
32000
32063
  const path3 = pendingUpgradeMarkerPath(slockHome);
32001
32064
  const tmp = `${path3}.tmp`;
32002
32065
  await mkdir9(slockHome, { recursive: true });
32003
- await writeFile8(tmp, `${JSON.stringify(marker, null, 2)}
32066
+ const stamped = { ...marker, schemaVersion: CURRENT_SCHEMA_VERSION };
32067
+ await writeFile8(tmp, `${JSON.stringify(stamped, null, 2)}
32004
32068
  `, "utf8");
32005
32069
  await rename2(tmp, path3);
32006
32070
  }
@@ -32009,7 +32073,8 @@ async function readPendingUpgradeMarker(slockHome) {
32009
32073
  const raw = await readFile8(pendingUpgradeMarkerPath(slockHome), "utf8");
32010
32074
  const parsed = JSON.parse(raw);
32011
32075
  if (typeof parsed.requestId === "string" && typeof parsed.targetVersion === "string" && typeof parsed.fromVersion === "string" && typeof parsed.startedAt === "string") {
32012
- return parsed;
32076
+ const schemaVersion = typeof parsed.schemaVersion === "number" ? parsed.schemaVersion : CURRENT_SCHEMA_VERSION;
32077
+ return { ...parsed, schemaVersion };
32013
32078
  }
32014
32079
  return null;
32015
32080
  } catch {
@@ -32128,7 +32193,7 @@ async function resolveAndRunSeaUpgrade(opts) {
32128
32193
  // src/internal/ipc-server.ts
32129
32194
  init_esm_shims();
32130
32195
  import { connect, createServer } from "net";
32131
- import { chmod as chmod5, mkdir as mkdir10, stat as stat4, unlink as unlink5 } from "fs/promises";
32196
+ import { chmod as chmod5, mkdir as mkdir10, stat as stat4, unlink as unlink6 } from "fs/promises";
32132
32197
  import { dirname as dirname9 } from "path";
32133
32198
 
32134
32199
  // src/internal/ipc-codec.ts
@@ -32239,7 +32304,7 @@ async function probeAndClearStaleSocket(socketPath) {
32239
32304
  return;
32240
32305
  }
32241
32306
  try {
32242
- await unlink5(socketPath);
32307
+ await unlink6(socketPath);
32243
32308
  } catch (err) {
32244
32309
  if (isErrnoException(err) && err.code === "ENOENT") return;
32245
32310
  throw err;
@@ -32505,12 +32570,8 @@ async function buildStatusReport(installRoot) {
32505
32570
  function pad(s, n) {
32506
32571
  return s.length >= n ? s : s + " ".repeat(n - s.length);
32507
32572
  }
32508
- async function runStatus(opts) {
32573
+ async function runStatus() {
32509
32574
  const report = await buildStatusReport(resolveSlockHome());
32510
- if (opts.json) {
32511
- info(JSON.stringify(report, null, 2));
32512
- return;
32513
- }
32514
32575
  info("");
32515
32576
  info(`SLOCK_HOME: ${report.slockHome}`);
32516
32577
  const loginDetail = report.userSessionError ? "no \u2014 user session file is invalid; re-run `slock-computer login`" : report.loggedIn ? `yes (user ${report.userId ?? "?"})` : "no \u2014 run `slock-computer login`";
@@ -32635,6 +32696,7 @@ function isServiceState(value) {
32635
32696
 
32636
32697
  // src/serviceState.ts
32637
32698
  var DEFAULT_STATE = {
32699
+ schemaVersion: CURRENT_SCHEMA_VERSION,
32638
32700
  state: "running",
32639
32701
  crashHistory: []
32640
32702
  };
@@ -32644,9 +32706,10 @@ async function readServiceState(slockHome) {
32644
32706
  const parsed = JSON.parse(raw);
32645
32707
  if (!parsed || typeof parsed !== "object") return { ...DEFAULT_STATE };
32646
32708
  const obj = parsed;
32709
+ const schemaVersion = typeof obj.schemaVersion === "number" ? obj.schemaVersion : CURRENT_SCHEMA_VERSION;
32647
32710
  const state = isServiceState(obj.state) ? obj.state : "running";
32648
32711
  const crashHistory = Array.isArray(obj.crashHistory) ? obj.crashHistory.filter(isCrashEntry) : [];
32649
- return { state, crashHistory };
32712
+ return { schemaVersion, state, crashHistory };
32650
32713
  } catch {
32651
32714
  return { ...DEFAULT_STATE };
32652
32715
  }
@@ -32654,7 +32717,8 @@ async function readServiceState(slockHome) {
32654
32717
  async function writeServiceState(slockHome, file) {
32655
32718
  const path3 = serviceStatePath(slockHome);
32656
32719
  await mkdir11(dirname10(path3), { recursive: true });
32657
- await writeFile9(path3, JSON.stringify(file), { mode: 384 });
32720
+ const stamped = { ...file, schemaVersion: CURRENT_SCHEMA_VERSION };
32721
+ await writeFile9(path3, JSON.stringify(stamped), { mode: 384 });
32658
32722
  }
32659
32723
  function isCrashEntry(value) {
32660
32724
  if (!value || typeof value !== "object") return false;
@@ -32738,7 +32802,7 @@ async function resolveTargetServerId(opts) {
32738
32802
  if (attachments.length === 1) return attachments[0].serverId;
32739
32803
  fail(
32740
32804
  "AMBIGUOUS_SERVER",
32741
- `Multiple servers attached (${attachments.map(attachmentLabel).join(", ")}). Pass \`--server /<serverSlug>\` (e.g. \`--server ${attachmentLabel(attachments[0])}\`) to choose one.`
32805
+ `Multiple servers attached (${attachments.map(attachmentLabel).join(", ")}). Pass the server slug positionally (e.g. \`${attachmentLabel(attachments[0])}\`) to choose one.`
32742
32806
  );
32743
32807
  }
32744
32808
  async function resolveTargetAttachment(opts) {
@@ -33028,17 +33092,13 @@ async function runReset(opts) {
33028
33092
  (client) => client.request("reset-service", void 0),
33029
33093
  () => resetService(slockHome)
33030
33094
  );
33031
- if (opts.json) {
33032
- info(JSON.stringify(result2));
33033
- return;
33034
- }
33035
33095
  info(
33036
33096
  `Reset service crash history (previous state: ${result2.previousState}; cleared ${result2.clearedCrashCount} entr${result2.clearedCrashCount === 1 ? "y" : "ies"}).`
33037
33097
  );
33038
33098
  info("Service state transitioned to `running`. Runners were not touched.");
33039
33099
  return;
33040
33100
  }
33041
- const serverId = await resolveTargetServerId({ server: opts.server });
33101
+ const serverId = await resolveTargetServerId({ server: opts.serverSlug });
33042
33102
  const result = await resetViaServiceOrDisk(
33043
33103
  slockHome,
33044
33104
  (client) => client.request("reset-runner", { serverId }),
@@ -33050,10 +33110,6 @@ async function runReset(opts) {
33050
33110
  `Runner for server ${serverId} was not found.`
33051
33111
  );
33052
33112
  }
33053
- if (opts.json) {
33054
- info(JSON.stringify(result));
33055
- return;
33056
- }
33057
33113
  info(
33058
33114
  `Reset runner crash history for server ${result.serverId} (previous state: ${result.previousState}; cleared ${result.clearedCrashCount} entr${result.clearedCrashCount === 1 ? "y" : "ies"}).`
33059
33115
  );
@@ -33626,7 +33682,7 @@ async function runStart(opts = {}, deps = {}) {
33626
33682
  info(
33627
33683
  `Managing ${sb.managedCount} of ${sb.attachedCount} attached server(s). Logs: ${sb.logPath}`
33628
33684
  );
33629
- info(`Per-server server-runner logs: ~/.slock/computer/servers/<serverId>/server-runner.log`);
33685
+ info(`Per-server runner logs: ~/.slock/computer/servers/<serverId>/runner.log`);
33630
33686
  info(`Check state with \`slock-computer status\`.`);
33631
33687
  }
33632
33688
  }
@@ -33747,6 +33803,8 @@ async function refreshUserSession(slockHome, serverUrl) {
33747
33803
  JSON.stringify(
33748
33804
  {
33749
33805
  kind: "user-session",
33806
+ // Stamp the current on-disk schema version on every write.
33807
+ schemaVersion: CURRENT_SCHEMA_VERSION,
33750
33808
  userId: session.userId,
33751
33809
  accessToken: body.accessToken,
33752
33810
  refreshToken: body.refreshToken,
@@ -34046,7 +34104,7 @@ async function runSetup(opts, deps = {}) {
34046
34104
  serverSlug: opts.serverSlug,
34047
34105
  serverUrl: opts.serverUrl,
34048
34106
  name: opts.name,
34049
- run: false,
34107
+ start: false,
34050
34108
  orchestrated: true
34051
34109
  });
34052
34110
  }
@@ -34089,16 +34147,6 @@ async function runRunnersList(opts) {
34089
34147
  `Could not list runners on ${label} (${block.code}). Check --server-url / server version.`
34090
34148
  );
34091
34149
  }
34092
- if (opts.json) {
34093
- info(
34094
- JSON.stringify(
34095
- { server: label, serverId: block.serverId, whitelist: block.whitelist, runners: block.runners },
34096
- null,
34097
- 2
34098
- )
34099
- );
34100
- return;
34101
- }
34102
34150
  if (block.runners.length === 0) {
34103
34151
  info(`No runners on server ${label}.`);
34104
34152
  return;
@@ -34115,7 +34163,7 @@ async function runRunnersList(opts) {
34115
34163
  }
34116
34164
  async function runRunnersStop(agentId, opts = {}) {
34117
34165
  if (!agentId || agentId.trim().length === 0) {
34118
- fail("AGENT_ID_REQUIRED", "Usage: slock-computer runners stop <agentId> [--server /<serverSlug>]");
34166
+ fail("AGENT_ID_REQUIRED", "Usage: slock-computer runners stop <agentId> [serverSlug]");
34119
34167
  }
34120
34168
  const a = await resolveTargetAttachment({ server: opts.server });
34121
34169
  const client = new RunnersClient(a.serverUrl, a.apiKey);
@@ -34217,14 +34265,6 @@ async function runDoctor(opts) {
34217
34265
  if (opts.serverId) {
34218
34266
  crashes = await readCrashHistory(slockHome, opts.serverId);
34219
34267
  }
34220
- if (opts.json) {
34221
- const payload = { ok: allOk, checks };
34222
- if (cleanupReport) payload.cleanup = cleanupReport;
34223
- if (crashes.length > 0) payload.crashes = crashes;
34224
- info(redactSecrets(JSON.stringify(payload, null, 2)));
34225
- process.exitCode = allOk ? 0 : 1;
34226
- return;
34227
- }
34228
34268
  info("");
34229
34269
  for (const c of checks) {
34230
34270
  info(redactSecrets(`${c.ok ? "\u2713" : "\u2717"} ${c.name.padEnd(48)} ${c.detail}`));
@@ -34240,7 +34280,7 @@ async function runDoctor(opts) {
34240
34280
  info(` - ${c.at}${code}${sig}`);
34241
34281
  }
34242
34282
  info(` \u2192 Once you fix the underlying issue, run`);
34243
- info(` \`slock-computer reset --runner --server ${opts.serverLabel ?? opts.serverId}\` to resume auto-restart.`);
34283
+ info(` \`slock-computer reset --runner ${opts.serverLabel ?? opts.serverId}\` to resume auto-restart.`);
34244
34284
  }
34245
34285
  if (cleanupReport) {
34246
34286
  info("");
@@ -34431,6 +34471,32 @@ async function runUpgradeCli(slockHome, opts, deps = {}) {
34431
34471
  "`slock-computer upgrade` self-upgrades the SEA single-binary; this run is not a SEA binary (npm-installed or source/dev). Re-install the SEA binary to upgrade: `curl -fsSL https://github.com/botiverse/slock/releases/latest/download/install.sh | sh`."
34432
34472
  );
34433
34473
  }
34474
+ if (opts.rollback) {
34475
+ if (opts.targetVersion !== void 0 || opts.channel !== void 0 || opts.dryRun) {
34476
+ fail(
34477
+ "UPGRADE_ROLLBACK_CONFLICT",
34478
+ "`--rollback` cannot be combined with --target-version, --channel, or --dry-run. Rollback restores the previous binary and resolves/downloads nothing."
34479
+ );
34480
+ }
34481
+ const currentBinaryPath2 = (deps.currentBinaryPath ?? (() => process.execPath))();
34482
+ const rollbackFn = deps.rollbackToPrevFn ?? rollbackToPrev;
34483
+ try {
34484
+ const r = await rollbackFn(currentBinaryPath2);
34485
+ info(
34486
+ `Rolled back: restored the previous binary from ${r.restoredFrom}. Restart the Computer service to run the restored binary (the managed supervisor does this automatically).`
34487
+ );
34488
+ } catch (e) {
34489
+ const msg = e instanceof Error ? e.message : String(e);
34490
+ if (/UPGRADE_NO_ROLLBACK/.test(msg)) {
34491
+ fail(
34492
+ "UPGRADE_NO_ROLLBACK",
34493
+ "Nothing to roll back: no previous binary (`<exe>.prev`) exists. A rollback target is only kept after a successful upgrade swap."
34494
+ );
34495
+ }
34496
+ fail("UPGRADE_SWAP_FAILED", `Rollback failed: ${msg}.`);
34497
+ }
34498
+ return;
34499
+ }
34434
34500
  let channel2;
34435
34501
  if (opts.channel !== void 0) {
34436
34502
  const parsed = parseChannel(opts.channel);
@@ -34604,9 +34670,12 @@ program2.name("slock-computer").description("Slock Computer \u2014 local-machine
34604
34670
  program2.command("login").description("Log in via device-code (one user identity per Computer / SLOCK_HOME).").option("--server-url <url>", `Slock API base URL; defaults to SLOCK_SERVER_URL or ${DEFAULT_SLOCK_SERVER_URL}`).action(withCliExit(async (opts) => {
34605
34671
  await runLogin({ serverUrl: opts.serverUrl });
34606
34672
  }));
34607
- program2.command("attach").argument("<serverSlug>", SERVER_SLUG_TARGET_DESC).description("Attach this Computer to one Slock server (add-not-replace; multi-server OK).").option("--server-url <url>", `Slock API base URL; defaults to the saved user session, SLOCK_SERVER_URL, or ${DEFAULT_SLOCK_SERVER_URL}`).option("--name <name>", "Computer display name; defaults to a sanitized hostname").option("--no-run", "only authorize + write local state; do not start the service").option("--foreground", FOREGROUND_DESC).action(withCliExit(async (serverSlug, opts) => {
34673
+ program2.command("logout").description("Log out (clear the saved user session for this Computer); per-server attachments are untouched.").action(withCliExit(async () => {
34674
+ await runLogout();
34675
+ }));
34676
+ program2.command("attach").argument("<serverSlug>", SERVER_SLUG_TARGET_DESC).description("Attach this Computer to one Slock server (add-not-replace; multi-server OK).").option("--server-url <url>", `Slock API base URL; defaults to the saved user session, SLOCK_SERVER_URL, or ${DEFAULT_SLOCK_SERVER_URL}`).option("--name <name>", "Computer display name; defaults to a sanitized hostname").option("--no-start", "only authorize + write local state; do not start the service").option("--foreground", FOREGROUND_DESC).action(withCliExit(async (serverSlug, opts) => {
34608
34677
  await withMutationLock(
34609
- () => runAttach({ serverSlug, serverUrl: opts.serverUrl, name: opts.name, run: opts.run, foreground: opts.foreground })
34678
+ () => runAttach({ serverSlug, serverUrl: opts.serverUrl, name: opts.name, start: opts.start, foreground: opts.foreground })
34610
34679
  );
34611
34680
  }));
34612
34681
  program2.command("setup").argument("<serverSlug>", SERVER_SLUG_TARGET_DESC).description("Set up this Computer for one server: login if needed, attach (or \xA7X.1 migrate-prompt) if needed, then start.").option("--server-url <url>", `Slock API base URL; defaults to the saved user session, SLOCK_SERVER_URL, or ${DEFAULT_SLOCK_SERVER_URL}`).option("--name <name>", "Computer display name for a new attachment; defaults to a sanitized hostname").option("--no-start", "stop after login + attach; do not start the service").option("--foreground", FOREGROUND_DESC).option("-y, --yes", "allow non-interactive setup after confirming the planned actions").action(
@@ -34626,10 +34695,10 @@ program2.command("setup").argument("<serverSlug>", SERVER_SLUG_TARGET_DESC).desc
34626
34695
  program2.command("detach").argument("<serverSlug>", `${SERVER_SLUG_TARGET_DESC} (the server to detach from this Computer)`).description("Remove ONE server's local attachment; never touches user-session or other servers.").action(withCliExit(async (serverSlug) => {
34627
34696
  await withMutationLock(async () => runDetach(await resolveTargetServerId({ server: serverSlug }), serverSlug));
34628
34697
  }));
34629
- program2.command("status").description("Show this Computer's aggregate state (login + service + per-server server-runners).").option("--json", "emit the machine-readable report").action(withCliExit(async (opts) => {
34630
- await runStatus({ json: opts.json });
34698
+ program2.command("status").description("Show this Computer's aggregate state (login + service + per-server runners).").action(withCliExit(async () => {
34699
+ await runStatus();
34631
34700
  }));
34632
- program2.command("start").argument("[serverSlug]", SERVER_SLUG_OPTIONAL_DESC).description("Start/ensure the Computer service (manages all per-server server-runners).").option("--foreground", FOREGROUND_DESC).action(withCliExit(async (serverSlug, opts) => {
34701
+ program2.command("start").argument("[serverSlug]", SERVER_SLUG_OPTIONAL_DESC).description("Start/ensure the Computer service (manages all per-server runners).").option("--foreground", FOREGROUND_DESC).action(withCliExit(async (serverSlug, opts) => {
34633
34702
  await withMutationLock(
34634
34703
  async () => runStart({
34635
34704
  foreground: opts.foreground,
@@ -34638,10 +34707,10 @@ program2.command("start").argument("[serverSlug]", SERVER_SLUG_OPTIONAL_DESC).de
34638
34707
  })
34639
34708
  );
34640
34709
  }));
34641
- program2.command("stop").description("Stop the Computer service (and all managed per-server server-runners).").action(withCliExit(async () => {
34710
+ program2.command("stop").description("Stop the Computer service (and all managed per-server runners).").action(withCliExit(async () => {
34642
34711
  await withMutationLock(() => runStop());
34643
34712
  }));
34644
- program2.command("restart").argument("[serverSlug]", SERVER_SLUG_OPTIONAL_DESC).description("Restart the Computer service (stop + start; all managed per-server server-runners).").option("--foreground", FOREGROUND_DESC).action(withCliExit(async (serverSlug, opts) => {
34713
+ program2.command("restart").argument("[serverSlug]", SERVER_SLUG_OPTIONAL_DESC).description("Restart the Computer service (stop + start; all managed per-server runners).").option("--foreground", FOREGROUND_DESC).action(withCliExit(async (serverSlug, opts) => {
34645
34714
  await withMutationLock(async () => {
34646
34715
  await runStop();
34647
34716
  await runStart({
@@ -34651,12 +34720,11 @@ program2.command("restart").argument("[serverSlug]", SERVER_SLUG_OPTIONAL_DESC).
34651
34720
  });
34652
34721
  });
34653
34722
  }));
34654
- program2.command("doctor").argument("[serverSlug]", `${SERVER_SLUG_OPTIONAL_DESC} (scopes recent-crash detail to that server)`).description("Diagnose login + per-server attachments + per-server preflight (no secrets).").option("--json", "emit the machine-readable report").option("--fix", "after diagnosis, run the local residue cleanup pass").action(
34723
+ program2.command("doctor").argument("[serverSlug]", `${SERVER_SLUG_OPTIONAL_DESC} (scopes recent-crash detail to that server)`).description("Diagnose login + per-server attachments + per-server preflight (no secrets).").option("--fix", "after diagnosis, run the local residue cleanup pass").action(
34655
34724
  withCliExit(
34656
34725
  async (serverSlug, opts) => {
34657
34726
  const serverId = serverSlug ? await resolveTargetServerId({ server: serverSlug }) : void 0;
34658
34727
  await runDoctor({
34659
- json: opts.json,
34660
34728
  cleanup: opts.fix,
34661
34729
  serverId,
34662
34730
  serverLabel: serverSlug
@@ -34664,27 +34732,26 @@ program2.command("doctor").argument("[serverSlug]", `${SERVER_SLUG_OPTIONAL_DESC
34664
34732
  }
34665
34733
  )
34666
34734
  );
34667
- program2.command("reset").description("Clear a degraded state and resume the service's auto-restart loop.").option("--service", "clear the service-level crash history (cascade record)").option("--runner", "clear a runner's crash history (selected by `--server`)").option("--server <slug>", "with `--runner`, select target server slug (required when \u22652 attached)").option("--json", "emit the machine-readable result").action(
34668
- withCliExit(async (opts) => {
34735
+ program2.command("reset").argument("[serverSlug]", `${SERVER_SLUG_OPTIONAL_DESC} (with \`--runner\`, selects the target runner)`).description("Clear a degraded state and resume the service's auto-restart loop.").option("--service", "clear the service-level crash history (cascade record)").option("--runner", "clear a runner's crash history (selected by the [serverSlug] positional)").action(
34736
+ withCliExit(async (serverSlug, opts) => {
34669
34737
  await withMutationLock(
34670
34738
  () => runReset({
34671
34739
  service: opts.service,
34672
34740
  runner: opts.runner,
34673
- server: opts.server,
34674
- json: opts.json
34741
+ serverSlug
34675
34742
  })
34676
34743
  );
34677
34744
  })
34678
34745
  );
34679
- program2.command("logs").argument("[serverSlug]", `${SERVER_SLUG_OPTIONAL_DESC} (required when \u22652 attached; ignored with --service)`).description("Tail one server's server-runner log (or the service log); secrets redacted.").option("--lines <n>", "trailing lines to show (default 200)", (v) => Number.parseInt(v, 10)).option("--service", "tail the global service log instead of a per-server server-runner log").action(withCliExit(async (serverSlug, opts) => {
34746
+ program2.command("logs").argument("[serverSlug]", `${SERVER_SLUG_OPTIONAL_DESC} (required when \u22652 attached; ignored with --service)`).description("Tail one server's runner log (or the service log); secrets redacted.").option("--lines <n>", "trailing lines to show (default 200)", (v) => Number.parseInt(v, 10)).option("--service", "tail the global service log instead of a per-server runner log").action(withCliExit(async (serverSlug, opts) => {
34680
34747
  await runLogs({ lines: opts.lines, server: serverSlug ?? null, service: !!opts.service });
34681
34748
  }));
34682
34749
  var runners = program2.command("runners").description("Computer runner control plane (per-server scoped; \xA712 whitelist server-side).");
34683
- runners.command("list").description("List runners on one attached server.").option("--json", "emit the machine-readable list").option("--server <slug>", "select target server slug (required when \u22652 attached)").action(withCliExit(async (opts) => {
34684
- await runRunnersList({ json: opts.json, server: opts.server ?? null });
34750
+ runners.command("list").argument("[serverSlug]", `${SERVER_SLUG_OPTIONAL_DESC} (required when \u22652 attached)`).description("List runners on one attached server.").action(withCliExit(async (serverSlug) => {
34751
+ await runRunnersList({ server: serverSlug ?? null });
34685
34752
  }));
34686
- runners.command("stop").argument("<agentId>", "id of the runner to stop").option("--server <slug>", "select target server slug (required when \u22652 attached)").description("Stop a runner (server-mediated; reuses the orchestrator's agent stop).").action(withCliExit(async (agentId, opts) => {
34687
- await withMutationLock(() => runRunnersStop(agentId, { server: opts.server ?? null }));
34753
+ runners.command("stop").argument("<agentId>", "id of the runner to stop").argument("[serverSlug]", `${SERVER_SLUG_OPTIONAL_DESC} (required when \u22652 attached)`).description("Stop a runner (server-mediated; reuses the orchestrator's agent stop).").action(withCliExit(async (agentId, serverSlug) => {
34754
+ await withMutationLock(() => runRunnersStop(agentId, { server: serverSlug ?? null }));
34688
34755
  }));
34689
34756
  var channel = program2.command("channel").description("Show or set the Computer release channel (latest | alpha | pinned:<semver>).");
34690
34757
  channel.command("show").description("Show the current Computer release channel (default `latest` when unset).").action(
@@ -34706,12 +34773,15 @@ program2.command("upgrade").description(
34706
34773
  "supervisor restarts on the swapped binary. SEA-only: a non-SEA (npm/dev)",
34707
34774
  "run has no single binary to self-swap and is refused (UPGRADE_SEA_ONLY)."
34708
34775
  ].join("\n")
34709
- ).option("--dry-run", "download + sha256-verify only; do not swap or restart").option("--channel <name>", "override channel for this invocation (latest | alpha | pinned:<semver>)").option("--target-version <semver>", "override target version explicitly (bypasses channel resolution)").action(
34776
+ ).option("--dry-run", "download + sha256-verify only; do not swap or restart").option("--channel <name>", "override channel for this invocation (latest | alpha | pinned:<semver>)").option("--target-version <semver>", "override target version explicitly (bypasses channel resolution)").option(
34777
+ "--rollback",
34778
+ "restore the previous binary (<exe>.prev) from the last successful upgrade; mutually exclusive with --target-version/--channel/--dry-run"
34779
+ ).action(
34710
34780
  withCliExit(
34711
34781
  async (opts) => {
34712
34782
  const slockHome = resolveSlockHome();
34713
34783
  const trigger = resolveUpgradeTrigger(process.env.SLOCK_UPGRADE_TRIGGER);
34714
- if (!opts.dryRun && !opts.channel) {
34784
+ if (!opts.dryRun && !opts.channel && !opts.rollback) {
34715
34785
  let client = null;
34716
34786
  try {
34717
34787
  client = await connectService(slockHome);
@@ -34742,6 +34812,7 @@ program2.command("upgrade").description(
34742
34812
  dryRun: opts.dryRun,
34743
34813
  channel: opts.channel,
34744
34814
  targetVersion: opts.targetVersion,
34815
+ rollback: opts.rollback,
34745
34816
  trigger
34746
34817
  })
34747
34818
  );
package/dist/lib/index.js CHANGED
@@ -333,6 +333,7 @@ import { constants as fsConstants } from "fs";
333
333
  import { createHash } from "crypto";
334
334
  import os from "os";
335
335
  import path from "path";
336
+ var CURRENT_SCHEMA_VERSION = 1;
336
337
  function computerDir(slockHome) {
337
338
  return path.join(slockHome, "computer");
338
339
  }
@@ -362,10 +363,10 @@ function legacyServerAttachmentPath(slockHome, serverId) {
362
363
  return path.join(serverDir(slockHome, serverId), "attachment.json");
363
364
  }
364
365
  function serverRunnerPidPath(slockHome, serverId) {
365
- return path.join(serverDir(slockHome, serverId), "server-runner.pid");
366
+ return path.join(serverDir(slockHome, serverId), "runner.pid");
366
367
  }
367
368
  function serverRunnerLogPath(slockHome, serverId) {
368
- return path.join(serverDir(slockHome, serverId), "server-runner.log");
369
+ return path.join(serverDir(slockHome, serverId), "runner.log");
369
370
  }
370
371
  function serverHealthPath(slockHome, serverId) {
371
372
  return path.join(serverDir(slockHome, serverId), "health.json");
@@ -388,8 +389,10 @@ function parseAttachment(raw) {
388
389
  try {
389
390
  const a = JSON.parse(raw);
390
391
  if (a.kind === "computer-attachment" && typeof a.serverId === "string" && typeof a.serverMachineId === "string" && typeof a.apiKey === "string" && a.apiKey.length > 0 && typeof a.serverUrl === "string") {
392
+ const schemaVersion = typeof a.schemaVersion === "number" ? a.schemaVersion : CURRENT_SCHEMA_VERSION;
391
393
  return {
392
394
  kind: "computer-attachment",
395
+ schemaVersion,
393
396
  serverId: a.serverId,
394
397
  serverSlug: typeof a.serverSlug === "string" && a.serverSlug.length > 0 ? a.serverSlug : void 0,
395
398
  serverMachineId: a.serverMachineId,
@@ -415,12 +418,42 @@ async function readServerAttachment(slockHome, serverId) {
415
418
  if (!isValidServerId(serverId)) return null;
416
419
  const current = await readAttachmentAt(serverAttachmentPath(slockHome, serverId));
417
420
  const legacy = await readAttachmentAt(legacyServerAttachmentPath(slockHome, serverId));
418
- if (!current) return legacy;
419
- if (!legacy) return current;
420
- if (legacy.adoptedFromLegacy === true && current.adoptedFromLegacy !== true && legacy.serverMachineId !== current.serverMachineId) {
421
- return legacy;
421
+ let effective;
422
+ let decidedByLegacy;
423
+ if (!current) {
424
+ effective = legacy;
425
+ decidedByLegacy = legacy !== null;
426
+ } else if (!legacy) {
427
+ effective = current;
428
+ decidedByLegacy = false;
429
+ } else if (
430
+ // Adopted-machine-still-wins: when the legacy attachment is an adopted
431
+ // (working) machine and the current one is a different, not-adopted machine
432
+ // (e.g. a fresh attach that the server rejects), prefer the legacy creds.
433
+ legacy.adoptedFromLegacy === true && current.adoptedFromLegacy !== true && legacy.serverMachineId !== current.serverMachineId
434
+ ) {
435
+ effective = legacy;
436
+ decidedByLegacy = true;
437
+ } else {
438
+ effective = current;
439
+ decidedByLegacy = false;
422
440
  }
423
- return current;
441
+ if (decidedByLegacy && effective) {
442
+ try {
443
+ await writeServerAttachment(slockHome, effective);
444
+ await unlink(legacyServerAttachmentPath(slockHome, serverId));
445
+ } catch {
446
+ }
447
+ }
448
+ return effective;
449
+ }
450
+ async function writeServerAttachment(slockHome, attachment) {
451
+ if (!isValidServerId(attachment.serverId)) return;
452
+ const path2 = serverAttachmentPath(slockHome, attachment.serverId);
453
+ await mkdir(dirname(path2), { recursive: true });
454
+ const stamped = { ...attachment, schemaVersion: CURRENT_SCHEMA_VERSION };
455
+ await writeFile(path2, JSON.stringify(stamped, null, 2), { mode: 384 });
456
+ await chmod(path2, 384);
424
457
  }
425
458
  async function listAttachedServerIds(slockHome) {
426
459
  let entries;
@@ -450,6 +483,9 @@ async function listServerAttachments(slockHome) {
450
483
  import { chmod as chmod2, mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
451
484
  import { dirname as dirname2 } from "path";
452
485
 
486
+ // src/login.ts
487
+ import { unlink as unlink2 } from "fs/promises";
488
+
453
489
  // src/services/login.ts
454
490
  import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
455
491
  import { dirname as dirname3 } from "path";
@@ -485,12 +521,12 @@ import { dirname as dirname10, join as joinPath } from "path";
485
521
  import { fileURLToPath } from "url";
486
522
 
487
523
  // src/cleanup.ts
488
- import { readdir as readdir3, stat as stat3, unlink as unlink3, rm, rmdir, rename, mkdir as mkdir6 } from "fs/promises";
524
+ import { readdir as readdir3, stat as stat3, unlink as unlink4, rm, rmdir, rename, mkdir as mkdir6 } from "fs/promises";
489
525
  import { spawn } from "child_process";
490
526
  import { join as join3 } from "path";
491
527
 
492
528
  // src/internal/process-primitives.ts
493
- import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile5, unlink as unlink2 } from "fs/promises";
529
+ import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile5, unlink as unlink3 } from "fs/promises";
494
530
  import { dirname as dirname5 } from "path";
495
531
  async function readPidfileAt(pidfilePath) {
496
532
  try {
@@ -515,7 +551,7 @@ function isProcessAlive(pid) {
515
551
  var TMP_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
516
552
 
517
553
  // src/health.ts
518
- import { readFile as readFile6, writeFile as writeFile6, unlink as unlink4, mkdir as mkdir7, appendFile as appendFile2 } from "fs/promises";
554
+ import { readFile as readFile6, writeFile as writeFile6, unlink as unlink5, mkdir as mkdir7, appendFile as appendFile2 } from "fs/promises";
519
555
  import { dirname as dirname6 } from "path";
520
556
  var CRASH_WINDOW_MS = 6e4;
521
557
  var DEGRADED_THRESHOLD = 3;
@@ -525,7 +561,9 @@ async function readHealthFile(slockHome, serverId) {
525
561
  const raw = await readFile6(serverHealthPath(slockHome, serverId), "utf8");
526
562
  const parsed = JSON.parse(raw);
527
563
  if (parsed && typeof parsed === "object" && Array.isArray(parsed.crashes)) {
528
- return parsed;
564
+ const file = parsed;
565
+ if (typeof file.schemaVersion !== "number") file.schemaVersion = CURRENT_SCHEMA_VERSION;
566
+ return file;
529
567
  }
530
568
  } catch {
531
569
  }
@@ -582,7 +620,7 @@ import { fetch as fetch3 } from "undici";
582
620
 
583
621
  // src/upgradeSea.ts
584
622
  import { createHash as createHash3 } from "crypto";
585
- import { chmod as chmod4, mkdir as mkdir9, readFile as readFile8, rename as rename2, rm as rm2, writeFile as writeFile8 } from "fs/promises";
623
+ import { access as access2, chmod as chmod4, mkdir as mkdir9, readFile as readFile8, rename as rename2, rm as rm2, writeFile as writeFile8 } from "fs/promises";
586
624
  import { join as join4 } from "path";
587
625
 
588
626
  // src/channel.ts
@@ -591,7 +629,7 @@ import { dirname as dirname7 } from "path";
591
629
 
592
630
  // src/internal/ipc-server.ts
593
631
  import { connect, createServer } from "net";
594
- import { chmod as chmod5, mkdir as mkdir10, stat as stat4, unlink as unlink5 } from "fs/promises";
632
+ import { chmod as chmod5, mkdir as mkdir10, stat as stat4, unlink as unlink6 } from "fs/promises";
595
633
  import { dirname as dirname8 } from "path";
596
634
 
597
635
  // src/internal/ipc-codec.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botiverse/raft-computer",
3
- "version": "0.0.60",
3
+ "version": "0.0.61",
4
4
  "description": "Canonical Raft Computer — standalone human/local-machine control-plane CLI (login + attach). Provides raft-computer plus the legacy slock-computer alias; distinct from the agent-facing @botiverse/raft CLI.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -31,7 +31,7 @@
31
31
  "commander": "^12.1.0",
32
32
  "proper-lockfile": "^4.1.2",
33
33
  "undici": "^7.24.7",
34
- "@botiverse/raft-daemon": "0.62.0"
34
+ "@botiverse/raft-daemon": "0.63.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@types/node": "^25.5.0",