@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.
- package/dist/index.js +237 -64
- 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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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)
|
|
93
|
-
|
|
94
|
-
|
|
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 :
|
|
125
|
-
if (res.status === 401
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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 (
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
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
|
|
1877
|
+
async function readUserSession(path2) {
|
|
1719
1878
|
try {
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
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
|
|
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: ${
|
|
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
|
|
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
|
|
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
|
|
2148
|
-
import { dirname as
|
|
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
|
|
2173
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
2591
|
-
await
|
|
2592
|
-
await
|
|
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
|
|
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 ??
|
|
2633
|
-
const fsRm = deps.fsRm ?? ((p) =>
|
|
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 ??
|
|
2656
|
-
const fsRm = deps.fsRm ?? ((p) =>
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
3375
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
3705
|
+
import { copyFile, mkdir as mkdir13, readFile as readFile13 } from "fs/promises";
|
|
3533
3706
|
import { createHash as createHash5 } from "crypto";
|
|
3534
|
-
import { dirname as
|
|
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
|
|
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
|
|
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
|
+
"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.
|
|
24
|
+
"@slock-ai/daemon": "0.53.0"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@types/node": "^25.5.0",
|