@kvell007/embed-labs-cli 0.1.0-alpha.21 → 0.1.0-alpha.22
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/README.md +6 -2
- package/dist/index.js +732 -65
- package/dist/index.js.map +1 -1
- package/dist/local-toolchain.d.ts +2 -0
- package/dist/local-toolchain.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { createHash, createHmac, randomBytes } from "node:crypto";
|
|
2
|
+
import { createHash, createHmac, generateKeyPairSync, randomBytes, sign as signCrypto } from "node:crypto";
|
|
3
3
|
import { constants } from "node:fs";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
|
-
import { access, cp, mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
|
|
5
|
+
import { access, chmod, cp, mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
|
|
6
6
|
import { createRequire } from "node:module";
|
|
7
|
-
import { homedir, tmpdir } from "node:os";
|
|
7
|
+
import { arch, hostname, homedir, platform, tmpdir } from "node:os";
|
|
8
8
|
import { basename, delimiter, dirname, join, resolve } from "node:path";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
|
-
import { startServer } from "@embed-labs/local-bridge";
|
|
11
10
|
import { composeBootLogoPackage } from "./image-compose.js";
|
|
12
11
|
import { buildTaishanPiQtSmoke, compileTaishanPiSingleFile, currentLocalToolchain, installLocalToolchain, latestLocalToolchain, validateLocalToolchain } from "./local-toolchain.js";
|
|
13
12
|
import { fail, ok } from "@embed-labs/protocol";
|
|
@@ -16,10 +15,14 @@ const CLI_MODULE_DIR = dirname(fileURLToPath(import.meta.url));
|
|
|
16
15
|
const SOURCE_CHECKOUT_ROOT = resolve(CLI_MODULE_DIR, "..", "..", "..");
|
|
17
16
|
const qrcodeTerminal = require("qrcode-terminal");
|
|
18
17
|
const DEFAULT_BRIDGE_URL = process.env.EMBED_BRIDGE_URL ?? "http://127.0.0.1:18083";
|
|
19
|
-
const DEFAULT_CLOUD_API_URL = process.env.EMBED_CLOUD_API_URL ?? "
|
|
18
|
+
const DEFAULT_CLOUD_API_URL = process.env.EMBED_CLOUD_API_URL ?? "https://api.embedboard.com";
|
|
20
19
|
const DEFAULT_AUTH_FILE = process.env.EMBED_AUTH_FILE ?? ".embed-labs/auth.json";
|
|
20
|
+
const DEFAULT_DEVICE_FILE = process.env.EMBED_DEVICE_FILE ?? join(dirname(DEFAULT_AUTH_FILE), "device.json");
|
|
21
21
|
const DEFAULT_DASHBOARD_URL = process.env.EMBED_DASHBOARD_URL ?? "https://api.embedboard.com/dashboard";
|
|
22
|
+
const EMBED_CLIENT_NAME = "embedlabs-cli";
|
|
23
|
+
const EMBED_CLIENT_VERSION = process.env.EMBED_CLIENT_VERSION ?? "0.1.0";
|
|
22
24
|
const DEFAULT_AGENT_ARTIFACT_DIR = process.env.EMBED_AGENT_ARTIFACT_DIR ?? ".embed-labs/artifacts";
|
|
25
|
+
const TOOL_INTEGRITY_RELOGIN_MESSAGE = "工具完整性校验失败,请在当前电脑重新执行 embedlabs auth login --token <key>";
|
|
23
26
|
const DOCTOR_USAGE = "Usage: embed doctor [--json]";
|
|
24
27
|
const DOCTOR_HTTP_TIMEOUT_MS = 8000;
|
|
25
28
|
const MIN_NODE_MAJOR = 20;
|
|
@@ -30,7 +33,14 @@ const DEFAULT_PLUGIN_RELEASE_URL = process.env.EMBED_PLUGIN_RELEASE_URL?.trim()
|
|
|
30
33
|
const PLUGIN_LIST_USAGE = "Usage: embed plugin list [--release-dir <dir>] [--release-url <url>] [--json]";
|
|
31
34
|
const CODEX_PLUGIN_NAME = "embed-labs";
|
|
32
35
|
const CODEX_MARKETPLACE_NAME = "embed-labs";
|
|
33
|
-
const
|
|
36
|
+
const LEGACY_CODEX_PLUGIN_NAMES = [
|
|
37
|
+
"dbt-agent",
|
|
38
|
+
"Dbt Agent",
|
|
39
|
+
"development-board-toolchain",
|
|
40
|
+
"development-board-toolchain-dev",
|
|
41
|
+
"deve"
|
|
42
|
+
];
|
|
43
|
+
const LEGACY_CODEX_MARKETPLACE_NAMES = new Set(["embed-labs-plugins", "plugins", "Plugins", "deve"]);
|
|
34
44
|
const PLUGIN_INSTALL_USAGE = "Usage: embed plugin install <codex|opencode|all> [--release-dir <dir>] [--release-url <url>] [--target <dir>] [--codex-target <dir>] [--opencode-target <dir>] [--force] [--json]";
|
|
35
45
|
const CLOUD_TASK_ARTIFACTS_USAGE = "Usage: embed cloud task artifacts <task_id> [--json]";
|
|
36
46
|
const CLOUD_TASK_EVIDENCE_USAGE = "Usage: embed cloud task evidence <task_id> [--json]";
|
|
@@ -91,6 +101,10 @@ const LOCAL_TOOLCHAIN_INSTALL_USAGE = "Usage: embed local toolchain install [--b
|
|
|
91
101
|
const LOCAL_TOOLCHAIN_VALIDATE_USAGE = "Usage: embed local toolchain validate [--release-root <path>] [--mode minimal|compile|qt|full|images] [--json]";
|
|
92
102
|
const LOCAL_COMPILE_TAISHANPI_USAGE = "Usage: embed local compile taishanpi --source <main.c|main.cpp> --output <artifact> [--release-root <path>] [--account <account_id>] [--json]";
|
|
93
103
|
const LOCAL_BUILD_QT_SMOKE_USAGE = "Usage: embed local build qt-smoke --build-dir <dir> [--source <qt-smoke-dir>] [--release-root <path>] [--account <account_id>] [--json]";
|
|
104
|
+
const AUTH_DEVICE_STATUS_USAGE = "Usage: embed auth device status [--json]";
|
|
105
|
+
const AUTH_DEVICE_LIST_USAGE = "Usage: embed auth device list [--json]";
|
|
106
|
+
const AUTH_DEVICE_REVOKE_USAGE = "Usage: embed auth device revoke <device_id> [--json]";
|
|
107
|
+
const AUTH_DEVICE_RENAME_USAGE = "Usage: embed auth device rename <device_id> --label <name> [--json]";
|
|
94
108
|
const BOARD_REGISTRY_LIST_USAGE = "Usage: embed board registry list [--json]";
|
|
95
109
|
const BOARD_REGISTRY_SHOW_USAGE = "Usage: embed board registry show <template_id> [--json]";
|
|
96
110
|
const BOARD_METHODS_USAGE = "Usage: embed board methods <template_id> [--json]";
|
|
@@ -100,9 +114,10 @@ const MODEL_LIST_USAGE = "Usage: embed model list [--json]";
|
|
|
100
114
|
const MODEL_DEFAULT_USAGE = "Usage: embed model default [--json]";
|
|
101
115
|
const SERVICE_MODES_USAGE = "Usage: embed service modes [--json]";
|
|
102
116
|
const AGENT_RUN_USAGE = "Usage: embed agent run --prompt <request> [--account <account_id>] [--workspace <workspace_id>] [--provider stub|openai|bai|cc|claude-code] [--model <model>] [--max-tool-calls 6] [--host <ip>] [--ports 22,15301] [--artifact <local_file>|--artifact-id <artifact_id>|--artifact-task <task_id>] [--artifact-output <path>] [--remote-path <path>] [--run] [--approve] [--json]";
|
|
117
|
+
let cachedLocalHardwareFingerprint;
|
|
103
118
|
const TOOL_LIST_USAGE = "Usage: embed tool list [--json]";
|
|
104
119
|
const TOOL_CALL_USAGE = "Usage: embed tool call <capability_id> [--input-json '<json>'] [--approve] [--json]";
|
|
105
|
-
const MCP_TOOL_EVENT_USAGE = "Usage: embed mcp log --tool <tool_name> [--client codex|opencode] [--mode local_ai|server_ai] [--server-model-used true|false] [--success true|false] [--request-id <id>] [--duration-ms <ms>] [--input-summary <text>] [--output-summary <text>] [--json]";
|
|
120
|
+
const MCP_TOOL_EVENT_USAGE = "Usage: embed mcp log --tool <tool_name> [--client codex|opencode] [--mode local_ai|server_ai] [--local-device-id <id>] [--server-model-used true|false] [--success true|false] [--request-id <id>] [--duration-ms <ms>] [--input-summary <text>] [--output-summary <text>] [--json]";
|
|
106
121
|
const BOARD_DEPLOY_TAISHANPI_USAGE = "Usage: embed deploy taishanpi --host <ip> --artifact <local_file> --approve [--user root] [--remote-path /userdata/embed-labs/apps/app] [--run] [--timeout 30] [--json]";
|
|
107
122
|
const CLOUD_TASK_EVENT_APPEND_USAGE = "Usage: embed cloud task event append <task_id> [--state <state>] [--progress-stage <stage>|--stage <stage>] [--progress-text <text>|--message <text>] [--progress-percent 0-100] [--severity info|warning|error] [--type <event_type>] [--artifact-json '<json>'] [--evidence-json '<json>'] [--json]";
|
|
108
123
|
const TASK_STATES = new Set([
|
|
@@ -152,11 +167,7 @@ async function main(argv) {
|
|
|
152
167
|
return output(parsed, await bridgePost("/v1/board/taishanpi/deploy", request), renderBoardDeployResult);
|
|
153
168
|
}
|
|
154
169
|
if (area === "bridge" && action === "start") {
|
|
155
|
-
|
|
156
|
-
host: stringFlag(parsed, "host"),
|
|
157
|
-
port: numberFlag(parsed, "port")
|
|
158
|
-
});
|
|
159
|
-
return await waitForever();
|
|
170
|
+
return await runBridgeStart(parsed);
|
|
160
171
|
}
|
|
161
172
|
if (area === "bridge" && action === "status") {
|
|
162
173
|
return output(parsed, await bridgeGet("/healthz"), renderBridgeStatus);
|
|
@@ -293,6 +304,31 @@ async function main(argv) {
|
|
|
293
304
|
if (area === "auth" && action === "status") {
|
|
294
305
|
return output(parsed, ok(await authStatus()), renderAuthStatus);
|
|
295
306
|
}
|
|
307
|
+
if (area === "auth" && action === "device") {
|
|
308
|
+
const deviceAction = parsed.command[2] ?? "status";
|
|
309
|
+
if (deviceAction === "status") {
|
|
310
|
+
const result = await authDeviceStatus(parsed);
|
|
311
|
+
return output(parsed, result, renderAuthDeviceStatus, result.ok ? 0 : 2);
|
|
312
|
+
}
|
|
313
|
+
if (deviceAction === "list") {
|
|
314
|
+
const result = await authDeviceList(parsed);
|
|
315
|
+
return output(parsed, result, renderAuthDeviceList, result.ok ? 0 : 2);
|
|
316
|
+
}
|
|
317
|
+
if (deviceAction === "revoke") {
|
|
318
|
+
const result = await authDeviceRevoke(parsed);
|
|
319
|
+
return output(parsed, result, renderAuthDevice, result.ok ? 0 : 2);
|
|
320
|
+
}
|
|
321
|
+
if (deviceAction === "rename") {
|
|
322
|
+
const result = await authDeviceRename(parsed);
|
|
323
|
+
return output(parsed, result, renderAuthDevice, result.ok ? 0 : 2);
|
|
324
|
+
}
|
|
325
|
+
return output(parsed, fail("invalid_args", [
|
|
326
|
+
AUTH_DEVICE_STATUS_USAGE,
|
|
327
|
+
AUTH_DEVICE_LIST_USAGE,
|
|
328
|
+
AUTH_DEVICE_REVOKE_USAGE,
|
|
329
|
+
AUTH_DEVICE_RENAME_USAGE
|
|
330
|
+
].join("\n")), undefined, 2);
|
|
331
|
+
}
|
|
296
332
|
if (area === "auth" && action === "logout") {
|
|
297
333
|
await rm(DEFAULT_AUTH_FILE, { force: true });
|
|
298
334
|
return output(parsed, ok(await authStatus()), renderAuthStatus);
|
|
@@ -1406,17 +1442,23 @@ async function cloudPost(path, body) {
|
|
|
1406
1442
|
async function cloudDownloadArtifact(artifactId, outputPath) {
|
|
1407
1443
|
try {
|
|
1408
1444
|
const headers = {};
|
|
1409
|
-
const
|
|
1410
|
-
if (token) {
|
|
1411
|
-
|
|
1412
|
-
|
|
1445
|
+
const auth = await cloudAuthConfig();
|
|
1446
|
+
if (auth.token) {
|
|
1447
|
+
if (auth.device) {
|
|
1448
|
+
const integrity = await validateLocalDeviceIntegrity(auth.device);
|
|
1449
|
+
if (!integrity.ok) {
|
|
1450
|
+
return integrity;
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
headers.authorization = `Bearer ${auth.token}`;
|
|
1454
|
+
addCloudRequestSignature(headers, "GET", `/v1/artifacts/${encodeURIComponent(artifactId)}/download`, "", auth.token, auth.device);
|
|
1413
1455
|
}
|
|
1414
1456
|
const response = await fetch(`${serviceBaseUrl(DEFAULT_CLOUD_API_URL)}/v1/artifacts/${encodeURIComponent(artifactId)}/download`, {
|
|
1415
1457
|
headers: Object.keys(headers).length > 0 ? headers : undefined
|
|
1416
1458
|
});
|
|
1417
1459
|
if (!response.ok) {
|
|
1418
1460
|
const parsed = await parseErrorResponse(response);
|
|
1419
|
-
return parsed ? enrichCloudAuthFailure(parsed, Boolean(token)) : fail("artifact_download_failed", `Artifact download failed with HTTP ${response.status}.`);
|
|
1461
|
+
return parsed ? enrichCloudAuthFailure(parsed, Boolean(auth.token)) : fail("artifact_download_failed", `Artifact download failed with HTTP ${response.status}.`);
|
|
1420
1462
|
}
|
|
1421
1463
|
const bytes = Buffer.from(await response.arrayBuffer());
|
|
1422
1464
|
const expectedSha256 = response.headers.get("x-embed-artifact-sha256")?.trim();
|
|
@@ -1449,10 +1491,16 @@ async function cloudRequest(method, path, body) {
|
|
|
1449
1491
|
if (bodyText) {
|
|
1450
1492
|
headers["content-type"] = "application/json";
|
|
1451
1493
|
}
|
|
1452
|
-
const
|
|
1453
|
-
if (token) {
|
|
1454
|
-
|
|
1455
|
-
|
|
1494
|
+
const auth = await cloudAuthConfig();
|
|
1495
|
+
if (auth.token) {
|
|
1496
|
+
if (auth.device) {
|
|
1497
|
+
const integrity = await validateLocalDeviceIntegrity(auth.device);
|
|
1498
|
+
if (!integrity.ok) {
|
|
1499
|
+
return integrity;
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
headers.authorization = `Bearer ${auth.token}`;
|
|
1503
|
+
addCloudRequestSignature(headers, method, path, bodyText, auth.token, auth.device);
|
|
1456
1504
|
}
|
|
1457
1505
|
const response = await fetch(`${serviceBaseUrl(DEFAULT_CLOUD_API_URL)}${path}`, {
|
|
1458
1506
|
method,
|
|
@@ -1460,7 +1508,7 @@ async function cloudRequest(method, path, body) {
|
|
|
1460
1508
|
body: body === undefined ? undefined : bodyText
|
|
1461
1509
|
});
|
|
1462
1510
|
const parsed = await response.json();
|
|
1463
|
-
return enrichCloudAuthFailure(parsed, Boolean(token));
|
|
1511
|
+
return enrichCloudAuthFailure(parsed, Boolean(auth.token));
|
|
1464
1512
|
}
|
|
1465
1513
|
catch (error) {
|
|
1466
1514
|
return fail("cloud_api_unreachable", error instanceof Error ? error.message : String(error), {
|
|
@@ -1468,8 +1516,42 @@ async function cloudRequest(method, path, body) {
|
|
|
1468
1516
|
});
|
|
1469
1517
|
}
|
|
1470
1518
|
}
|
|
1519
|
+
async function validateLocalDeviceIntegrity(device) {
|
|
1520
|
+
const current = await localHardwareFingerprint();
|
|
1521
|
+
if (current.fingerprint_hash === device.fingerprint_hash) {
|
|
1522
|
+
return ok(undefined);
|
|
1523
|
+
}
|
|
1524
|
+
return fail("tool_integrity_check_failed", TOOL_INTEGRITY_RELOGIN_MESSAGE, {
|
|
1525
|
+
remediation: [
|
|
1526
|
+
"当前 Embed Labs CLI/插件配置绑定的电脑与本机硬件唯一码不一致。",
|
|
1527
|
+
TOOL_INTEGRITY_RELOGIN_MESSAGE,
|
|
1528
|
+
"如果账号设备数量已达上限,请先在原电脑或用户后台撤销旧设备。"
|
|
1529
|
+
].join("\n"),
|
|
1530
|
+
details: {
|
|
1531
|
+
expected_fingerprint_hash: device.fingerprint_hash,
|
|
1532
|
+
current_fingerprint_hash: current.fingerprint_hash,
|
|
1533
|
+
platform: current.platform,
|
|
1534
|
+
arch: current.arch,
|
|
1535
|
+
fingerprint_source: current.source
|
|
1536
|
+
}
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1471
1539
|
function enrichCloudAuthFailure(response, hadToken) {
|
|
1472
|
-
if (response.ok
|
|
1540
|
+
if (response.ok) {
|
|
1541
|
+
return response;
|
|
1542
|
+
}
|
|
1543
|
+
if (response.error.code.startsWith("device_") || response.error.code.startsWith("request_signature_")) {
|
|
1544
|
+
return fail(response.error.code, response.error.message, {
|
|
1545
|
+
remediation: [
|
|
1546
|
+
"This computer is not fully registered for the configured Embed Labs API Token.",
|
|
1547
|
+
"Run: embedlabs auth login --token <your_token>",
|
|
1548
|
+
"Then verify with: embedlabs auth device status",
|
|
1549
|
+
"If the account already has too many devices, revoke one with: embedlabs auth device revoke <device_id>"
|
|
1550
|
+
].join("\n"),
|
|
1551
|
+
details: response.error.details
|
|
1552
|
+
});
|
|
1553
|
+
}
|
|
1554
|
+
if (response.error.code !== "unauthorized") {
|
|
1473
1555
|
return response;
|
|
1474
1556
|
}
|
|
1475
1557
|
if (!hadToken) {
|
|
@@ -1501,7 +1583,7 @@ function cloudAuthSetupDetails() {
|
|
|
1501
1583
|
auth_file: DEFAULT_AUTH_FILE
|
|
1502
1584
|
};
|
|
1503
1585
|
}
|
|
1504
|
-
function addCloudRequestSignature(headers, method, pathWithQuery, bodyText, token) {
|
|
1586
|
+
function addCloudRequestSignature(headers, method, pathWithQuery, bodyText, token, device) {
|
|
1505
1587
|
if (process.env.EMBED_CLOUD_API_SIGNING === "0") {
|
|
1506
1588
|
return;
|
|
1507
1589
|
}
|
|
@@ -1509,11 +1591,21 @@ function addCloudRequestSignature(headers, method, pathWithQuery, bodyText, toke
|
|
|
1509
1591
|
const nonce = randomBytes(16).toString("hex");
|
|
1510
1592
|
const bodySha256 = createHash("sha256").update(bodyText).digest("hex");
|
|
1511
1593
|
const keyId = createHash("sha256").update(token).digest("hex").slice(0, 16);
|
|
1512
|
-
const canonical =
|
|
1594
|
+
const canonical = device
|
|
1595
|
+
? cloudRequestCanonicalStringV2(method, pathWithQuery, timestamp, nonce, bodySha256, device, EMBED_CLIENT_NAME, EMBED_CLIENT_VERSION)
|
|
1596
|
+
: cloudRequestCanonicalString(method, pathWithQuery, timestamp, nonce, bodySha256);
|
|
1513
1597
|
headers["x-embed-key-id"] = keyId;
|
|
1514
1598
|
headers["x-embed-timestamp"] = timestamp;
|
|
1515
1599
|
headers["x-embed-nonce"] = nonce;
|
|
1516
1600
|
headers["x-embed-body-sha256"] = bodySha256;
|
|
1601
|
+
if (device) {
|
|
1602
|
+
headers["x-embed-signature-version"] = "v2";
|
|
1603
|
+
headers["x-embed-device-id"] = device.device_id;
|
|
1604
|
+
headers["x-embed-device-fingerprint-sha256"] = device.fingerprint_hash;
|
|
1605
|
+
headers["x-embed-client-name"] = EMBED_CLIENT_NAME;
|
|
1606
|
+
headers["x-embed-client-version"] = EMBED_CLIENT_VERSION;
|
|
1607
|
+
headers["x-embed-device-signature"] = signCrypto(null, Buffer.from(canonical), device.private_key_pem).toString("base64url");
|
|
1608
|
+
}
|
|
1517
1609
|
headers["x-embed-signature"] = createHmac("sha256", token).update(canonical).digest("hex");
|
|
1518
1610
|
}
|
|
1519
1611
|
function cloudRequestCanonicalString(method, pathWithQuery, timestamp, nonce, bodySha256) {
|
|
@@ -1525,6 +1617,19 @@ function cloudRequestCanonicalString(method, pathWithQuery, timestamp, nonce, bo
|
|
|
1525
1617
|
bodySha256
|
|
1526
1618
|
].join("\n");
|
|
1527
1619
|
}
|
|
1620
|
+
function cloudRequestCanonicalStringV2(method, pathWithQuery, timestamp, nonce, bodySha256, device, clientName, clientVersion) {
|
|
1621
|
+
return [
|
|
1622
|
+
method.toUpperCase(),
|
|
1623
|
+
normalizeCloudPathForSignature(pathWithQuery),
|
|
1624
|
+
timestamp,
|
|
1625
|
+
nonce,
|
|
1626
|
+
bodySha256,
|
|
1627
|
+
device.device_id,
|
|
1628
|
+
device.fingerprint_hash,
|
|
1629
|
+
clientName,
|
|
1630
|
+
clientVersion
|
|
1631
|
+
].join("\n");
|
|
1632
|
+
}
|
|
1528
1633
|
function normalizeCloudPathForSignature(pathWithQuery) {
|
|
1529
1634
|
try {
|
|
1530
1635
|
const parsed = new URL(pathWithQuery, "http://embed.local");
|
|
@@ -1951,17 +2056,22 @@ async function cleanupLegacyCodexPluginRemnants(targetRoot) {
|
|
|
1951
2056
|
const warnings = [];
|
|
1952
2057
|
const stoppedProcesses = await stopLegacyCodexPluginProcesses(warnings);
|
|
1953
2058
|
const legacyPaths = [
|
|
1954
|
-
join(targetRoot, "
|
|
1955
|
-
join(targetRoot, "development-board-toolchain"),
|
|
1956
|
-
join(targetRoot, "cache", CODEX_MARKETPLACE_NAME, CODEX_PLUGIN_NAME),
|
|
1957
|
-
join(targetRoot, "cache", "embed-labs", "dbt-agent"),
|
|
1958
|
-
join(targetRoot, "cache", "dbt-agent"),
|
|
1959
|
-
join(targetRoot, "cache", "plugins", "dbt-agent")
|
|
2059
|
+
join(targetRoot, "cache", CODEX_MARKETPLACE_NAME, CODEX_PLUGIN_NAME)
|
|
1960
2060
|
];
|
|
2061
|
+
for (const marketplaceName of LEGACY_CODEX_MARKETPLACE_NAMES) {
|
|
2062
|
+
legacyPaths.push(join(targetRoot, "cache", marketplaceName, CODEX_PLUGIN_NAME));
|
|
2063
|
+
}
|
|
2064
|
+
for (const pluginName of LEGACY_CODEX_PLUGIN_NAMES) {
|
|
2065
|
+
legacyPaths.push(join(targetRoot, pluginName));
|
|
2066
|
+
legacyPaths.push(join(targetRoot, "cache", pluginName));
|
|
2067
|
+
for (const marketplaceName of [CODEX_MARKETPLACE_NAME, ...LEGACY_CODEX_MARKETPLACE_NAMES]) {
|
|
2068
|
+
legacyPaths.push(join(targetRoot, "cache", marketplaceName, pluginName));
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
1961
2071
|
legacyPaths.push(...await discoverLegacyCodexCachePaths(targetRoot));
|
|
1962
2072
|
if (resolve(targetRoot) === resolve(defaultCodexPluginRoot())) {
|
|
1963
2073
|
legacyPaths.push(join(defaultCodexHome(), ".tmp", "plugins"), join(defaultCodexHome(), ".tmp", "plugins.sha"), join(defaultCodexHome(), "ambient-suggestions"), join(defaultCodexHome(), "skills", "dbt-agent"), join(defaultCodexHome(), "skills", "development-board-toolchain-dev"), join(defaultCodexHome(), "memories", "skills", "dbt-agent-live-board-ops"), join(defaultCodexHome(), "memories", "skills", "dbt-agent-platform-plugin-maintenance"));
|
|
1964
|
-
legacyPaths.push(...await discoverLegacyHomeAgentsMarketplacePaths(warnings), ...legacyDevelopmentBoardRuntimePluginPaths());
|
|
2074
|
+
legacyPaths.push(...legacyCodexLocalMarketplacePaths(), ...await discoverLegacyHomeAgentsMarketplacePaths(warnings), ...await discoverLegacyCodexProjectMarketplacePaths(warnings), ...legacyDevelopmentBoardRuntimePluginPaths());
|
|
1965
2075
|
}
|
|
1966
2076
|
for (const candidate of legacyPaths) {
|
|
1967
2077
|
try {
|
|
@@ -2054,6 +2164,11 @@ function legacyDevelopmentBoardRuntimePluginPaths() {
|
|
|
2054
2164
|
join(homedir(), "Library", "Application Support", "development-board-toolchain", "runtime", "opencode_plugin")
|
|
2055
2165
|
];
|
|
2056
2166
|
}
|
|
2167
|
+
function legacyCodexLocalMarketplacePaths() {
|
|
2168
|
+
return Array.from(LEGACY_CODEX_MARKETPLACE_NAMES)
|
|
2169
|
+
.filter((name) => name !== CODEX_MARKETPLACE_NAME)
|
|
2170
|
+
.map((name) => join(defaultCodexHome(), "local-marketplaces", name));
|
|
2171
|
+
}
|
|
2057
2172
|
async function discoverLegacyHomeAgentsMarketplacePaths(warnings) {
|
|
2058
2173
|
const paths = [];
|
|
2059
2174
|
const pluginRoot = join(homedir(), ".agents", "plugins");
|
|
@@ -2080,9 +2195,72 @@ async function discoverLegacyHomeAgentsMarketplacePaths(warnings) {
|
|
|
2080
2195
|
return paths;
|
|
2081
2196
|
}
|
|
2082
2197
|
function isLegacyHomeAgentsMarketplace(text) {
|
|
2083
|
-
return
|
|
2084
|
-
|
|
2085
|
-
|
|
2198
|
+
return marketplaceTextHasLegacyCodexPlugin(text);
|
|
2199
|
+
}
|
|
2200
|
+
async function discoverLegacyCodexProjectMarketplacePaths(warnings) {
|
|
2201
|
+
const configPath = codexConfigPath();
|
|
2202
|
+
let text = "";
|
|
2203
|
+
try {
|
|
2204
|
+
text = await readFile(configPath, "utf8");
|
|
2205
|
+
}
|
|
2206
|
+
catch {
|
|
2207
|
+
return [];
|
|
2208
|
+
}
|
|
2209
|
+
const paths = new Set();
|
|
2210
|
+
for (const projectPath of legacyCodexProjectPathsFromConfig(text)) {
|
|
2211
|
+
for (const candidate of [
|
|
2212
|
+
join(projectPath, ".agents", "plugins", "marketplace.json"),
|
|
2213
|
+
join(projectPath, "platform_plugin", ".agents", "plugins", "marketplace.json"),
|
|
2214
|
+
join(projectPath, "platform_plugins", "codex_plugin", ".agents", "plugins", "marketplace.json")
|
|
2215
|
+
]) {
|
|
2216
|
+
try {
|
|
2217
|
+
if (!await pathExists(candidate))
|
|
2218
|
+
continue;
|
|
2219
|
+
const current = await readFile(candidate, "utf8");
|
|
2220
|
+
if (marketplaceTextHasLegacyCodexPlugin(current)) {
|
|
2221
|
+
paths.add(candidate);
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
catch (error) {
|
|
2225
|
+
warnings.push(`Could not inspect legacy Codex project marketplace ${candidate}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
return Array.from(paths);
|
|
2230
|
+
}
|
|
2231
|
+
function legacyCodexProjectPathsFromConfig(text) {
|
|
2232
|
+
const paths = [];
|
|
2233
|
+
const lines = text.match(/[^\n]*\n|[^\n]+$/g) ?? [];
|
|
2234
|
+
for (const line of lines) {
|
|
2235
|
+
const table = parseTomlTableHeader(line);
|
|
2236
|
+
const match = table ? /^projects\."([^"]+)"$/.exec(table) : undefined;
|
|
2237
|
+
if (match?.[1] && /DBT-Agent-Project|development-board-toolchain|dbt-agent/i.test(match[1])) {
|
|
2238
|
+
paths.push(match[1].replace(/\\"/g, '"'));
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
return paths;
|
|
2242
|
+
}
|
|
2243
|
+
function marketplaceTextHasLegacyCodexPlugin(text) {
|
|
2244
|
+
try {
|
|
2245
|
+
const parsed = JSON.parse(text);
|
|
2246
|
+
const marketplaceName = typeof parsed.name === "string" ? parsed.name : "";
|
|
2247
|
+
const marketplaceDisplayName = typeof parsed.interface?.displayName === "string" ? parsed.interface.displayName : "";
|
|
2248
|
+
const marketplaceLooksLegacy = legacyTextHasCodexPluginResidue(marketplaceName)
|
|
2249
|
+
|| legacyTextHasCodexPluginResidue(marketplaceDisplayName)
|
|
2250
|
+
|| LEGACY_CODEX_MARKETPLACE_NAMES.has(marketplaceName);
|
|
2251
|
+
return (parsed.plugins ?? []).some((plugin) => {
|
|
2252
|
+
const values = [
|
|
2253
|
+
plugin.name,
|
|
2254
|
+
plugin.category,
|
|
2255
|
+
plugin.source?.path,
|
|
2256
|
+
plugin.interface?.displayName
|
|
2257
|
+
].filter((value) => typeof value === "string");
|
|
2258
|
+
return values.some(legacyTextHasCodexPluginResidue) || marketplaceLooksLegacy && values.some((value) => /embed-labs|dbt|development-board/i.test(value));
|
|
2259
|
+
});
|
|
2260
|
+
}
|
|
2261
|
+
catch {
|
|
2262
|
+
return legacyTextHasCodexPluginResidue(text);
|
|
2263
|
+
}
|
|
2086
2264
|
}
|
|
2087
2265
|
async function discoverLegacyCodexCachePaths(targetRoot) {
|
|
2088
2266
|
const paths = [];
|
|
@@ -2092,8 +2270,12 @@ async function discoverLegacyCodexCachePaths(targetRoot) {
|
|
|
2092
2270
|
for (const entry of marketplaces) {
|
|
2093
2271
|
if (!entry.isDirectory())
|
|
2094
2272
|
continue;
|
|
2095
|
-
|
|
2096
|
-
|
|
2273
|
+
if (LEGACY_CODEX_MARKETPLACE_NAMES.has(entry.name)) {
|
|
2274
|
+
paths.push(join(cacheRoot, entry.name, CODEX_PLUGIN_NAME));
|
|
2275
|
+
}
|
|
2276
|
+
for (const pluginName of LEGACY_CODEX_PLUGIN_NAMES) {
|
|
2277
|
+
paths.push(join(cacheRoot, entry.name, pluginName));
|
|
2278
|
+
}
|
|
2097
2279
|
}
|
|
2098
2280
|
}
|
|
2099
2281
|
catch {
|
|
@@ -2139,6 +2321,8 @@ function isLegacyCodexHistoryMention(line) {
|
|
|
2139
2321
|
|| line.includes("plugin://dbt-agent@embed-labs")
|
|
2140
2322
|
|| line.includes("development-board-toolchain-dev")
|
|
2141
2323
|
|| line.includes("development-board-toolchain")
|
|
2324
|
+
|| /plugin:\/\/Dbt Agent@/i.test(line)
|
|
2325
|
+
|| /plugin:\/\/deve@/i.test(line)
|
|
2142
2326
|
|| /dbt-agent/i.test(line);
|
|
2143
2327
|
}
|
|
2144
2328
|
function removeLegacyCodexConfigTables(text) {
|
|
@@ -2167,11 +2351,15 @@ function parseTomlTableHeader(line) {
|
|
|
2167
2351
|
}
|
|
2168
2352
|
function isLegacyCodexConfigTable(table) {
|
|
2169
2353
|
return /^plugins\."dbt-agent@[^"]+"$/.test(table)
|
|
2354
|
+
|| /^plugins\."Dbt Agent@[^"]+"$/i.test(table)
|
|
2355
|
+
|| /^plugins\."deve@[^"]+"$/i.test(table)
|
|
2170
2356
|
|| isLegacyEmbedLabsCodexMarketplaceConfigTable(table)
|
|
2171
2357
|
|| table === "mcp_servers.dbt-agent"
|
|
2172
2358
|
|| table.startsWith("mcp_servers.dbt-agent.")
|
|
2173
2359
|
|| table === 'mcp_servers."dbt-agent"'
|
|
2174
2360
|
|| table.startsWith('mcp_servers."dbt-agent".')
|
|
2361
|
+
|| table === "mcp_servers.deve"
|
|
2362
|
+
|| table.startsWith("mcp_servers.deve.")
|
|
2175
2363
|
|| /^projects\."[^"]*\/DBT-Agent-Project(?:\/[^"]*)?"$/.test(table);
|
|
2176
2364
|
}
|
|
2177
2365
|
function isLegacyEmbedLabsCodexMarketplaceConfigTable(table) {
|
|
@@ -2185,6 +2373,12 @@ function isLegacyEmbedLabsCodexMarketplaceConfigTable(table) {
|
|
|
2185
2373
|
}
|
|
2186
2374
|
return false;
|
|
2187
2375
|
}
|
|
2376
|
+
function legacyTextHasCodexPluginResidue(value) {
|
|
2377
|
+
return /dbt-agent|Dbt Agent|development-board-toolchain|development-board-toolchain-dev/i.test(value)
|
|
2378
|
+
|| value === "deve"
|
|
2379
|
+
|| value.replace(/\\/g, "/").includes("/plugins/deve")
|
|
2380
|
+
|| value.includes("plugin://deve@");
|
|
2381
|
+
}
|
|
2188
2382
|
function legacyCodexCleanupWarning(cleanup) {
|
|
2189
2383
|
const parts = [];
|
|
2190
2384
|
if (cleanup.legacy_removed_paths.length > 0) {
|
|
@@ -2209,12 +2403,19 @@ async function cleanupLegacyOpenCodePluginRemnants(targetRoot, globalInstall) {
|
|
|
2209
2403
|
const warnings = [];
|
|
2210
2404
|
const legacyPaths = [
|
|
2211
2405
|
join(targetRoot, "plugins", "development-board-toolchain.js"),
|
|
2406
|
+
join(targetRoot, "plugins", "development-board-toolchain-dev.js"),
|
|
2212
2407
|
join(targetRoot, "plugins", "dbt-agent.js"),
|
|
2408
|
+
join(targetRoot, "plugins", "Dbt Agent.js"),
|
|
2409
|
+
join(targetRoot, "plugins", "deve.js"),
|
|
2410
|
+
join(targetRoot, "plugins", "deve"),
|
|
2411
|
+
join(targetRoot, "node_modules", "development-board-toolchain"),
|
|
2412
|
+
join(targetRoot, "node_modules", "development-board-toolchain-dev"),
|
|
2213
2413
|
join(targetRoot, "node_modules", "dbt-agent")
|
|
2214
2414
|
];
|
|
2215
2415
|
if (globalInstall) {
|
|
2216
2416
|
legacyPaths.push(join(targetRoot, "plugins", "embed-labs.js"));
|
|
2217
2417
|
legacyPaths.push(...await discoverLegacyOpenCodeBackupPaths(targetRoot, warnings));
|
|
2418
|
+
legacyPaths.push(...await discoverLegacyOpenCodePluginCachePaths(targetRoot, warnings));
|
|
2218
2419
|
}
|
|
2219
2420
|
for (const candidate of legacyPaths) {
|
|
2220
2421
|
try {
|
|
@@ -2243,7 +2444,7 @@ async function discoverLegacyOpenCodeBackupPaths(targetRoot, warnings) {
|
|
|
2243
2444
|
const filePath = join(targetRoot, entry.name);
|
|
2244
2445
|
try {
|
|
2245
2446
|
const current = await readFile(filePath, "utf8");
|
|
2246
|
-
if (
|
|
2447
|
+
if (legacyTextHasCodexPluginResidue(current)) {
|
|
2247
2448
|
paths.push(filePath);
|
|
2248
2449
|
}
|
|
2249
2450
|
}
|
|
@@ -2257,6 +2458,26 @@ async function discoverLegacyOpenCodeBackupPaths(targetRoot, warnings) {
|
|
|
2257
2458
|
}
|
|
2258
2459
|
return paths;
|
|
2259
2460
|
}
|
|
2461
|
+
async function discoverLegacyOpenCodePluginCachePaths(targetRoot, warnings) {
|
|
2462
|
+
const paths = [];
|
|
2463
|
+
const cacheRoot = join(targetRoot, ".embed-labs", "plugin-cache");
|
|
2464
|
+
try {
|
|
2465
|
+
const entries = await readdir(cacheRoot, { withFileTypes: true });
|
|
2466
|
+
for (const entry of entries) {
|
|
2467
|
+
if (!entry.isFile())
|
|
2468
|
+
continue;
|
|
2469
|
+
if (/^(embed-labs|embed-labs-opencode-plugin)-\d+\.\d+\.\d+\.tgz$/.test(entry.name)) {
|
|
2470
|
+
paths.push(join(cacheRoot, entry.name));
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
catch (error) {
|
|
2475
|
+
if (error.code !== "ENOENT") {
|
|
2476
|
+
warnings.push(`Could not inspect OpenCode plugin cache ${cacheRoot}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
return paths;
|
|
2480
|
+
}
|
|
2260
2481
|
function legacyOpenCodeCleanupWarning(cleanup) {
|
|
2261
2482
|
const parts = [];
|
|
2262
2483
|
if (cleanup.legacy_removed_paths.length > 0) {
|
|
@@ -2553,6 +2774,97 @@ async function resolveExecutableOnPath(name) {
|
|
|
2553
2774
|
}
|
|
2554
2775
|
return undefined;
|
|
2555
2776
|
}
|
|
2777
|
+
async function runBridgeStart(parsed) {
|
|
2778
|
+
const host = stringFlag(parsed, "host");
|
|
2779
|
+
const port = numberFlag(parsed, "port");
|
|
2780
|
+
const bridge = await resolveBridgeLauncher();
|
|
2781
|
+
const args = [...bridge.args];
|
|
2782
|
+
if (host) {
|
|
2783
|
+
args.push("--host", host);
|
|
2784
|
+
}
|
|
2785
|
+
if (port !== undefined) {
|
|
2786
|
+
args.push("--port", String(port));
|
|
2787
|
+
}
|
|
2788
|
+
const env = {
|
|
2789
|
+
...process.env,
|
|
2790
|
+
...(host ? { EMBED_BRIDGE_HOST: host } : {}),
|
|
2791
|
+
...(port !== undefined ? { EMBED_BRIDGE_PORT: String(port) } : {})
|
|
2792
|
+
};
|
|
2793
|
+
const child = spawn(bridge.command, args, {
|
|
2794
|
+
stdio: "inherit",
|
|
2795
|
+
env
|
|
2796
|
+
});
|
|
2797
|
+
const forwardSignal = (signal) => {
|
|
2798
|
+
if (!child.killed) {
|
|
2799
|
+
child.kill(signal);
|
|
2800
|
+
}
|
|
2801
|
+
};
|
|
2802
|
+
process.once("SIGINT", forwardSignal);
|
|
2803
|
+
process.once("SIGTERM", forwardSignal);
|
|
2804
|
+
return await new Promise((resolveCode) => {
|
|
2805
|
+
child.on("error", (error) => {
|
|
2806
|
+
process.off("SIGINT", forwardSignal);
|
|
2807
|
+
process.off("SIGTERM", forwardSignal);
|
|
2808
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
2809
|
+
resolveCode(1);
|
|
2810
|
+
});
|
|
2811
|
+
child.on("close", (code, signal) => {
|
|
2812
|
+
process.off("SIGINT", forwardSignal);
|
|
2813
|
+
process.off("SIGTERM", forwardSignal);
|
|
2814
|
+
if (signal === "SIGINT" || signal === "SIGTERM") {
|
|
2815
|
+
resolveCode(0);
|
|
2816
|
+
}
|
|
2817
|
+
else {
|
|
2818
|
+
resolveCode(code ?? 0);
|
|
2819
|
+
}
|
|
2820
|
+
});
|
|
2821
|
+
});
|
|
2822
|
+
}
|
|
2823
|
+
async function resolveBridgeLauncher() {
|
|
2824
|
+
const explicitBinary = process.env.EMBED_LOCAL_BRIDGE_BINARY?.trim();
|
|
2825
|
+
if (explicitBinary) {
|
|
2826
|
+
try {
|
|
2827
|
+
await access(explicitBinary, constants.X_OK);
|
|
2828
|
+
return { command: explicitBinary, args: [] };
|
|
2829
|
+
}
|
|
2830
|
+
catch {
|
|
2831
|
+
// Fall through so the package launcher can print its clearer repair message.
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
const pathBinary = await resolveExecutableOnPath(process.platform === "win32" ? "embed-local-bridge.cmd" : "embed-local-bridge");
|
|
2835
|
+
if (pathBinary) {
|
|
2836
|
+
return { command: pathBinary, args: [] };
|
|
2837
|
+
}
|
|
2838
|
+
const packageLauncher = await resolveBridgePackageLauncher();
|
|
2839
|
+
if (packageLauncher) {
|
|
2840
|
+
return { command: process.execPath, args: [packageLauncher] };
|
|
2841
|
+
}
|
|
2842
|
+
return {
|
|
2843
|
+
command: process.execPath,
|
|
2844
|
+
args: [resolve(SOURCE_CHECKOUT_ROOT, "packages", "local-bridge", "dist", "index.js")]
|
|
2845
|
+
};
|
|
2846
|
+
}
|
|
2847
|
+
async function resolveBridgePackageLauncher() {
|
|
2848
|
+
const candidates = [];
|
|
2849
|
+
try {
|
|
2850
|
+
const packageJson = require.resolve("@embed-labs/local-bridge/package.json");
|
|
2851
|
+
candidates.push(join(dirname(packageJson), "dist", "index.js"));
|
|
2852
|
+
}
|
|
2853
|
+
catch {
|
|
2854
|
+
// Source checkout fallback below.
|
|
2855
|
+
}
|
|
2856
|
+
candidates.push(resolve(SOURCE_CHECKOUT_ROOT, "packages", "local-bridge", "dist", "index.js"));
|
|
2857
|
+
for (const candidate of candidates) {
|
|
2858
|
+
try {
|
|
2859
|
+
await access(candidate, constants.R_OK);
|
|
2860
|
+
return candidate;
|
|
2861
|
+
}
|
|
2862
|
+
catch {
|
|
2863
|
+
// Keep looking.
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
return undefined;
|
|
2867
|
+
}
|
|
2556
2868
|
function defaultOpenCodeRoot() {
|
|
2557
2869
|
return globalOpenCodeRoot();
|
|
2558
2870
|
}
|
|
@@ -2592,12 +2904,26 @@ async function ensureOpenCodeGlobalPluginConfig() {
|
|
|
2592
2904
|
return removed;
|
|
2593
2905
|
}
|
|
2594
2906
|
function isLegacyOpenCodePluginConfigEntry(item) {
|
|
2595
|
-
const normalized = item.replace(/\\/g, "/");
|
|
2596
|
-
|
|
2597
|
-
|
|
2907
|
+
const normalized = item.trim().replace(/\\/g, "/");
|
|
2908
|
+
const pathOnly = normalized.split(/[?#]/, 1)[0] || normalized;
|
|
2909
|
+
return normalized === "dbt-agent"
|
|
2910
|
+
|| normalized === "Dbt Agent"
|
|
2911
|
+
|| normalized === "deve"
|
|
2912
|
+
|| normalized === "development-board-toolchain"
|
|
2913
|
+
|| normalized === "development-board-toolchain-dev"
|
|
2914
|
+
|| normalized === "./plugins/deve"
|
|
2915
|
+
|| normalized === "./plugins/deve.js"
|
|
2916
|
+
|| /(?:^|\/)plugins\/deve(?:\.js)?$/.test(pathOnly)
|
|
2917
|
+
|| /(?:^|\/)plugins\/dbt-agent(?:\.js)?$/.test(pathOnly)
|
|
2598
2918
|
|| normalized === "./plugins/development-board-toolchain"
|
|
2599
2919
|
|| normalized === "./plugins/development-board-toolchain.js"
|
|
2600
|
-
|| normalized
|
|
2920
|
+
|| normalized === "./plugins/development-board-toolchain-dev"
|
|
2921
|
+
|| normalized === "./plugins/development-board-toolchain-dev.js"
|
|
2922
|
+
|| pathOnly.endsWith("/plugins/development-board-toolchain")
|
|
2923
|
+
|| pathOnly.endsWith("/plugins/development-board-toolchain.js")
|
|
2924
|
+
|| pathOnly.endsWith("/plugins/development-board-toolchain-dev")
|
|
2925
|
+
|| pathOnly.endsWith("/plugins/development-board-toolchain-dev.js")
|
|
2926
|
+
|| normalized.includes("dbt-agent")
|
|
2601
2927
|
|| normalized.includes("development-board-toolchain");
|
|
2602
2928
|
}
|
|
2603
2929
|
async function openCodeDuplicatePluginWarning(targetRoot) {
|
|
@@ -2691,19 +3017,65 @@ async function parseErrorResponse(response) {
|
|
|
2691
3017
|
return undefined;
|
|
2692
3018
|
}
|
|
2693
3019
|
async function cloudAuthToken() {
|
|
3020
|
+
return (await cloudAuthConfig()).token;
|
|
3021
|
+
}
|
|
3022
|
+
async function cloudAuthConfig() {
|
|
2694
3023
|
const envToken = process.env.EMBED_API_TOKEN?.trim();
|
|
2695
3024
|
if (envToken) {
|
|
2696
|
-
|
|
3025
|
+
const fileConfig = await readLocalAuthFile();
|
|
3026
|
+
return {
|
|
3027
|
+
...fileConfig,
|
|
3028
|
+
token: envToken,
|
|
3029
|
+
profile: process.env.EMBED_AUTH_PROFILE ?? fileConfig.profile ?? "default",
|
|
3030
|
+
source: "env"
|
|
3031
|
+
};
|
|
2697
3032
|
}
|
|
3033
|
+
const fileConfig = await readLocalAuthFile();
|
|
3034
|
+
return {
|
|
3035
|
+
...fileConfig,
|
|
3036
|
+
token: fileConfig.token?.trim() || undefined,
|
|
3037
|
+
profile: fileConfig.profile ?? "default",
|
|
3038
|
+
source: fileConfig.token ? "file" : undefined
|
|
3039
|
+
};
|
|
3040
|
+
}
|
|
3041
|
+
async function readLocalAuthFile() {
|
|
2698
3042
|
try {
|
|
2699
3043
|
const parsed = JSON.parse(await readFile(DEFAULT_AUTH_FILE, "utf8"));
|
|
2700
|
-
|
|
2701
|
-
return fileToken || undefined;
|
|
3044
|
+
return normalizeLocalAuthFile(parsed);
|
|
2702
3045
|
}
|
|
2703
3046
|
catch {
|
|
2704
|
-
return
|
|
3047
|
+
return {};
|
|
2705
3048
|
}
|
|
2706
3049
|
}
|
|
3050
|
+
function normalizeLocalAuthFile(parsed) {
|
|
3051
|
+
const device = isJsonObject(parsed.device) ? parsed.device : undefined;
|
|
3052
|
+
const normalizedDevice = device && typeof device.device_id === "string" && typeof device.fingerprint_hash === "string" && typeof device.private_key_pem === "string"
|
|
3053
|
+
? {
|
|
3054
|
+
device_id: device.device_id,
|
|
3055
|
+
fingerprint_hash: device.fingerprint_hash,
|
|
3056
|
+
private_key_pem: device.private_key_pem,
|
|
3057
|
+
public_key_pem: typeof device.public_key_pem === "string" ? device.public_key_pem : undefined,
|
|
3058
|
+
label: typeof device.label === "string" ? device.label : undefined,
|
|
3059
|
+
platform: typeof device.platform === "string" ? device.platform : undefined,
|
|
3060
|
+
arch: typeof device.arch === "string" ? device.arch : undefined,
|
|
3061
|
+
hostname_hash: typeof device.hostname_hash === "string" ? device.hostname_hash : undefined,
|
|
3062
|
+
registered_at: typeof device.registered_at === "string" ? device.registered_at : undefined
|
|
3063
|
+
}
|
|
3064
|
+
: undefined;
|
|
3065
|
+
return {
|
|
3066
|
+
profile: typeof parsed.profile === "string" ? parsed.profile : undefined,
|
|
3067
|
+
token: typeof parsed.token === "string" ? parsed.token.trim() : undefined,
|
|
3068
|
+
updated_at: typeof parsed.updated_at === "string" ? parsed.updated_at : undefined,
|
|
3069
|
+
account_id: typeof parsed.account_id === "string" ? parsed.account_id : undefined,
|
|
3070
|
+
api_key_id: typeof parsed.api_key_id === "string" ? parsed.api_key_id : undefined,
|
|
3071
|
+
device: normalizedDevice
|
|
3072
|
+
};
|
|
3073
|
+
}
|
|
3074
|
+
async function writeLocalAuthFile(config) {
|
|
3075
|
+
await mkdir(dirname(DEFAULT_AUTH_FILE), { recursive: true });
|
|
3076
|
+
await writeFile(DEFAULT_AUTH_FILE, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
3077
|
+
await chmod(DEFAULT_AUTH_FILE, 0o600).catch(() => undefined);
|
|
3078
|
+
}
|
|
2707
3079
|
function serviceBaseUrl(url) {
|
|
2708
3080
|
return url.replace(/\/+$/, "");
|
|
2709
3081
|
}
|
|
@@ -4162,30 +4534,272 @@ async function authLogin(parsed) {
|
|
|
4162
4534
|
return fail("invalid_args", "Usage: embed auth login --token <token> [--profile default] [--json]");
|
|
4163
4535
|
}
|
|
4164
4536
|
const updatedAt = new Date().toISOString();
|
|
4165
|
-
|
|
4166
|
-
await
|
|
4167
|
-
|
|
4537
|
+
const current = await readLocalAuthFile();
|
|
4538
|
+
const localDevice = await buildLocalDeviceAuth(parsed, current.device);
|
|
4539
|
+
const registration = await registerLocalDevice(token.trim(), localDevice.registration);
|
|
4540
|
+
if (!registration.ok) {
|
|
4541
|
+
return fail(registration.error.code, registration.error.message, {
|
|
4542
|
+
remediation: registration.error.remediation,
|
|
4543
|
+
details: registration.error.details
|
|
4544
|
+
});
|
|
4545
|
+
}
|
|
4546
|
+
const device = {
|
|
4547
|
+
device_id: registration.data.device.device_id,
|
|
4548
|
+
fingerprint_hash: localDevice.device.fingerprint_hash,
|
|
4549
|
+
private_key_pem: localDevice.device.private_key_pem,
|
|
4550
|
+
public_key_pem: localDevice.device.public_key_pem,
|
|
4551
|
+
label: registration.data.device.label ?? localDevice.device.label,
|
|
4552
|
+
platform: registration.data.device.platform ?? localDevice.device.platform,
|
|
4553
|
+
arch: registration.data.device.arch ?? localDevice.device.arch,
|
|
4554
|
+
hostname_hash: registration.data.device.hostname_hash ?? localDevice.device.hostname_hash,
|
|
4555
|
+
registered_at: registration.data.device.first_seen_at
|
|
4556
|
+
};
|
|
4557
|
+
await writeLocalAuthFile({
|
|
4558
|
+
profile,
|
|
4559
|
+
token: token.trim(),
|
|
4560
|
+
updated_at: updatedAt,
|
|
4561
|
+
account_id: registration.data.device.account_id,
|
|
4562
|
+
api_key_id: registration.data.device.api_key_id,
|
|
4563
|
+
device
|
|
4564
|
+
});
|
|
4565
|
+
return ok({
|
|
4566
|
+
authenticated: true,
|
|
4567
|
+
profile,
|
|
4568
|
+
source: "file",
|
|
4569
|
+
updated_at: updatedAt,
|
|
4570
|
+
account_id: registration.data.device.account_id,
|
|
4571
|
+
api_key_id: registration.data.device.api_key_id,
|
|
4572
|
+
device_id: device.device_id,
|
|
4573
|
+
device_fingerprint_hash: device.fingerprint_hash,
|
|
4574
|
+
device_label: device.label,
|
|
4575
|
+
device_registered_at: device.registered_at,
|
|
4576
|
+
device_private_key_configured: true
|
|
4577
|
+
});
|
|
4168
4578
|
}
|
|
4169
4579
|
async function authStatus() {
|
|
4170
|
-
|
|
4580
|
+
const envToken = process.env.EMBED_API_TOKEN?.trim();
|
|
4581
|
+
const file = await readLocalAuthFile();
|
|
4582
|
+
const deviceIntegrity = file.device
|
|
4583
|
+
? (await validateLocalDeviceIntegrity(file.device)).ok ? "ok" : "failed"
|
|
4584
|
+
: "unbound";
|
|
4585
|
+
if (envToken) {
|
|
4171
4586
|
return {
|
|
4172
4587
|
authenticated: true,
|
|
4173
4588
|
profile: process.env.EMBED_AUTH_PROFILE ?? "default",
|
|
4174
|
-
source: "env"
|
|
4589
|
+
source: "env",
|
|
4590
|
+
account_id: file.account_id,
|
|
4591
|
+
api_key_id: file.api_key_id,
|
|
4592
|
+
device_id: file.device?.device_id,
|
|
4593
|
+
device_fingerprint_hash: file.device?.fingerprint_hash,
|
|
4594
|
+
device_label: file.device?.label,
|
|
4595
|
+
device_registered_at: file.device?.registered_at,
|
|
4596
|
+
device_private_key_configured: Boolean(file.device?.private_key_pem),
|
|
4597
|
+
device_integrity: deviceIntegrity
|
|
4175
4598
|
};
|
|
4176
4599
|
}
|
|
4600
|
+
return {
|
|
4601
|
+
authenticated: Boolean(file.token?.trim()),
|
|
4602
|
+
profile: file.profile ?? "default",
|
|
4603
|
+
source: file.token ? "file" : undefined,
|
|
4604
|
+
updated_at: file.updated_at,
|
|
4605
|
+
account_id: file.account_id,
|
|
4606
|
+
api_key_id: file.api_key_id,
|
|
4607
|
+
device_id: file.device?.device_id,
|
|
4608
|
+
device_fingerprint_hash: file.device?.fingerprint_hash,
|
|
4609
|
+
device_label: file.device?.label,
|
|
4610
|
+
device_registered_at: file.device?.registered_at,
|
|
4611
|
+
device_private_key_configured: Boolean(file.device?.private_key_pem),
|
|
4612
|
+
device_integrity: deviceIntegrity
|
|
4613
|
+
};
|
|
4614
|
+
}
|
|
4615
|
+
async function authDeviceStatus(parsed) {
|
|
4616
|
+
const unknownFlag = firstUnknownFlag(parsed, ["json"]);
|
|
4617
|
+
const unexpected = parsed.command.slice(3);
|
|
4618
|
+
if (unknownFlag || unexpected.length > 0) {
|
|
4619
|
+
return fail("invalid_args", unknownFlag ? `Unknown flag --${unknownFlag}. ${AUTH_DEVICE_STATUS_USAGE}` : AUTH_DEVICE_STATUS_USAGE);
|
|
4620
|
+
}
|
|
4621
|
+
const local = await authStatus();
|
|
4622
|
+
const remote = local.authenticated ? await cloudGet("/v1/me/devices") : undefined;
|
|
4623
|
+
if (remote && !remote.ok) {
|
|
4624
|
+
return ok({ local });
|
|
4625
|
+
}
|
|
4626
|
+
return ok({ local, remote: remote?.data });
|
|
4627
|
+
}
|
|
4628
|
+
async function authDeviceList(parsed) {
|
|
4629
|
+
const unknownFlag = firstUnknownFlag(parsed, ["json"]);
|
|
4630
|
+
const unexpected = parsed.command.slice(3);
|
|
4631
|
+
if (unknownFlag || unexpected.length > 0) {
|
|
4632
|
+
return fail("invalid_args", unknownFlag ? `Unknown flag --${unknownFlag}. ${AUTH_DEVICE_LIST_USAGE}` : AUTH_DEVICE_LIST_USAGE);
|
|
4633
|
+
}
|
|
4634
|
+
return await cloudGet("/v1/me/devices");
|
|
4635
|
+
}
|
|
4636
|
+
async function authDeviceRevoke(parsed) {
|
|
4637
|
+
const unknownFlag = firstUnknownFlag(parsed, ["json"]);
|
|
4638
|
+
if (unknownFlag) {
|
|
4639
|
+
return fail("invalid_args", `Unknown flag --${unknownFlag}. ${AUTH_DEVICE_REVOKE_USAGE}`);
|
|
4640
|
+
}
|
|
4641
|
+
const id = commandId(parsed, 3, "device_id", AUTH_DEVICE_REVOKE_USAGE);
|
|
4642
|
+
if (!id.ok) {
|
|
4643
|
+
return fail("invalid_args", id.error);
|
|
4644
|
+
}
|
|
4645
|
+
return await cloudPost(`/v1/me/devices/${encodeURIComponent(id.value)}/revoke`, {});
|
|
4646
|
+
}
|
|
4647
|
+
async function authDeviceRename(parsed) {
|
|
4648
|
+
const unknownFlag = firstUnknownFlag(parsed, ["json", "label"]);
|
|
4649
|
+
if (unknownFlag) {
|
|
4650
|
+
return fail("invalid_args", `Unknown flag --${unknownFlag}. ${AUTH_DEVICE_RENAME_USAGE}`);
|
|
4651
|
+
}
|
|
4652
|
+
const id = commandId(parsed, 3, "device_id", AUTH_DEVICE_RENAME_USAGE);
|
|
4653
|
+
if (!id.ok) {
|
|
4654
|
+
return fail("invalid_args", id.error);
|
|
4655
|
+
}
|
|
4656
|
+
const label = stringFlag(parsed, "label");
|
|
4657
|
+
if (!label?.trim()) {
|
|
4658
|
+
return fail("invalid_args", AUTH_DEVICE_RENAME_USAGE);
|
|
4659
|
+
}
|
|
4660
|
+
const updated = await cloudPost(`/v1/me/devices/${encodeURIComponent(id.value)}`, { label: label.trim() });
|
|
4661
|
+
if (updated.ok) {
|
|
4662
|
+
const auth = await readLocalAuthFile();
|
|
4663
|
+
if (auth.device?.device_id === updated.data.device_id) {
|
|
4664
|
+
await writeLocalAuthFile({ ...auth, device: { ...auth.device, label: updated.data.label } });
|
|
4665
|
+
}
|
|
4666
|
+
}
|
|
4667
|
+
return updated;
|
|
4668
|
+
}
|
|
4669
|
+
async function buildLocalDeviceAuth(parsed, existing) {
|
|
4670
|
+
const fingerprint = await localHardwareFingerprint();
|
|
4671
|
+
const keyPair = existing?.fingerprint_hash === fingerprint.fingerprint_hash && existing.private_key_pem && existing.public_key_pem
|
|
4672
|
+
? { privateKeyPem: existing.private_key_pem, publicKeyPem: existing.public_key_pem }
|
|
4673
|
+
: generateLocalDeviceKeyPair();
|
|
4674
|
+
const label = stringFlag(parsed, "label")?.trim()
|
|
4675
|
+
|| existing?.label
|
|
4676
|
+
|| `${fingerprint.platform} ${fingerprint.arch}`;
|
|
4677
|
+
const device = {
|
|
4678
|
+
device_id: existing?.fingerprint_hash === fingerprint.fingerprint_hash ? existing.device_id : "",
|
|
4679
|
+
fingerprint_hash: fingerprint.fingerprint_hash,
|
|
4680
|
+
private_key_pem: keyPair.privateKeyPem,
|
|
4681
|
+
public_key_pem: keyPair.publicKeyPem,
|
|
4682
|
+
label,
|
|
4683
|
+
platform: fingerprint.platform,
|
|
4684
|
+
arch: fingerprint.arch,
|
|
4685
|
+
hostname_hash: fingerprint.hostname_hash,
|
|
4686
|
+
registered_at: existing?.registered_at
|
|
4687
|
+
};
|
|
4688
|
+
return {
|
|
4689
|
+
device,
|
|
4690
|
+
registration: {
|
|
4691
|
+
fingerprint_hash: fingerprint.fingerprint_hash,
|
|
4692
|
+
public_key: keyPair.publicKeyPem,
|
|
4693
|
+
label,
|
|
4694
|
+
platform: fingerprint.platform,
|
|
4695
|
+
arch: fingerprint.arch,
|
|
4696
|
+
hostname_hash: fingerprint.hostname_hash,
|
|
4697
|
+
client_name: EMBED_CLIENT_NAME,
|
|
4698
|
+
client_version: EMBED_CLIENT_VERSION,
|
|
4699
|
+
metadata: {
|
|
4700
|
+
fingerprint_version: "v1",
|
|
4701
|
+
fingerprint_source: fingerprint.source
|
|
4702
|
+
}
|
|
4703
|
+
}
|
|
4704
|
+
};
|
|
4705
|
+
}
|
|
4706
|
+
function generateLocalDeviceKeyPair() {
|
|
4707
|
+
const { privateKey, publicKey } = generateKeyPairSync("ed25519");
|
|
4708
|
+
return {
|
|
4709
|
+
privateKeyPem: privateKey.export({ type: "pkcs8", format: "pem" }).toString(),
|
|
4710
|
+
publicKeyPem: publicKey.export({ type: "spki", format: "pem" }).toString()
|
|
4711
|
+
};
|
|
4712
|
+
}
|
|
4713
|
+
async function registerLocalDevice(token, body) {
|
|
4177
4714
|
try {
|
|
4178
|
-
const
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
source: "file",
|
|
4183
|
-
updated_at: typeof parsed.updated_at === "string" ? parsed.updated_at : undefined
|
|
4715
|
+
const bodyText = JSON.stringify(body);
|
|
4716
|
+
const headers = {
|
|
4717
|
+
"content-type": "application/json",
|
|
4718
|
+
authorization: `Bearer ${token}`
|
|
4184
4719
|
};
|
|
4720
|
+
addCloudRequestSignature(headers, "POST", "/v1/me/devices/register", bodyText, token);
|
|
4721
|
+
const response = await fetch(`${serviceBaseUrl(DEFAULT_CLOUD_API_URL)}/v1/me/devices/register`, {
|
|
4722
|
+
method: "POST",
|
|
4723
|
+
headers,
|
|
4724
|
+
body: bodyText
|
|
4725
|
+
});
|
|
4726
|
+
const parsed = await response.json();
|
|
4727
|
+
return enrichCloudAuthFailure(parsed, true);
|
|
4728
|
+
}
|
|
4729
|
+
catch (error) {
|
|
4730
|
+
return fail("cloud_api_unreachable", error instanceof Error ? error.message : String(error), {
|
|
4731
|
+
remediation: `Check that embed cloud-api is running at ${DEFAULT_CLOUD_API_URL}. Start it with: npm run cloud-api`
|
|
4732
|
+
});
|
|
4733
|
+
}
|
|
4734
|
+
}
|
|
4735
|
+
async function localHardwareFingerprint() {
|
|
4736
|
+
cachedLocalHardwareFingerprint ??= localHardwareFingerprintUncached();
|
|
4737
|
+
return await cachedLocalHardwareFingerprint;
|
|
4738
|
+
}
|
|
4739
|
+
async function localHardwareFingerprintUncached() {
|
|
4740
|
+
const platformName = platform();
|
|
4741
|
+
const archName = arch();
|
|
4742
|
+
const raw = await localHardwareId(platformName);
|
|
4743
|
+
const fingerprintHash = createHash("sha256")
|
|
4744
|
+
.update(`embed-labs:device:v1:${platformName}:${raw.value}`)
|
|
4745
|
+
.digest("hex");
|
|
4746
|
+
const hostnameHash = createHash("sha256")
|
|
4747
|
+
.update(`embed-labs:hostname:v1:${hostname()}`)
|
|
4748
|
+
.digest("hex");
|
|
4749
|
+
return {
|
|
4750
|
+
fingerprint_hash: fingerprintHash,
|
|
4751
|
+
platform: platformName,
|
|
4752
|
+
arch: archName,
|
|
4753
|
+
hostname_hash: hostnameHash,
|
|
4754
|
+
source: raw.source
|
|
4755
|
+
};
|
|
4756
|
+
}
|
|
4757
|
+
async function localHardwareId(platformName) {
|
|
4758
|
+
if (platformName === "darwin") {
|
|
4759
|
+
const result = await runLocalProcess("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"]);
|
|
4760
|
+
const match = /"IOPlatformUUID"\s*=\s*"([^"]+)"/.exec(result.stdout);
|
|
4761
|
+
if (match?.[1]) {
|
|
4762
|
+
return { value: match[1], source: "macos_ioplatformuuid" };
|
|
4763
|
+
}
|
|
4764
|
+
}
|
|
4765
|
+
if (platformName === "win32") {
|
|
4766
|
+
const result = await runLocalProcess("reg", ["query", "HKLM\\SOFTWARE\\Microsoft\\Cryptography", "/v", "MachineGuid"]);
|
|
4767
|
+
const match = /MachineGuid\s+REG_\w+\s+([^\r\n]+)/.exec(result.stdout);
|
|
4768
|
+
if (match?.[1]?.trim()) {
|
|
4769
|
+
return { value: match[1].trim(), source: "windows_machineguid" };
|
|
4770
|
+
}
|
|
4771
|
+
}
|
|
4772
|
+
if (platformName === "linux") {
|
|
4773
|
+
for (const pathValue of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
4774
|
+
try {
|
|
4775
|
+
const value = (await readFile(pathValue, "utf8")).trim();
|
|
4776
|
+
if (value) {
|
|
4777
|
+
return { value, source: `linux:${pathValue}` };
|
|
4778
|
+
}
|
|
4779
|
+
}
|
|
4780
|
+
catch {
|
|
4781
|
+
// Try the next stable machine id location.
|
|
4782
|
+
}
|
|
4783
|
+
}
|
|
4784
|
+
}
|
|
4785
|
+
const generated = await localGeneratedInstallId();
|
|
4786
|
+
return { value: generated, source: "generated_install_id" };
|
|
4787
|
+
}
|
|
4788
|
+
async function localGeneratedInstallId() {
|
|
4789
|
+
try {
|
|
4790
|
+
const parsed = JSON.parse(await readFile(DEFAULT_DEVICE_FILE, "utf8"));
|
|
4791
|
+
if (typeof parsed.generated_install_id === "string" && parsed.generated_install_id.trim()) {
|
|
4792
|
+
return parsed.generated_install_id.trim();
|
|
4793
|
+
}
|
|
4185
4794
|
}
|
|
4186
4795
|
catch {
|
|
4187
|
-
|
|
4796
|
+
// Fall through and create a local-only fallback id.
|
|
4188
4797
|
}
|
|
4798
|
+
const generated = `install_${randomBytes(24).toString("base64url")}`;
|
|
4799
|
+
await mkdir(dirname(DEFAULT_DEVICE_FILE), { recursive: true });
|
|
4800
|
+
await writeFile(DEFAULT_DEVICE_FILE, `${JSON.stringify({ generated_install_id: generated, created_at: new Date().toISOString() }, null, 2)}\n`, "utf8");
|
|
4801
|
+
await chmod(DEFAULT_DEVICE_FILE, 0o600).catch(() => undefined);
|
|
4802
|
+
return generated;
|
|
4189
4803
|
}
|
|
4190
4804
|
function accountCreateBody(parsed) {
|
|
4191
4805
|
const unknownFlag = firstUnknownFlag(parsed, ["json", "email", "display-name"]);
|
|
@@ -4351,6 +4965,8 @@ function mcpToolEventBody(parsed) {
|
|
|
4351
4965
|
"mode",
|
|
4352
4966
|
"server-model-used",
|
|
4353
4967
|
"success",
|
|
4968
|
+
"local-device-id",
|
|
4969
|
+
"local_device_id",
|
|
4354
4970
|
"request-id",
|
|
4355
4971
|
"duration-ms",
|
|
4356
4972
|
"input-summary",
|
|
@@ -4377,6 +4993,9 @@ function mcpToolEventBody(parsed) {
|
|
|
4377
4993
|
const modeResult = optionalTrimmedStringFlag(parsed, "mode");
|
|
4378
4994
|
if (modeResult.error)
|
|
4379
4995
|
return modeResult.error;
|
|
4996
|
+
const localDeviceResult = optionalTrimmedStringAliasFlag(parsed, ["local-device-id", "local_device_id"], "local-device-id");
|
|
4997
|
+
if (localDeviceResult.error)
|
|
4998
|
+
return localDeviceResult.error;
|
|
4380
4999
|
const requestIdResult = optionalTrimmedStringFlag(parsed, "request-id");
|
|
4381
5000
|
if (requestIdResult.error)
|
|
4382
5001
|
return requestIdResult.error;
|
|
@@ -4400,12 +5019,14 @@ function mcpToolEventBody(parsed) {
|
|
|
4400
5019
|
tool_name: toolResult.value,
|
|
4401
5020
|
client: clientResult.value,
|
|
4402
5021
|
mode: modeResult.value,
|
|
5022
|
+
local_device_id: localDeviceResult.value,
|
|
4403
5023
|
server_model_used: serverModelUsed,
|
|
4404
5024
|
success,
|
|
4405
5025
|
request_id: requestIdResult.value,
|
|
4406
5026
|
duration_ms: durationResult.value,
|
|
4407
5027
|
input_summary: inputSummaryResult.value,
|
|
4408
|
-
output_summary: outputSummaryResult.value
|
|
5028
|
+
output_summary: outputSummaryResult.value,
|
|
5029
|
+
metadata: localDeviceResult.value ? { local_device_id: localDeviceResult.value } : undefined
|
|
4409
5030
|
});
|
|
4410
5031
|
}
|
|
4411
5032
|
function usageSummaryRequest(parsed) {
|
|
@@ -4923,7 +5544,9 @@ function localToolchainAuthContext(auth, accountId) {
|
|
|
4923
5544
|
authenticated: auth.authenticated,
|
|
4924
5545
|
profile: auth.profile,
|
|
4925
5546
|
source: auth.source,
|
|
4926
|
-
account_id: accountId
|
|
5547
|
+
account_id: accountId,
|
|
5548
|
+
api_key_id: auth.api_key_id,
|
|
5549
|
+
device_id: auth.device_id
|
|
4927
5550
|
};
|
|
4928
5551
|
}
|
|
4929
5552
|
function usageEventsRequest(parsed) {
|
|
@@ -5394,9 +6017,53 @@ function renderAgentRunResult(result) {
|
|
|
5394
6017
|
return lines.join("\n");
|
|
5395
6018
|
}
|
|
5396
6019
|
function renderAuthStatus(status) {
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
6020
|
+
if (!status.authenticated) {
|
|
6021
|
+
return `Not authenticated profile=${status.profile}`;
|
|
6022
|
+
}
|
|
6023
|
+
return [
|
|
6024
|
+
`Authenticated profile=${status.profile}${status.source ? ` source=${status.source}` : ""}`,
|
|
6025
|
+
status.account_id ? `account=${status.account_id}` : "",
|
|
6026
|
+
status.api_key_id ? `api_key=${status.api_key_id}` : "",
|
|
6027
|
+
status.device_id ? `device=${status.device_id}` : "device=not_registered",
|
|
6028
|
+
status.device_label ? `device_label=${status.device_label}` : "",
|
|
6029
|
+
status.device_integrity ? `device_integrity=${status.device_integrity}` : "",
|
|
6030
|
+
status.device_private_key_configured === false ? "device_private_key=missing" : ""
|
|
6031
|
+
].filter(Boolean).join("\n");
|
|
6032
|
+
}
|
|
6033
|
+
function renderAuthDeviceStatus(status) {
|
|
6034
|
+
const lines = [renderAuthStatus(status.local)];
|
|
6035
|
+
if (status.remote) {
|
|
6036
|
+
const activeCount = status.remote.devices.filter((device) => device.status === "active").length;
|
|
6037
|
+
lines.push(`remote_devices=${activeCount}/${status.remote.device_limit}`);
|
|
6038
|
+
const localDevice = status.local.device_id
|
|
6039
|
+
? status.remote.devices.find((device) => device.device_id === status.local.device_id)
|
|
6040
|
+
: undefined;
|
|
6041
|
+
if (localDevice) {
|
|
6042
|
+
lines.push(`remote_current=${renderAuthDevice(localDevice)}`);
|
|
6043
|
+
}
|
|
6044
|
+
}
|
|
6045
|
+
return lines.join("\n");
|
|
6046
|
+
}
|
|
6047
|
+
function renderAuthDeviceList(result) {
|
|
6048
|
+
if (result.devices.length === 0) {
|
|
6049
|
+
return `No registered devices. device_limit=${result.device_limit}`;
|
|
6050
|
+
}
|
|
6051
|
+
return [
|
|
6052
|
+
`device_limit=${result.device_limit}`,
|
|
6053
|
+
...result.devices.map(renderAuthDevice)
|
|
6054
|
+
].join("\n");
|
|
6055
|
+
}
|
|
6056
|
+
function renderAuthDevice(device) {
|
|
6057
|
+
return [
|
|
6058
|
+
`${device.device_id} account=${device.account_id}`,
|
|
6059
|
+
device.api_key_id ? `api_key=${device.api_key_id}` : "",
|
|
6060
|
+
`status=${device.status}`,
|
|
6061
|
+
device.label ? `label=${device.label}` : "",
|
|
6062
|
+
device.platform ? `platform=${device.platform}` : "",
|
|
6063
|
+
device.arch ? `arch=${device.arch}` : "",
|
|
6064
|
+
`last_seen_at=${device.last_seen_at}`,
|
|
6065
|
+
device.revoked_at ? `revoked_at=${device.revoked_at}` : ""
|
|
6066
|
+
].filter(Boolean).join(" ");
|
|
5400
6067
|
}
|
|
5401
6068
|
function renderAccount(account) {
|
|
5402
6069
|
return [
|
|
@@ -6909,7 +7576,7 @@ Help:
|
|
|
6909
7576
|
|
|
6910
7577
|
Environment:
|
|
6911
7578
|
EMBED_BRIDGE_URL=http://127.0.0.1:18083
|
|
6912
|
-
EMBED_CLOUD_API_URL=
|
|
7579
|
+
EMBED_CLOUD_API_URL=https://api.embedboard.com
|
|
6913
7580
|
EMBED_API_TOKEN=<token>
|
|
6914
7581
|
CODEX_HOME=~/.codex
|
|
6915
7582
|
|
|
@@ -7148,7 +7815,7 @@ Usage:
|
|
|
7148
7815
|
|
|
7149
7816
|
Environment:
|
|
7150
7817
|
EMBED_BRIDGE_URL=http://127.0.0.1:18083
|
|
7151
|
-
EMBED_CLOUD_API_URL=
|
|
7818
|
+
EMBED_CLOUD_API_URL=https://api.embedboard.com
|
|
7152
7819
|
EMBED_API_TOKEN=<token>
|
|
7153
7820
|
CODEX_HOME=~/.codex
|
|
7154
7821
|
`);
|