@slock-ai/computer 0.0.16 → 0.0.18

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" };
62
153
  }
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 };
154
+ const match = roster.find((r) => r.apiKeyFingerprint === fp);
155
+ if (!match) {
156
+ return { ok: false, code: "MIGRATE_FROM_NOT_OWNED" };
157
+ }
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,6 +308,9 @@ function serverDir(slockHome, serverId) {
128
308
  function serverAttachmentPath(slockHome, serverId) {
129
309
  return path.join(serverDir(slockHome, serverId), "runner.state.json");
130
310
  }
311
+ function legacyServerAttachmentPath(slockHome, serverId) {
312
+ return path.join(serverDir(slockHome, serverId), "attachment.json");
313
+ }
131
314
  function serverRunnerPidPath(slockHome, serverId) {
132
315
  return path.join(serverDir(slockHome, serverId), "server-runner.pid");
133
316
  }
@@ -161,9 +344,6 @@ function serviceLogPath(slockHome) {
161
344
  }
162
345
 
163
346
  // src/serverState.ts
164
- import { readFile as readFile2, readdir as readdir2, writeFile, mkdir, unlink, access, chmod } from "fs/promises";
165
- import { dirname } from "path";
166
- import { constants as fsConstants } from "fs";
167
347
  function parseAttachment(raw) {
168
348
  try {
169
349
  const a = JSON.parse(raw);
@@ -175,21 +355,33 @@ function parseAttachment(raw) {
175
355
  serverMachineId: a.serverMachineId,
176
356
  apiKey: a.apiKey,
177
357
  serverUrl: a.serverUrl,
178
- attachedAt: typeof a.attachedAt === "string" ? a.attachedAt : void 0
358
+ attachedAt: typeof a.attachedAt === "string" ? a.attachedAt : void 0,
359
+ adoptedFromLegacy: a.adoptedFromLegacy === true ? true : void 0,
360
+ legacyMachineId: typeof a.legacyMachineId === "string" ? a.legacyMachineId : void 0
179
361
  };
180
362
  }
181
363
  } catch {
182
364
  }
183
365
  return null;
184
366
  }
185
- async function readServerAttachment(slockHome, serverId) {
186
- if (!isValidServerId(serverId)) return null;
367
+ async function readAttachmentAt(path2) {
187
368
  try {
188
- return parseAttachment(await readFile2(serverAttachmentPath(slockHome, serverId), "utf8"));
369
+ return parseAttachment(await readFile2(path2, "utf8"));
189
370
  } catch {
190
371
  return null;
191
372
  }
192
373
  }
374
+ async function readServerAttachment(slockHome, serverId) {
375
+ if (!isValidServerId(serverId)) return null;
376
+ const current = await readAttachmentAt(serverAttachmentPath(slockHome, serverId));
377
+ const legacy = await readAttachmentAt(legacyServerAttachmentPath(slockHome, serverId));
378
+ if (!current) return legacy;
379
+ if (!legacy) return current;
380
+ if (legacy.adoptedFromLegacy === true && current.adoptedFromLegacy !== true && legacy.serverMachineId !== current.serverMachineId) {
381
+ return legacy;
382
+ }
383
+ return current;
384
+ }
193
385
  async function listAttachedServerIds(slockHome) {
194
386
  let entries;
195
387
  try {
@@ -214,12 +406,37 @@ async function listServerAttachments(slockHome) {
214
406
  return out;
215
407
  }
216
408
 
217
- // src/internal/process-primitives.ts
218
- import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2, unlink as unlink2 } from "fs/promises";
409
+ // src/services/attach.ts
410
+ import { chmod as chmod2, mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
219
411
  import { dirname as dirname2 } from "path";
412
+
413
+ // src/services/login.ts
414
+ import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
415
+ import { dirname as dirname3 } from "path";
416
+
417
+ // src/services/adoptLegacy.ts
418
+ import { chmod as chmod3, mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4, appendFile, stat as stat2 } from "fs/promises";
419
+ import { createHash as createHash2 } from "crypto";
420
+ import { dirname as dirname4, join as join2 } from "path";
421
+ import { setTimeout as delay } from "timers/promises";
422
+
423
+ // src/service.ts
424
+ import { spawn as spawn2 } from "child_process";
425
+ import { mkdir as mkdir8, readFile as readFile7, writeFile as writeFile7, open, rename as rename2 } from "fs/promises";
426
+ import { dirname as dirname7, join as joinPath } from "path";
427
+ import { fileURLToPath } from "url";
428
+
429
+ // src/cleanup.ts
430
+ import { readdir as readdir3, stat as stat3, unlink as unlink3, rm, rmdir, rename, mkdir as mkdir6 } from "fs/promises";
431
+ import { spawn } from "child_process";
432
+ import { join as join3 } from "path";
433
+
434
+ // src/internal/process-primitives.ts
435
+ import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile5, unlink as unlink2 } from "fs/promises";
436
+ import { dirname as dirname5 } from "path";
220
437
  async function readPidfileAt(pidfilePath) {
221
438
  try {
222
- const raw = (await readFile3(pidfilePath, "utf8")).trim();
439
+ const raw = (await readFile5(pidfilePath, "utf8")).trim();
223
440
  const pid = Number.parseInt(raw, 10);
224
441
  return Number.isInteger(pid) && pid > 0 ? pid : null;
225
442
  } catch {
@@ -236,6 +453,37 @@ function isProcessAlive(pid) {
236
453
  }
237
454
  }
238
455
 
456
+ // src/cleanup.ts
457
+ var TMP_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
458
+
459
+ // src/health.ts
460
+ import { readFile as readFile6, writeFile as writeFile6, unlink as unlink4, mkdir as mkdir7, appendFile as appendFile2 } from "fs/promises";
461
+ import { dirname as dirname6 } from "path";
462
+ var CRASH_WINDOW_MS = 6e4;
463
+ var DEGRADED_THRESHOLD = 3;
464
+ async function readHealthFile(slockHome, serverId) {
465
+ if (!isValidServerId(serverId)) return { crashes: [] };
466
+ try {
467
+ const raw = await readFile6(serverHealthPath(slockHome, serverId), "utf8");
468
+ const parsed = JSON.parse(raw);
469
+ if (parsed && typeof parsed === "object" && Array.isArray(parsed.crashes)) {
470
+ return parsed;
471
+ }
472
+ } catch {
473
+ }
474
+ return { crashes: [] };
475
+ }
476
+ async function isDegraded(slockHome, serverId, nowMs = Date.now()) {
477
+ const file = await readHealthFile(slockHome, serverId);
478
+ if (file.fatalConfig) return true;
479
+ const cutoffMs = nowMs - CRASH_WINDOW_MS;
480
+ const recent = file.crashes.filter((c) => {
481
+ const t = new Date(c.at).getTime();
482
+ return Number.isFinite(t) && t >= cutoffMs;
483
+ });
484
+ return recent.length >= DEGRADED_THRESHOLD;
485
+ }
486
+
239
487
  // src/internal/service-pid-fallback.ts
240
488
  async function findLiveServicePidReadOnly(slockHome, deps = {}) {
241
489
  const readPidfile = deps.readPidfile ?? readPidfileAt;
@@ -271,38 +519,60 @@ async function walkFallback(slockHome, readPidfile, isAlive, clearStale) {
271
519
  };
272
520
  }
273
521
 
274
- // src/health.ts
275
- import { readFile as readFile4, writeFile as writeFile3, unlink as unlink3, mkdir as mkdir3, appendFile } from "fs/promises";
276
- import { dirname as dirname3 } from "path";
277
- var CRASH_WINDOW_MS = 6e4;
278
- var DEGRADED_THRESHOLD = 3;
279
- async function readHealthFile(slockHome, serverId) {
280
- if (!isValidServerId(serverId)) return { crashes: [] };
281
- try {
282
- const raw = await readFile4(serverHealthPath(slockHome, serverId), "utf8");
283
- const parsed = JSON.parse(raw);
284
- if (parsed && typeof parsed === "object" && Array.isArray(parsed.crashes)) {
285
- return parsed;
522
+ // src/services/detach.ts
523
+ import { fetch as fetch2 } from "undici";
524
+
525
+ // src/setup.ts
526
+ var MIGRATION_FRESH_TRIGGERS = [
527
+ "empty-intersection",
528
+ "explicit-zero",
529
+ "eof",
530
+ "non-tty",
531
+ "server-unavailable"
532
+ ];
533
+ async function pickMigrationCandidateFromInput(candidates, read, write) {
534
+ write(
535
+ "Migration: detected legacy daemon(s) on this Computer that match the target server.\n"
536
+ );
537
+ candidates.forEach((c, i) => {
538
+ const host = c.hostname ? ` (${c.hostname})` : "";
539
+ const seen = c.lastSeenAt ? ` last seen ${c.lastSeenAt}` : "";
540
+ write(` ${i + 1}. ${c.machineName}${host}${seen}
541
+ `);
542
+ write(` local: ${c.localPath}
543
+ `);
544
+ });
545
+ write(" 0. Fresh attach (skip migration)\n");
546
+ write(" m. Migrate from a different on-disk path\n");
547
+ while (true) {
548
+ write("Choose [1]: ");
549
+ const { line, eof } = await read();
550
+ if (eof) return { kind: "fresh" };
551
+ const trimmed = line.trim();
552
+ const norm = trimmed.toLowerCase();
553
+ if (norm === "m") {
554
+ write("Path to legacy machine dir or owner.json: ");
555
+ const { line: pathLine } = await read();
556
+ return { kind: "manual", path: pathLine.trim() };
286
557
  }
287
- } catch {
558
+ if (norm.length === 0) return { kind: "candidate", index: 0 };
559
+ if (norm === "0") return { kind: "fresh" };
560
+ const n = Number.parseInt(norm, 10);
561
+ if (Number.isFinite(n) && String(n) === norm && n >= 1 && n <= candidates.length) {
562
+ return { kind: "candidate", index: n - 1 };
563
+ }
564
+ write(
565
+ `Invalid selection "${trimmed}". Enter 1..${candidates.length}, 0, or m.
566
+ `
567
+ );
288
568
  }
289
- return { crashes: [] };
290
- }
291
- async function isDegraded(slockHome, serverId, nowMs = Date.now()) {
292
- const file = await readHealthFile(slockHome, serverId);
293
- if (file.fatalConfig) return true;
294
- const cutoffMs = nowMs - CRASH_WINDOW_MS;
295
- const recent = file.crashes.filter((c) => {
296
- const t = new Date(c.at).getTime();
297
- return Number.isFinite(t) && t >= cutoffMs;
298
- });
299
- return recent.length >= DEGRADED_THRESHOLD;
300
569
  }
301
570
 
302
571
  // src/status.ts
572
+ import { readFile as readFile9 } from "fs/promises";
303
573
  async function readUserSession(path2) {
304
574
  try {
305
- const parsed = JSON.parse(await readFile5(path2, "utf8"));
575
+ const parsed = JSON.parse(await readFile9(path2, "utf8"));
306
576
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
307
577
  return { state: "present", session: parsed, error: null };
308
578
  }
@@ -368,48 +638,6 @@ async function buildStatusReport(installRoot) {
368
638
  };
369
639
  }
370
640
 
371
- // src/apiClient.ts
372
- import { fetch } from "undici";
373
- var RunnersClient = class {
374
- constructor(baseUrl, computerApiKey) {
375
- this.baseUrl = baseUrl;
376
- this.computerApiKey = computerApiKey;
377
- }
378
- url(p) {
379
- return new URL(p, this.baseUrl).toString();
380
- }
381
- async list() {
382
- const res = await fetch(this.url("/internal/computer/runners"), {
383
- method: "GET",
384
- headers: { Authorization: `Bearer ${this.computerApiKey}` }
385
- });
386
- const body = await res.json().catch(() => null);
387
- if (res.status === 200 && body && Array.isArray(body.runners)) {
388
- return {
389
- status: "success",
390
- whitelist: Array.isArray(body.whitelist) ? body.whitelist : [],
391
- runners: body.runners
392
- };
393
- }
394
- if (res.status === 401 || res.status === 403) return { status: "unauthorized" };
395
- const code = body && typeof body.code === "string" ? body.code : `http_${res.status}`;
396
- return { status: "error", code };
397
- }
398
- async stop(agentId) {
399
- const res = await fetch(this.url(`/internal/computer/runners/${encodeURIComponent(agentId)}/stop`), {
400
- method: "POST",
401
- headers: { Authorization: `Bearer ${this.computerApiKey}`, "Content-Type": "application/json" },
402
- body: "{}"
403
- });
404
- if (res.status === 200) return { status: "success" };
405
- if (res.status === 404) return { status: "not_found" };
406
- if (res.status === 401 || res.status === 403) return { status: "unauthorized" };
407
- const body = await res.json().catch(() => null);
408
- const code = body && typeof body.code === "string" ? body.code : `http_${res.status}`;
409
- return { status: "error", code };
410
- }
411
- };
412
-
413
641
  // src/lib/readers.ts
414
642
  async function readServiceStatus(installRoot) {
415
643
  return buildStatusReport(installRoot);
@@ -497,7 +725,7 @@ function isServiceState(value) {
497
725
  }
498
726
 
499
727
  // src/upgradeLog.ts
500
- import { chmod as chmod2, mkdir as mkdir4, open } from "fs/promises";
728
+ import { chmod as chmod5, mkdir as mkdir10, open as open2 } from "fs/promises";
501
729
  var UPGRADE_ERROR_CODES = [
502
730
  "UPGRADE_DEPS_CHANGED",
503
731
  "UPGRADE_NETWORK_FAILED",
@@ -541,7 +769,9 @@ function assertUpgradeLogEntry(entry) {
541
769
  }
542
770
  export {
543
771
  IPC_ERROR_CODES,
772
+ LegacyMachinesClient,
544
773
  MIGRATION_DETECTION_KINDS,
774
+ MIGRATION_FRESH_TRIGGERS,
545
775
  RUNNER_STATE_VALUES,
546
776
  SERVICE_STATE_VALUES,
547
777
  STATE_READER_ERROR_CODES,
@@ -555,6 +785,8 @@ export {
555
785
  isRunnerState,
556
786
  isServiceState,
557
787
  listRunners,
788
+ pickMigrationCandidateFromInput,
558
789
  readRunnerStatus,
559
- readServiceStatus
790
+ readServiceStatus,
791
+ validateManualMigratePath
560
792
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slock-ai/computer",
3
- "version": "0.0.16",
3
+ "version": "0.0.18",
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": {