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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/lib/index.js CHANGED
@@ -31,73 +31,253 @@ var StateReaderError = class extends Error {
31
31
  this.code = code;
32
32
  }
33
33
  };
34
- var MIGRATION_DETECTION_KINDS = ["match", "no-match", "ambiguous"];
34
+ var MIGRATION_DETECTION_KINDS = ["candidates", "server-unavailable"];
35
35
  function isMigrationDetectionKind(value) {
36
36
  return typeof value === "string" && MIGRATION_DETECTION_KINDS.includes(value);
37
37
  }
38
38
 
39
39
  // src/lib/migration.ts
40
- import { readdir, readFile } from "fs/promises";
41
- import { join } from "path";
40
+ import { readdir, readFile, stat } from "fs/promises";
41
+ import { join, resolve } from "path";
42
42
  var MACHINE_DIR_PREFIX = "machine-";
43
- async function readOwnerEvidence(installRoot, machineDirName) {
44
- const ownerFile = join(
45
- installRoot,
46
- "machines",
47
- machineDirName,
48
- "daemon.lock",
49
- "owner.json"
50
- );
43
+ var FINGERPRINT_HEX_RE = /^[0-9a-f]{16}$/;
44
+ async function readLocalOwners(installRoot) {
45
+ const machinesDir = join(installRoot, "machines");
46
+ let entries;
47
+ try {
48
+ entries = await readdir(machinesDir);
49
+ } catch {
50
+ return [];
51
+ }
52
+ const owners = [];
53
+ for (const name of entries) {
54
+ if (!name.startsWith(MACHINE_DIR_PREFIX)) continue;
55
+ const ownerPath = join(machinesDir, name, "daemon.lock", "owner.json");
56
+ let raw;
57
+ try {
58
+ raw = await readFile(ownerPath, "utf8");
59
+ } catch {
60
+ continue;
61
+ }
62
+ let parsed;
63
+ try {
64
+ parsed = JSON.parse(raw);
65
+ } catch {
66
+ continue;
67
+ }
68
+ const fp = parsed.apiKeyFingerprint;
69
+ if (typeof fp !== "string" || !FINGERPRINT_HEX_RE.test(fp)) continue;
70
+ owners.push({ apiKeyFingerprint: fp, localPath: ownerPath });
71
+ }
72
+ return owners;
73
+ }
74
+ function indexLocalByFingerprint(owners) {
75
+ const map = /* @__PURE__ */ new Map();
76
+ for (const owner of owners) {
77
+ if (!map.has(owner.apiKeyFingerprint)) {
78
+ map.set(owner.apiKeyFingerprint, owner);
79
+ }
80
+ }
81
+ return map;
82
+ }
83
+ function intersect(localByFp, roster) {
84
+ const out = [];
85
+ for (const entry of roster) {
86
+ const local = localByFp.get(entry.apiKeyFingerprint);
87
+ if (!local) continue;
88
+ out.push({
89
+ apiKeyFingerprint: entry.apiKeyFingerprint,
90
+ daemonId: entry.daemonId,
91
+ localPath: local.localPath,
92
+ machineName: entry.machineName,
93
+ ...entry.hostname ? { hostname: entry.hostname } : {},
94
+ ...entry.lastSeenAt ? { lastSeenAt: entry.lastSeenAt } : {}
95
+ });
96
+ }
97
+ out.sort((a, b) => {
98
+ const aSeen = a.lastSeenAt ?? "";
99
+ const bSeen = b.lastSeenAt ?? "";
100
+ if (aSeen !== bSeen) {
101
+ if (aSeen === "") return 1;
102
+ if (bSeen === "") return -1;
103
+ return aSeen < bSeen ? 1 : -1;
104
+ }
105
+ return a.apiKeyFingerprint < b.apiKeyFingerprint ? -1 : a.apiKeyFingerprint > b.apiKeyFingerprint ? 1 : 0;
106
+ });
107
+ return out;
108
+ }
109
+ async function detectLegacyMigration(installRoot, serverSlug, client) {
110
+ const localOwners = await readLocalOwners(installRoot);
111
+ if (localOwners.length === 0) {
112
+ return { kind: "candidates", candidates: [] };
113
+ }
114
+ const result = await client.list(serverSlug);
115
+ if (result.status !== "success") {
116
+ return { kind: "server-unavailable" };
117
+ }
118
+ const localByFp = indexLocalByFingerprint(localOwners);
119
+ return { kind: "candidates", candidates: intersect(localByFp, result.entries) };
120
+ }
121
+ async function validateManualMigratePath(inputPath, roster) {
122
+ const absInput = resolve(inputPath);
123
+ let stats;
124
+ try {
125
+ stats = await stat(absInput);
126
+ } catch {
127
+ return { ok: false, code: "MIGRATE_FROM_NOT_FOUND" };
128
+ }
129
+ const ownerPath = stats.isDirectory() ? join(absInput, "daemon.lock", "owner.json") : absInput;
130
+ const candidateOwnerPath = await firstReadableOwner([
131
+ ownerPath,
132
+ join(absInput, "owner.json"),
133
+ absInput
134
+ ]);
135
+ if (!candidateOwnerPath) {
136
+ return { ok: false, code: "MIGRATE_FROM_INVALID" };
137
+ }
51
138
  let raw;
52
139
  try {
53
- raw = await readFile(ownerFile, "utf8");
140
+ raw = await readFile(candidateOwnerPath, "utf8");
54
141
  } catch {
55
- return {};
142
+ return { ok: false, code: "MIGRATE_FROM_INVALID" };
56
143
  }
57
144
  let parsed;
58
145
  try {
59
146
  parsed = JSON.parse(raw);
60
147
  } catch {
61
- return {};
148
+ return { ok: false, code: "MIGRATE_FROM_INVALID" };
149
+ }
150
+ const fp = parsed.apiKeyFingerprint;
151
+ if (typeof fp !== "string" || !FINGERPRINT_HEX_RE.test(fp)) {
152
+ return { ok: false, code: "MIGRATE_FROM_INVALID" };
153
+ }
154
+ const match = roster.find((r) => r.apiKeyFingerprint === fp);
155
+ if (!match) {
156
+ return { ok: false, code: "MIGRATE_FROM_NOT_OWNED" };
62
157
  }
63
- const machineName = typeof parsed.hostname === "string" && parsed.hostname.length > 0 ? parsed.hostname : void 0;
64
- const serverUrl = typeof parsed.serverUrl === "string" && parsed.serverUrl.length > 0 ? parsed.serverUrl : void 0;
65
- return { machineName, serverUrl };
158
+ return {
159
+ ok: true,
160
+ candidate: {
161
+ apiKeyFingerprint: match.apiKeyFingerprint,
162
+ daemonId: match.daemonId,
163
+ localPath: candidateOwnerPath,
164
+ machineName: match.machineName,
165
+ ...match.hostname ? { hostname: match.hostname } : {},
166
+ ...match.lastSeenAt ? { lastSeenAt: match.lastSeenAt } : {}
167
+ }
168
+ };
66
169
  }
67
- async function detectLegacyMigration(installRoot, loggedInUserId) {
68
- void loggedInUserId;
69
- const machinesDir = join(installRoot, "machines");
70
- let entries;
71
- try {
72
- entries = await readdir(machinesDir);
73
- } catch {
74
- return { kind: "no-match" };
170
+ async function firstReadableOwner(paths) {
171
+ const seen = /* @__PURE__ */ new Set();
172
+ for (const p of paths) {
173
+ if (seen.has(p)) continue;
174
+ seen.add(p);
175
+ try {
176
+ const st = await stat(p);
177
+ if (st.isFile()) return p;
178
+ } catch {
179
+ }
75
180
  }
76
- const candidates = [];
77
- for (const name of entries) {
78
- if (!name.startsWith(MACHINE_DIR_PREFIX)) continue;
79
- const evidence = await readOwnerEvidence(installRoot, name);
80
- candidates.push({
81
- machineId: name,
82
- machineName: evidence.machineName,
83
- serverUrl: evidence.serverUrl
181
+ return null;
182
+ }
183
+
184
+ // src/apiClient.ts
185
+ import { fetch } from "undici";
186
+ var LegacyMachinesClient = class {
187
+ constructor(baseUrl, accessToken) {
188
+ this.baseUrl = baseUrl;
189
+ this.accessToken = accessToken;
190
+ }
191
+ url(p) {
192
+ return new URL(p, this.baseUrl).toString();
193
+ }
194
+ async list(serverSlug) {
195
+ let res;
196
+ try {
197
+ res = await fetch(
198
+ this.url(`/api/computer/legacy-machines?serverSlug=${encodeURIComponent(serverSlug)}`),
199
+ {
200
+ method: "GET",
201
+ headers: { Authorization: `Bearer ${this.accessToken}` }
202
+ }
203
+ );
204
+ } catch {
205
+ return { status: "error", code: "request_failed" };
206
+ }
207
+ const body = await res.json().catch(() => null);
208
+ if (res.status === 200 && body && Array.isArray(body.entries)) {
209
+ const entries = body.entries.map((raw) => {
210
+ if (!raw || typeof raw !== "object") return null;
211
+ const e = raw;
212
+ if (typeof e.daemonId !== "string" || typeof e.apiKeyFingerprint !== "string" || typeof e.machineName !== "string") {
213
+ return null;
214
+ }
215
+ return {
216
+ daemonId: e.daemonId,
217
+ apiKeyFingerprint: e.apiKeyFingerprint,
218
+ machineName: e.machineName,
219
+ hostname: typeof e.hostname === "string" ? e.hostname : null,
220
+ lastSeenAt: typeof e.lastSeenAt === "string" ? e.lastSeenAt : null
221
+ };
222
+ }).filter((entry) => entry !== null);
223
+ return { status: "success", entries };
224
+ }
225
+ if (res.status === 401) return { status: "auth_required" };
226
+ if (res.status === 403) return { status: "not_authorized" };
227
+ if (res.status === 404) return { status: "disabled" };
228
+ const code = body && typeof body.code === "string" ? body.code : `http_${res.status}`;
229
+ return { status: "error", code };
230
+ }
231
+ };
232
+ var RunnersClient = class {
233
+ constructor(baseUrl, computerApiKey) {
234
+ this.baseUrl = baseUrl;
235
+ this.computerApiKey = computerApiKey;
236
+ }
237
+ url(p) {
238
+ return new URL(p, this.baseUrl).toString();
239
+ }
240
+ async list() {
241
+ const res = await fetch(this.url("/internal/computer/runners"), {
242
+ method: "GET",
243
+ headers: { Authorization: `Bearer ${this.computerApiKey}` }
84
244
  });
245
+ const body = await res.json().catch(() => null);
246
+ if (res.status === 200 && body && Array.isArray(body.runners)) {
247
+ return {
248
+ status: "success",
249
+ whitelist: Array.isArray(body.whitelist) ? body.whitelist : [],
250
+ runners: body.runners
251
+ };
252
+ }
253
+ if (res.status === 401 || res.status === 403) return { status: "unauthorized" };
254
+ const code = body && typeof body.code === "string" ? body.code : `http_${res.status}`;
255
+ return { status: "error", code };
85
256
  }
86
- if (candidates.length === 0) return { kind: "no-match" };
87
- if (candidates.length === 1) {
88
- const only = candidates[0];
89
- return {
90
- kind: "match",
91
- machineId: only.machineId,
92
- machineName: only.machineName,
93
- serverUrl: only.serverUrl
94
- };
257
+ async stop(agentId) {
258
+ const res = await fetch(this.url(`/internal/computer/runners/${encodeURIComponent(agentId)}/stop`), {
259
+ method: "POST",
260
+ headers: { Authorization: `Bearer ${this.computerApiKey}`, "Content-Type": "application/json" },
261
+ body: "{}"
262
+ });
263
+ if (res.status === 200) return { status: "success" };
264
+ if (res.status === 404) return { status: "not_found" };
265
+ if (res.status === 401 || res.status === 403) return { status: "unauthorized" };
266
+ const body = await res.json().catch(() => null);
267
+ const code = body && typeof body.code === "string" ? body.code : `http_${res.status}`;
268
+ return { status: "error", code };
95
269
  }
96
- return { kind: "ambiguous", candidates };
97
- }
270
+ };
98
271
 
99
- // src/status.ts
100
- import { readFile as readFile5 } from "fs/promises";
272
+ // src/setup.ts
273
+ import { chmod as chmod4, mkdir as mkdir9, readFile as readFile8, rename as rename3, rm as rm2, writeFile as writeFile8 } from "fs/promises";
274
+ import { dirname as dirname8 } from "path";
275
+ import { fetch as fetch3 } from "undici";
276
+
277
+ // src/serverState.ts
278
+ import { readFile as readFile2, readdir as readdir2, writeFile, mkdir, unlink, access, chmod } from "fs/promises";
279
+ import { dirname } from "path";
280
+ import { constants as fsConstants } from "fs";
101
281
 
102
282
  // src/paths.ts
103
283
  import { createHash } from "crypto";
@@ -128,26 +308,39 @@ function serverDir(slockHome, serverId) {
128
308
  function serverAttachmentPath(slockHome, serverId) {
129
309
  return path.join(serverDir(slockHome, serverId), "runner.state.json");
130
310
  }
131
- function serverDaemonPidPath(slockHome, serverId) {
132
- return path.join(serverDir(slockHome, serverId), "daemon.pid");
311
+ function serverRunnerPidPath(slockHome, serverId) {
312
+ return path.join(serverDir(slockHome, serverId), "server-runner.pid");
133
313
  }
134
- function serverDaemonLogPath(slockHome, serverId) {
135
- return path.join(serverDir(slockHome, serverId), "daemon.log");
314
+ function serverRunnerLogPath(slockHome, serverId) {
315
+ return path.join(serverDir(slockHome, serverId), "server-runner.log");
136
316
  }
137
317
  function serverHealthPath(slockHome, serverId) {
138
318
  return path.join(serverDir(slockHome, serverId), "health.json");
139
319
  }
320
+ function serviceRunDir(slockHome) {
321
+ return path.join(computerDir(slockHome), "run");
322
+ }
140
323
  function servicePidPath(slockHome) {
324
+ return path.join(serviceRunDir(slockHome), "service.pid");
325
+ }
326
+ function legacyServicePidPath(slockHome) {
141
327
  return path.join(computerDir(slockHome), "service.pid");
142
328
  }
329
+ function legacySupervisorPidPath(slockHome) {
330
+ return path.join(computerDir(slockHome), "supervisor.pid");
331
+ }
332
+ function servicePidReadFallback(slockHome) {
333
+ return [
334
+ servicePidPath(slockHome),
335
+ legacyServicePidPath(slockHome),
336
+ legacySupervisorPidPath(slockHome)
337
+ ];
338
+ }
143
339
  function serviceLogPath(slockHome) {
144
- return path.join(computerDir(slockHome), "service.log");
340
+ return path.join(serviceRunDir(slockHome), "service.log");
145
341
  }
146
342
 
147
343
  // src/serverState.ts
148
- import { readFile as readFile2, readdir as readdir2, writeFile, mkdir, unlink, access, chmod } from "fs/promises";
149
- import { dirname } from "path";
150
- import { constants as fsConstants } from "fs";
151
344
  function parseAttachment(raw) {
152
345
  try {
153
346
  const a = JSON.parse(raw);
@@ -198,12 +391,37 @@ async function listServerAttachments(slockHome) {
198
391
  return out;
199
392
  }
200
393
 
201
- // src/internal/process-primitives.ts
202
- import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2, unlink as unlink2 } from "fs/promises";
394
+ // src/services/attach.ts
395
+ import { chmod as chmod2, mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
203
396
  import { dirname as dirname2 } from "path";
397
+
398
+ // src/services/login.ts
399
+ import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
400
+ import { dirname as dirname3 } from "path";
401
+
402
+ // src/services/adoptLegacy.ts
403
+ import { chmod as chmod3, mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4, appendFile, stat as stat2 } from "fs/promises";
404
+ import { createHash as createHash2 } from "crypto";
405
+ import { dirname as dirname4, join as join2 } from "path";
406
+ import { setTimeout as delay } from "timers/promises";
407
+
408
+ // src/service.ts
409
+ import { spawn as spawn2 } from "child_process";
410
+ import { mkdir as mkdir8, readFile as readFile7, writeFile as writeFile7, open, rename as rename2 } from "fs/promises";
411
+ import { dirname as dirname7, join as joinPath } from "path";
412
+ import { fileURLToPath } from "url";
413
+
414
+ // src/cleanup.ts
415
+ import { readdir as readdir3, stat as stat3, unlink as unlink3, rm, rmdir, rename, mkdir as mkdir6 } from "fs/promises";
416
+ import { spawn } from "child_process";
417
+ import { join as join3 } from "path";
418
+
419
+ // src/internal/process-primitives.ts
420
+ import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile5, unlink as unlink2 } from "fs/promises";
421
+ import { dirname as dirname5 } from "path";
204
422
  async function readPidfileAt(pidfilePath) {
205
423
  try {
206
- const raw = (await readFile3(pidfilePath, "utf8")).trim();
424
+ const raw = (await readFile5(pidfilePath, "utf8")).trim();
207
425
  const pid = Number.parseInt(raw, 10);
208
426
  return Number.isInteger(pid) && pid > 0 ? pid : null;
209
427
  } catch {
@@ -220,15 +438,18 @@ function isProcessAlive(pid) {
220
438
  }
221
439
  }
222
440
 
441
+ // src/cleanup.ts
442
+ var TMP_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
443
+
223
444
  // src/health.ts
224
- import { readFile as readFile4, writeFile as writeFile3, unlink as unlink3, mkdir as mkdir3, appendFile } from "fs/promises";
225
- import { dirname as dirname3 } from "path";
445
+ import { readFile as readFile6, writeFile as writeFile6, unlink as unlink4, mkdir as mkdir7, appendFile as appendFile2 } from "fs/promises";
446
+ import { dirname as dirname6 } from "path";
226
447
  var CRASH_WINDOW_MS = 6e4;
227
448
  var DEGRADED_THRESHOLD = 3;
228
449
  async function readHealthFile(slockHome, serverId) {
229
450
  if (!isValidServerId(serverId)) return { crashes: [] };
230
451
  try {
231
- const raw = await readFile4(serverHealthPath(slockHome, serverId), "utf8");
452
+ const raw = await readFile6(serverHealthPath(slockHome, serverId), "utf8");
232
453
  const parsed = JSON.parse(raw);
233
454
  if (parsed && typeof parsed === "object" && Array.isArray(parsed.crashes)) {
234
455
  return parsed;
@@ -248,10 +469,95 @@ async function isDegraded(slockHome, serverId, nowMs = Date.now()) {
248
469
  return recent.length >= DEGRADED_THRESHOLD;
249
470
  }
250
471
 
472
+ // src/internal/service-pid-fallback.ts
473
+ async function findLiveServicePidReadOnly(slockHome, deps = {}) {
474
+ const readPidfile = deps.readPidfile ?? readPidfileAt;
475
+ const isAlive = deps.isProcessAlive ?? isProcessAlive;
476
+ return walkFallback(slockHome, readPidfile, isAlive, async () => void 0);
477
+ }
478
+ async function walkFallback(slockHome, readPidfile, isAlive, clearStale) {
479
+ const candidates = servicePidReadFallback(slockHome);
480
+ let firstStalePidfile = null;
481
+ let firstStalePid = null;
482
+ for (const candidate of candidates) {
483
+ const candidatePid = await readPidfile(candidate);
484
+ if (candidatePid === null) continue;
485
+ if (isAlive(candidatePid)) {
486
+ return {
487
+ pid: candidatePid,
488
+ pidfilePath: candidate,
489
+ firstStalePidfile,
490
+ firstStalePid
491
+ };
492
+ }
493
+ await clearStale(candidate);
494
+ if (firstStalePidfile === null) {
495
+ firstStalePidfile = candidate;
496
+ firstStalePid = candidatePid;
497
+ }
498
+ }
499
+ return {
500
+ pid: null,
501
+ pidfilePath: candidates[0],
502
+ firstStalePidfile,
503
+ firstStalePid
504
+ };
505
+ }
506
+
507
+ // src/services/detach.ts
508
+ import { fetch as fetch2 } from "undici";
509
+
510
+ // src/setup.ts
511
+ var MIGRATION_FRESH_TRIGGERS = [
512
+ "empty-intersection",
513
+ "explicit-zero",
514
+ "eof",
515
+ "non-tty",
516
+ "server-unavailable"
517
+ ];
518
+ async function pickMigrationCandidateFromInput(candidates, read, write) {
519
+ write(
520
+ "Migration: detected legacy daemon(s) on this Computer that match the target server.\n"
521
+ );
522
+ candidates.forEach((c, i) => {
523
+ const host = c.hostname ? ` (${c.hostname})` : "";
524
+ const seen = c.lastSeenAt ? ` last seen ${c.lastSeenAt}` : "";
525
+ write(` ${i + 1}. ${c.machineName}${host}${seen}
526
+ `);
527
+ write(` local: ${c.localPath}
528
+ `);
529
+ });
530
+ write(" 0. Fresh attach (skip migration)\n");
531
+ write(" m. Migrate from a different on-disk path\n");
532
+ while (true) {
533
+ write("Choose [1]: ");
534
+ const { line, eof } = await read();
535
+ if (eof) return { kind: "fresh" };
536
+ const trimmed = line.trim();
537
+ const norm = trimmed.toLowerCase();
538
+ if (norm === "m") {
539
+ write("Path to legacy machine dir or owner.json: ");
540
+ const { line: pathLine } = await read();
541
+ return { kind: "manual", path: pathLine.trim() };
542
+ }
543
+ if (norm.length === 0) return { kind: "candidate", index: 0 };
544
+ if (norm === "0") return { kind: "fresh" };
545
+ const n = Number.parseInt(norm, 10);
546
+ if (Number.isFinite(n) && String(n) === norm && n >= 1 && n <= candidates.length) {
547
+ return { kind: "candidate", index: n - 1 };
548
+ }
549
+ write(
550
+ `Invalid selection "${trimmed}". Enter 1..${candidates.length}, 0, or m.
551
+ `
552
+ );
553
+ }
554
+ }
555
+
251
556
  // src/status.ts
557
+ import { readFile as readFile9 } from "fs/promises";
252
558
  async function readUserSession(path2) {
253
559
  try {
254
- const parsed = JSON.parse(await readFile5(path2, "utf8"));
560
+ const parsed = JSON.parse(await readFile9(path2, "utf8"));
255
561
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
256
562
  return { state: "present", session: parsed, error: null };
257
563
  }
@@ -274,6 +580,10 @@ async function pidStatus(pidfile) {
274
580
  const pid = await readPidfileAt(pidfile);
275
581
  return pid !== null && isProcessAlive(pid) ? { running: true, pid } : { running: false };
276
582
  }
583
+ async function serviceState(slockHome) {
584
+ const { pid } = await findLiveServicePidReadOnly(slockHome);
585
+ return pid !== null ? { running: true, pid } : { running: false };
586
+ }
277
587
  async function deriveHealth(slockHome, serverId, daemon) {
278
588
  if (!daemon.running) return "offline";
279
589
  if (await isDegraded(slockHome, serverId)) return "degraded";
@@ -284,19 +594,19 @@ async function buildStatusReport(installRoot) {
284
594
  const session = sessionRead.session;
285
595
  const attachments = await listServerAttachments(installRoot);
286
596
  const service = {
287
- ...await pidStatus(servicePidPath(installRoot)),
597
+ ...await serviceState(installRoot),
288
598
  logPath: serviceLogPath(installRoot)
289
599
  };
290
600
  const servers = [];
291
601
  for (const a of attachments) {
292
- const daemon = await pidStatus(serverDaemonPidPath(installRoot, a.serverId));
602
+ const daemon = await pidStatus(serverRunnerPidPath(installRoot, a.serverId));
293
603
  servers.push({
294
604
  serverId: a.serverId,
295
605
  serverSlug: a.serverSlug ?? null,
296
606
  serverMachineId: a.serverMachineId,
297
607
  serverUrl: a.serverUrl,
298
608
  attachedAt: a.attachedAt ?? null,
299
- daemonLogPath: serverDaemonLogPath(installRoot, a.serverId),
609
+ serverRunnerLogPath: serverRunnerLogPath(installRoot, a.serverId),
300
610
  daemon,
301
611
  health: await deriveHealth(installRoot, a.serverId, daemon)
302
612
  });
@@ -313,48 +623,6 @@ async function buildStatusReport(installRoot) {
313
623
  };
314
624
  }
315
625
 
316
- // src/apiClient.ts
317
- import { fetch } from "undici";
318
- var RunnersClient = class {
319
- constructor(baseUrl, computerApiKey) {
320
- this.baseUrl = baseUrl;
321
- this.computerApiKey = computerApiKey;
322
- }
323
- url(p) {
324
- return new URL(p, this.baseUrl).toString();
325
- }
326
- async list() {
327
- const res = await fetch(this.url("/internal/computer/runners"), {
328
- method: "GET",
329
- headers: { Authorization: `Bearer ${this.computerApiKey}` }
330
- });
331
- const body = await res.json().catch(() => null);
332
- if (res.status === 200 && body && Array.isArray(body.runners)) {
333
- return {
334
- status: "success",
335
- whitelist: Array.isArray(body.whitelist) ? body.whitelist : [],
336
- runners: body.runners
337
- };
338
- }
339
- if (res.status === 401 || res.status === 403) return { status: "unauthorized" };
340
- const code = body && typeof body.code === "string" ? body.code : `http_${res.status}`;
341
- return { status: "error", code };
342
- }
343
- async stop(agentId) {
344
- const res = await fetch(this.url(`/internal/computer/runners/${encodeURIComponent(agentId)}/stop`), {
345
- method: "POST",
346
- headers: { Authorization: `Bearer ${this.computerApiKey}`, "Content-Type": "application/json" },
347
- body: "{}"
348
- });
349
- if (res.status === 200) return { status: "success" };
350
- if (res.status === 404) return { status: "not_found" };
351
- if (res.status === 401 || res.status === 403) return { status: "unauthorized" };
352
- const body = await res.json().catch(() => null);
353
- const code = body && typeof body.code === "string" ? body.code : `http_${res.status}`;
354
- return { status: "error", code };
355
- }
356
- };
357
-
358
626
  // src/lib/readers.ts
359
627
  async function readServiceStatus(installRoot) {
360
628
  return buildStatusReport(installRoot);
@@ -442,7 +710,7 @@ function isServiceState(value) {
442
710
  }
443
711
 
444
712
  // src/upgradeLog.ts
445
- import { chmod as chmod2, mkdir as mkdir4, open } from "fs/promises";
713
+ import { chmod as chmod5, mkdir as mkdir10, open as open2 } from "fs/promises";
446
714
  var UPGRADE_ERROR_CODES = [
447
715
  "UPGRADE_DEPS_CHANGED",
448
716
  "UPGRADE_NETWORK_FAILED",
@@ -486,7 +754,9 @@ function assertUpgradeLogEntry(entry) {
486
754
  }
487
755
  export {
488
756
  IPC_ERROR_CODES,
757
+ LegacyMachinesClient,
489
758
  MIGRATION_DETECTION_KINDS,
759
+ MIGRATION_FRESH_TRIGGERS,
490
760
  RUNNER_STATE_VALUES,
491
761
  SERVICE_STATE_VALUES,
492
762
  STATE_READER_ERROR_CODES,
@@ -500,6 +770,8 @@ export {
500
770
  isRunnerState,
501
771
  isServiceState,
502
772
  listRunners,
773
+ pickMigrationCandidateFromInput,
503
774
  readRunnerStatus,
504
- readServiceStatus
775
+ readServiceStatus,
776
+ validateManualMigratePath
505
777
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slock-ai/computer",
3
- "version": "0.0.16-alpha.0",
3
+ "version": "0.0.17",
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": {
@@ -28,7 +28,7 @@
28
28
  "commander": "^12.1.0",
29
29
  "proper-lockfile": "^4.1.2",
30
30
  "undici": "^7.24.7",
31
- "@slock-ai/daemon": "0.55.2-alpha.0"
31
+ "@slock-ai/daemon": "0.55.1"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@types/node": "^25.5.0",