@kvell007/embed-labs-cli 0.1.0-alpha.20 → 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 +747 -66
- 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,16 +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", "embed-labs", "dbt-agent"),
|
|
1957
|
-
join(targetRoot, "cache", "dbt-agent"),
|
|
1958
|
-
join(targetRoot, "cache", "plugins", "dbt-agent")
|
|
2059
|
+
join(targetRoot, "cache", CODEX_MARKETPLACE_NAME, CODEX_PLUGIN_NAME)
|
|
1959
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
|
+
}
|
|
1960
2071
|
legacyPaths.push(...await discoverLegacyCodexCachePaths(targetRoot));
|
|
1961
2072
|
if (resolve(targetRoot) === resolve(defaultCodexPluginRoot())) {
|
|
1962
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"));
|
|
1963
|
-
legacyPaths.push(...await discoverLegacyHomeAgentsMarketplacePaths(warnings), ...legacyDevelopmentBoardRuntimePluginPaths());
|
|
2074
|
+
legacyPaths.push(...legacyCodexLocalMarketplacePaths(), ...await discoverLegacyHomeAgentsMarketplacePaths(warnings), ...await discoverLegacyCodexProjectMarketplacePaths(warnings), ...legacyDevelopmentBoardRuntimePluginPaths());
|
|
1964
2075
|
}
|
|
1965
2076
|
for (const candidate of legacyPaths) {
|
|
1966
2077
|
try {
|
|
@@ -2053,6 +2164,11 @@ function legacyDevelopmentBoardRuntimePluginPaths() {
|
|
|
2053
2164
|
join(homedir(), "Library", "Application Support", "development-board-toolchain", "runtime", "opencode_plugin")
|
|
2054
2165
|
];
|
|
2055
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
|
+
}
|
|
2056
2172
|
async function discoverLegacyHomeAgentsMarketplacePaths(warnings) {
|
|
2057
2173
|
const paths = [];
|
|
2058
2174
|
const pluginRoot = join(homedir(), ".agents", "plugins");
|
|
@@ -2079,9 +2195,72 @@ async function discoverLegacyHomeAgentsMarketplacePaths(warnings) {
|
|
|
2079
2195
|
return paths;
|
|
2080
2196
|
}
|
|
2081
2197
|
function isLegacyHomeAgentsMarketplace(text) {
|
|
2082
|
-
return
|
|
2083
|
-
|
|
2084
|
-
|
|
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
|
+
}
|
|
2085
2264
|
}
|
|
2086
2265
|
async function discoverLegacyCodexCachePaths(targetRoot) {
|
|
2087
2266
|
const paths = [];
|
|
@@ -2091,8 +2270,12 @@ async function discoverLegacyCodexCachePaths(targetRoot) {
|
|
|
2091
2270
|
for (const entry of marketplaces) {
|
|
2092
2271
|
if (!entry.isDirectory())
|
|
2093
2272
|
continue;
|
|
2094
|
-
|
|
2095
|
-
|
|
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
|
+
}
|
|
2096
2279
|
}
|
|
2097
2280
|
}
|
|
2098
2281
|
catch {
|
|
@@ -2138,6 +2321,8 @@ function isLegacyCodexHistoryMention(line) {
|
|
|
2138
2321
|
|| line.includes("plugin://dbt-agent@embed-labs")
|
|
2139
2322
|
|| line.includes("development-board-toolchain-dev")
|
|
2140
2323
|
|| line.includes("development-board-toolchain")
|
|
2324
|
+
|| /plugin:\/\/Dbt Agent@/i.test(line)
|
|
2325
|
+
|| /plugin:\/\/deve@/i.test(line)
|
|
2141
2326
|
|| /dbt-agent/i.test(line);
|
|
2142
2327
|
}
|
|
2143
2328
|
function removeLegacyCodexConfigTables(text) {
|
|
@@ -2166,11 +2351,15 @@ function parseTomlTableHeader(line) {
|
|
|
2166
2351
|
}
|
|
2167
2352
|
function isLegacyCodexConfigTable(table) {
|
|
2168
2353
|
return /^plugins\."dbt-agent@[^"]+"$/.test(table)
|
|
2354
|
+
|| /^plugins\."Dbt Agent@[^"]+"$/i.test(table)
|
|
2355
|
+
|| /^plugins\."deve@[^"]+"$/i.test(table)
|
|
2169
2356
|
|| isLegacyEmbedLabsCodexMarketplaceConfigTable(table)
|
|
2170
2357
|
|| table === "mcp_servers.dbt-agent"
|
|
2171
2358
|
|| table.startsWith("mcp_servers.dbt-agent.")
|
|
2172
2359
|
|| table === 'mcp_servers."dbt-agent"'
|
|
2173
2360
|
|| table.startsWith('mcp_servers."dbt-agent".')
|
|
2361
|
+
|| table === "mcp_servers.deve"
|
|
2362
|
+
|| table.startsWith("mcp_servers.deve.")
|
|
2174
2363
|
|| /^projects\."[^"]*\/DBT-Agent-Project(?:\/[^"]*)?"$/.test(table);
|
|
2175
2364
|
}
|
|
2176
2365
|
function isLegacyEmbedLabsCodexMarketplaceConfigTable(table) {
|
|
@@ -2184,10 +2373,16 @@ function isLegacyEmbedLabsCodexMarketplaceConfigTable(table) {
|
|
|
2184
2373
|
}
|
|
2185
2374
|
return false;
|
|
2186
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
|
+
}
|
|
2187
2382
|
function legacyCodexCleanupWarning(cleanup) {
|
|
2188
2383
|
const parts = [];
|
|
2189
2384
|
if (cleanup.legacy_removed_paths.length > 0) {
|
|
2190
|
-
parts.push(`removed ${cleanup.legacy_removed_paths.length} legacy Codex plugin path(s)`);
|
|
2385
|
+
parts.push(`removed ${cleanup.legacy_removed_paths.length} stale/legacy Codex plugin path(s)`);
|
|
2191
2386
|
}
|
|
2192
2387
|
if (cleanup.legacy_removed_config_tables?.length) {
|
|
2193
2388
|
parts.push(`removed ${cleanup.legacy_removed_config_tables.length} legacy Codex config table(s)`);
|
|
@@ -2201,19 +2396,26 @@ function legacyCodexCleanupWarning(cleanup) {
|
|
|
2201
2396
|
if (cleanup.warnings?.length) {
|
|
2202
2397
|
parts.push(`cleanup warning(s): ${cleanup.warnings.join("; ")}`);
|
|
2203
2398
|
}
|
|
2204
|
-
return parts.length > 0 ? `
|
|
2399
|
+
return parts.length > 0 ? `Codex plugin cleanup: ${parts.join(", ")}.` : undefined;
|
|
2205
2400
|
}
|
|
2206
2401
|
async function cleanupLegacyOpenCodePluginRemnants(targetRoot, globalInstall) {
|
|
2207
2402
|
const removedPaths = [];
|
|
2208
2403
|
const warnings = [];
|
|
2209
2404
|
const legacyPaths = [
|
|
2210
2405
|
join(targetRoot, "plugins", "development-board-toolchain.js"),
|
|
2406
|
+
join(targetRoot, "plugins", "development-board-toolchain-dev.js"),
|
|
2211
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"),
|
|
2212
2413
|
join(targetRoot, "node_modules", "dbt-agent")
|
|
2213
2414
|
];
|
|
2214
2415
|
if (globalInstall) {
|
|
2215
2416
|
legacyPaths.push(join(targetRoot, "plugins", "embed-labs.js"));
|
|
2216
2417
|
legacyPaths.push(...await discoverLegacyOpenCodeBackupPaths(targetRoot, warnings));
|
|
2418
|
+
legacyPaths.push(...await discoverLegacyOpenCodePluginCachePaths(targetRoot, warnings));
|
|
2217
2419
|
}
|
|
2218
2420
|
for (const candidate of legacyPaths) {
|
|
2219
2421
|
try {
|
|
@@ -2242,7 +2444,7 @@ async function discoverLegacyOpenCodeBackupPaths(targetRoot, warnings) {
|
|
|
2242
2444
|
const filePath = join(targetRoot, entry.name);
|
|
2243
2445
|
try {
|
|
2244
2446
|
const current = await readFile(filePath, "utf8");
|
|
2245
|
-
if (
|
|
2447
|
+
if (legacyTextHasCodexPluginResidue(current)) {
|
|
2246
2448
|
paths.push(filePath);
|
|
2247
2449
|
}
|
|
2248
2450
|
}
|
|
@@ -2256,6 +2458,26 @@ async function discoverLegacyOpenCodeBackupPaths(targetRoot, warnings) {
|
|
|
2256
2458
|
}
|
|
2257
2459
|
return paths;
|
|
2258
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
|
+
}
|
|
2259
2481
|
function legacyOpenCodeCleanupWarning(cleanup) {
|
|
2260
2482
|
const parts = [];
|
|
2261
2483
|
if (cleanup.legacy_removed_paths.length > 0) {
|
|
@@ -2358,6 +2580,7 @@ async function maybeRegisterCodexMarketplace(parsed, targetRoot, targetPath) {
|
|
|
2358
2580
|
await rm(marketplacePluginPath, { recursive: true, force: true });
|
|
2359
2581
|
await mkdir(dirname(marketplacePluginPath), { recursive: true });
|
|
2360
2582
|
await cp(targetPath, marketplacePluginPath, { recursive: true });
|
|
2583
|
+
await refreshCodexPluginCache(targetPath);
|
|
2361
2584
|
await writeCodexLocalMarketplaceManifest(marketplacePath);
|
|
2362
2585
|
const warning = await upsertCodexPluginMarketplaceConfig(marketplacePath);
|
|
2363
2586
|
return warning ? { registered: true, marketplacePath, warning } : { registered: true, marketplacePath };
|
|
@@ -2373,6 +2596,18 @@ async function maybeRegisterCodexMarketplace(parsed, targetRoot, targetPath) {
|
|
|
2373
2596
|
function defaultCodexLocalMarketplaceRoot() {
|
|
2374
2597
|
return join(defaultCodexHome(), "local-marketplaces", CODEX_PLUGIN_NAME);
|
|
2375
2598
|
}
|
|
2599
|
+
async function refreshCodexPluginCache(targetPath) {
|
|
2600
|
+
const manifestPath = join(targetPath, ".codex-plugin", "plugin.json");
|
|
2601
|
+
const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
|
|
2602
|
+
const version = typeof manifest.version === "string" && manifest.version.trim()
|
|
2603
|
+
? manifest.version.trim()
|
|
2604
|
+
: "local";
|
|
2605
|
+
const cachePluginRoot = join(defaultCodexPluginRoot(), "cache", CODEX_MARKETPLACE_NAME, CODEX_PLUGIN_NAME);
|
|
2606
|
+
const cacheVersionPath = join(cachePluginRoot, version);
|
|
2607
|
+
await rm(cachePluginRoot, { recursive: true, force: true });
|
|
2608
|
+
await mkdir(dirname(cacheVersionPath), { recursive: true });
|
|
2609
|
+
await cp(targetPath, cacheVersionPath, { recursive: true });
|
|
2610
|
+
}
|
|
2376
2611
|
async function writeCodexLocalMarketplaceManifest(marketplacePath) {
|
|
2377
2612
|
const manifestPath = join(marketplacePath, ".agents", "plugins", "marketplace.json");
|
|
2378
2613
|
const manifest = {
|
|
@@ -2539,6 +2774,97 @@ async function resolveExecutableOnPath(name) {
|
|
|
2539
2774
|
}
|
|
2540
2775
|
return undefined;
|
|
2541
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
|
+
}
|
|
2542
2868
|
function defaultOpenCodeRoot() {
|
|
2543
2869
|
return globalOpenCodeRoot();
|
|
2544
2870
|
}
|
|
@@ -2578,12 +2904,26 @@ async function ensureOpenCodeGlobalPluginConfig() {
|
|
|
2578
2904
|
return removed;
|
|
2579
2905
|
}
|
|
2580
2906
|
function isLegacyOpenCodePluginConfigEntry(item) {
|
|
2581
|
-
const normalized = item.replace(/\\/g, "/");
|
|
2582
|
-
|
|
2583
|
-
|
|
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)
|
|
2584
2918
|
|| normalized === "./plugins/development-board-toolchain"
|
|
2585
2919
|
|| normalized === "./plugins/development-board-toolchain.js"
|
|
2586
|
-
|| 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")
|
|
2587
2927
|
|| normalized.includes("development-board-toolchain");
|
|
2588
2928
|
}
|
|
2589
2929
|
async function openCodeDuplicatePluginWarning(targetRoot) {
|
|
@@ -2677,19 +3017,65 @@ async function parseErrorResponse(response) {
|
|
|
2677
3017
|
return undefined;
|
|
2678
3018
|
}
|
|
2679
3019
|
async function cloudAuthToken() {
|
|
3020
|
+
return (await cloudAuthConfig()).token;
|
|
3021
|
+
}
|
|
3022
|
+
async function cloudAuthConfig() {
|
|
2680
3023
|
const envToken = process.env.EMBED_API_TOKEN?.trim();
|
|
2681
3024
|
if (envToken) {
|
|
2682
|
-
|
|
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
|
+
};
|
|
2683
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() {
|
|
2684
3042
|
try {
|
|
2685
3043
|
const parsed = JSON.parse(await readFile(DEFAULT_AUTH_FILE, "utf8"));
|
|
2686
|
-
|
|
2687
|
-
return fileToken || undefined;
|
|
3044
|
+
return normalizeLocalAuthFile(parsed);
|
|
2688
3045
|
}
|
|
2689
3046
|
catch {
|
|
2690
|
-
return
|
|
3047
|
+
return {};
|
|
2691
3048
|
}
|
|
2692
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
|
+
}
|
|
2693
3079
|
function serviceBaseUrl(url) {
|
|
2694
3080
|
return url.replace(/\/+$/, "");
|
|
2695
3081
|
}
|
|
@@ -4148,30 +4534,272 @@ async function authLogin(parsed) {
|
|
|
4148
4534
|
return fail("invalid_args", "Usage: embed auth login --token <token> [--profile default] [--json]");
|
|
4149
4535
|
}
|
|
4150
4536
|
const updatedAt = new Date().toISOString();
|
|
4151
|
-
|
|
4152
|
-
await
|
|
4153
|
-
|
|
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
|
+
});
|
|
4154
4578
|
}
|
|
4155
4579
|
async function authStatus() {
|
|
4156
|
-
|
|
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) {
|
|
4157
4586
|
return {
|
|
4158
4587
|
authenticated: true,
|
|
4159
4588
|
profile: process.env.EMBED_AUTH_PROFILE ?? "default",
|
|
4160
|
-
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
|
|
4161
4598
|
};
|
|
4162
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) {
|
|
4163
4714
|
try {
|
|
4164
|
-
const
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
source: "file",
|
|
4169
|
-
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}`
|
|
4170
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
|
+
}
|
|
4171
4794
|
}
|
|
4172
4795
|
catch {
|
|
4173
|
-
|
|
4796
|
+
// Fall through and create a local-only fallback id.
|
|
4174
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;
|
|
4175
4803
|
}
|
|
4176
4804
|
function accountCreateBody(parsed) {
|
|
4177
4805
|
const unknownFlag = firstUnknownFlag(parsed, ["json", "email", "display-name"]);
|
|
@@ -4337,6 +4965,8 @@ function mcpToolEventBody(parsed) {
|
|
|
4337
4965
|
"mode",
|
|
4338
4966
|
"server-model-used",
|
|
4339
4967
|
"success",
|
|
4968
|
+
"local-device-id",
|
|
4969
|
+
"local_device_id",
|
|
4340
4970
|
"request-id",
|
|
4341
4971
|
"duration-ms",
|
|
4342
4972
|
"input-summary",
|
|
@@ -4363,6 +4993,9 @@ function mcpToolEventBody(parsed) {
|
|
|
4363
4993
|
const modeResult = optionalTrimmedStringFlag(parsed, "mode");
|
|
4364
4994
|
if (modeResult.error)
|
|
4365
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;
|
|
4366
4999
|
const requestIdResult = optionalTrimmedStringFlag(parsed, "request-id");
|
|
4367
5000
|
if (requestIdResult.error)
|
|
4368
5001
|
return requestIdResult.error;
|
|
@@ -4386,12 +5019,14 @@ function mcpToolEventBody(parsed) {
|
|
|
4386
5019
|
tool_name: toolResult.value,
|
|
4387
5020
|
client: clientResult.value,
|
|
4388
5021
|
mode: modeResult.value,
|
|
5022
|
+
local_device_id: localDeviceResult.value,
|
|
4389
5023
|
server_model_used: serverModelUsed,
|
|
4390
5024
|
success,
|
|
4391
5025
|
request_id: requestIdResult.value,
|
|
4392
5026
|
duration_ms: durationResult.value,
|
|
4393
5027
|
input_summary: inputSummaryResult.value,
|
|
4394
|
-
output_summary: outputSummaryResult.value
|
|
5028
|
+
output_summary: outputSummaryResult.value,
|
|
5029
|
+
metadata: localDeviceResult.value ? { local_device_id: localDeviceResult.value } : undefined
|
|
4395
5030
|
});
|
|
4396
5031
|
}
|
|
4397
5032
|
function usageSummaryRequest(parsed) {
|
|
@@ -4909,7 +5544,9 @@ function localToolchainAuthContext(auth, accountId) {
|
|
|
4909
5544
|
authenticated: auth.authenticated,
|
|
4910
5545
|
profile: auth.profile,
|
|
4911
5546
|
source: auth.source,
|
|
4912
|
-
account_id: accountId
|
|
5547
|
+
account_id: accountId,
|
|
5548
|
+
api_key_id: auth.api_key_id,
|
|
5549
|
+
device_id: auth.device_id
|
|
4913
5550
|
};
|
|
4914
5551
|
}
|
|
4915
5552
|
function usageEventsRequest(parsed) {
|
|
@@ -5380,9 +6017,53 @@ function renderAgentRunResult(result) {
|
|
|
5380
6017
|
return lines.join("\n");
|
|
5381
6018
|
}
|
|
5382
6019
|
function renderAuthStatus(status) {
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
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(" ");
|
|
5386
6067
|
}
|
|
5387
6068
|
function renderAccount(account) {
|
|
5388
6069
|
return [
|
|
@@ -6895,7 +7576,7 @@ Help:
|
|
|
6895
7576
|
|
|
6896
7577
|
Environment:
|
|
6897
7578
|
EMBED_BRIDGE_URL=http://127.0.0.1:18083
|
|
6898
|
-
EMBED_CLOUD_API_URL=
|
|
7579
|
+
EMBED_CLOUD_API_URL=https://api.embedboard.com
|
|
6899
7580
|
EMBED_API_TOKEN=<token>
|
|
6900
7581
|
CODEX_HOME=~/.codex
|
|
6901
7582
|
|
|
@@ -7134,7 +7815,7 @@ Usage:
|
|
|
7134
7815
|
|
|
7135
7816
|
Environment:
|
|
7136
7817
|
EMBED_BRIDGE_URL=http://127.0.0.1:18083
|
|
7137
|
-
EMBED_CLOUD_API_URL=
|
|
7818
|
+
EMBED_CLOUD_API_URL=https://api.embedboard.com
|
|
7138
7819
|
EMBED_API_TOKEN=<token>
|
|
7139
7820
|
CODEX_HOME=~/.codex
|
|
7140
7821
|
`);
|