@slock-ai/computer 0.0.3 → 0.0.5

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.
Files changed (2) hide show
  1. package/dist/index.js +237 -64
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -68,15 +68,21 @@ var ComputerAttachClient = class {
68
68
  }
69
69
  /** POST /api/computer/attach — user-authed; issues sk_computer_*. */
70
70
  async attach(serverSlug, name) {
71
- const res = await fetch2(this.url("/api/computer/attach"), {
72
- method: "POST",
73
- headers: {
74
- "Content-Type": "application/json",
75
- Authorization: `Bearer ${this.accessToken}`
76
- },
77
- body: JSON.stringify({ serverSlug, name })
78
- });
71
+ let res;
72
+ try {
73
+ res = await fetch2(this.url("/api/computer/attach"), {
74
+ method: "POST",
75
+ headers: {
76
+ "Content-Type": "application/json",
77
+ Authorization: `Bearer ${this.accessToken}`
78
+ },
79
+ body: JSON.stringify({ serverSlug, name })
80
+ });
81
+ } catch {
82
+ return { status: "error", code: "request_failed" };
83
+ }
79
84
  const body = await res.json().catch(() => null);
85
+ const code = body && typeof body.code === "string" ? body.code : void 0;
80
86
  if (res.status === 201 && body && typeof body.apiKey === "string") {
81
87
  return {
82
88
  status: "success",
@@ -89,9 +95,11 @@ var ComputerAttachClient = class {
89
95
  }
90
96
  if (res.status === 401) return { status: "error", code: "session_invalid" };
91
97
  if (res.status === 403) return { status: "not_authorized" };
92
- if (res.status === 404) return { status: "disabled" };
93
- const code = body && typeof body.code === "string" ? body.code : `http_${res.status}`;
94
- return { status: "error", code };
98
+ if (res.status === 404) {
99
+ if (code === "not_authorized" || code === "server_not_found") return { status: "server_not_found" };
100
+ if (!code || code === "computer_attach_disabled") return { status: "disabled" };
101
+ }
102
+ return { status: "error", code: code ?? `http_${res.status}` };
95
103
  }
96
104
  /**
97
105
  * POST /api/computer/adopt-legacy — task #39 PR-J1 (RFC v8.2 §5.11).
@@ -121,15 +129,18 @@ var ComputerAttachClient = class {
121
129
  resumed: body.resumed === true
122
130
  };
123
131
  }
124
- const code = body && typeof body.code === "string" ? body.code : `http_${res.status}`;
125
- if (res.status === 401 && code === "legacy_key_invalid") {
126
- return { status: "legacy_key_invalid" };
132
+ const code = body && typeof body.code === "string" ? body.code : void 0;
133
+ if (res.status === 401) {
134
+ if (code === "legacy_key_invalid") return { status: "legacy_key_invalid" };
135
+ if (code === "auth_required") return { status: "auth_required" };
136
+ return { status: "unexpected_response", httpStatus: res.status, ...code ? { code } : {} };
127
137
  }
128
138
  if (res.status === 409 && code === "legacy_machine_key_migrated") {
129
139
  return { status: "legacy_machine_key_migrated" };
130
140
  }
131
141
  if (res.status === 403) return { status: "not_authorized" };
132
142
  if (res.status === 404) return { status: "disabled" };
143
+ if (!code) return { status: "unexpected_response", httpStatus: res.status };
133
144
  return { status: "error", code };
134
145
  }
135
146
  /**
@@ -512,6 +523,12 @@ async function runAttach(opts) {
512
523
  "Not authorized to attach to that server. Check the server slug and that you're a member."
513
524
  );
514
525
  }
526
+ if (attached.status === "server_not_found") {
527
+ fail(
528
+ "ATTACH_SERVER_NOT_FOUND",
529
+ `Server ${formatServerSlugDisplay(slugForServer)} was not found on ${baseUrl}. Check the slug spelling and --server-url, then retry.`
530
+ );
531
+ }
515
532
  if (attached.status === "error") {
516
533
  if (attached.code === "session_invalid") {
517
534
  fail(
@@ -519,6 +536,12 @@ async function runAttach(opts) {
519
536
  "Your user session is no longer valid. Re-run `slock-computer login`."
520
537
  );
521
538
  }
539
+ if (attached.code === "request_failed") {
540
+ fail(
541
+ "ATTACH_REQUEST_FAILED",
542
+ `Could not reach ${baseUrl} while attaching to ${formatServerSlugDisplay(slugForServer)}. Check --server-url / network connectivity, then retry.`
543
+ );
544
+ }
522
545
  if (attached.code === "COMPUTER_NAME_COLLISION") {
523
546
  fail(
524
547
  "COMPUTER_NAME_COLLISION",
@@ -688,6 +711,43 @@ async function stopLegacyDaemonByOwnerFile(ownerFile) {
688
711
  }
689
712
  return { attempted: true, pid, outcome: "timed_out" };
690
713
  }
714
+ async function readLegacyOwnerEvidence(ownerFile) {
715
+ let raw;
716
+ try {
717
+ raw = await readFile3(ownerFile, "utf8");
718
+ } catch {
719
+ return { ownerFile, reason: "owner_file_absent" };
720
+ }
721
+ let owner;
722
+ try {
723
+ owner = JSON.parse(raw);
724
+ } catch {
725
+ return { ownerFile, reason: "owner_json_unparseable" };
726
+ }
727
+ const pid = typeof owner.pid === "number" ? owner.pid : void 0;
728
+ return {
729
+ ownerFile,
730
+ pid,
731
+ alive: typeof pid === "number" ? isProcessAlive(pid) : void 0,
732
+ startedAt: typeof owner.startedAt === "string" ? owner.startedAt : void 0,
733
+ serverUrl: typeof owner.serverUrl === "string" ? owner.serverUrl : void 0,
734
+ reason: typeof pid === "number" ? void 0 : "owner_json_missing_pid"
735
+ };
736
+ }
737
+ function formatLegacyOwnerEvidence(slockHome, evidence) {
738
+ const fields = [
739
+ `SLOCK_HOME=${slockHome}`,
740
+ `owner=${evidence.ownerFile}`
741
+ ];
742
+ if (typeof evidence.pid === "number") {
743
+ fields.push(`pid=${evidence.pid}`);
744
+ if (typeof evidence.alive === "boolean") fields.push(`alive=${evidence.alive}`);
745
+ }
746
+ if (evidence.startedAt) fields.push(`startedAt=${evidence.startedAt}`);
747
+ if (evidence.serverUrl) fields.push(`serverUrl=${evidence.serverUrl}`);
748
+ if (evidence.reason) fields.push(`ownerStatus=${evidence.reason}`);
749
+ return fields.join(" ");
750
+ }
691
751
  async function runAdoptLegacy(inputs) {
692
752
  const slockHome = resolveSlockHome();
693
753
  const sessionFile = userSessionPath(slockHome);
@@ -718,6 +778,7 @@ async function runAdoptLegacy(inputs) {
718
778
  const client = new ComputerAttachClient(baseUrl, session.accessToken);
719
779
  const adoptStartedAt = /* @__PURE__ */ new Date();
720
780
  const legacyOwnerFile = legacyLockOwnerPath(slockHome, legacy.rawKey);
781
+ const ownerEvidence = await readLegacyOwnerEvidence(legacyOwnerFile);
721
782
  const result = await client.adoptLegacy(legacy.rawKey, inputs.name);
722
783
  legacy.rawKey = void 0;
723
784
  if (result.status === "disabled") {
@@ -743,7 +804,7 @@ async function runAdoptLegacy(inputs) {
743
804
  });
744
805
  fail(
745
806
  "LEGACY_KEY_INVALID",
746
- "Server rejected the legacy api key (unknown / wrong server / malformed). Verify the key and --server-url."
807
+ `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.`
747
808
  );
748
809
  }
749
810
  if (result.status === "legacy_machine_key_migrated") {
@@ -756,7 +817,20 @@ async function runAdoptLegacy(inputs) {
756
817
  });
757
818
  fail(
758
819
  "LEGACY_MACHINE_KEY_MIGRATED",
759
- "This machine has already been adopted; the legacy key is no longer accepted. Use `slock-computer attach` to add another Computer attachment."
820
+ `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.`
821
+ );
822
+ }
823
+ if (result.status === "auth_required") {
824
+ await appendAdoptionLog(slockHome, {
825
+ mode: legacy.mode,
826
+ redactedPrefix: legacy.redactedPrefix,
827
+ startedAt: adoptStartedAt,
828
+ outcome: "failed",
829
+ failureReason: "auth_required"
830
+ });
831
+ fail(
832
+ "ADOPT_AUTH_REQUIRED",
833
+ `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.`
760
834
  );
761
835
  }
762
836
  if (result.status === "not_authorized") {
@@ -772,6 +846,21 @@ async function runAdoptLegacy(inputs) {
772
846
  "Not authorized to adopt this machine on this server. Check that you are a current member."
773
847
  );
774
848
  }
849
+ if (result.status === "unexpected_response") {
850
+ await appendAdoptionLog(slockHome, {
851
+ mode: legacy.mode,
852
+ redactedPrefix: legacy.redactedPrefix,
853
+ startedAt: adoptStartedAt,
854
+ outcome: "failed",
855
+ failureReason: result.code ? `unexpected_response_${result.code}` : "unexpected_response_missing_code"
856
+ });
857
+ const responseDetail = result.code ? `status ${result.httpStatus}, code ${result.code}` : `status ${result.httpStatus}, missing error code`;
858
+ 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." : "";
859
+ fail(
860
+ "ADOPT_UNEXPECTED_RESPONSE",
861
+ `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.`
862
+ );
863
+ }
775
864
  if (result.status === "error") {
776
865
  await appendAdoptionLog(slockHome, {
777
866
  mode: legacy.mode,
@@ -780,7 +869,10 @@ async function runAdoptLegacy(inputs) {
780
869
  outcome: "failed",
781
870
  failureReason: result.code
782
871
  });
783
- fail("ADOPT_FAILED", `Adoption failed (${result.code}).`);
872
+ fail(
873
+ "ADOPT_FAILED",
874
+ `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.`
875
+ );
784
876
  }
785
877
  info(result.resumed ? "Adopted (resumed prior attachment); running preflight\u2026" : "Adopted; running preflight\u2026");
786
878
  const pre = await client.preflight(result.apiKey);
@@ -900,8 +992,9 @@ async function appendAdoptionLog(slockHome, line) {
900
992
  }
901
993
 
902
994
  // src/setup.ts
903
- import { readdir as readdir3, readFile as readFile6 } from "fs/promises";
904
- import { join as join3 } from "path";
995
+ 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";
996
+ import { dirname as dirname8, join as join3 } from "path";
997
+ import { fetch as fetch3 } from "undici";
905
998
 
906
999
  // src/supervisor.ts
907
1000
  import { spawn as spawn2 } from "child_process";
@@ -1591,14 +1684,74 @@ async function bestEffortServerRevoke(serverUrl, apiKey, serverId) {
1591
1684
  }
1592
1685
 
1593
1686
  // src/setup.ts
1687
+ var USER_SESSION_EXPIRY_LEEWAY_MS = 3e4;
1594
1688
  async function hasValidUserSession(slockHome) {
1595
1689
  try {
1596
1690
  const parsed = JSON.parse(await readFile6(userSessionPath(slockHome), "utf8"));
1597
- return parsed.kind === "user-session" && typeof parsed.accessToken === "string" && parsed.accessToken.length > 0;
1691
+ return parsed.kind === "user-session" && typeof parsed.accessToken === "string" && parsed.accessToken.length > 0 && !isJwtExpired(parsed.accessToken);
1598
1692
  } catch {
1599
1693
  return false;
1600
1694
  }
1601
1695
  }
1696
+ function isJwtExpired(token, nowMs = Date.now()) {
1697
+ const [, payload] = token.split(".");
1698
+ if (!payload) return false;
1699
+ try {
1700
+ const parsed = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
1701
+ return typeof parsed.exp === "number" && parsed.exp * 1e3 <= nowMs + USER_SESSION_EXPIRY_LEEWAY_MS;
1702
+ } catch {
1703
+ return false;
1704
+ }
1705
+ }
1706
+ async function refreshUserSession(slockHome, serverUrl) {
1707
+ const file = userSessionPath(slockHome);
1708
+ let session;
1709
+ try {
1710
+ session = JSON.parse(await readFile6(file, "utf8"));
1711
+ } catch {
1712
+ return false;
1713
+ }
1714
+ if (session.kind !== "user-session" || typeof session.refreshToken !== "string" || session.refreshToken.length === 0) {
1715
+ return false;
1716
+ }
1717
+ const baseUrl = resolveServerUrl(serverUrl, session.serverUrl, process.env.SLOCK_SERVER_URL);
1718
+ const tmpFile = `${file}.${process.pid}.${Date.now()}.tmp`;
1719
+ try {
1720
+ const res = await fetch3(new URL("/api/auth/refresh", baseUrl).toString(), {
1721
+ method: "POST",
1722
+ headers: { "Content-Type": "application/json" },
1723
+ body: JSON.stringify({ refreshToken: session.refreshToken })
1724
+ });
1725
+ const body = await res.json().catch(() => null);
1726
+ if (res.status !== 200 || typeof body?.accessToken !== "string" || typeof body.refreshToken !== "string") {
1727
+ return false;
1728
+ }
1729
+ await mkdir8(dirname8(file), { recursive: true });
1730
+ await writeFile7(
1731
+ tmpFile,
1732
+ JSON.stringify(
1733
+ {
1734
+ kind: "user-session",
1735
+ userId: session.userId,
1736
+ accessToken: body.accessToken,
1737
+ refreshToken: body.refreshToken,
1738
+ serverUrl: baseUrl,
1739
+ createdAt: session.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
1740
+ refreshedAt: (/* @__PURE__ */ new Date()).toISOString()
1741
+ },
1742
+ null,
1743
+ 2
1744
+ ),
1745
+ { mode: 384 }
1746
+ );
1747
+ await chmod4(tmpFile, 384);
1748
+ await rename3(tmpFile, file);
1749
+ return true;
1750
+ } catch {
1751
+ await rm2(tmpFile, { force: true }).catch(() => void 0);
1752
+ return false;
1753
+ }
1754
+ }
1602
1755
  async function hasLiveLegacyDaemon(slockHome) {
1603
1756
  let machineDirs;
1604
1757
  try {
@@ -1630,6 +1783,7 @@ async function runSetup(opts, deps = {}) {
1630
1783
  const attach = deps.runAttach ?? runAttach;
1631
1784
  const adopt = deps.runAdoptLegacy ?? runAdoptLegacy;
1632
1785
  const start = deps.runStart ?? runStart;
1786
+ const refreshSession = deps.refreshUserSession ?? refreshUserSession;
1633
1787
  const legacyDaemonCheck = deps.hasLiveLegacyDaemon ?? hasLiveLegacyDaemon;
1634
1788
  if (!isTty && !opts.yes) {
1635
1789
  fail(
@@ -1646,13 +1800,18 @@ async function runSetup(opts, deps = {}) {
1646
1800
  const label = formatServerSlugDisplay(opts.serverSlug);
1647
1801
  info(`Setting up Slock Computer for ${label}\u2026`);
1648
1802
  if (!await hasValidUserSession(slockHome)) {
1649
- if (!isTty) {
1650
- fail(
1651
- "NON_INTERACTIVE_SETUP_REQUIRES_FLAGS",
1652
- "No user session is available and setup is running non-interactively. Run `slock-computer login` in a terminal first, then re-run setup with --yes."
1653
- );
1803
+ if (await refreshSession(slockHome, opts.serverUrl)) {
1804
+ info("User session: refreshed.");
1805
+ } else {
1806
+ if (!isTty) {
1807
+ fail(
1808
+ "NON_INTERACTIVE_SETUP_REQUIRES_FLAGS",
1809
+ "No valid user session is available and setup is running non-interactively. Run `slock-computer login` in a terminal first, then re-run setup with --yes."
1810
+ );
1811
+ }
1812
+ info("User session: missing or expired; starting login.");
1813
+ await login({ serverUrl: opts.serverUrl });
1654
1814
  }
1655
- await login({ serverUrl: opts.serverUrl });
1656
1815
  } else {
1657
1816
  info("User session: already logged in.");
1658
1817
  }
@@ -1715,11 +1874,22 @@ async function runSetup(opts, deps = {}) {
1715
1874
 
1716
1875
  // src/status.ts
1717
1876
  import { readFile as readFile7 } from "fs/promises";
1718
- async function readJsonSafe(path2) {
1877
+ async function readUserSession(path2) {
1719
1878
  try {
1720
- return JSON.parse(await readFile7(path2, "utf8"));
1721
- } catch {
1722
- return null;
1879
+ const parsed = JSON.parse(await readFile7(path2, "utf8"));
1880
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1881
+ return { state: "present", session: parsed, error: null };
1882
+ }
1883
+ return { state: "invalid", session: null, error: "not a JSON object" };
1884
+ } catch (err) {
1885
+ if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") {
1886
+ return { state: "missing", session: null, error: null };
1887
+ }
1888
+ return {
1889
+ state: "invalid",
1890
+ session: null,
1891
+ error: err instanceof Error ? err.message : String(err)
1892
+ };
1723
1893
  }
1724
1894
  }
1725
1895
  function str(v) {
@@ -1736,7 +1906,8 @@ async function deriveHealth(slockHome, serverId, daemon) {
1736
1906
  }
1737
1907
  async function buildStatusReport() {
1738
1908
  const slockHome = resolveSlockHome();
1739
- const session = await readJsonSafe(userSessionPath(slockHome));
1909
+ const sessionRead = await readUserSession(userSessionPath(slockHome));
1910
+ const session = sessionRead.session;
1740
1911
  const attachments = await listServerAttachments(slockHome);
1741
1912
  const supervisor = {
1742
1913
  ...await pidStatus(supervisorPidPath(slockHome)),
@@ -1762,6 +1933,7 @@ async function buildStatusReport() {
1762
1933
  loggedIn,
1763
1934
  userId: session ? str(session.userId) : null,
1764
1935
  loginServerUrl: session ? str(session.serverUrl) : null,
1936
+ userSessionError: sessionRead.state === "invalid" ? sessionRead.error : null,
1765
1937
  supervisor,
1766
1938
  servers
1767
1939
  };
@@ -1777,8 +1949,9 @@ async function runStatus(opts) {
1777
1949
  }
1778
1950
  info("");
1779
1951
  info(`SLOCK_HOME: ${report.slockHome}`);
1952
+ 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`";
1780
1953
  info(
1781
- `Logged in: ${report.loggedIn ? `yes (user ${report.userId ?? "?"})` : "no \u2014 run `slock-computer login`"}`
1954
+ `Logged in: ${loginDetail}`
1782
1955
  );
1783
1956
  if (report.loginServerUrl) info(`Login server: ${report.loginServerUrl}`);
1784
1957
  info(
@@ -1957,7 +2130,7 @@ async function runDoctorChecks() {
1957
2130
  const checks = [];
1958
2131
  checks.push({ name: "SLOCK_HOME", ok: true, detail: report.slockHome });
1959
2132
  checks.push(
1960
- report.loggedIn ? { name: "user session", ok: true, detail: `logged in (user ${report.userId ?? "?"})` } : { name: "user session", ok: false, detail: "not logged in \u2014 run `slock-computer login`" }
2133
+ report.userSessionError ? { name: "user session", ok: false, detail: "invalid user session file \u2014 re-run `slock-computer login`" } : report.loggedIn ? { name: "user session", ok: true, detail: `logged in (user ${report.userId ?? "?"})` } : { name: "user session", ok: false, detail: "not logged in \u2014 run `slock-computer login`" }
1961
2134
  );
1962
2135
  checks.push(
1963
2136
  report.supervisor.running ? { name: "supervisor", ok: true, detail: `running (pid ${report.supervisor.pid})` } : {
@@ -2100,13 +2273,13 @@ async function runLogs(opts) {
2100
2273
 
2101
2274
  // src/concurrency.ts
2102
2275
  import lockfile from "proper-lockfile";
2103
- import { mkdir as mkdir8 } from "fs/promises";
2276
+ import { mkdir as mkdir9 } from "fs/promises";
2104
2277
  import { join as join4 } from "path";
2105
2278
  var STALE_LOCK_THRESHOLD_MS = 6e4;
2106
2279
  async function withMutationLock(fn) {
2107
2280
  const slockHome = resolveSlockHome();
2108
2281
  const lockTarget = computerDir(slockHome);
2109
- await mkdir8(lockTarget, { recursive: true });
2282
+ await mkdir9(lockTarget, { recursive: true });
2110
2283
  const lockfilePath = join4(lockTarget, ".lock");
2111
2284
  let release = null;
2112
2285
  try {
@@ -2144,8 +2317,8 @@ async function withMutationLock(fn) {
2144
2317
  }
2145
2318
 
2146
2319
  // src/channel.ts
2147
- import { readFile as readFile9, writeFile as writeFile7, mkdir as mkdir9 } from "fs/promises";
2148
- import { dirname as dirname8 } from "path";
2320
+ import { readFile as readFile9, writeFile as writeFile8, mkdir as mkdir10 } from "fs/promises";
2321
+ import { dirname as dirname9 } from "path";
2149
2322
  var DEFAULT_CHANNEL = "latest";
2150
2323
  var SEMVER_RE = /^\d+\.\d+\.\d+(-[\w.]+)?$/;
2151
2324
  function parseChannel(raw) {
@@ -2169,8 +2342,8 @@ async function readChannel(slockHome) {
2169
2342
  }
2170
2343
  async function writeChannel(slockHome, channel2) {
2171
2344
  const p = channelPath(slockHome);
2172
- await mkdir9(dirname8(p), { recursive: true });
2173
- await writeFile7(p, `${channel2}
2345
+ await mkdir10(dirname9(p), { recursive: true });
2346
+ await writeFile8(p, `${channel2}
2174
2347
  `, { mode: 384 });
2175
2348
  }
2176
2349
  async function runChannelShow(slockHome) {
@@ -2194,11 +2367,11 @@ async function runChannelSet(slockHome, raw) {
2194
2367
  // src/upgradeCli.ts
2195
2368
  import { readFile as readFile12 } from "fs/promises";
2196
2369
  import { fileURLToPath as fileURLToPath2 } from "url";
2197
- import { dirname as dirname9, join as join7 } from "path";
2370
+ import { dirname as dirname10, join as join7 } from "path";
2198
2371
 
2199
2372
  // src/upgrade.ts
2200
2373
  import { spawn as spawn4 } from "child_process";
2201
- import { mkdir as mkdir10, readFile as readFile11, writeFile as writeFile8, rm as rm2, rename as rename3 } from "fs/promises";
2374
+ import { mkdir as mkdir11, readFile as readFile11, writeFile as writeFile9, rm as rm3, rename as rename4 } from "fs/promises";
2202
2375
  import { join as join6 } from "path";
2203
2376
  import { createHash as createHash3 } from "crypto";
2204
2377
 
@@ -2476,7 +2649,7 @@ async function stagePhase(slockHome, version, deps = {}) {
2476
2649
  const npmPack = deps.npmPack ?? defaultNpmPack;
2477
2650
  const fsReadFile = deps.fsReadFile ?? readFile11;
2478
2651
  const stagedPath = upgradeStagingDir(slockHome, version);
2479
- await mkdir10(stagedPath, { recursive: true });
2652
+ await mkdir11(stagedPath, { recursive: true });
2480
2653
  const packageRef = `@slock-ai/computer@${version}`;
2481
2654
  const result = await npmPack(stagedPath, packageRef);
2482
2655
  if (result.exitCode !== 0) {
@@ -2577,7 +2750,7 @@ async function defaultFetchAdvertisedHash(version) {
2577
2750
  }
2578
2751
  async function cleanupStaged(slockHome, version) {
2579
2752
  try {
2580
- await rm2(upgradeStagingDir(slockHome, version), { recursive: true, force: true });
2753
+ await rm3(upgradeStagingDir(slockHome, version), { recursive: true, force: true });
2581
2754
  } catch {
2582
2755
  }
2583
2756
  }
@@ -2587,9 +2760,9 @@ function upgradeSnapshotPath(slockHome) {
2587
2760
  async function snapshotPhase(slockHome, snap) {
2588
2761
  const path2 = upgradeSnapshotPath(slockHome);
2589
2762
  const tmp = `${path2}.tmp`;
2590
- await mkdir10(computerDir(slockHome), { recursive: true });
2591
- await writeFile8(tmp, JSON.stringify(snap, null, 2), { mode: 384 });
2592
- await rename3(tmp, path2);
2763
+ await mkdir11(computerDir(slockHome), { recursive: true });
2764
+ await writeFile9(tmp, JSON.stringify(snap, null, 2), { mode: 384 });
2765
+ await rename4(tmp, path2);
2593
2766
  }
2594
2767
  async function clearUpgradeSnapshot(slockHome) {
2595
2768
  try {
@@ -2600,7 +2773,7 @@ async function clearUpgradeSnapshot(slockHome) {
2600
2773
  }
2601
2774
  async function extractTarball(tarballPath, destDir, deps = {}) {
2602
2775
  const tarSpawn = deps.tarSpawn ?? defaultTarSpawn;
2603
- await mkdir10(destDir, { recursive: true });
2776
+ await mkdir11(destDir, { recursive: true });
2604
2777
  const result = await tarSpawn(tarballPath, destDir);
2605
2778
  if (result.exitCode !== 0) {
2606
2779
  const err = new Error(
@@ -2629,8 +2802,8 @@ function defaultTarSpawn(tarballPath, destDir) {
2629
2802
  });
2630
2803
  }
2631
2804
  async function swapPhase(currentBinaryDir, stagedBinaryDir, deps = {}) {
2632
- const fsRename = deps.fsRename ?? rename3;
2633
- const fsRm = deps.fsRm ?? ((p) => rm2(p, { recursive: true, force: true }));
2805
+ const fsRename = deps.fsRename ?? rename4;
2806
+ const fsRm = deps.fsRm ?? ((p) => rm3(p, { recursive: true, force: true }));
2634
2807
  const prevBinaryDir = `${currentBinaryDir}.prev`;
2635
2808
  try {
2636
2809
  await fsRm(prevBinaryDir);
@@ -2652,8 +2825,8 @@ async function swapPhase(currentBinaryDir, stagedBinaryDir, deps = {}) {
2652
2825
  return { prevBinaryDir, newBinaryDir: currentBinaryDir };
2653
2826
  }
2654
2827
  async function rollbackSwap(currentBinaryDir, deps = {}) {
2655
- const fsRename = deps.fsRename ?? rename3;
2656
- const fsRm = deps.fsRm ?? ((p) => rm2(p, { recursive: true, force: true }));
2828
+ const fsRename = deps.fsRename ?? rename4;
2829
+ const fsRm = deps.fsRm ?? ((p) => rm3(p, { recursive: true, force: true }));
2657
2830
  const prevBinaryDir = `${currentBinaryDir}.prev`;
2658
2831
  try {
2659
2832
  await fsRm(currentBinaryDir);
@@ -2871,7 +3044,7 @@ async function cleanupSuccessPhase(slockHome, version, prevBinaryDir) {
2871
3044
  await cleanupStaged(slockHome, version);
2872
3045
  await clearUpgradeSnapshot(slockHome);
2873
3046
  try {
2874
- await rm2(prevBinaryDir, { recursive: true, force: true });
3047
+ await rm3(prevBinaryDir, { recursive: true, force: true });
2875
3048
  } catch {
2876
3049
  }
2877
3050
  }
@@ -3284,7 +3457,7 @@ async function defaultFetchDistTags() {
3284
3457
  }
3285
3458
  function defaultCurrentBinaryDir() {
3286
3459
  const here = fileURLToPath2(import.meta.url);
3287
- return dirname9(dirname9(here));
3460
+ return dirname10(dirname10(here));
3288
3461
  }
3289
3462
  async function defaultCurrentVersion() {
3290
3463
  const pkgPath = join7(defaultCurrentBinaryDir(), "package.json");
@@ -3300,7 +3473,7 @@ async function defaultCurrentVersion() {
3300
3473
  }
3301
3474
 
3302
3475
  // src/upgradeTestHarness.ts
3303
- import { mkdir as mkdir11, readdir as readdir4, stat as stat3, writeFile as writeFile9 } from "fs/promises";
3476
+ import { mkdir as mkdir12, readdir as readdir4, stat as stat3, writeFile as writeFile10 } from "fs/promises";
3304
3477
  import { join as join8 } from "path";
3305
3478
  import { createHash as createHash4 } from "crypto";
3306
3479
  var PHASES = /* @__PURE__ */ new Set([
@@ -3359,7 +3532,7 @@ function buildSimulatedDeps(slockHome, opts) {
3359
3532
  return { tarballPath: "", exitCode: 1, stderr: "simulated stage failure" };
3360
3533
  }
3361
3534
  const filename = `slock-ai-computer-${targetVersion}.tgz`;
3362
- await writeFile9(join8(cwd, filename), tarballBytes);
3535
+ await writeFile10(join8(cwd, filename), tarballBytes);
3363
3536
  return { tarballPath: join8(cwd, filename), exitCode: 0, stderr: "" };
3364
3537
  },
3365
3538
  fetchAdvertisedHash: async () => {
@@ -3371,8 +3544,8 @@ function buildSimulatedDeps(slockHome, opts) {
3371
3544
  if (opts.simulateFail === "extract") {
3372
3545
  return { exitCode: 1, stderr: "simulated extract failure" };
3373
3546
  }
3374
- await mkdir11(join8(destDir, "package"), { recursive: true });
3375
- await writeFile9(join8(destDir, "package", "marker.txt"), `NEW@${targetVersion}`);
3547
+ await mkdir12(join8(destDir, "package"), { recursive: true });
3548
+ await writeFile10(join8(destDir, "package", "marker.txt"), `NEW@${targetVersion}`);
3376
3549
  return { exitCode: 0, stderr: "" };
3377
3550
  },
3378
3551
  fsRename: opts.simulateFail === "swap" ? async () => {
@@ -3432,7 +3605,7 @@ function buildSimulatedDeps(slockHome, opts) {
3432
3605
  }
3433
3606
  async function arrangeSnapshotFailure(slockHome) {
3434
3607
  const snapshotPath = join8(slockHome, "computer", "upgrade-snapshot.json");
3435
- await mkdir11(snapshotPath, { recursive: true });
3608
+ await mkdir12(snapshotPath, { recursive: true });
3436
3609
  }
3437
3610
  async function pathInfo(path2) {
3438
3611
  try {
@@ -3487,12 +3660,12 @@ async function runUpgradeTestHarness(slockHome, opts, writer = (s) => process.st
3487
3660
  process.exitCode = 1;
3488
3661
  return;
3489
3662
  }
3490
- await mkdir11(slockHome, { recursive: true });
3663
+ await mkdir12(slockHome, { recursive: true });
3491
3664
  if (opts.simulateFail === "snapshot") {
3492
3665
  await arrangeSnapshotFailure(slockHome);
3493
3666
  }
3494
3667
  const { opts: upgradeOpts } = buildSimulatedDeps(slockHome, opts);
3495
- await mkdir11(upgradeOpts.currentBinaryDir, { recursive: true });
3668
+ await mkdir12(upgradeOpts.currentBinaryDir, { recursive: true });
3496
3669
  let outcome;
3497
3670
  try {
3498
3671
  outcome = await runUpgrade(slockHome, upgradeOpts);
@@ -3529,9 +3702,9 @@ async function runUpgradeTestHarness(slockHome, opts, writer = (s) => process.st
3529
3702
  }
3530
3703
 
3531
3704
  // src/upgradeInstallSmoke.ts
3532
- import { copyFile, mkdir as mkdir12, readFile as readFile13 } from "fs/promises";
3705
+ import { copyFile, mkdir as mkdir13, readFile as readFile13 } from "fs/promises";
3533
3706
  import { createHash as createHash5 } from "crypto";
3534
- import { dirname as dirname10, isAbsolute, join as join9, resolve as pathResolve } from "path";
3707
+ import { dirname as dirname11, isAbsolute, join as join9, resolve as pathResolve } from "path";
3535
3708
  import { fileURLToPath as fileURLToPath3 } from "url";
3536
3709
  async function runUpgradeInstallSmoke(slockHome, opts, deps = {}) {
3537
3710
  if (typeof opts.packageTarball !== "string" || opts.packageTarball.trim().length === 0) {
@@ -3552,7 +3725,7 @@ async function runUpgradeInstallSmoke(slockHome, opts, deps = {}) {
3552
3725
  const spawnFreshSupervisor = deps.spawnFreshSupervisor ?? (async (h) => {
3553
3726
  await spawnDetachedSupervisor(h);
3554
3727
  });
3555
- await mkdir12(slockHome, { recursive: true });
3728
+ await mkdir13(slockHome, { recursive: true });
3556
3729
  const outcome = await runUpgrade(slockHome, {
3557
3730
  targetVersion: opts.targetVersion,
3558
3731
  fromVersion,
@@ -3605,7 +3778,7 @@ async function runUpgradeInstallSmokeCli(slockHome, opts, writer = (s) => proces
3605
3778
  }
3606
3779
  function defaultCurrentBinaryDirLocal() {
3607
3780
  const here = fileURLToPath3(import.meta.url);
3608
- return dirname10(dirname10(here));
3781
+ return dirname11(dirname11(here));
3609
3782
  }
3610
3783
  async function defaultCurrentVersionLocal() {
3611
3784
  const pkgPath = join9(defaultCurrentBinaryDirLocal(), "package.json");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slock-ai/computer",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "Slock Computer — standalone human/local-machine control-plane CLI (login + attach). Distinct from the agent-facing @slock-ai/cli.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -21,7 +21,7 @@
21
21
  "commander": "^12.1.0",
22
22
  "proper-lockfile": "^4.1.2",
23
23
  "undici": "^7.24.7",
24
- "@slock-ai/daemon": "0.52.2"
24
+ "@slock-ai/daemon": "0.53.0"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/node": "^25.5.0",