@marina-cloud/cli 0.0.1 → 0.0.2
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/marina.mjs +88 -59
- package/package.json +1 -1
package/dist/marina.mjs
CHANGED
|
@@ -75,6 +75,7 @@ function writeLink(dir, link) {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
// src/api.ts
|
|
78
|
+
var LOGIN_EXCHANGE_TIMEOUT_MS = 15e3;
|
|
78
79
|
var ApiError = class extends Error {
|
|
79
80
|
code;
|
|
80
81
|
status;
|
|
@@ -102,11 +103,24 @@ async function request(path, init) {
|
|
|
102
103
|
return body;
|
|
103
104
|
}
|
|
104
105
|
async function exchangeCliLogin(code, codeVerifier) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
let res;
|
|
107
|
+
try {
|
|
108
|
+
res = await fetch(`${apiUrl()}/cli/auth/exchange`, {
|
|
109
|
+
method: "POST",
|
|
110
|
+
headers: { "content-type": "application/json" },
|
|
111
|
+
body: JSON.stringify({ code, code_verifier: codeVerifier }),
|
|
112
|
+
signal: AbortSignal.timeout(LOGIN_EXCHANGE_TIMEOUT_MS)
|
|
113
|
+
});
|
|
114
|
+
} catch (error) {
|
|
115
|
+
if (error.name === "TimeoutError") {
|
|
116
|
+
throw new ApiError(
|
|
117
|
+
"login_timeout",
|
|
118
|
+
"the login confirmation request timed out after 15 seconds \u2014 run `marina setup` to try again",
|
|
119
|
+
408
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
110
124
|
const body = await res.json().catch(() => ({}));
|
|
111
125
|
if (!res.ok || !body.token) {
|
|
112
126
|
throw new ApiError(
|
|
@@ -1414,22 +1428,53 @@ import { spawn } from "node:child_process";
|
|
|
1414
1428
|
import { createServer } from "node:http";
|
|
1415
1429
|
import { hostname } from "node:os";
|
|
1416
1430
|
var LOGIN_TIMEOUT_MS = 5 * 6e4;
|
|
1431
|
+
var PROGRESS_INTERVAL_MS = 1e3;
|
|
1417
1432
|
var page = (title, message) => `<!doctype html>
|
|
1418
1433
|
<html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width">
|
|
1419
1434
|
<title>${title}</title><style>
|
|
1420
1435
|
body{margin:0;display:grid;place-items:center;min-height:100vh;font:15px/1.5 system-ui,sans-serif;color:#142238;background:#faf9f7}
|
|
1421
1436
|
main{text-align:center;padding:32px}h1{font-size:22px;margin:0 0 6px}p{margin:0;color:#667085}
|
|
1422
1437
|
</style></head><body><main><h1>${title}</h1><p>${message}</p></main></body></html>`;
|
|
1423
|
-
var
|
|
1438
|
+
var closeLoginServer = (server) => new Promise((resolve2) => {
|
|
1439
|
+
server.close(() => resolve2());
|
|
1440
|
+
server.closeAllConnections();
|
|
1441
|
+
});
|
|
1424
1442
|
function openBrowser(url) {
|
|
1425
1443
|
const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "explorer.exe" : "xdg-open";
|
|
1426
1444
|
const child = spawn(command, [url], { detached: true, stdio: "ignore" });
|
|
1427
1445
|
child.on("error", () => void 0);
|
|
1428
1446
|
child.unref();
|
|
1429
1447
|
}
|
|
1430
|
-
function
|
|
1448
|
+
function timeoutLabel(timeoutMs) {
|
|
1449
|
+
if (timeoutMs % 6e4 === 0) {
|
|
1450
|
+
const minutes = timeoutMs / 6e4;
|
|
1451
|
+
return `${String(minutes)} minute${minutes === 1 ? "" : "s"}`;
|
|
1452
|
+
}
|
|
1453
|
+
const seconds = Math.ceil(timeoutMs / 1e3);
|
|
1454
|
+
return `${String(seconds)} second${seconds === 1 ? "" : "s"}`;
|
|
1455
|
+
}
|
|
1456
|
+
function waitForLoginCallback(server, expectedState, onProgress, timeoutMs = LOGIN_TIMEOUT_MS) {
|
|
1431
1457
|
return new Promise((resolve2, reject) => {
|
|
1432
|
-
const
|
|
1458
|
+
const startedAt = Date.now();
|
|
1459
|
+
const waiting = () => {
|
|
1460
|
+
const elapsedMs = Date.now() - startedAt;
|
|
1461
|
+
onProgress({
|
|
1462
|
+
phase: "waiting",
|
|
1463
|
+
elapsedMs,
|
|
1464
|
+
remainingMs: Math.max(0, timeoutMs - elapsedMs)
|
|
1465
|
+
});
|
|
1466
|
+
};
|
|
1467
|
+
waiting();
|
|
1468
|
+
const progress = setInterval(waiting, PROGRESS_INTERVAL_MS);
|
|
1469
|
+
progress.unref();
|
|
1470
|
+
const timer = setTimeout(() => {
|
|
1471
|
+
clearInterval(progress);
|
|
1472
|
+
reject(
|
|
1473
|
+
new Error(
|
|
1474
|
+
`browser login timed out after ${timeoutLabel(timeoutMs)} \u2014 run \`marina setup\` to try again`
|
|
1475
|
+
)
|
|
1476
|
+
);
|
|
1477
|
+
}, timeoutMs);
|
|
1433
1478
|
timer.unref();
|
|
1434
1479
|
server.on("request", (request2, response) => {
|
|
1435
1480
|
const url = new URL(request2.url ?? "/", "http://127.0.0.1");
|
|
@@ -1439,6 +1484,7 @@ function callbackCode(server, expectedState) {
|
|
|
1439
1484
|
}
|
|
1440
1485
|
response.setHeader("cache-control", "no-store");
|
|
1441
1486
|
response.setHeader("content-type", "text/html; charset=utf-8");
|
|
1487
|
+
response.setHeader("connection", "close");
|
|
1442
1488
|
const state = url.searchParams.get("state");
|
|
1443
1489
|
const code = url.searchParams.get("code");
|
|
1444
1490
|
const error = url.searchParams.get("error");
|
|
@@ -1448,6 +1494,7 @@ function callbackCode(server, expectedState) {
|
|
|
1448
1494
|
return;
|
|
1449
1495
|
}
|
|
1450
1496
|
clearTimeout(timer);
|
|
1497
|
+
clearInterval(progress);
|
|
1451
1498
|
if (error || !code) {
|
|
1452
1499
|
response.writeHead(400);
|
|
1453
1500
|
response.end(
|
|
@@ -1460,6 +1507,7 @@ function callbackCode(server, expectedState) {
|
|
|
1460
1507
|
);
|
|
1461
1508
|
return;
|
|
1462
1509
|
}
|
|
1510
|
+
onProgress({ phase: "received" });
|
|
1463
1511
|
response.writeHead(200);
|
|
1464
1512
|
response.end(
|
|
1465
1513
|
page("Marina CLI is signed in", "You can close this window and return to the terminal."),
|
|
@@ -1468,7 +1516,7 @@ function callbackCode(server, expectedState) {
|
|
|
1468
1516
|
});
|
|
1469
1517
|
});
|
|
1470
1518
|
}
|
|
1471
|
-
async function loginWithBrowser(onOpen) {
|
|
1519
|
+
async function loginWithBrowser(onOpen, onProgress = () => void 0) {
|
|
1472
1520
|
const verifier = randomBytes(32).toString("base64url");
|
|
1473
1521
|
const challenge = createHash("sha256").update(verifier).digest("base64url");
|
|
1474
1522
|
const state = randomBytes(32).toString("base64url");
|
|
@@ -1489,13 +1537,13 @@ async function loginWithBrowser(onOpen) {
|
|
|
1489
1537
|
"device_name",
|
|
1490
1538
|
`Marina CLI on ${hostname().split(".")[0]}`.slice(0, 80)
|
|
1491
1539
|
);
|
|
1492
|
-
const codePromise =
|
|
1540
|
+
const codePromise = waitForLoginCallback(server, state, onProgress);
|
|
1493
1541
|
onOpen(authorize.toString());
|
|
1494
1542
|
openBrowser(authorize.toString());
|
|
1495
1543
|
const code = await codePromise;
|
|
1496
1544
|
return await exchangeCliLogin(code, verifier);
|
|
1497
1545
|
} finally {
|
|
1498
|
-
await
|
|
1546
|
+
await closeLoginServer(server);
|
|
1499
1547
|
}
|
|
1500
1548
|
}
|
|
1501
1549
|
|
|
@@ -1690,21 +1738,6 @@ function install(target, update) {
|
|
|
1690
1738
|
status: existing === null ? "installed" : "updated"
|
|
1691
1739
|
};
|
|
1692
1740
|
}
|
|
1693
|
-
function autoInstallSkills() {
|
|
1694
|
-
const detectedTargets = targets();
|
|
1695
|
-
const installed = [];
|
|
1696
|
-
const updatesAvailable = [];
|
|
1697
|
-
for (const target of detectedTargets) {
|
|
1698
|
-
const result2 = install(target, false);
|
|
1699
|
-
if (result2) installed.push(result2);
|
|
1700
|
-
else updatesAvailable.push(target.agent);
|
|
1701
|
-
}
|
|
1702
|
-
return {
|
|
1703
|
-
detected: detectedTargets.map((target) => target.agent),
|
|
1704
|
-
installed,
|
|
1705
|
-
updatesAvailable
|
|
1706
|
-
};
|
|
1707
|
-
}
|
|
1708
1741
|
function installSkills(agent) {
|
|
1709
1742
|
const selected = targets(agent);
|
|
1710
1743
|
if (selected.length === 0) {
|
|
@@ -1716,7 +1749,7 @@ function installSkills(agent) {
|
|
|
1716
1749
|
// package.json
|
|
1717
1750
|
var package_default = {
|
|
1718
1751
|
name: "@marina-cloud/cli",
|
|
1719
|
-
version: "0.0.
|
|
1752
|
+
version: "0.0.2",
|
|
1720
1753
|
description: "Command-line client for Marina Cloud",
|
|
1721
1754
|
homepage: "https://github.com/marina-hq/marina#readme",
|
|
1722
1755
|
bugs: {
|
|
@@ -2061,27 +2094,6 @@ async function main() {
|
|
|
2061
2094
|
return;
|
|
2062
2095
|
}
|
|
2063
2096
|
shouldCheckForUpdates = true;
|
|
2064
|
-
let skillBootstrap = null;
|
|
2065
|
-
if (command !== "skills") {
|
|
2066
|
-
try {
|
|
2067
|
-
skillBootstrap = autoInstallSkills();
|
|
2068
|
-
for (const installed of skillBootstrap.installed) {
|
|
2069
|
-
if (installed.status === "installed") {
|
|
2070
|
-
say(dim(`installed Marina skill for ${installed.agent}`));
|
|
2071
|
-
}
|
|
2072
|
-
}
|
|
2073
|
-
if (skillBootstrap.updatesAvailable.length > 0) {
|
|
2074
|
-
const agent = skillBootstrap.updatesAvailable.length > 1 ? "all" : skillBootstrap.updatesAvailable[0];
|
|
2075
|
-
say(
|
|
2076
|
-
dim(
|
|
2077
|
-
`Marina skill update available \u2014 run marina skills install --agent ${agent ?? "all"}`
|
|
2078
|
-
)
|
|
2079
|
-
);
|
|
2080
|
-
}
|
|
2081
|
-
} catch {
|
|
2082
|
-
say(dim("could not install the Marina agent skill; run marina skills install later"));
|
|
2083
|
-
}
|
|
2084
|
-
}
|
|
2085
2097
|
switch (command) {
|
|
2086
2098
|
case "setup":
|
|
2087
2099
|
case "login": {
|
|
@@ -2089,24 +2101,41 @@ async function main() {
|
|
|
2089
2101
|
if (token) {
|
|
2090
2102
|
saveToken(token);
|
|
2091
2103
|
} else {
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2104
|
+
let lastReportedRemaining = -1;
|
|
2105
|
+
const signedIn = await loginWithBrowser(
|
|
2106
|
+
(url) => {
|
|
2107
|
+
say("Opening your browser to sign in\u2026");
|
|
2108
|
+
say(dim(url));
|
|
2109
|
+
},
|
|
2110
|
+
(progress) => {
|
|
2111
|
+
if (progress.phase === "waiting") {
|
|
2112
|
+
const remainingSeconds = Math.ceil(progress.remainingMs / 1e3);
|
|
2113
|
+
if (lastReportedRemaining < 0) {
|
|
2114
|
+
say(
|
|
2115
|
+
dim(
|
|
2116
|
+
`Waiting for browser authorization (${String(Math.ceil(remainingSeconds / 60))} minute timeout)\u2026`
|
|
2117
|
+
)
|
|
2118
|
+
);
|
|
2119
|
+
lastReportedRemaining = remainingSeconds;
|
|
2120
|
+
} else if (remainingSeconds <= lastReportedRemaining - 15) {
|
|
2121
|
+
const minutes = Math.floor(remainingSeconds / 60);
|
|
2122
|
+
const seconds = String(remainingSeconds % 60).padStart(2, "0");
|
|
2123
|
+
say(dim(`Still waiting\u2026 ${String(minutes)}:${seconds} remaining`));
|
|
2124
|
+
lastReportedRemaining = remainingSeconds;
|
|
2125
|
+
}
|
|
2126
|
+
} else if (progress.phase === "received") {
|
|
2127
|
+
say(dim("Authorization received. Completing sign-in\u2026"));
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
);
|
|
2096
2131
|
saveToken(signedIn.token);
|
|
2097
2132
|
}
|
|
2098
2133
|
say(`${green("ok")} signed in ${dim(`(${apiUrl()})`)}`);
|
|
2099
|
-
if (skillBootstrap?.detected.length === 0) {
|
|
2100
|
-
say(
|
|
2101
|
-
dim("no Codex or Claude installation detected \u2014 use marina skills install --agent later")
|
|
2102
|
-
);
|
|
2103
|
-
}
|
|
2104
2134
|
result({
|
|
2105
2135
|
command: "setup",
|
|
2106
2136
|
signed_in: true,
|
|
2107
2137
|
api: apiUrl(),
|
|
2108
|
-
profile: profilePath()
|
|
2109
|
-
skills: skillBootstrap?.installed ?? []
|
|
2138
|
+
profile: profilePath()
|
|
2110
2139
|
});
|
|
2111
2140
|
return;
|
|
2112
2141
|
}
|