@slock-ai/computer 0.0.7 → 0.0.9
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 +218 -27
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -314,9 +314,16 @@ function formatUpgradeLogTimestamp(date = /* @__PURE__ */ new Date()) {
|
|
|
314
314
|
|
|
315
315
|
// src/output.ts
|
|
316
316
|
var CliExit = class extends Error {
|
|
317
|
-
|
|
318
|
-
|
|
317
|
+
/**
|
|
318
|
+
* v8.3.3 PR-2c — carry the closed-set / stderr token through the thrown
|
|
319
|
+
* error so callers can pattern-match without parsing stderr. Optional
|
|
320
|
+
* because some callsites throw `new CliExit(code)` directly without a
|
|
321
|
+
* named token (e.g. the harness EX_CONFIG paths).
|
|
322
|
+
*/
|
|
323
|
+
constructor(exitCode, code) {
|
|
324
|
+
super(`CliExit(${exitCode}${code ? ` ${code}` : ""})`);
|
|
319
325
|
this.exitCode = exitCode;
|
|
326
|
+
this.code = code;
|
|
320
327
|
this.name = "CliExit";
|
|
321
328
|
}
|
|
322
329
|
};
|
|
@@ -327,7 +334,7 @@ function info(line) {
|
|
|
327
334
|
function fail(code, message, exitCode = 1) {
|
|
328
335
|
process.stderr.write(`${JSON.stringify({ ok: false, code, message })}
|
|
329
336
|
`);
|
|
330
|
-
throw new CliExit(exitCode);
|
|
337
|
+
throw new CliExit(exitCode, code);
|
|
331
338
|
}
|
|
332
339
|
|
|
333
340
|
// src/serverUrl.ts
|
|
@@ -351,7 +358,11 @@ async function runLogin(opts) {
|
|
|
351
358
|
try {
|
|
352
359
|
grant = await client.authorize("slock-computer");
|
|
353
360
|
} catch (err) {
|
|
354
|
-
|
|
361
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
362
|
+
fail(
|
|
363
|
+
"DEVICE_AUTHORIZE_FAILED",
|
|
364
|
+
`Could not start device login at ${baseUrl}: ${reason}. Check that the server URL is correct and reachable.`
|
|
365
|
+
);
|
|
355
366
|
}
|
|
356
367
|
const verificationUri = grant.verificationUriComplete || grant.verificationUri;
|
|
357
368
|
const verifyUrl = new URL(verificationUri, baseUrl).toString();
|
|
@@ -1692,6 +1703,49 @@ async function runStart(opts = {}, deps = {}) {
|
|
|
1692
1703
|
info(`Per-server daemon logs: ~/.slock/computer/servers/<serverId>/daemon.log`);
|
|
1693
1704
|
info(`Check state with \`slock-computer status\`.`);
|
|
1694
1705
|
}
|
|
1706
|
+
async function runStop(deps = {}) {
|
|
1707
|
+
const slockHome = resolveSlockHome();
|
|
1708
|
+
const readPidfile = deps.readPidfile ?? readPidfileAt;
|
|
1709
|
+
const isAlive = deps.isProcessAlive ?? isProcessAlive2;
|
|
1710
|
+
const killer = deps.killSupervisor ?? ((pid2) => {
|
|
1711
|
+
process.kill(pid2, "SIGTERM");
|
|
1712
|
+
});
|
|
1713
|
+
const sleep2 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
1714
|
+
const pollIntervalMs = deps.pollIntervalMs ?? 200;
|
|
1715
|
+
const timeoutMs = deps.timeoutMs ?? 5e3;
|
|
1716
|
+
const pidfilePath = supervisorPidPath(slockHome);
|
|
1717
|
+
const pid = await readPidfile(pidfilePath);
|
|
1718
|
+
if (pid === null) {
|
|
1719
|
+
info("Supervisor not running.");
|
|
1720
|
+
return;
|
|
1721
|
+
}
|
|
1722
|
+
if (!isAlive(pid)) {
|
|
1723
|
+
await clearPidfileAt(pidfilePath);
|
|
1724
|
+
info(`Supervisor not running (cleared stale pidfile for pid ${pid}).`);
|
|
1725
|
+
return;
|
|
1726
|
+
}
|
|
1727
|
+
try {
|
|
1728
|
+
killer(pid);
|
|
1729
|
+
} catch (err) {
|
|
1730
|
+
fail(
|
|
1731
|
+
"STOP_SIGNAL_FAILED",
|
|
1732
|
+
`Failed to send SIGTERM to supervisor (pid ${pid}): ${err instanceof Error ? err.message : String(err)}. Check process permissions or run: kill ${pid}`
|
|
1733
|
+
);
|
|
1734
|
+
}
|
|
1735
|
+
const deadline = Date.now() + timeoutMs;
|
|
1736
|
+
while (Date.now() < deadline) {
|
|
1737
|
+
if (!isAlive(pid)) {
|
|
1738
|
+
await clearPidfileAt(pidfilePath);
|
|
1739
|
+
info(`Stopped supervisor (pid ${pid}).`);
|
|
1740
|
+
return;
|
|
1741
|
+
}
|
|
1742
|
+
await sleep2(pollIntervalMs);
|
|
1743
|
+
}
|
|
1744
|
+
fail(
|
|
1745
|
+
"STOP_TIMEOUT",
|
|
1746
|
+
`Supervisor (pid ${pid}) did not exit within ${timeoutMs}ms after SIGTERM. Force-kill with: kill -9 ${pid}`
|
|
1747
|
+
);
|
|
1748
|
+
}
|
|
1695
1749
|
async function runDetach(serverId, serverLabel = serverId) {
|
|
1696
1750
|
assertValidServerId(serverId);
|
|
1697
1751
|
const slockHome = resolveSlockHome();
|
|
@@ -3353,7 +3407,49 @@ async function locateStagedTarball(stagedPath) {
|
|
|
3353
3407
|
// src/upgradeLog.ts
|
|
3354
3408
|
import { chmod as chmod5, mkdir as mkdir12, open as open2 } from "fs/promises";
|
|
3355
3409
|
var FILE_MODE = 384;
|
|
3410
|
+
var UPGRADE_ERROR_CODES = [
|
|
3411
|
+
"UPGRADE_DEPS_CHANGED",
|
|
3412
|
+
"UPGRADE_NETWORK_FAILED",
|
|
3413
|
+
"UPGRADE_INTEGRITY_FAILED",
|
|
3414
|
+
"UPGRADE_SWAP_FAILED",
|
|
3415
|
+
"UPGRADE_RESTART_FAILED",
|
|
3416
|
+
"UPGRADE_NO_TARGET",
|
|
3417
|
+
"UPGRADE_ALREADY_RUNNING"
|
|
3418
|
+
];
|
|
3419
|
+
var UPGRADE_ERROR_CODE_SET = new Set(UPGRADE_ERROR_CODES);
|
|
3420
|
+
function assertUpgradeLogEntry(entry) {
|
|
3421
|
+
const e = entry;
|
|
3422
|
+
if (e.outcome === "ok") {
|
|
3423
|
+
if (e.errorCode !== void 0) {
|
|
3424
|
+
throw new TypeError(
|
|
3425
|
+
`UpgradeLogEntry contract violation: outcome="ok" must not carry errorCode (got "${e.errorCode}").`
|
|
3426
|
+
);
|
|
3427
|
+
}
|
|
3428
|
+
} else if (e.outcome === "err") {
|
|
3429
|
+
if (e.errorCode === void 0) {
|
|
3430
|
+
throw new TypeError(
|
|
3431
|
+
`UpgradeLogEntry contract violation: outcome="err" requires errorCode from the v8.3.3 closed-set (${UPGRADE_ERROR_CODES.join(" | ")}).`
|
|
3432
|
+
);
|
|
3433
|
+
}
|
|
3434
|
+
if (!UPGRADE_ERROR_CODE_SET.has(e.errorCode)) {
|
|
3435
|
+
throw new TypeError(
|
|
3436
|
+
`UpgradeLogEntry contract violation: errorCode "${e.errorCode}" is not in the v8.3.3 closed 7-value set (${UPGRADE_ERROR_CODES.join(" | ")}).`
|
|
3437
|
+
);
|
|
3438
|
+
}
|
|
3439
|
+
} else {
|
|
3440
|
+
throw new TypeError(
|
|
3441
|
+
`UpgradeLogEntry contract violation: outcome must be "ok" or "err" (got "${e.outcome}").`
|
|
3442
|
+
);
|
|
3443
|
+
}
|
|
3444
|
+
if (typeof e.fromBundle?.computerVersion !== "string" || e.fromBundle.computerVersion.length === 0) {
|
|
3445
|
+
throw new TypeError(`UpgradeLogEntry contract violation: fromBundle.computerVersion must be a non-empty string.`);
|
|
3446
|
+
}
|
|
3447
|
+
if (typeof e.toBundle?.computerVersion !== "string" || e.toBundle.computerVersion.length === 0) {
|
|
3448
|
+
throw new TypeError(`UpgradeLogEntry contract violation: toBundle.computerVersion must be a non-empty string.`);
|
|
3449
|
+
}
|
|
3450
|
+
}
|
|
3356
3451
|
async function appendUpgradeLogEntry(slockHome, entry) {
|
|
3452
|
+
assertUpgradeLogEntry(entry);
|
|
3357
3453
|
await mkdir12(computerDir(slockHome), { recursive: true });
|
|
3358
3454
|
const path2 = upgradeLogPath(slockHome);
|
|
3359
3455
|
const at = entry.at ?? formatUpgradeLogTimestamp();
|
|
@@ -3371,6 +3467,22 @@ async function appendUpgradeLogEntry(slockHome, entry) {
|
|
|
3371
3467
|
}
|
|
3372
3468
|
|
|
3373
3469
|
// src/upgradeCli.ts
|
|
3470
|
+
function isEphemeralNpxContext(binaryDir) {
|
|
3471
|
+
return binaryDir.split(/[\\/]/).includes("_npx");
|
|
3472
|
+
}
|
|
3473
|
+
async function readBundledDaemonVersion(binaryDir) {
|
|
3474
|
+
try {
|
|
3475
|
+
const pkgPath = join7(binaryDir, "package.json");
|
|
3476
|
+
const raw = await readFile12(pkgPath, "utf8");
|
|
3477
|
+
const parsed = JSON.parse(raw);
|
|
3478
|
+
const pinned = parsed.dependencies?.["@slock-ai/daemon"];
|
|
3479
|
+
if (typeof pinned !== "string" || pinned.length === 0) return null;
|
|
3480
|
+
if (pinned === "workspace:*") return null;
|
|
3481
|
+
return pinned;
|
|
3482
|
+
} catch {
|
|
3483
|
+
return null;
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3374
3486
|
async function defaultSpawnFreshSupervisor(slockHome) {
|
|
3375
3487
|
await spawnDetachedSupervisor(slockHome);
|
|
3376
3488
|
}
|
|
@@ -3420,8 +3532,24 @@ async function runUpgradeCli(slockHome, opts, deps = {}) {
|
|
|
3420
3532
|
}
|
|
3421
3533
|
const fromVersion = await (deps.currentVersion ?? defaultCurrentVersion)();
|
|
3422
3534
|
const currentBinaryDir = (deps.currentBinaryDir ?? defaultCurrentBinaryDir)();
|
|
3535
|
+
const isEphemeralFn = deps.isEphemeralNpxContext ?? isEphemeralNpxContext;
|
|
3536
|
+
if (isEphemeralFn(currentBinaryDir)) {
|
|
3537
|
+
fail(
|
|
3538
|
+
"UPGRADE_EPHEMERAL_CONTEXT",
|
|
3539
|
+
`You're running upgrade via the npx ephemeral entrypoint, which can't reach your installed Computer service. Please run the installed \`slock-computer upgrade\` instead. If \`slock-computer\` isn't on your PATH, install it globally first: \`npm i -g @slock-ai/computer@latest\`.`
|
|
3540
|
+
);
|
|
3541
|
+
}
|
|
3423
3542
|
if (targetVersion === fromVersion) {
|
|
3424
|
-
info(`Already at ${fromVersion} (channel ${channel2}).
|
|
3543
|
+
info(`Already at ${fromVersion} (channel ${channel2}). No upgrade target.`);
|
|
3544
|
+
await appendUpgradeLogEntry(slockHome, {
|
|
3545
|
+
fromBundle: { computerVersion: fromVersion },
|
|
3546
|
+
toBundle: { computerVersion: fromVersion },
|
|
3547
|
+
channel: channel2,
|
|
3548
|
+
trigger: opts.trigger ?? "cli",
|
|
3549
|
+
outcome: "err",
|
|
3550
|
+
errorCode: "UPGRADE_NO_TARGET"
|
|
3551
|
+
}).catch(() => {
|
|
3552
|
+
});
|
|
3425
3553
|
return;
|
|
3426
3554
|
}
|
|
3427
3555
|
info(`Planning upgrade: ${fromVersion} \u2192 ${targetVersion} (channel ${channel2}).`);
|
|
@@ -3472,9 +3600,9 @@ async function runUpgradeCli(slockHome, opts, deps = {}) {
|
|
|
3472
3600
|
spawnFreshSupervisor: () => spawnFreshSupervisor(slockHome)
|
|
3473
3601
|
}
|
|
3474
3602
|
});
|
|
3475
|
-
const
|
|
3476
|
-
|
|
3477
|
-
}
|
|
3603
|
+
const readBundledFn = deps.readBundledDaemonVersion ?? readBundledDaemonVersion;
|
|
3604
|
+
const bundledDaemonVersion = await readBundledFn(currentBinaryDir);
|
|
3605
|
+
const bundle = (version) => bundledDaemonVersion !== null ? { computerVersion: version, bundledDaemonVersion } : { computerVersion: version };
|
|
3478
3606
|
const logTrigger = opts.trigger ?? "cli";
|
|
3479
3607
|
if (outcome.ok) {
|
|
3480
3608
|
info(
|
|
@@ -3490,8 +3618,28 @@ async function runUpgradeCli(slockHome, opts, deps = {}) {
|
|
|
3490
3618
|
});
|
|
3491
3619
|
return;
|
|
3492
3620
|
}
|
|
3493
|
-
const code = mapFailurePhaseToCode(outcome);
|
|
3494
3621
|
const rolledBack = outcome.rolledBack === true ? " (swap rolled back)" : "";
|
|
3622
|
+
if (outcome.phase === "drain") {
|
|
3623
|
+
info(
|
|
3624
|
+
`Upgrade deferred: --drain=defer and daemons are busy. Re-run when idle, or use --force to interrupt in-flight turns.`
|
|
3625
|
+
);
|
|
3626
|
+
return;
|
|
3627
|
+
}
|
|
3628
|
+
if (outcome.phase === "cleanup") {
|
|
3629
|
+
info(
|
|
3630
|
+
`Upgrade ${fromVersion} \u2192 ${targetVersion} succeeded; cleanup phase reported a non-fatal issue: ${outcome.reason ?? "unknown"}. Active layout + supervisor are healthy.`
|
|
3631
|
+
);
|
|
3632
|
+
await appendUpgradeLogEntry(slockHome, {
|
|
3633
|
+
fromBundle: bundle(fromVersion),
|
|
3634
|
+
toBundle: bundle(targetVersion),
|
|
3635
|
+
channel: channel2,
|
|
3636
|
+
trigger: logTrigger,
|
|
3637
|
+
outcome: "ok"
|
|
3638
|
+
}).catch(() => {
|
|
3639
|
+
});
|
|
3640
|
+
return;
|
|
3641
|
+
}
|
|
3642
|
+
const code = mapFailurePhaseToCode(outcome);
|
|
3495
3643
|
await appendUpgradeLogEntry(slockHome, {
|
|
3496
3644
|
fromBundle: bundle(fromVersion),
|
|
3497
3645
|
toBundle: bundle(targetVersion),
|
|
@@ -3508,26 +3656,27 @@ async function runUpgradeCli(slockHome, opts, deps = {}) {
|
|
|
3508
3656
|
}
|
|
3509
3657
|
function mapFailurePhaseToCode(outcome) {
|
|
3510
3658
|
switch (outcome.phase) {
|
|
3511
|
-
case "drain":
|
|
3512
|
-
return "UPGRADE_DEFERRED";
|
|
3513
3659
|
case "stage":
|
|
3514
|
-
return "
|
|
3660
|
+
return "UPGRADE_NETWORK_FAILED";
|
|
3515
3661
|
case "verify":
|
|
3516
|
-
return "
|
|
3662
|
+
return "UPGRADE_INTEGRITY_FAILED";
|
|
3517
3663
|
case "preflight":
|
|
3518
3664
|
return "UPGRADE_DEPS_CHANGED";
|
|
3519
3665
|
case "snapshot":
|
|
3520
|
-
return "
|
|
3666
|
+
return "UPGRADE_SWAP_FAILED";
|
|
3521
3667
|
case "extract":
|
|
3522
|
-
return "
|
|
3668
|
+
return "UPGRADE_INTEGRITY_FAILED";
|
|
3523
3669
|
case "swap":
|
|
3524
3670
|
return "UPGRADE_SWAP_FAILED";
|
|
3525
3671
|
case "restart":
|
|
3526
3672
|
return "UPGRADE_RESTART_FAILED";
|
|
3527
3673
|
case "rolling_health":
|
|
3528
|
-
return "
|
|
3674
|
+
return "UPGRADE_RESTART_FAILED";
|
|
3675
|
+
case "drain":
|
|
3529
3676
|
case "cleanup":
|
|
3530
|
-
|
|
3677
|
+
throw new Error(
|
|
3678
|
+
`mapFailurePhaseToCode: phase=${outcome.phase} should be handled by caller (silenced), not mapped to a closed-set code`
|
|
3679
|
+
);
|
|
3531
3680
|
}
|
|
3532
3681
|
}
|
|
3533
3682
|
async function defaultFetchDistTags() {
|
|
@@ -3888,6 +4037,19 @@ async function defaultCurrentVersionLocal() {
|
|
|
3888
4037
|
);
|
|
3889
4038
|
}
|
|
3890
4039
|
|
|
4040
|
+
// src/version.ts
|
|
4041
|
+
import { createRequire as createRequire2 } from "module";
|
|
4042
|
+
function readComputerVersion(moduleUrl = import.meta.url) {
|
|
4043
|
+
try {
|
|
4044
|
+
const require2 = createRequire2(moduleUrl);
|
|
4045
|
+
const pkg = require2("../package.json");
|
|
4046
|
+
return typeof pkg.version === "string" && pkg.version.length > 0 ? pkg.version : "0.0.0-dev";
|
|
4047
|
+
} catch {
|
|
4048
|
+
return "0.0.0-dev";
|
|
4049
|
+
}
|
|
4050
|
+
}
|
|
4051
|
+
var COMPUTER_VERSION = readComputerVersion();
|
|
4052
|
+
|
|
3891
4053
|
// src/index.ts
|
|
3892
4054
|
function withCliExit(fn) {
|
|
3893
4055
|
return async (...args) => {
|
|
@@ -3903,7 +4065,7 @@ function withCliExit(fn) {
|
|
|
3903
4065
|
};
|
|
3904
4066
|
}
|
|
3905
4067
|
var program = new Command();
|
|
3906
|
-
program.name("slock-computer").description("Slock Computer \u2014 local-machine control plane (login + N per-server attachments).").version(
|
|
4068
|
+
program.name("slock-computer").description("Slock Computer \u2014 local-machine control plane (login + N per-server attachments).").version(COMPUTER_VERSION);
|
|
3907
4069
|
program.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) => {
|
|
3908
4070
|
await runLogin({ serverUrl: opts.serverUrl });
|
|
3909
4071
|
}));
|
|
@@ -3961,6 +4123,9 @@ program.command("start").argument("[serverSlug]", "optional: verify this server
|
|
|
3961
4123
|
})
|
|
3962
4124
|
);
|
|
3963
4125
|
}));
|
|
4126
|
+
program.command("stop").description("Stop the Computer supervisor (and all managed per-server daemons).").action(withCliExit(async () => {
|
|
4127
|
+
await withMutationLock(() => runStop());
|
|
4128
|
+
}));
|
|
3964
4129
|
program.command("doctor").argument("[serverSlug]", "optional: scope detail (recent crashes) to one server").description("Diagnose login + per-server attachments + per-server preflight (no secrets).").option("--json", "emit the machine-readable report").option("--cleanup", "after diagnosis, run the local residue cleanup pass").option("--fix", "alias for --cleanup (same behavior)").option("--reset-health", "clear <serverSlug>'s crash history so supervisor resumes auto-restart").action(
|
|
3965
4130
|
withCliExit(
|
|
3966
4131
|
async (serverSlug, opts) => {
|
|
@@ -4020,15 +4185,41 @@ program.command("upgrade").description(
|
|
|
4020
4185
|
}
|
|
4021
4186
|
drain = opts.drain;
|
|
4022
4187
|
}
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4188
|
+
const slockHome = resolveSlockHome();
|
|
4189
|
+
try {
|
|
4190
|
+
await withMutationLock(
|
|
4191
|
+
() => runUpgradeCli(slockHome, {
|
|
4192
|
+
dryRun: opts.dryRun,
|
|
4193
|
+
channel: opts.channel,
|
|
4194
|
+
targetVersion: opts.targetVersion,
|
|
4195
|
+
drain,
|
|
4196
|
+
force: opts.force
|
|
4197
|
+
})
|
|
4198
|
+
);
|
|
4199
|
+
} catch (err) {
|
|
4200
|
+
if (err instanceof CliExit && err.code === "CONCURRENT_OPERATION") {
|
|
4201
|
+
try {
|
|
4202
|
+
const currentVersion = await defaultCurrentVersion();
|
|
4203
|
+
const channel2 = opts.channel ?? await readChannel(slockHome);
|
|
4204
|
+
await appendUpgradeLogEntry(slockHome, {
|
|
4205
|
+
fromBundle: { computerVersion: currentVersion },
|
|
4206
|
+
toBundle: { computerVersion: currentVersion },
|
|
4207
|
+
channel: channel2,
|
|
4208
|
+
trigger: "cli",
|
|
4209
|
+
outcome: "err",
|
|
4210
|
+
errorCode: "UPGRADE_ALREADY_RUNNING"
|
|
4211
|
+
}).catch(() => {
|
|
4212
|
+
});
|
|
4213
|
+
} catch {
|
|
4214
|
+
}
|
|
4215
|
+
process.stderr.write(
|
|
4216
|
+
`slock-computer: UPGRADE_ALREADY_RUNNING: Another upgrade attempt is currently holding the mutation lock. Wait for it to finish and retry, or check \`~/.slock/computer/upgrade.log\` for the in-flight attempt.
|
|
4217
|
+
`
|
|
4218
|
+
);
|
|
4219
|
+
throw new CliExit(1, "UPGRADE_ALREADY_RUNNING");
|
|
4220
|
+
}
|
|
4221
|
+
throw err;
|
|
4222
|
+
}
|
|
4032
4223
|
}
|
|
4033
4224
|
)
|
|
4034
4225
|
);
|
package/package.json
CHANGED