@kvell007/embed-labs-cli 0.1.0-alpha.73 → 0.1.0-alpha.75
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 +23 -87
- package/dist/embed-labs-mcp-bridge.mjs +182 -2315
- package/dist/index.js +186 -2442
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/dist/install-progress.d.ts +0 -2
- package/dist/install-progress.js +0 -75
- package/dist/install-progress.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,28 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { createHash
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
3
|
import { constants } from "node:fs";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
|
-
import { access,
|
|
5
|
+
import { access, cp, mkdir, mkdtemp, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
6
6
|
import { createRequire } from "node:module";
|
|
7
|
-
import {
|
|
7
|
+
import { homedir, 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";
|
|
10
11
|
import { composeBootLogoPackage } from "./image-compose.js";
|
|
11
|
-
import { buildTaishanPiQtSmoke, compileTaishanPiSingleFile, currentLocalToolchain, installLocalToolchain, latestLocalToolchain,
|
|
12
|
+
import { buildTaishanPiQtSmoke, compileTaishanPiSingleFile, currentLocalToolchain, installLocalToolchain, latestLocalToolchain, validateLocalToolchain } from "./local-toolchain.js";
|
|
12
13
|
import { fail, ok } from "@embed-labs/protocol";
|
|
13
14
|
const require = createRequire(import.meta.url);
|
|
14
15
|
const CLI_MODULE_DIR = dirname(fileURLToPath(import.meta.url));
|
|
15
16
|
const SOURCE_CHECKOUT_ROOT = resolve(CLI_MODULE_DIR, "..", "..", "..");
|
|
16
17
|
const qrcodeTerminal = require("qrcode-terminal");
|
|
17
18
|
const DEFAULT_BRIDGE_URL = process.env.EMBED_BRIDGE_URL ?? "http://127.0.0.1:18083";
|
|
18
|
-
const DEFAULT_CLOUD_API_URL = process.env.EMBED_CLOUD_API_URL ?? "
|
|
19
|
+
const DEFAULT_CLOUD_API_URL = process.env.EMBED_CLOUD_API_URL ?? "http://127.0.0.1:18100";
|
|
19
20
|
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
|
-
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";
|
|
24
21
|
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>";
|
|
26
22
|
const DOCTOR_USAGE = "Usage: embed doctor [--json]";
|
|
27
23
|
const DOCTOR_HTTP_TIMEOUT_MS = 8000;
|
|
28
24
|
const MIN_NODE_MAJOR = 20;
|
|
@@ -31,20 +27,7 @@ const DEVICE_PROBE_USAGE = "Usage: embed device probe --host <host> --ports 22,1
|
|
|
31
27
|
const QUERY_USAGE = "Usage: embed query <natural language request> [--account <account_id>] [--qr] [--json]";
|
|
32
28
|
const DEFAULT_PLUGIN_RELEASE_URL = process.env.EMBED_PLUGIN_RELEASE_URL?.trim() || "https://api.embedboard.com/plugin-releases/agent-plugins/latest";
|
|
33
29
|
const PLUGIN_LIST_USAGE = "Usage: embed plugin list [--release-dir <dir>] [--release-url <url>] [--json]";
|
|
34
|
-
const CODEX_PLUGIN_NAME = "embed-labs";
|
|
35
|
-
const CODEX_MARKETPLACE_NAME = "embed-labs";
|
|
36
|
-
const MCP_START_USAGE = "Usage: embed mcp start [--bridge-path <path>]";
|
|
37
|
-
const LEGACY_CODEX_PLUGIN_NAMES = [
|
|
38
|
-
"dbt-agent",
|
|
39
|
-
"Dbt Agent",
|
|
40
|
-
"development-board-toolchain",
|
|
41
|
-
"development-board-toolchain-dev",
|
|
42
|
-
"deve"
|
|
43
|
-
];
|
|
44
|
-
const LEGACY_CODEX_MARKETPLACE_NAMES = new Set(["embed-labs-plugins", "plugins", "Plugins", "deve"]);
|
|
45
30
|
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]";
|
|
46
|
-
const PLUGIN_UPDATE_CHECK_USAGE = "Usage: embed plugin update check [--release-url <url>] [--target <dir>] [--codex-target <dir>] [--opencode-target <dir>] [--json]";
|
|
47
|
-
const PLUGIN_UPDATE_USAGE = "Usage: embed plugin update <codex|opencode|all> [--release-url <url>] [--target <dir>] [--codex-target <dir>] [--opencode-target <dir>] [--json]";
|
|
48
31
|
const CLOUD_TASK_ARTIFACTS_USAGE = "Usage: embed cloud task artifacts <task_id> [--json]";
|
|
49
32
|
const CLOUD_TASK_EVIDENCE_USAGE = "Usage: embed cloud task evidence <task_id> [--json]";
|
|
50
33
|
const ARTIFACT_STATUS_USAGE = "Usage: embed artifact status <artifact_id> [--json]";
|
|
@@ -98,36 +81,24 @@ const BUILD_IMAGE_BOOT_LOGO_USAGE = "Usage: embed build image boot-logo --logo <
|
|
|
98
81
|
const IMAGE_BOOT_LOGO_COMPOSE_USAGE = "Usage: embed image boot-logo compose --package <boot-logo-package.json> --base-image <boot.img|image.img> --output <image> [--manifest <manifest.json>] [--force] [--json]";
|
|
99
82
|
const BUILD_IMAGE_DTB_USAGE = "Usage: embed build image dtb --dtb <local.dtb|local.dts> [--input-format auto|dtb|dts] [--account <account_id>] [--project <project_id>] [--board taishanpi] [--variant 1M-RK3566] [--output <package.json>] [--json]";
|
|
100
83
|
const IMAGE_DTB_COMPOSE_USAGE = "Usage: embed image dtb compose --package <dtb-package.json> --base-image <boot.img|image.img> --output <image> [--manifest <manifest.json>] [--force] [--json]";
|
|
101
|
-
const
|
|
102
|
-
const LOCAL_TOOLCHAIN_INSTALLED_USAGE = "Usage: embed local toolchain installed [--board taishanpi-1m-rk3566|pico2w-rp2350-monitor|coloreasypico2-rp2350-monitor] [--channel stable] [--metadata-root <path>] [--install-root <path>] [--json]";
|
|
103
|
-
const LOCAL_TOOLCHAIN_LATEST_USAGE = "Usage: embed local toolchain latest [--board taishanpi-1m-rk3566|pico2w-rp2350-monitor|coloreasypico2-rp2350-monitor] [--channel stable] [--metadata-root <path>] [--json]";
|
|
84
|
+
const LOCAL_TOOLCHAIN_LATEST_USAGE = "Usage: embed local toolchain latest [--board taishanpi-1m-rk3566] [--channel stable] [--metadata-root <path>] [--json]";
|
|
104
85
|
const LOCAL_TOOLCHAIN_CURRENT_USAGE = "Usage: embed local toolchain current [--install-root <path>] [--json]";
|
|
105
|
-
const LOCAL_TOOLCHAIN_INSTALL_USAGE = "Usage: embed local toolchain install [--board taishanpi-1m-rk3566
|
|
106
|
-
const
|
|
107
|
-
const LOCAL_TOOLCHAIN_VALIDATE_USAGE = "Usage: embed local toolchain validate [--board taishanpi-1m-rk3566|pico2w-rp2350-monitor|coloreasypico2-rp2350-monitor] [--release-root <path>] [--mode minimal|runtime|compile|qt|firmware|full|images] [--json]";
|
|
108
|
-
const LOCAL_WSL_STATUS_USAGE = "Usage: embed local wsl status [--json]";
|
|
109
|
-
const LOCAL_WSL_INSTALL_USAGE = "Usage: embed local wsl install [--distribution Ubuntu] [--no-launch true|false] [--web-download true|false] [--timeout-ms 600000] [--json]";
|
|
86
|
+
const LOCAL_TOOLCHAIN_INSTALL_USAGE = "Usage: embed local toolchain install [--board taishanpi-1m-rk3566] [--channel stable] [--metadata-root <path>] [--source-url <tar.gz-url>|--source-release-root <path>] [--install-root <path>] [--force] [--json]\nDefault source: the production download channel at download.embedboard.com.";
|
|
87
|
+
const LOCAL_TOOLCHAIN_VALIDATE_USAGE = "Usage: embed local toolchain validate [--release-root <path>] [--json]";
|
|
110
88
|
const LOCAL_COMPILE_TAISHANPI_USAGE = "Usage: embed local compile taishanpi --source <main.c|main.cpp> --output <artifact> [--release-root <path>] [--account <account_id>] [--json]";
|
|
111
|
-
const LOCAL_BUILD_QT_SMOKE_USAGE = "Usage: embed local build qt-smoke --build-dir <dir> [--source <qt-
|
|
112
|
-
const AUTH_DEVICE_STATUS_USAGE = "Usage: embed auth device status [--json]";
|
|
113
|
-
const AUTH_DEVICE_LIST_USAGE = "Usage: embed auth device list [--json]";
|
|
114
|
-
const AUTH_DEVICE_REVOKE_USAGE = "Usage: embed auth device revoke <device_id> [--json]";
|
|
115
|
-
const AUTH_DEVICE_RENAME_USAGE = "Usage: embed auth device rename <device_id> --label <name> [--json]";
|
|
89
|
+
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]";
|
|
116
90
|
const BOARD_REGISTRY_LIST_USAGE = "Usage: embed board registry list [--json]";
|
|
117
91
|
const BOARD_REGISTRY_SHOW_USAGE = "Usage: embed board registry show <template_id> [--json]";
|
|
118
92
|
const BOARD_METHODS_USAGE = "Usage: embed board methods <template_id> [--json]";
|
|
119
93
|
const BOARD_KNOWLEDGE_USAGE = "Usage: embed board knowledge <template_id> [--json]";
|
|
120
|
-
const BOARD_KNOWLEDGE_SEARCH_USAGE = "Usage: embed board knowledge search <template_id> --query <text> [--source board_pack|build_template|registry] [--limit 5] [--json]";
|
|
121
94
|
const BOARD_KNOWLEDGE_FILE_USAGE = "Usage: embed board knowledge file <template_id> --source board_pack|build_template|registry --path <relative_path> [--output <local_path>] [--json]";
|
|
122
95
|
const MODEL_LIST_USAGE = "Usage: embed model list [--json]";
|
|
123
96
|
const MODEL_DEFAULT_USAGE = "Usage: embed model default [--json]";
|
|
124
97
|
const SERVICE_MODES_USAGE = "Usage: embed service modes [--json]";
|
|
125
|
-
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
|
|
126
|
-
let cachedLocalHardwareFingerprint;
|
|
98
|
+
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]";
|
|
127
99
|
const TOOL_LIST_USAGE = "Usage: embed tool list [--json]";
|
|
128
100
|
const TOOL_CALL_USAGE = "Usage: embed tool call <capability_id> [--input-json '<json>'] [--approve] [--json]";
|
|
129
|
-
const
|
|
130
|
-
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-command <cmd>] [--run] [--timeout 30] [--json]";
|
|
101
|
+
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]";
|
|
131
102
|
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]";
|
|
132
103
|
const TASK_STATES = new Set([
|
|
133
104
|
"created",
|
|
@@ -176,17 +147,15 @@ async function main(argv) {
|
|
|
176
147
|
return output(parsed, await bridgePost("/v1/board/taishanpi/deploy", request), renderBoardDeployResult);
|
|
177
148
|
}
|
|
178
149
|
if (area === "bridge" && action === "start") {
|
|
179
|
-
|
|
150
|
+
startServer({
|
|
151
|
+
host: stringFlag(parsed, "host"),
|
|
152
|
+
port: numberFlag(parsed, "port")
|
|
153
|
+
});
|
|
154
|
+
return await waitForever();
|
|
180
155
|
}
|
|
181
156
|
if (area === "bridge" && action === "status") {
|
|
182
157
|
return output(parsed, await bridgeGet("/healthz"), renderBridgeStatus);
|
|
183
158
|
}
|
|
184
|
-
if (area === "mcp" && action === "start") {
|
|
185
|
-
return await runMcpStart(parsed);
|
|
186
|
-
}
|
|
187
|
-
if (area === "mcp") {
|
|
188
|
-
return output(parsed, fail("invalid_args", MCP_START_USAGE), undefined, 2);
|
|
189
|
-
}
|
|
190
159
|
if (area === "cloud" && action === "status") {
|
|
191
160
|
return output(parsed, await cloudGet("/healthz"), renderCloudStatus);
|
|
192
161
|
}
|
|
@@ -247,17 +216,6 @@ async function main(argv) {
|
|
|
247
216
|
return output(parsed, await cloudGet(`/v1/board-registry/templates/${encodeURIComponent(idResult.value)}/methods`), renderBoardMethods);
|
|
248
217
|
}
|
|
249
218
|
if (action === "knowledge") {
|
|
250
|
-
if (parsed.command[2] === "search") {
|
|
251
|
-
const request = boardKnowledgeSearchRequest(parsed);
|
|
252
|
-
if (typeof request === "string") {
|
|
253
|
-
return output(parsed, fail("invalid_args", request), undefined, 2);
|
|
254
|
-
}
|
|
255
|
-
const params = new URLSearchParams({ q: request.query, limit: String(request.limit) });
|
|
256
|
-
if (request.source) {
|
|
257
|
-
params.set("source", request.source);
|
|
258
|
-
}
|
|
259
|
-
return output(parsed, await cloudGet(`/v1/board-registry/templates/${encodeURIComponent(request.templateId)}/knowledge-search?${params.toString()}`), renderBoardKnowledgeSearch);
|
|
260
|
-
}
|
|
261
219
|
if (parsed.command[2] === "file") {
|
|
262
220
|
const request = boardKnowledgeFileRequest(parsed);
|
|
263
221
|
if (typeof request === "string") {
|
|
@@ -283,7 +241,6 @@ async function main(argv) {
|
|
|
283
241
|
BOARD_REGISTRY_SHOW_USAGE,
|
|
284
242
|
BOARD_METHODS_USAGE,
|
|
285
243
|
BOARD_KNOWLEDGE_USAGE,
|
|
286
|
-
BOARD_KNOWLEDGE_SEARCH_USAGE,
|
|
287
244
|
BOARD_KNOWLEDGE_FILE_USAGE
|
|
288
245
|
].join("\n")), undefined, 2);
|
|
289
246
|
}
|
|
@@ -322,18 +279,7 @@ async function main(argv) {
|
|
|
322
279
|
const result = await pluginInstall(parsed);
|
|
323
280
|
return output(parsed, result, renderPluginInstall, result.ok ? 0 : 2);
|
|
324
281
|
}
|
|
325
|
-
|
|
326
|
-
if (parsed.command[2] === "check") {
|
|
327
|
-
const result = await pluginUpdateCheck(parsed);
|
|
328
|
-
return output(parsed, result, renderPluginUpdateCheck, result.ok ? 0 : 2);
|
|
329
|
-
}
|
|
330
|
-
if (["codex", "opencode", "all"].includes(parsed.command[2] ?? "")) {
|
|
331
|
-
const result = await pluginUpdate(parsed);
|
|
332
|
-
return output(parsed, result, renderPluginInstall, result.ok ? 0 : 2);
|
|
333
|
-
}
|
|
334
|
-
return output(parsed, fail("invalid_args", [PLUGIN_UPDATE_CHECK_USAGE, PLUGIN_UPDATE_USAGE].join("\n")), undefined, 2);
|
|
335
|
-
}
|
|
336
|
-
return output(parsed, fail("invalid_args", [PLUGIN_LIST_USAGE, PLUGIN_INSTALL_USAGE, PLUGIN_UPDATE_CHECK_USAGE, PLUGIN_UPDATE_USAGE].join("\n")), undefined, 2);
|
|
282
|
+
return output(parsed, fail("invalid_args", [PLUGIN_LIST_USAGE, PLUGIN_INSTALL_USAGE].join("\n")), undefined, 2);
|
|
337
283
|
}
|
|
338
284
|
if (area === "auth" && action === "login") {
|
|
339
285
|
const result = await authLogin(parsed);
|
|
@@ -342,31 +288,6 @@ async function main(argv) {
|
|
|
342
288
|
if (area === "auth" && action === "status") {
|
|
343
289
|
return output(parsed, ok(await authStatus()), renderAuthStatus);
|
|
344
290
|
}
|
|
345
|
-
if (area === "auth" && action === "device") {
|
|
346
|
-
const deviceAction = parsed.command[2] ?? "status";
|
|
347
|
-
if (deviceAction === "status") {
|
|
348
|
-
const result = await authDeviceStatus(parsed);
|
|
349
|
-
return output(parsed, result, renderAuthDeviceStatus, result.ok ? 0 : 2);
|
|
350
|
-
}
|
|
351
|
-
if (deviceAction === "list") {
|
|
352
|
-
const result = await authDeviceList(parsed);
|
|
353
|
-
return output(parsed, result, renderAuthDeviceList, result.ok ? 0 : 2);
|
|
354
|
-
}
|
|
355
|
-
if (deviceAction === "revoke") {
|
|
356
|
-
const result = await authDeviceRevoke(parsed);
|
|
357
|
-
return output(parsed, result, renderAuthDevice, result.ok ? 0 : 2);
|
|
358
|
-
}
|
|
359
|
-
if (deviceAction === "rename") {
|
|
360
|
-
const result = await authDeviceRename(parsed);
|
|
361
|
-
return output(parsed, result, renderAuthDevice, result.ok ? 0 : 2);
|
|
362
|
-
}
|
|
363
|
-
return output(parsed, fail("invalid_args", [
|
|
364
|
-
AUTH_DEVICE_STATUS_USAGE,
|
|
365
|
-
AUTH_DEVICE_LIST_USAGE,
|
|
366
|
-
AUTH_DEVICE_REVOKE_USAGE,
|
|
367
|
-
AUTH_DEVICE_RENAME_USAGE
|
|
368
|
-
].join("\n")), undefined, 2);
|
|
369
|
-
}
|
|
370
291
|
if (area === "auth" && action === "logout") {
|
|
371
292
|
await rm(DEFAULT_AUTH_FILE, { force: true });
|
|
372
293
|
return output(parsed, ok(await authStatus()), renderAuthStatus);
|
|
@@ -418,9 +339,6 @@ async function main(argv) {
|
|
|
418
339
|
ACCOUNT_KEY_REVOKE_USAGE
|
|
419
340
|
].join("\n")), undefined, 2);
|
|
420
341
|
}
|
|
421
|
-
if (area === "usage" || area === "billing") {
|
|
422
|
-
return output(parsed, quotaAndBillingDisabled(), undefined, 2);
|
|
423
|
-
}
|
|
424
342
|
if (area === "usage") {
|
|
425
343
|
if (action === "record") {
|
|
426
344
|
const body = usageRecordBody(parsed);
|
|
@@ -449,16 +367,6 @@ async function main(argv) {
|
|
|
449
367
|
USAGE_EVENTS_USAGE
|
|
450
368
|
].join("\n")), undefined, 2);
|
|
451
369
|
}
|
|
452
|
-
if (area === "mcp") {
|
|
453
|
-
if (action === "log" || action === "tool-event") {
|
|
454
|
-
const body = mcpToolEventBody(parsed);
|
|
455
|
-
if (typeof body === "string") {
|
|
456
|
-
return output(parsed, fail("invalid_args", body), undefined, 2);
|
|
457
|
-
}
|
|
458
|
-
return output(parsed, await cloudPost("/v1/mcp/tool-events", body), renderMcpToolEvent);
|
|
459
|
-
}
|
|
460
|
-
return output(parsed, fail("invalid_args", MCP_TOOL_EVENT_USAGE), undefined, 2);
|
|
461
|
-
}
|
|
462
370
|
if (area === "billing") {
|
|
463
371
|
if (action === "statement") {
|
|
464
372
|
const request = billingStatementRequest(parsed);
|
|
@@ -598,20 +506,6 @@ async function main(argv) {
|
|
|
598
506
|
return output(parsed, fail("invalid_args", [IMAGE_BOOT_LOGO_COMPOSE_USAGE, IMAGE_DTB_COMPOSE_USAGE].join("\n")), undefined, 2);
|
|
599
507
|
}
|
|
600
508
|
if (area === "local") {
|
|
601
|
-
if (action === "toolchain" && parsed.command[2] === "list") {
|
|
602
|
-
const request = localToolchainListRequest(parsed);
|
|
603
|
-
if (typeof request === "string") {
|
|
604
|
-
return output(parsed, fail("invalid_args", request), undefined, 2);
|
|
605
|
-
}
|
|
606
|
-
return output(parsed, ok(await listLocalToolchainEnvironments(request)), renderLocalToolchainList);
|
|
607
|
-
}
|
|
608
|
-
if (action === "toolchain" && parsed.command[2] === "installed") {
|
|
609
|
-
const request = localToolchainListRequest(parsed, LOCAL_TOOLCHAIN_INSTALLED_USAGE);
|
|
610
|
-
if (typeof request === "string") {
|
|
611
|
-
return output(parsed, fail("invalid_args", request), undefined, 2);
|
|
612
|
-
}
|
|
613
|
-
return output(parsed, ok(await listLocalToolchainEnvironments({ ...request, installedOnly: true })), renderLocalToolchainList);
|
|
614
|
-
}
|
|
615
509
|
if (action === "toolchain" && parsed.command[2] === "latest") {
|
|
616
510
|
const request = localToolchainLatestRequest(parsed);
|
|
617
511
|
if (typeof request === "string") {
|
|
@@ -633,34 +527,12 @@ async function main(argv) {
|
|
|
633
527
|
}
|
|
634
528
|
return output(parsed, ok(await installLocalToolchain(request)), renderLocalToolchainInstall);
|
|
635
529
|
}
|
|
636
|
-
if (action === "toolchain" && (parsed.command[2] === "uninstall" || parsed.command[2] === "remove")) {
|
|
637
|
-
const request = localToolchainUninstallRequest(parsed);
|
|
638
|
-
if (typeof request === "string") {
|
|
639
|
-
return output(parsed, fail("invalid_args", request), undefined, 2);
|
|
640
|
-
}
|
|
641
|
-
return output(parsed, ok(await uninstallLocalToolchain(request)), renderLocalToolchainUninstall);
|
|
642
|
-
}
|
|
643
530
|
if (action === "toolchain" && parsed.command[2] === "validate") {
|
|
644
531
|
const request = localToolchainValidateRequest(parsed);
|
|
645
532
|
if (typeof request === "string") {
|
|
646
533
|
return output(parsed, fail("invalid_args", request), undefined, 2);
|
|
647
534
|
}
|
|
648
|
-
return output(parsed, ok(await validateLocalToolchain(request)), renderLocalToolchainValidation);
|
|
649
|
-
}
|
|
650
|
-
if (action === "wsl" && parsed.command[2] === "status") {
|
|
651
|
-
const unknownFlag = firstUnknownFlag(parsed, ["json"]);
|
|
652
|
-
const extra = parsed.command.slice(3);
|
|
653
|
-
if (unknownFlag || extra.length > 0) {
|
|
654
|
-
return output(parsed, fail("invalid_args", unknownFlag ? `Unknown flag --${unknownFlag}. ${LOCAL_WSL_STATUS_USAGE}` : LOCAL_WSL_STATUS_USAGE), undefined, 2);
|
|
655
|
-
}
|
|
656
|
-
return output(parsed, ok(await windowsWslStatus()), renderWindowsWslStatus);
|
|
657
|
-
}
|
|
658
|
-
if (action === "wsl" && parsed.command[2] === "install") {
|
|
659
|
-
const request = localWslInstallRequest(parsed);
|
|
660
|
-
if (typeof request === "string") {
|
|
661
|
-
return output(parsed, fail("invalid_args", request), undefined, 2);
|
|
662
|
-
}
|
|
663
|
-
return output(parsed, ok(await windowsWslInstall(request)), renderWindowsWslInstall);
|
|
535
|
+
return output(parsed, ok(await validateLocalToolchain(request.releaseRoot)), renderLocalToolchainValidation);
|
|
664
536
|
}
|
|
665
537
|
if (action === "compile" && parsed.command[2] === "taishanpi") {
|
|
666
538
|
const request = localCompileTaishanPiRequest(parsed, await authStatus());
|
|
@@ -677,15 +549,10 @@ async function main(argv) {
|
|
|
677
549
|
return output(parsed, ok(await buildTaishanPiQtSmoke(request)), renderLocalCompileResult);
|
|
678
550
|
}
|
|
679
551
|
return output(parsed, fail("invalid_args", [
|
|
680
|
-
LOCAL_TOOLCHAIN_LIST_USAGE,
|
|
681
|
-
LOCAL_TOOLCHAIN_INSTALLED_USAGE,
|
|
682
552
|
LOCAL_TOOLCHAIN_LATEST_USAGE,
|
|
683
553
|
LOCAL_TOOLCHAIN_CURRENT_USAGE,
|
|
684
554
|
LOCAL_TOOLCHAIN_INSTALL_USAGE,
|
|
685
|
-
LOCAL_TOOLCHAIN_UNINSTALL_USAGE,
|
|
686
555
|
LOCAL_TOOLCHAIN_VALIDATE_USAGE,
|
|
687
|
-
LOCAL_WSL_STATUS_USAGE,
|
|
688
|
-
LOCAL_WSL_INSTALL_USAGE,
|
|
689
556
|
LOCAL_COMPILE_TAISHANPI_USAGE,
|
|
690
557
|
LOCAL_BUILD_QT_SMOKE_USAGE
|
|
691
558
|
].join("\n")), undefined, 2);
|
|
@@ -1117,18 +984,11 @@ async function main(argv) {
|
|
|
1117
984
|
return output(parsed, fail("unknown_command", `Unknown command: ${parsed.command.join(" ")}`), undefined, 2);
|
|
1118
985
|
}
|
|
1119
986
|
catch (error) {
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
remediation: commandFailureRemediation(message)
|
|
987
|
+
return output(parsed, fail("command_failed", error instanceof Error ? error.message : String(error), {
|
|
988
|
+
remediation: `Check that embed-local-bridge is running at ${DEFAULT_BRIDGE_URL}. Start it with: embed bridge start`
|
|
1123
989
|
}), undefined, 1);
|
|
1124
990
|
}
|
|
1125
991
|
}
|
|
1126
|
-
function commandFailureRemediation(message) {
|
|
1127
|
-
if (message.includes("TaishanPi") && message.includes("WSL2")) {
|
|
1128
|
-
return "Run: embedlabs local wsl status";
|
|
1129
|
-
}
|
|
1130
|
-
return `Check that embed-local-bridge is running at ${DEFAULT_BRIDGE_URL}. Start it with: embed bridge start`;
|
|
1131
|
-
}
|
|
1132
992
|
function parseArgs(argv) {
|
|
1133
993
|
const command = [];
|
|
1134
994
|
const flags = {};
|
|
@@ -1238,7 +1098,6 @@ function flashBody(parsed, includeApproval) {
|
|
|
1238
1098
|
variant_id: stringFlag(parsed, "variant"),
|
|
1239
1099
|
hardware_profile_id: stringFlag(parsed, "hardware-profile"),
|
|
1240
1100
|
profile_id: stringFlag(parsed, "profile"),
|
|
1241
|
-
local_device_id: stringFlag(parsed, "local-device-id") ?? stringFlag(parsed, "device-id"),
|
|
1242
1101
|
image_dir: stringFlag(parsed, "image-dir"),
|
|
1243
1102
|
artifact_path: stringFlag(parsed, "artifact"),
|
|
1244
1103
|
target_volume_path: stringFlag(parsed, "target-volume")
|
|
@@ -1267,11 +1126,11 @@ async function doctor() {
|
|
|
1267
1126
|
const bridgeHealth = await apiDoctorCheck("bridge_health", "Local Bridge health", `${bridgeBaseUrl}/healthz`, "bridge_unreachable", `Local Bridge is unreachable at ${bridgeBaseUrl}.`, renderHealthSummary, healthStatus);
|
|
1268
1127
|
checks.push(bridgeHealth);
|
|
1269
1128
|
if (isUsableDoctorCheck(bridgeHealth)) {
|
|
1270
|
-
checks.push(await apiDoctorCheck("device_scan", "Device
|
|
1129
|
+
checks.push(await apiDoctorCheck("device_scan", "Device scan", `${bridgeBaseUrl}/v1/devices`, "bridge_unreachable", `Local Bridge is unreachable at ${bridgeBaseUrl}.`, renderDeviceScanSummary, warningIfWarnings));
|
|
1271
1130
|
checks.push(await apiDoctorCheck("debug_tools", "Debug tool scan", `${bridgeBaseUrl}/v1/debug/tools`, "bridge_unreachable", `Local Bridge is unreachable at ${bridgeBaseUrl}.`, renderDebugToolScanSummary, warningIfWarnings));
|
|
1272
1131
|
}
|
|
1273
1132
|
else {
|
|
1274
|
-
checks.push(dependentDoctorCheck("device_scan", "Device
|
|
1133
|
+
checks.push(dependentDoctorCheck("device_scan", "Device scan", `${bridgeBaseUrl}/v1/devices`, "Device scan requires a reachable Local Bridge."));
|
|
1275
1134
|
checks.push(dependentDoctorCheck("debug_tools", "Debug tool scan", `${bridgeBaseUrl}/v1/debug/tools`, "Debug tool scan requires a reachable Local Bridge."));
|
|
1276
1135
|
}
|
|
1277
1136
|
checks.push(await apiDoctorCheck("cloud_api_health", "Cloud API health", `${cloudBaseUrl}/healthz`, "cloud_api_unreachable", `Cloud API is unreachable at ${cloudBaseUrl}.`, renderHealthSummary, healthStatus));
|
|
@@ -1330,8 +1189,7 @@ function authDoctorCheck(status) {
|
|
|
1330
1189
|
: {
|
|
1331
1190
|
code: "auth_not_ready",
|
|
1332
1191
|
message: "No CLI auth token is configured.",
|
|
1333
|
-
remediation:
|
|
1334
|
-
details: cloudAuthSetupDetails()
|
|
1192
|
+
remediation: "Run: embed auth login --token <token>"
|
|
1335
1193
|
}
|
|
1336
1194
|
};
|
|
1337
1195
|
}
|
|
@@ -1464,7 +1322,7 @@ function warningIfWarnings(data) {
|
|
|
1464
1322
|
}
|
|
1465
1323
|
function renderDeviceScanSummary(result) {
|
|
1466
1324
|
const warningText = result.warnings?.length ? ` ${result.warnings.length} warning(s).` : "";
|
|
1467
|
-
return `Device
|
|
1325
|
+
return `Device scan completed: ${result.devices.length} device(s), ${result.usb.length} USB item(s), ${result.serial.length} serial port(s).${warningText}`;
|
|
1468
1326
|
}
|
|
1469
1327
|
function renderDebugToolScanSummary(result) {
|
|
1470
1328
|
const available = result.tools.filter((tool) => tool.available).length;
|
|
@@ -1482,142 +1340,16 @@ function isApiResponse(value) {
|
|
|
1482
1340
|
return isJsonObject(error) && typeof error.code === "string" && typeof error.message === "string";
|
|
1483
1341
|
}
|
|
1484
1342
|
async function bridgeGet(path) {
|
|
1485
|
-
|
|
1343
|
+
const response = await fetch(`${DEFAULT_BRIDGE_URL}${path}`);
|
|
1344
|
+
return await response.json();
|
|
1486
1345
|
}
|
|
1487
1346
|
async function bridgePost(path, body) {
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
const makeRequest = async () => {
|
|
1493
|
-
const response = await fetch(`${DEFAULT_BRIDGE_URL}${path}`, {
|
|
1494
|
-
method,
|
|
1495
|
-
headers: bridgeHeaders(method, path, method === "POST" ? bodyText : "", method === "POST" ? { "content-type": "application/json" } : {}),
|
|
1496
|
-
body: method === "POST" ? bodyText : undefined
|
|
1497
|
-
});
|
|
1498
|
-
return await response.json();
|
|
1499
|
-
};
|
|
1500
|
-
try {
|
|
1501
|
-
return await makeRequest();
|
|
1502
|
-
}
|
|
1503
|
-
catch (error) {
|
|
1504
|
-
if (!isBridgeConnectionFailure(error)) {
|
|
1505
|
-
throw error;
|
|
1506
|
-
}
|
|
1507
|
-
const started = await ensureBridgeStartedForRequest();
|
|
1508
|
-
if (!started.ok) {
|
|
1509
|
-
return started;
|
|
1510
|
-
}
|
|
1511
|
-
return await makeRequest();
|
|
1512
|
-
}
|
|
1513
|
-
}
|
|
1514
|
-
function bridgeHeaders(method, path, bodyText, base = {}) {
|
|
1515
|
-
const token = process.env.EMBED_BRIDGE_TOKEN?.trim();
|
|
1516
|
-
if (!token) {
|
|
1517
|
-
return base;
|
|
1518
|
-
}
|
|
1519
|
-
const headers = {
|
|
1520
|
-
...base,
|
|
1521
|
-
authorization: `Bearer ${token}`
|
|
1522
|
-
};
|
|
1523
|
-
addBridgeRequestSignature(headers, method, path, bodyText, token);
|
|
1524
|
-
return headers;
|
|
1525
|
-
}
|
|
1526
|
-
function addBridgeRequestSignature(headers, method, pathWithQuery, bodyText, token) {
|
|
1527
|
-
if (process.env.EMBED_BRIDGE_SIGNING === "0") {
|
|
1528
|
-
return;
|
|
1529
|
-
}
|
|
1530
|
-
const timestamp = String(Math.floor(Date.now() / 1000));
|
|
1531
|
-
const nonce = randomBytes(16).toString("hex");
|
|
1532
|
-
const bodySha256 = createHash("sha256").update(bodyText).digest("hex");
|
|
1533
|
-
const keyId = createHash("sha256").update(token).digest("hex").slice(0, 16);
|
|
1534
|
-
const canonical = cloudRequestCanonicalString(method, pathWithQuery, timestamp, nonce, bodySha256);
|
|
1535
|
-
headers["x-embed-key-id"] = keyId;
|
|
1536
|
-
headers["x-embed-timestamp"] = timestamp;
|
|
1537
|
-
headers["x-embed-nonce"] = nonce;
|
|
1538
|
-
headers["x-embed-body-sha256"] = bodySha256;
|
|
1539
|
-
headers["x-embed-signature"] = createHmac("sha256", token).update(canonical).digest("hex");
|
|
1540
|
-
}
|
|
1541
|
-
function isBridgeConnectionFailure(error) {
|
|
1542
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1543
|
-
return message.includes("fetch failed") ||
|
|
1544
|
-
message.includes("ECONNREFUSED") ||
|
|
1545
|
-
message.includes("ECONNRESET") ||
|
|
1546
|
-
message.includes("UND_ERR_SOCKET");
|
|
1547
|
-
}
|
|
1548
|
-
async function ensureBridgeStartedForRequest() {
|
|
1549
|
-
if (process.env.EMBED_BRIDGE_AUTO_START === "0") {
|
|
1550
|
-
return fail("bridge_unavailable", `embed-local-bridge is not running at ${DEFAULT_BRIDGE_URL}.`, {
|
|
1551
|
-
remediation: `Start it with: embed bridge start`
|
|
1552
|
-
});
|
|
1553
|
-
}
|
|
1554
|
-
let bridgeURL;
|
|
1555
|
-
try {
|
|
1556
|
-
bridgeURL = new URL(DEFAULT_BRIDGE_URL);
|
|
1557
|
-
}
|
|
1558
|
-
catch {
|
|
1559
|
-
return fail("bridge_url_invalid", `EMBED_BRIDGE_URL is not a valid URL: ${DEFAULT_BRIDGE_URL}`);
|
|
1560
|
-
}
|
|
1561
|
-
if (!isLocalBridgeURL(bridgeURL)) {
|
|
1562
|
-
return fail("bridge_unavailable", `embed-local-bridge is not reachable at ${DEFAULT_BRIDGE_URL}.`, {
|
|
1563
|
-
remediation: `Start the bridge for that host, or set EMBED_BRIDGE_URL to a local bridge URL.`
|
|
1564
|
-
});
|
|
1565
|
-
}
|
|
1566
|
-
const launcher = await resolveBridgeLauncher();
|
|
1567
|
-
const host = bridgeURL.hostname === "::1" ? "::1" : bridgeURL.hostname || "127.0.0.1";
|
|
1568
|
-
const port = bridgeURL.port || "18083";
|
|
1569
|
-
const env = {
|
|
1570
|
-
...process.env,
|
|
1571
|
-
EMBED_BRIDGE_HOST: host,
|
|
1572
|
-
EMBED_BRIDGE_PORT: port
|
|
1573
|
-
};
|
|
1574
|
-
const child = spawn(launcher.command, [...launcher.args, "--host", host, "--port", port], {
|
|
1575
|
-
cwd: process.cwd(),
|
|
1576
|
-
detached: true,
|
|
1577
|
-
stdio: "ignore",
|
|
1578
|
-
env
|
|
1579
|
-
});
|
|
1580
|
-
child.unref();
|
|
1581
|
-
const ready = await waitForBridgeHealth(bridgeURL, 8000);
|
|
1582
|
-
if (!ready.ok) {
|
|
1583
|
-
return ready;
|
|
1584
|
-
}
|
|
1585
|
-
return ok({
|
|
1586
|
-
started: true,
|
|
1587
|
-
bridge_url: DEFAULT_BRIDGE_URL,
|
|
1588
|
-
command: launcher.command
|
|
1347
|
+
const response = await fetch(`${DEFAULT_BRIDGE_URL}${path}`, {
|
|
1348
|
+
method: "POST",
|
|
1349
|
+
headers: { "content-type": "application/json" },
|
|
1350
|
+
body: JSON.stringify(body)
|
|
1589
1351
|
});
|
|
1590
|
-
|
|
1591
|
-
function isLocalBridgeURL(url) {
|
|
1592
|
-
const host = url.hostname.toLowerCase();
|
|
1593
|
-
return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
|
|
1594
|
-
}
|
|
1595
|
-
async function waitForBridgeHealth(bridgeURL, timeoutMs) {
|
|
1596
|
-
const deadline = Date.now() + timeoutMs;
|
|
1597
|
-
let lastError = "";
|
|
1598
|
-
while (Date.now() < deadline) {
|
|
1599
|
-
try {
|
|
1600
|
-
const response = await fetch(new URL("/healthz", bridgeURL), {
|
|
1601
|
-
headers: bridgeHeaders("GET", "/healthz", "")
|
|
1602
|
-
});
|
|
1603
|
-
const parsed = await response.json();
|
|
1604
|
-
if (parsed.ok) {
|
|
1605
|
-
return parsed;
|
|
1606
|
-
}
|
|
1607
|
-
lastError = parsed.error?.message ?? `HTTP ${response.status}`;
|
|
1608
|
-
}
|
|
1609
|
-
catch (error) {
|
|
1610
|
-
lastError = error instanceof Error ? error.message : String(error);
|
|
1611
|
-
}
|
|
1612
|
-
await delay(100);
|
|
1613
|
-
}
|
|
1614
|
-
return fail("bridge_start_failed", `embed-local-bridge did not become healthy at ${DEFAULT_BRIDGE_URL}.`, {
|
|
1615
|
-
remediation: `Run embed bridge start in a separate terminal and retry.`,
|
|
1616
|
-
details: { last_error: lastError }
|
|
1617
|
-
});
|
|
1618
|
-
}
|
|
1619
|
-
function delay(ms) {
|
|
1620
|
-
return new Promise((resolveDelay) => setTimeout(resolveDelay, ms));
|
|
1352
|
+
return await response.json();
|
|
1621
1353
|
}
|
|
1622
1354
|
async function cloudGet(path) {
|
|
1623
1355
|
return await cloudRequest("GET", path);
|
|
@@ -1628,23 +1360,16 @@ async function cloudPost(path, body) {
|
|
|
1628
1360
|
async function cloudDownloadArtifact(artifactId, outputPath) {
|
|
1629
1361
|
try {
|
|
1630
1362
|
const headers = {};
|
|
1631
|
-
const
|
|
1632
|
-
if (
|
|
1633
|
-
|
|
1634
|
-
const integrity = await validateLocalDeviceIntegrity(auth.device);
|
|
1635
|
-
if (!integrity.ok) {
|
|
1636
|
-
return integrity;
|
|
1637
|
-
}
|
|
1638
|
-
}
|
|
1639
|
-
headers.authorization = `Bearer ${auth.token}`;
|
|
1640
|
-
addCloudRequestSignature(headers, "GET", `/v1/artifacts/${encodeURIComponent(artifactId)}/download`, "", auth.token, auth.device);
|
|
1363
|
+
const token = await cloudAuthToken();
|
|
1364
|
+
if (token) {
|
|
1365
|
+
headers.authorization = `Bearer ${token}`;
|
|
1641
1366
|
}
|
|
1642
1367
|
const response = await fetch(`${serviceBaseUrl(DEFAULT_CLOUD_API_URL)}/v1/artifacts/${encodeURIComponent(artifactId)}/download`, {
|
|
1643
1368
|
headers: Object.keys(headers).length > 0 ? headers : undefined
|
|
1644
1369
|
});
|
|
1645
1370
|
if (!response.ok) {
|
|
1646
1371
|
const parsed = await parseErrorResponse(response);
|
|
1647
|
-
return parsed
|
|
1372
|
+
return parsed ?? fail("artifact_download_failed", `Artifact download failed with HTTP ${response.status}.`);
|
|
1648
1373
|
}
|
|
1649
1374
|
const bytes = Buffer.from(await response.arrayBuffer());
|
|
1650
1375
|
const expectedSha256 = response.headers.get("x-embed-artifact-sha256")?.trim();
|
|
@@ -1673,28 +1398,19 @@ async function cloudDownloadArtifact(artifactId, outputPath) {
|
|
|
1673
1398
|
async function cloudRequest(method, path, body) {
|
|
1674
1399
|
try {
|
|
1675
1400
|
const headers = {};
|
|
1676
|
-
|
|
1677
|
-
if (bodyText) {
|
|
1401
|
+
if (body !== undefined) {
|
|
1678
1402
|
headers["content-type"] = "application/json";
|
|
1679
1403
|
}
|
|
1680
|
-
const
|
|
1681
|
-
if (
|
|
1682
|
-
|
|
1683
|
-
const integrity = await validateLocalDeviceIntegrity(auth.device);
|
|
1684
|
-
if (!integrity.ok) {
|
|
1685
|
-
return integrity;
|
|
1686
|
-
}
|
|
1687
|
-
}
|
|
1688
|
-
headers.authorization = `Bearer ${auth.token}`;
|
|
1689
|
-
addCloudRequestSignature(headers, method, path, bodyText, auth.token, auth.device);
|
|
1404
|
+
const token = await cloudAuthToken();
|
|
1405
|
+
if (token) {
|
|
1406
|
+
headers.authorization = `Bearer ${token}`;
|
|
1690
1407
|
}
|
|
1691
1408
|
const response = await fetch(`${serviceBaseUrl(DEFAULT_CLOUD_API_URL)}${path}`, {
|
|
1692
1409
|
method,
|
|
1693
1410
|
headers: Object.keys(headers).length > 0 ? headers : undefined,
|
|
1694
|
-
body: body === undefined ? undefined :
|
|
1411
|
+
body: body === undefined ? undefined : JSON.stringify(body)
|
|
1695
1412
|
});
|
|
1696
|
-
|
|
1697
|
-
return enrichCloudAuthFailure(parsed, Boolean(auth.token));
|
|
1413
|
+
return await response.json();
|
|
1698
1414
|
}
|
|
1699
1415
|
catch (error) {
|
|
1700
1416
|
return fail("cloud_api_unreachable", error instanceof Error ? error.message : String(error), {
|
|
@@ -1702,129 +1418,6 @@ async function cloudRequest(method, path, body) {
|
|
|
1702
1418
|
});
|
|
1703
1419
|
}
|
|
1704
1420
|
}
|
|
1705
|
-
async function validateLocalDeviceIntegrity(device) {
|
|
1706
|
-
const current = await localHardwareFingerprint();
|
|
1707
|
-
if (current.fingerprint_hash === device.fingerprint_hash) {
|
|
1708
|
-
return ok(undefined);
|
|
1709
|
-
}
|
|
1710
|
-
return fail("tool_integrity_check_failed", TOOL_INTEGRITY_RELOGIN_MESSAGE, {
|
|
1711
|
-
remediation: [
|
|
1712
|
-
"当前 Embed Labs CLI/插件配置绑定的电脑与本机硬件唯一码不一致。",
|
|
1713
|
-
TOOL_INTEGRITY_RELOGIN_MESSAGE,
|
|
1714
|
-
"如果账号设备数量已达上限,请先在原电脑或用户后台撤销旧设备。"
|
|
1715
|
-
].join("\n"),
|
|
1716
|
-
details: {
|
|
1717
|
-
expected_fingerprint_hash: device.fingerprint_hash,
|
|
1718
|
-
current_fingerprint_hash: current.fingerprint_hash,
|
|
1719
|
-
platform: current.platform,
|
|
1720
|
-
arch: current.arch,
|
|
1721
|
-
fingerprint_source: current.source
|
|
1722
|
-
}
|
|
1723
|
-
});
|
|
1724
|
-
}
|
|
1725
|
-
function enrichCloudAuthFailure(response, hadToken) {
|
|
1726
|
-
if (response.ok) {
|
|
1727
|
-
return response;
|
|
1728
|
-
}
|
|
1729
|
-
if (response.error.code.startsWith("device_") || response.error.code.startsWith("request_signature_")) {
|
|
1730
|
-
return fail(response.error.code, response.error.message, {
|
|
1731
|
-
remediation: [
|
|
1732
|
-
"This computer is not fully registered for the configured Embed Labs API Token.",
|
|
1733
|
-
"Run: embedlabs auth login --token <your_token>",
|
|
1734
|
-
"Then verify with: embedlabs auth device status",
|
|
1735
|
-
"If the account already has too many devices, revoke one with: embedlabs auth device revoke <device_id>"
|
|
1736
|
-
].join("\n"),
|
|
1737
|
-
details: response.error.details
|
|
1738
|
-
});
|
|
1739
|
-
}
|
|
1740
|
-
if (response.error.code !== "unauthorized") {
|
|
1741
|
-
return response;
|
|
1742
|
-
}
|
|
1743
|
-
if (!hadToken) {
|
|
1744
|
-
return fail("auth_token_missing", "Embed Labs API Token is not configured. Register or sign in, create an API Token, then configure it locally before using cloud and plugin services.", {
|
|
1745
|
-
remediation: cloudAuthSetupRemediation(),
|
|
1746
|
-
details: cloudAuthSetupDetails()
|
|
1747
|
-
});
|
|
1748
|
-
}
|
|
1749
|
-
return fail("auth_token_rejected", "The configured Embed Labs API Token was rejected by the server. Recreate or copy a fresh token from the dashboard and sign in again.", {
|
|
1750
|
-
remediation: cloudAuthSetupRemediation(),
|
|
1751
|
-
details: cloudAuthSetupDetails()
|
|
1752
|
-
});
|
|
1753
|
-
}
|
|
1754
|
-
function cloudAuthSetupRemediation() {
|
|
1755
|
-
return [
|
|
1756
|
-
`1. Open ${DEFAULT_DASHBOARD_URL} and register or sign in.`,
|
|
1757
|
-
"2. Create or copy your Embed Labs API Token from the user dashboard.",
|
|
1758
|
-
"3. Run: embedlabs auth login --token <your_token>",
|
|
1759
|
-
"4. For automation, set: EMBED_API_TOKEN=<your_token>",
|
|
1760
|
-
"5. Verify with: embedlabs auth status"
|
|
1761
|
-
].join("\n");
|
|
1762
|
-
}
|
|
1763
|
-
function cloudAuthSetupDetails() {
|
|
1764
|
-
return {
|
|
1765
|
-
dashboard_url: DEFAULT_DASHBOARD_URL,
|
|
1766
|
-
login_command: "embedlabs auth login --token <your_token>",
|
|
1767
|
-
env_var: "EMBED_API_TOKEN",
|
|
1768
|
-
auth_status_command: "embedlabs auth status",
|
|
1769
|
-
auth_file: DEFAULT_AUTH_FILE
|
|
1770
|
-
};
|
|
1771
|
-
}
|
|
1772
|
-
function addCloudRequestSignature(headers, method, pathWithQuery, bodyText, token, device) {
|
|
1773
|
-
if (process.env.EMBED_CLOUD_API_SIGNING === "0") {
|
|
1774
|
-
return;
|
|
1775
|
-
}
|
|
1776
|
-
const timestamp = String(Math.floor(Date.now() / 1000));
|
|
1777
|
-
const nonce = randomBytes(16).toString("hex");
|
|
1778
|
-
const bodySha256 = createHash("sha256").update(bodyText).digest("hex");
|
|
1779
|
-
const keyId = createHash("sha256").update(token).digest("hex").slice(0, 16);
|
|
1780
|
-
const canonical = device
|
|
1781
|
-
? cloudRequestCanonicalStringV2(method, pathWithQuery, timestamp, nonce, bodySha256, device, EMBED_CLIENT_NAME, EMBED_CLIENT_VERSION)
|
|
1782
|
-
: cloudRequestCanonicalString(method, pathWithQuery, timestamp, nonce, bodySha256);
|
|
1783
|
-
headers["x-embed-key-id"] = keyId;
|
|
1784
|
-
headers["x-embed-timestamp"] = timestamp;
|
|
1785
|
-
headers["x-embed-nonce"] = nonce;
|
|
1786
|
-
headers["x-embed-body-sha256"] = bodySha256;
|
|
1787
|
-
if (device) {
|
|
1788
|
-
headers["x-embed-signature-version"] = "v2";
|
|
1789
|
-
headers["x-embed-device-id"] = device.device_id;
|
|
1790
|
-
headers["x-embed-device-fingerprint-sha256"] = device.fingerprint_hash;
|
|
1791
|
-
headers["x-embed-client-name"] = EMBED_CLIENT_NAME;
|
|
1792
|
-
headers["x-embed-client-version"] = EMBED_CLIENT_VERSION;
|
|
1793
|
-
headers["x-embed-device-signature"] = signCrypto(null, Buffer.from(canonical), device.private_key_pem).toString("base64url");
|
|
1794
|
-
}
|
|
1795
|
-
headers["x-embed-signature"] = createHmac("sha256", token).update(canonical).digest("hex");
|
|
1796
|
-
}
|
|
1797
|
-
function cloudRequestCanonicalString(method, pathWithQuery, timestamp, nonce, bodySha256) {
|
|
1798
|
-
return [
|
|
1799
|
-
method.toUpperCase(),
|
|
1800
|
-
normalizeCloudPathForSignature(pathWithQuery),
|
|
1801
|
-
timestamp,
|
|
1802
|
-
nonce,
|
|
1803
|
-
bodySha256
|
|
1804
|
-
].join("\n");
|
|
1805
|
-
}
|
|
1806
|
-
function cloudRequestCanonicalStringV2(method, pathWithQuery, timestamp, nonce, bodySha256, device, clientName, clientVersion) {
|
|
1807
|
-
return [
|
|
1808
|
-
method.toUpperCase(),
|
|
1809
|
-
normalizeCloudPathForSignature(pathWithQuery),
|
|
1810
|
-
timestamp,
|
|
1811
|
-
nonce,
|
|
1812
|
-
bodySha256,
|
|
1813
|
-
device.device_id,
|
|
1814
|
-
device.fingerprint_hash,
|
|
1815
|
-
clientName,
|
|
1816
|
-
clientVersion
|
|
1817
|
-
].join("\n");
|
|
1818
|
-
}
|
|
1819
|
-
function normalizeCloudPathForSignature(pathWithQuery) {
|
|
1820
|
-
try {
|
|
1821
|
-
const parsed = new URL(pathWithQuery, "http://embed.local");
|
|
1822
|
-
return `${parsed.pathname}${parsed.search}`;
|
|
1823
|
-
}
|
|
1824
|
-
catch {
|
|
1825
|
-
return pathWithQuery.startsWith("/") ? pathWithQuery : `/${pathWithQuery}`;
|
|
1826
|
-
}
|
|
1827
|
-
}
|
|
1828
1421
|
async function pluginList(parsed) {
|
|
1829
1422
|
const releaseDir = stringFlag(parsed, "release-dir");
|
|
1830
1423
|
const manifest = releaseDir ? await readPluginReleaseManifest(releaseDir) : undefined;
|
|
@@ -1924,112 +1517,13 @@ async function pluginInstall(parsed) {
|
|
|
1924
1517
|
await rm(tempDir, { recursive: true, force: true });
|
|
1925
1518
|
}
|
|
1926
1519
|
}
|
|
1927
|
-
async function pluginUpdateCheck(parsed) {
|
|
1928
|
-
const unknownFlag = firstUnknownFlag(parsed, ["release-url", "target", "codex-target", "opencode-target", "json"]);
|
|
1929
|
-
if (unknownFlag) {
|
|
1930
|
-
return fail("invalid_args", `Unknown flag --${unknownFlag}. ${PLUGIN_UPDATE_CHECK_USAGE}`);
|
|
1931
|
-
}
|
|
1932
|
-
const unexpected = parsed.command.slice(3);
|
|
1933
|
-
if (unexpected.length > 0) {
|
|
1934
|
-
return fail("invalid_args", `Unexpected argument: ${unexpected[0]}. ${PLUGIN_UPDATE_CHECK_USAGE}`);
|
|
1935
|
-
}
|
|
1936
|
-
const remoteManifest = await fetchRemotePluginManifest(parsed);
|
|
1937
|
-
if (!remoteManifest.ok) {
|
|
1938
|
-
return remoteManifest;
|
|
1939
|
-
}
|
|
1940
|
-
const manifest = remoteManifest.data;
|
|
1941
|
-
const codexPackage = manifest.packages?.find((item) => item.id === "codex-embed-labs");
|
|
1942
|
-
const opencodePackage = manifest.packages?.find((item) => item.id === "opencode-embed-labs");
|
|
1943
|
-
const codexTarget = join(codexPluginTargetRoot(parsed, true), CODEX_PLUGIN_NAME);
|
|
1944
|
-
const openCodeTarget = openCodePluginTargetRoot(parsed, true);
|
|
1945
|
-
return ok({
|
|
1946
|
-
release_url: pluginReleaseBaseUrl(parsed),
|
|
1947
|
-
latest_version: manifest.version,
|
|
1948
|
-
release_notes: normalizedReleaseNotes(manifest.release_notes),
|
|
1949
|
-
plugins: [
|
|
1950
|
-
await pluginUpdateItem({
|
|
1951
|
-
id: "codex",
|
|
1952
|
-
displayName: "Embed Labs Codex plugin",
|
|
1953
|
-
targetPath: codexTarget,
|
|
1954
|
-
installedVersion: await installedCodexPluginVersion(codexTarget),
|
|
1955
|
-
latestVersion: codexPackage?.version ?? manifest.version,
|
|
1956
|
-
releaseFile: codexPackage?.file,
|
|
1957
|
-
updateCommand: "embedlabs plugin update codex"
|
|
1958
|
-
}),
|
|
1959
|
-
await pluginUpdateItem({
|
|
1960
|
-
id: "opencode",
|
|
1961
|
-
displayName: "Embed Labs OpenCode plugin",
|
|
1962
|
-
targetPath: openCodeTarget,
|
|
1963
|
-
installedVersion: await installedOpenCodePluginVersion(openCodeTarget),
|
|
1964
|
-
latestVersion: opencodePackage?.version ?? manifest.version,
|
|
1965
|
-
releaseFile: opencodePackage?.file,
|
|
1966
|
-
updateCommand: "embedlabs plugin update opencode"
|
|
1967
|
-
})
|
|
1968
|
-
]
|
|
1969
|
-
});
|
|
1970
|
-
}
|
|
1971
|
-
function normalizedReleaseNotes(notes) {
|
|
1972
|
-
if (!Array.isArray(notes)) {
|
|
1973
|
-
return [];
|
|
1974
|
-
}
|
|
1975
|
-
return notes
|
|
1976
|
-
.filter((item) => typeof item === "string" && item.trim().length > 0)
|
|
1977
|
-
.map((item) => item.trim());
|
|
1978
|
-
}
|
|
1979
|
-
async function pluginUpdate(parsed) {
|
|
1980
|
-
const unknownFlag = firstUnknownFlag(parsed, ["release-url", "target", "codex-target", "opencode-target", "json"]);
|
|
1981
|
-
if (unknownFlag) {
|
|
1982
|
-
return fail("invalid_args", `Unknown flag --${unknownFlag}. ${PLUGIN_UPDATE_USAGE}`);
|
|
1983
|
-
}
|
|
1984
|
-
const target = parsed.command[2];
|
|
1985
|
-
if (!target || !["codex", "opencode", "all"].includes(target)) {
|
|
1986
|
-
return fail("invalid_args", PLUGIN_UPDATE_USAGE);
|
|
1987
|
-
}
|
|
1988
|
-
const unexpected = parsed.command.slice(3);
|
|
1989
|
-
if (unexpected.length > 0) {
|
|
1990
|
-
return fail("invalid_args", `Unexpected argument: ${unexpected[0]}. ${PLUGIN_UPDATE_USAGE}`);
|
|
1991
|
-
}
|
|
1992
|
-
const installParsed = {
|
|
1993
|
-
...parsed,
|
|
1994
|
-
command: ["plugin", "install", target],
|
|
1995
|
-
flags: { ...parsed.flags, force: true }
|
|
1996
|
-
};
|
|
1997
|
-
return await pluginInstall(installParsed);
|
|
1998
|
-
}
|
|
1999
|
-
async function pluginUpdateItem(input) {
|
|
2000
|
-
const installed = !!input.installedVersion;
|
|
2001
|
-
const updateAvailable = !!input.latestVersion && input.installedVersion !== input.latestVersion;
|
|
2002
|
-
const notes = [];
|
|
2003
|
-
if (!installed) {
|
|
2004
|
-
notes.push("Plugin is not installed in the selected target.");
|
|
2005
|
-
}
|
|
2006
|
-
else if (updateAvailable) {
|
|
2007
|
-
notes.push("A newer plugin release is available. Run the update command, then restart Codex/OpenCode.");
|
|
2008
|
-
}
|
|
2009
|
-
else {
|
|
2010
|
-
notes.push("Plugin is up to date for the selected release channel.");
|
|
2011
|
-
}
|
|
2012
|
-
return {
|
|
2013
|
-
id: input.id,
|
|
2014
|
-
display_name: input.displayName,
|
|
2015
|
-
installed,
|
|
2016
|
-
installed_version: input.installedVersion,
|
|
2017
|
-
latest_version: input.latestVersion,
|
|
2018
|
-
update_available: updateAvailable,
|
|
2019
|
-
target_path: input.targetPath,
|
|
2020
|
-
release_file: input.releaseFile,
|
|
2021
|
-
update_command: input.updateCommand,
|
|
2022
|
-
notes
|
|
2023
|
-
};
|
|
2024
|
-
}
|
|
2025
1520
|
async function installCodexPlugin(parsed, context) {
|
|
2026
1521
|
const source = await resolveCodexPluginSource(context);
|
|
2027
1522
|
if (!source.ok) {
|
|
2028
1523
|
return source;
|
|
2029
1524
|
}
|
|
2030
1525
|
const targetRoot = codexPluginTargetRoot(parsed, context.installingAll);
|
|
2031
|
-
const targetPath = join(targetRoot,
|
|
2032
|
-
const legacyCleanup = await cleanupLegacyCodexPluginRemnants(targetRoot);
|
|
1526
|
+
const targetPath = join(targetRoot, "embed-labs");
|
|
2033
1527
|
if (await pathExists(targetPath) && !booleanFlag(parsed, "force")) {
|
|
2034
1528
|
return fail("plugin_already_installed", `Codex plugin already exists at ${targetPath}.`, {
|
|
2035
1529
|
remediation: "Pass --force to replace it, or pass --codex-target/--target to install into a different directory."
|
|
@@ -2039,24 +1533,16 @@ async function installCodexPlugin(parsed, context) {
|
|
|
2039
1533
|
await mkdir(targetRoot, { recursive: true });
|
|
2040
1534
|
await cp(source.data.sourcePath, targetPath, { recursive: true });
|
|
2041
1535
|
const mcpRegistration = await maybeRegisterCodexMcp(parsed, targetRoot, targetPath);
|
|
2042
|
-
const marketplaceRegistration = await maybeRegisterCodexMarketplace(parsed, targetRoot, targetPath);
|
|
2043
1536
|
return ok({
|
|
2044
1537
|
id: "codex",
|
|
2045
1538
|
target_path: targetPath,
|
|
2046
1539
|
source: source.data.sourceLabel,
|
|
2047
1540
|
version: source.data.version,
|
|
2048
1541
|
command_hint: mcpRegistration.registered
|
|
2049
|
-
?
|
|
2050
|
-
? "Codex MCP and plugin marketplace entry were registered. Fully restart Codex to reload @Embed Labs."
|
|
2051
|
-
: "Codex MCP was registered. Start a new Codex session to reload tools.")
|
|
1542
|
+
? "Codex MCP was registered. Start a new Codex session to reload tools."
|
|
2052
1543
|
: mcpRegistration.hint,
|
|
2053
|
-
warning: legacyCodexCleanupWarning(legacyCleanup),
|
|
2054
1544
|
mcp_registered: mcpRegistration.registered,
|
|
2055
|
-
mcp_warning: mcpRegistration.warning
|
|
2056
|
-
marketplace_registered: marketplaceRegistration.registered,
|
|
2057
|
-
marketplace_path: marketplaceRegistration.marketplacePath,
|
|
2058
|
-
marketplace_warning: marketplaceRegistration.warning,
|
|
2059
|
-
cleanup: legacyCleanup
|
|
1545
|
+
mcp_warning: mcpRegistration.warning
|
|
2060
1546
|
});
|
|
2061
1547
|
}
|
|
2062
1548
|
async function installOpenCodePlugin(parsed, context) {
|
|
@@ -2065,17 +1551,15 @@ async function installOpenCodePlugin(parsed, context) {
|
|
|
2065
1551
|
return source;
|
|
2066
1552
|
}
|
|
2067
1553
|
const targetRoot = openCodePluginTargetRoot(parsed, context.installingAll);
|
|
2068
|
-
const
|
|
2069
|
-
|
|
2070
|
-
const legacyCleanup = await cleanupLegacyOpenCodePluginRemnants(targetRoot, globalInstall);
|
|
2071
|
-
if (!globalInstall && await pathExists(wrapperPath) && !booleanFlag(parsed, "force")) {
|
|
1554
|
+
const wrapperPath = join(targetRoot, "plugins", "development-board-toolchain.js");
|
|
1555
|
+
if (await pathExists(wrapperPath) && !booleanFlag(parsed, "force")) {
|
|
2072
1556
|
return fail("plugin_already_installed", `OpenCode plugin wrapper already exists at ${wrapperPath}.`, {
|
|
2073
1557
|
remediation: "Pass --force to replace it, or pass --opencode-target/--target to install into a different directory."
|
|
2074
1558
|
});
|
|
2075
1559
|
}
|
|
2076
1560
|
await mkdir(join(targetRoot, "plugins"), { recursive: true });
|
|
2077
1561
|
const packagePath = await prepareOpenCodePackageForInstall(targetRoot, source.data.packagePath);
|
|
2078
|
-
const npmResult = await runLocalProcess(
|
|
1562
|
+
const npmResult = await runLocalProcess("npm", [
|
|
2079
1563
|
"install",
|
|
2080
1564
|
"--prefix",
|
|
2081
1565
|
targetRoot,
|
|
@@ -2094,25 +1578,15 @@ async function installOpenCodePlugin(parsed, context) {
|
|
|
2094
1578
|
});
|
|
2095
1579
|
}
|
|
2096
1580
|
await ensureOpenCodeInstallPackageJson(targetRoot);
|
|
2097
|
-
|
|
2098
|
-
await rm(wrapperPath, { force: true });
|
|
2099
|
-
legacyCleanup.legacy_removed_config_entries?.push(...await ensureOpenCodeGlobalPluginConfig());
|
|
2100
|
-
}
|
|
2101
|
-
else {
|
|
2102
|
-
await writeFile(wrapperPath, `export { default, DevelopmentBoardToolchainPlugin } from "embed-labs";\n`, "utf8");
|
|
2103
|
-
}
|
|
1581
|
+
await writeFile(wrapperPath, `export { default, DevelopmentBoardToolchainPlugin } from "embed-labs";\n`, "utf8");
|
|
2104
1582
|
const duplicateWarning = await openCodeDuplicatePluginWarning(targetRoot);
|
|
2105
|
-
const cleanupWarning = legacyOpenCodeCleanupWarning(legacyCleanup);
|
|
2106
1583
|
return ok({
|
|
2107
1584
|
id: "opencode",
|
|
2108
1585
|
target_path: targetRoot,
|
|
2109
1586
|
source: source.data.sourceLabel,
|
|
2110
1587
|
version: source.data.version,
|
|
2111
|
-
command_hint:
|
|
2112
|
-
|
|
2113
|
-
: "Start OpenCode from the project containing this .opencode directory.",
|
|
2114
|
-
warning: combineWarnings(cleanupWarning, duplicateWarning),
|
|
2115
|
-
cleanup: legacyCleanup
|
|
1588
|
+
command_hint: "Start OpenCode from the project containing this .opencode directory.",
|
|
1589
|
+
warning: duplicateWarning
|
|
2116
1590
|
});
|
|
2117
1591
|
}
|
|
2118
1592
|
async function resolveCodexPluginSource(context) {
|
|
@@ -2167,7 +1641,7 @@ async function resolveOpenCodePluginSource(context) {
|
|
|
2167
1641
|
remediation: "Run from the Embed-Labs-Cloud repo root or pass --release-dir pointing to a plugin release directory."
|
|
2168
1642
|
});
|
|
2169
1643
|
}
|
|
2170
|
-
const packed = await runLocalProcess(
|
|
1644
|
+
const packed = await runLocalProcess("npm", ["pack", packagePath, "--pack-destination", context.tempDir, "--json"]);
|
|
2171
1645
|
if (packed.code !== 0) {
|
|
2172
1646
|
return fail("opencode_plugin_pack_failed", "npm pack failed while preparing the OpenCode plugin source package.", {
|
|
2173
1647
|
details: {
|
|
@@ -2326,465 +1800,14 @@ function openCodePluginTargetRoot(parsed, installingAll) {
|
|
|
2326
1800
|
return resolve(target ?? defaultOpenCodeRoot());
|
|
2327
1801
|
}
|
|
2328
1802
|
function defaultCodexPluginRoot() {
|
|
2329
|
-
return join(
|
|
2330
|
-
}
|
|
2331
|
-
function defaultCodexHome() {
|
|
2332
|
-
return resolve(process.env.CODEX_HOME?.trim() || join(homedir(), ".codex"));
|
|
2333
|
-
}
|
|
2334
|
-
function codexConfigPath() {
|
|
2335
|
-
return join(defaultCodexHome(), "config.toml");
|
|
2336
|
-
}
|
|
2337
|
-
async function cleanupLegacyCodexPluginRemnants(targetRoot) {
|
|
2338
|
-
const removedPaths = [];
|
|
2339
|
-
const removedConfigTables = [];
|
|
2340
|
-
const warnings = [];
|
|
2341
|
-
const stoppedProcesses = await stopLegacyCodexPluginProcesses(warnings);
|
|
2342
|
-
const legacyPaths = [
|
|
2343
|
-
join(targetRoot, "cache", CODEX_MARKETPLACE_NAME, CODEX_PLUGIN_NAME)
|
|
2344
|
-
];
|
|
2345
|
-
for (const marketplaceName of LEGACY_CODEX_MARKETPLACE_NAMES) {
|
|
2346
|
-
legacyPaths.push(join(targetRoot, "cache", marketplaceName, CODEX_PLUGIN_NAME));
|
|
2347
|
-
}
|
|
2348
|
-
for (const pluginName of LEGACY_CODEX_PLUGIN_NAMES) {
|
|
2349
|
-
legacyPaths.push(join(targetRoot, pluginName));
|
|
2350
|
-
legacyPaths.push(join(targetRoot, "cache", pluginName));
|
|
2351
|
-
for (const marketplaceName of [CODEX_MARKETPLACE_NAME, ...LEGACY_CODEX_MARKETPLACE_NAMES]) {
|
|
2352
|
-
legacyPaths.push(join(targetRoot, "cache", marketplaceName, pluginName));
|
|
2353
|
-
}
|
|
2354
|
-
}
|
|
2355
|
-
legacyPaths.push(...await discoverLegacyCodexCachePaths(targetRoot));
|
|
2356
|
-
if (resolve(targetRoot) === resolve(defaultCodexPluginRoot())) {
|
|
2357
|
-
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"));
|
|
2358
|
-
legacyPaths.push(...legacyCodexLocalMarketplacePaths(), ...await discoverLegacyHomeAgentsMarketplacePaths(warnings), ...await discoverLegacyCodexProjectMarketplacePaths(warnings), ...legacyDevelopmentBoardRuntimePluginPaths());
|
|
2359
|
-
}
|
|
2360
|
-
for (const candidate of legacyPaths) {
|
|
2361
|
-
try {
|
|
2362
|
-
if (await pathExists(candidate)) {
|
|
2363
|
-
await rm(candidate, { recursive: true, force: true });
|
|
2364
|
-
removedPaths.push(candidate);
|
|
2365
|
-
}
|
|
2366
|
-
}
|
|
2367
|
-
catch (error) {
|
|
2368
|
-
warnings.push(`Could not remove ${candidate}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2369
|
-
}
|
|
2370
|
-
}
|
|
2371
|
-
if (resolve(targetRoot) === resolve(defaultCodexPluginRoot())) {
|
|
2372
|
-
const configPath = codexConfigPath();
|
|
2373
|
-
try {
|
|
2374
|
-
if (await pathExists(configPath)) {
|
|
2375
|
-
const current = await readFile(configPath, "utf8");
|
|
2376
|
-
const updated = removeLegacyCodexConfigTables(current);
|
|
2377
|
-
if (updated.text !== current) {
|
|
2378
|
-
await writeFile(configPath, updated.text, "utf8");
|
|
2379
|
-
}
|
|
2380
|
-
removedConfigTables.push(...updated.removedTables);
|
|
2381
|
-
}
|
|
2382
|
-
}
|
|
2383
|
-
catch (error) {
|
|
2384
|
-
warnings.push(`Could not update ${configPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2385
|
-
}
|
|
2386
|
-
}
|
|
2387
|
-
const removedHistoryEntries = await cleanupLegacyCodexTextState(warnings);
|
|
2388
|
-
return {
|
|
2389
|
-
legacy_removed_paths: Array.from(new Set(removedPaths)),
|
|
2390
|
-
legacy_removed_config_tables: removedConfigTables,
|
|
2391
|
-
legacy_removed_history_entries: removedHistoryEntries,
|
|
2392
|
-
legacy_stopped_processes: stoppedProcesses,
|
|
2393
|
-
...(warnings.length > 0 ? { warnings } : {})
|
|
2394
|
-
};
|
|
2395
|
-
}
|
|
2396
|
-
async function stopLegacyCodexPluginProcesses(warnings) {
|
|
2397
|
-
if (process.platform === "win32")
|
|
2398
|
-
return 0;
|
|
2399
|
-
try {
|
|
2400
|
-
const ps = await runLocalProcess("ps", ["-axo", "pid=,command="]);
|
|
2401
|
-
if (ps.code !== 0)
|
|
2402
|
-
return 0;
|
|
2403
|
-
let stopped = 0;
|
|
2404
|
-
for (const line of ps.stdout.split("\n")) {
|
|
2405
|
-
const match = /^\s*(\d+)\s+(.+)$/.exec(line);
|
|
2406
|
-
if (!match)
|
|
2407
|
-
continue;
|
|
2408
|
-
const pid = Number(match[1]);
|
|
2409
|
-
const command = match[2] || "";
|
|
2410
|
-
if (!isLegacyCodexPluginProcess(command))
|
|
2411
|
-
continue;
|
|
2412
|
-
try {
|
|
2413
|
-
process.kill(pid, "SIGTERM");
|
|
2414
|
-
stopped += 1;
|
|
2415
|
-
}
|
|
2416
|
-
catch (error) {
|
|
2417
|
-
warnings.push(`Could not stop legacy Codex plugin process ${pid}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2418
|
-
}
|
|
2419
|
-
}
|
|
2420
|
-
return stopped;
|
|
2421
|
-
}
|
|
2422
|
-
catch (error) {
|
|
2423
|
-
warnings.push(`Could not scan legacy Codex plugin processes: ${error instanceof Error ? error.message : String(error)}`);
|
|
2424
|
-
return 0;
|
|
2425
|
-
}
|
|
2426
|
-
}
|
|
2427
|
-
function isLegacyCodexPluginProcess(command) {
|
|
2428
|
-
const trimmed = command.trim();
|
|
2429
|
-
return /^\/.*\/dbt-agent-mcp-bridge(?:\s|$)/.test(trimmed)
|
|
2430
|
-
|| /^dbt-agent-mcp-bridge(?:\s|$)/.test(trimmed)
|
|
2431
|
-
|| legacyDevelopmentBoardRuntimeProcessPatterns().some((pattern) => pattern.test(trimmed));
|
|
2432
|
-
}
|
|
2433
|
-
function legacyDevelopmentBoardRuntimeProcessPatterns() {
|
|
2434
|
-
const home = escapeRegExp(homedir());
|
|
2435
|
-
return [
|
|
2436
|
-
new RegExp(`^${home}/Library/development-board-toolchain/agent/bin/dbt-agentd(?:\\s|$)`),
|
|
2437
|
-
new RegExp(`^${home}/Library/Application Support/development-board-toolchain/agent/bin/dbt-agentd(?:\\s|$)`),
|
|
2438
|
-
new RegExp(`^${home}/Library/development-board-toolchain/runtime/dbtctl\\s+status(?:\\s|$)`),
|
|
2439
|
-
new RegExp(`^${home}/Library/Application Support/development-board-toolchain/runtime/dbtctl\\s+status(?:\\s|$)`),
|
|
2440
|
-
new RegExp(`^${home}/.*?/DBT-Agent\\.app/Contents/MacOS/DBT-Agent(?:\\s|$)`)
|
|
2441
|
-
];
|
|
2442
|
-
}
|
|
2443
|
-
function legacyDevelopmentBoardRuntimePluginPaths() {
|
|
2444
|
-
return [
|
|
2445
|
-
join(homedir(), "Library", "development-board-toolchain", "runtime", "editor_plugins"),
|
|
2446
|
-
join(homedir(), "Library", "development-board-toolchain", "runtime", "opencode_plugin"),
|
|
2447
|
-
join(homedir(), "Library", "Application Support", "development-board-toolchain", "runtime", "editor_plugins"),
|
|
2448
|
-
join(homedir(), "Library", "Application Support", "development-board-toolchain", "runtime", "opencode_plugin")
|
|
2449
|
-
];
|
|
2450
|
-
}
|
|
2451
|
-
function legacyCodexLocalMarketplacePaths() {
|
|
2452
|
-
return Array.from(LEGACY_CODEX_MARKETPLACE_NAMES)
|
|
2453
|
-
.filter((name) => name !== CODEX_MARKETPLACE_NAME)
|
|
2454
|
-
.map((name) => join(defaultCodexHome(), "local-marketplaces", name));
|
|
2455
|
-
}
|
|
2456
|
-
async function discoverLegacyHomeAgentsMarketplacePaths(warnings) {
|
|
2457
|
-
const paths = [];
|
|
2458
|
-
const pluginRoot = join(homedir(), ".agents", "plugins");
|
|
2459
|
-
try {
|
|
2460
|
-
const entries = await readdir(pluginRoot, { withFileTypes: true });
|
|
2461
|
-
for (const entry of entries) {
|
|
2462
|
-
if (!entry.isFile() || !entry.name.startsWith("marketplace.json"))
|
|
2463
|
-
continue;
|
|
2464
|
-
const filePath = join(pluginRoot, entry.name);
|
|
2465
|
-
try {
|
|
2466
|
-
const current = await readFile(filePath, "utf8");
|
|
2467
|
-
if (isLegacyHomeAgentsMarketplace(current)) {
|
|
2468
|
-
paths.push(filePath);
|
|
2469
|
-
}
|
|
2470
|
-
}
|
|
2471
|
-
catch (error) {
|
|
2472
|
-
warnings.push(`Could not inspect legacy Codex home marketplace ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2473
|
-
}
|
|
2474
|
-
}
|
|
2475
|
-
}
|
|
2476
|
-
catch {
|
|
2477
|
-
return paths;
|
|
2478
|
-
}
|
|
2479
|
-
return paths;
|
|
2480
|
-
}
|
|
2481
|
-
function isLegacyHomeAgentsMarketplace(text) {
|
|
2482
|
-
return marketplaceTextHasLegacyCodexPlugin(text);
|
|
2483
|
-
}
|
|
2484
|
-
async function discoverLegacyCodexProjectMarketplacePaths(warnings) {
|
|
2485
|
-
const configPath = codexConfigPath();
|
|
2486
|
-
let text = "";
|
|
2487
|
-
try {
|
|
2488
|
-
text = await readFile(configPath, "utf8");
|
|
2489
|
-
}
|
|
2490
|
-
catch {
|
|
2491
|
-
return [];
|
|
2492
|
-
}
|
|
2493
|
-
const paths = new Set();
|
|
2494
|
-
for (const projectPath of legacyCodexProjectPathsFromConfig(text)) {
|
|
2495
|
-
for (const candidate of [
|
|
2496
|
-
join(projectPath, ".agents", "plugins", "marketplace.json"),
|
|
2497
|
-
join(projectPath, "platform_plugin", ".agents", "plugins", "marketplace.json"),
|
|
2498
|
-
join(projectPath, "platform_plugins", "codex_plugin", ".agents", "plugins", "marketplace.json")
|
|
2499
|
-
]) {
|
|
2500
|
-
try {
|
|
2501
|
-
if (!await pathExists(candidate))
|
|
2502
|
-
continue;
|
|
2503
|
-
const current = await readFile(candidate, "utf8");
|
|
2504
|
-
if (marketplaceTextHasLegacyCodexPlugin(current)) {
|
|
2505
|
-
paths.add(candidate);
|
|
2506
|
-
}
|
|
2507
|
-
}
|
|
2508
|
-
catch (error) {
|
|
2509
|
-
warnings.push(`Could not inspect legacy Codex project marketplace ${candidate}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2510
|
-
}
|
|
2511
|
-
}
|
|
2512
|
-
}
|
|
2513
|
-
return Array.from(paths);
|
|
2514
|
-
}
|
|
2515
|
-
function legacyCodexProjectPathsFromConfig(text) {
|
|
2516
|
-
const paths = [];
|
|
2517
|
-
const lines = text.match(/[^\n]*\n|[^\n]+$/g) ?? [];
|
|
2518
|
-
for (const line of lines) {
|
|
2519
|
-
const table = parseTomlTableHeader(line);
|
|
2520
|
-
const match = table ? /^projects\."([^"]+)"$/.exec(table) : undefined;
|
|
2521
|
-
if (match?.[1] && /DBT-Agent-Project|development-board-toolchain|dbt-agent/i.test(match[1])) {
|
|
2522
|
-
paths.push(match[1].replace(/\\"/g, '"'));
|
|
2523
|
-
}
|
|
2524
|
-
}
|
|
2525
|
-
return paths;
|
|
2526
|
-
}
|
|
2527
|
-
function marketplaceTextHasLegacyCodexPlugin(text) {
|
|
2528
|
-
try {
|
|
2529
|
-
const parsed = JSON.parse(text);
|
|
2530
|
-
const marketplaceName = typeof parsed.name === "string" ? parsed.name : "";
|
|
2531
|
-
const marketplaceDisplayName = typeof parsed.interface?.displayName === "string" ? parsed.interface.displayName : "";
|
|
2532
|
-
const marketplaceLooksLegacy = legacyTextHasCodexPluginResidue(marketplaceName)
|
|
2533
|
-
|| legacyTextHasCodexPluginResidue(marketplaceDisplayName)
|
|
2534
|
-
|| LEGACY_CODEX_MARKETPLACE_NAMES.has(marketplaceName);
|
|
2535
|
-
return (parsed.plugins ?? []).some((plugin) => {
|
|
2536
|
-
const values = [
|
|
2537
|
-
plugin.name,
|
|
2538
|
-
plugin.category,
|
|
2539
|
-
plugin.source?.path,
|
|
2540
|
-
plugin.interface?.displayName
|
|
2541
|
-
].filter((value) => typeof value === "string");
|
|
2542
|
-
return values.some(legacyTextHasCodexPluginResidue) || marketplaceLooksLegacy && values.some((value) => /embed-labs|dbt|development-board/i.test(value));
|
|
2543
|
-
});
|
|
2544
|
-
}
|
|
2545
|
-
catch {
|
|
2546
|
-
return legacyTextHasCodexPluginResidue(text);
|
|
2547
|
-
}
|
|
2548
|
-
}
|
|
2549
|
-
async function discoverLegacyCodexCachePaths(targetRoot) {
|
|
2550
|
-
const paths = [];
|
|
2551
|
-
const cacheRoot = join(targetRoot, "cache");
|
|
2552
|
-
try {
|
|
2553
|
-
const marketplaces = await readdir(cacheRoot, { withFileTypes: true });
|
|
2554
|
-
for (const entry of marketplaces) {
|
|
2555
|
-
if (!entry.isDirectory())
|
|
2556
|
-
continue;
|
|
2557
|
-
if (LEGACY_CODEX_MARKETPLACE_NAMES.has(entry.name)) {
|
|
2558
|
-
paths.push(join(cacheRoot, entry.name, CODEX_PLUGIN_NAME));
|
|
2559
|
-
}
|
|
2560
|
-
for (const pluginName of LEGACY_CODEX_PLUGIN_NAMES) {
|
|
2561
|
-
paths.push(join(cacheRoot, entry.name, pluginName));
|
|
2562
|
-
}
|
|
2563
|
-
}
|
|
2564
|
-
}
|
|
2565
|
-
catch {
|
|
2566
|
-
return paths;
|
|
2567
|
-
}
|
|
2568
|
-
return paths;
|
|
2569
|
-
}
|
|
2570
|
-
async function cleanupLegacyCodexTextState(warnings) {
|
|
2571
|
-
let removed = 0;
|
|
2572
|
-
removed += await cleanupLegacyCodexTextFile(join(defaultCodexHome(), "history.jsonl"), warnings);
|
|
2573
|
-
removed += await cleanupLegacyCodexTextFile(join(defaultCodexHome(), "session_index.jsonl"), warnings);
|
|
2574
|
-
removed += await cleanupLegacyCodexTextFile(join(defaultCodexHome(), "rules", "default.rules"), warnings);
|
|
2575
|
-
return removed;
|
|
2576
|
-
}
|
|
2577
|
-
async function cleanupLegacyCodexTextFile(filePath, warnings) {
|
|
2578
|
-
try {
|
|
2579
|
-
if (!await pathExists(filePath))
|
|
2580
|
-
return 0;
|
|
2581
|
-
const current = await readFile(filePath, "utf8");
|
|
2582
|
-
const lines = current.split("\n");
|
|
2583
|
-
let removed = 0;
|
|
2584
|
-
const kept = lines.filter((line) => {
|
|
2585
|
-
if (!line)
|
|
2586
|
-
return true;
|
|
2587
|
-
if (isLegacyCodexHistoryMention(line)) {
|
|
2588
|
-
removed += 1;
|
|
2589
|
-
return false;
|
|
2590
|
-
}
|
|
2591
|
-
return true;
|
|
2592
|
-
});
|
|
2593
|
-
if (removed > 0) {
|
|
2594
|
-
await writeFile(filePath, kept.join("\n"), "utf8");
|
|
2595
|
-
}
|
|
2596
|
-
return removed;
|
|
2597
|
-
}
|
|
2598
|
-
catch (error) {
|
|
2599
|
-
warnings.push(`Could not clean Codex legacy text state ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2600
|
-
return 0;
|
|
2601
|
-
}
|
|
2602
|
-
}
|
|
2603
|
-
function isLegacyCodexHistoryMention(line) {
|
|
2604
|
-
return line.includes("plugin://dbt-agent@plugins")
|
|
2605
|
-
|| line.includes("plugin://dbt-agent@embed-labs")
|
|
2606
|
-
|| line.includes("development-board-toolchain-dev")
|
|
2607
|
-
|| line.includes("development-board-toolchain")
|
|
2608
|
-
|| /plugin:\/\/Dbt Agent@/i.test(line)
|
|
2609
|
-
|| /plugin:\/\/deve@/i.test(line)
|
|
2610
|
-
|| /dbt-agent/i.test(line);
|
|
2611
|
-
}
|
|
2612
|
-
function removeLegacyCodexConfigTables(text) {
|
|
2613
|
-
const lines = text.match(/[^\n]*\n|[^\n]+$/g) ?? [];
|
|
2614
|
-
const output = [];
|
|
2615
|
-
const removedTables = [];
|
|
2616
|
-
let skipping = false;
|
|
2617
|
-
for (const line of lines) {
|
|
2618
|
-
const table = parseTomlTableHeader(line);
|
|
2619
|
-
if (table) {
|
|
2620
|
-
skipping = isLegacyCodexConfigTable(table);
|
|
2621
|
-
if (skipping) {
|
|
2622
|
-
removedTables.push(table);
|
|
2623
|
-
continue;
|
|
2624
|
-
}
|
|
2625
|
-
}
|
|
2626
|
-
if (!skipping) {
|
|
2627
|
-
output.push(line);
|
|
2628
|
-
}
|
|
2629
|
-
}
|
|
2630
|
-
return { text: output.join("").replace(/\n{3,}/g, "\n\n"), removedTables };
|
|
2631
|
-
}
|
|
2632
|
-
function parseTomlTableHeader(line) {
|
|
2633
|
-
const match = /^\s*\[([^\]]+)\]\s*(?:#.*)?$/.exec(line);
|
|
2634
|
-
return match?.[1]?.trim();
|
|
2635
|
-
}
|
|
2636
|
-
function isLegacyCodexConfigTable(table) {
|
|
2637
|
-
return /^plugins\."dbt-agent@[^"]+"$/.test(table)
|
|
2638
|
-
|| /^plugins\."Dbt Agent@[^"]+"$/i.test(table)
|
|
2639
|
-
|| /^plugins\."deve@[^"]+"$/i.test(table)
|
|
2640
|
-
|| isLegacyEmbedLabsCodexMarketplaceConfigTable(table)
|
|
2641
|
-
|| table === "mcp_servers.dbt-agent"
|
|
2642
|
-
|| table.startsWith("mcp_servers.dbt-agent.")
|
|
2643
|
-
|| table === 'mcp_servers."dbt-agent"'
|
|
2644
|
-
|| table.startsWith('mcp_servers."dbt-agent".')
|
|
2645
|
-
|| table === "mcp_servers.deve"
|
|
2646
|
-
|| table.startsWith("mcp_servers.deve.")
|
|
2647
|
-
|| /^projects\."[^"]*\/DBT-Agent-Project(?:\/[^"]*)?"$/.test(table);
|
|
2648
|
-
}
|
|
2649
|
-
function isLegacyEmbedLabsCodexMarketplaceConfigTable(table) {
|
|
2650
|
-
for (const marketplaceName of LEGACY_CODEX_MARKETPLACE_NAMES) {
|
|
2651
|
-
if (table === `marketplaces.${marketplaceName}`) {
|
|
2652
|
-
return true;
|
|
2653
|
-
}
|
|
2654
|
-
if (table === `plugins."${CODEX_PLUGIN_NAME}@${marketplaceName}"`) {
|
|
2655
|
-
return true;
|
|
2656
|
-
}
|
|
2657
|
-
}
|
|
2658
|
-
return false;
|
|
2659
|
-
}
|
|
2660
|
-
function legacyTextHasCodexPluginResidue(value) {
|
|
2661
|
-
return /dbt-agent|Dbt Agent|development-board-toolchain|development-board-toolchain-dev/i.test(value)
|
|
2662
|
-
|| value === "deve"
|
|
2663
|
-
|| value.replace(/\\/g, "/").includes("/plugins/deve")
|
|
2664
|
-
|| value.includes("plugin://deve@");
|
|
2665
|
-
}
|
|
2666
|
-
function legacyCodexCleanupWarning(cleanup) {
|
|
2667
|
-
const parts = [];
|
|
2668
|
-
if (cleanup.legacy_removed_paths.length > 0) {
|
|
2669
|
-
parts.push(`removed ${cleanup.legacy_removed_paths.length} stale/legacy Codex plugin path(s)`);
|
|
2670
|
-
}
|
|
2671
|
-
if (cleanup.legacy_removed_config_tables?.length) {
|
|
2672
|
-
parts.push(`removed ${cleanup.legacy_removed_config_tables.length} legacy Codex config table(s)`);
|
|
2673
|
-
}
|
|
2674
|
-
if (cleanup.legacy_removed_history_entries) {
|
|
2675
|
-
parts.push(`removed ${cleanup.legacy_removed_history_entries} legacy Codex text-state mention(s)`);
|
|
2676
|
-
}
|
|
2677
|
-
if (cleanup.legacy_stopped_processes) {
|
|
2678
|
-
parts.push(`stopped ${cleanup.legacy_stopped_processes} legacy Codex plugin process(es)`);
|
|
2679
|
-
}
|
|
2680
|
-
if (cleanup.warnings?.length) {
|
|
2681
|
-
parts.push(`cleanup warning(s): ${cleanup.warnings.join("; ")}`);
|
|
2682
|
-
}
|
|
2683
|
-
return parts.length > 0 ? `Codex plugin cleanup: ${parts.join(", ")}.` : undefined;
|
|
2684
|
-
}
|
|
2685
|
-
async function cleanupLegacyOpenCodePluginRemnants(targetRoot, globalInstall) {
|
|
2686
|
-
const removedPaths = [];
|
|
2687
|
-
const warnings = [];
|
|
2688
|
-
const legacyPaths = [
|
|
2689
|
-
join(targetRoot, "plugins", "development-board-toolchain.js"),
|
|
2690
|
-
join(targetRoot, "plugins", "development-board-toolchain-dev.js"),
|
|
2691
|
-
join(targetRoot, "plugins", "dbt-agent.js"),
|
|
2692
|
-
join(targetRoot, "plugins", "Dbt Agent.js"),
|
|
2693
|
-
join(targetRoot, "plugins", "deve.js"),
|
|
2694
|
-
join(targetRoot, "plugins", "deve"),
|
|
2695
|
-
join(targetRoot, "node_modules", "development-board-toolchain"),
|
|
2696
|
-
join(targetRoot, "node_modules", "development-board-toolchain-dev"),
|
|
2697
|
-
join(targetRoot, "node_modules", "dbt-agent")
|
|
2698
|
-
];
|
|
2699
|
-
if (globalInstall) {
|
|
2700
|
-
legacyPaths.push(join(targetRoot, "plugins", "embed-labs.js"));
|
|
2701
|
-
legacyPaths.push(...await discoverLegacyOpenCodeBackupPaths(targetRoot, warnings));
|
|
2702
|
-
legacyPaths.push(...await discoverLegacyOpenCodePluginCachePaths(targetRoot, warnings));
|
|
2703
|
-
}
|
|
2704
|
-
for (const candidate of legacyPaths) {
|
|
2705
|
-
try {
|
|
2706
|
-
if (await pathExists(candidate)) {
|
|
2707
|
-
await rm(candidate, { recursive: true, force: true });
|
|
2708
|
-
removedPaths.push(candidate);
|
|
2709
|
-
}
|
|
2710
|
-
}
|
|
2711
|
-
catch (error) {
|
|
2712
|
-
warnings.push(`Could not remove ${candidate}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2713
|
-
}
|
|
2714
|
-
}
|
|
2715
|
-
return {
|
|
2716
|
-
legacy_removed_paths: removedPaths,
|
|
2717
|
-
legacy_removed_config_entries: [],
|
|
2718
|
-
...(warnings.length > 0 ? { warnings } : {})
|
|
2719
|
-
};
|
|
2720
|
-
}
|
|
2721
|
-
async function discoverLegacyOpenCodeBackupPaths(targetRoot, warnings) {
|
|
2722
|
-
const paths = [];
|
|
2723
|
-
try {
|
|
2724
|
-
const entries = await readdir(targetRoot, { withFileTypes: true });
|
|
2725
|
-
for (const entry of entries) {
|
|
2726
|
-
if (!entry.isFile() || !entry.name.includes(".bak"))
|
|
2727
|
-
continue;
|
|
2728
|
-
const filePath = join(targetRoot, entry.name);
|
|
2729
|
-
try {
|
|
2730
|
-
const current = await readFile(filePath, "utf8");
|
|
2731
|
-
if (legacyTextHasCodexPluginResidue(current)) {
|
|
2732
|
-
paths.push(filePath);
|
|
2733
|
-
}
|
|
2734
|
-
}
|
|
2735
|
-
catch (error) {
|
|
2736
|
-
warnings.push(`Could not inspect legacy OpenCode backup ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2737
|
-
}
|
|
2738
|
-
}
|
|
2739
|
-
}
|
|
2740
|
-
catch {
|
|
2741
|
-
return paths;
|
|
2742
|
-
}
|
|
2743
|
-
return paths;
|
|
2744
|
-
}
|
|
2745
|
-
async function discoverLegacyOpenCodePluginCachePaths(targetRoot, warnings) {
|
|
2746
|
-
const paths = [];
|
|
2747
|
-
const cacheRoot = join(targetRoot, ".embed-labs", "plugin-cache");
|
|
2748
|
-
try {
|
|
2749
|
-
const entries = await readdir(cacheRoot, { withFileTypes: true });
|
|
2750
|
-
for (const entry of entries) {
|
|
2751
|
-
if (!entry.isFile())
|
|
2752
|
-
continue;
|
|
2753
|
-
if (/^(embed-labs|embed-labs-opencode-plugin)-\d+\.\d+\.\d+\.tgz$/.test(entry.name)) {
|
|
2754
|
-
paths.push(join(cacheRoot, entry.name));
|
|
2755
|
-
}
|
|
2756
|
-
}
|
|
2757
|
-
}
|
|
2758
|
-
catch (error) {
|
|
2759
|
-
if (error.code !== "ENOENT") {
|
|
2760
|
-
warnings.push(`Could not inspect OpenCode plugin cache ${cacheRoot}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2761
|
-
}
|
|
2762
|
-
}
|
|
2763
|
-
return paths;
|
|
2764
|
-
}
|
|
2765
|
-
function legacyOpenCodeCleanupWarning(cleanup) {
|
|
2766
|
-
const parts = [];
|
|
2767
|
-
if (cleanup.legacy_removed_paths.length > 0) {
|
|
2768
|
-
parts.push(`removed ${cleanup.legacy_removed_paths.length} legacy OpenCode plugin path(s)`);
|
|
2769
|
-
}
|
|
2770
|
-
if (cleanup.legacy_removed_config_entries?.length) {
|
|
2771
|
-
parts.push(`removed ${cleanup.legacy_removed_config_entries.length} legacy OpenCode config entry(s)`);
|
|
2772
|
-
}
|
|
2773
|
-
if (cleanup.warnings?.length) {
|
|
2774
|
-
parts.push(`cleanup warning(s): ${cleanup.warnings.join("; ")}`);
|
|
2775
|
-
}
|
|
2776
|
-
return parts.length > 0 ? `Legacy OpenCode cleanup: ${parts.join(", ")}.` : undefined;
|
|
2777
|
-
}
|
|
2778
|
-
function combineWarnings(...warnings) {
|
|
2779
|
-
const actual = warnings.filter((warning) => Boolean(warning));
|
|
2780
|
-
return actual.length > 0 ? actual.join(" ") : undefined;
|
|
1803
|
+
return join(process.env.CODEX_HOME?.trim() || join(homedir(), ".codex"), "plugins");
|
|
2781
1804
|
}
|
|
2782
1805
|
async function maybeRegisterCodexMcp(parsed, targetRoot, targetPath) {
|
|
2783
1806
|
const explicitTarget = Boolean(stringFlag(parsed, "target") || stringFlag(parsed, "codex-target"));
|
|
2784
1807
|
if (explicitTarget && process.env.EMBED_CODEX_MCP_REGISTER !== "1") {
|
|
2785
1808
|
return {
|
|
2786
1809
|
registered: false,
|
|
2787
|
-
hint:
|
|
1810
|
+
hint: `Installed into a custom target. Register manually with: codex mcp add embed-labs -- node ${join(targetPath, "scripts", "embed-labs-mcp-bridge.mjs")}`
|
|
2788
1811
|
};
|
|
2789
1812
|
}
|
|
2790
1813
|
const bridgePath = join(targetPath, "scripts", "embed-labs-mcp-bridge.mjs");
|
|
@@ -2803,12 +1826,11 @@ async function maybeRegisterCodexMcp(parsed, targetRoot, targetPath) {
|
|
|
2803
1826
|
};
|
|
2804
1827
|
}
|
|
2805
1828
|
const embedCliBin = process.env.EMBED_CLI_BIN?.trim() || await resolveExecutableOnPath("embedlabs") || await resolveExecutableOnPath("embed") || "";
|
|
2806
|
-
const mcpLauncher = await resolveEmbedCliMcpLauncher(embedCliBin);
|
|
2807
1829
|
const authFile = resolve(process.env.EMBED_AUTH_FILE?.trim() || DEFAULT_AUTH_FILE);
|
|
2808
1830
|
const cloudUrl = pluginMcpCloudApiUrl(parsed);
|
|
2809
1831
|
const existing = await runLocalProcess(codexBin, ["mcp", "get", "embed-labs", "--json"]);
|
|
2810
|
-
if (existing.code === 0 && codexMcpAlreadyRegistered(existing.stdout, bridgePath, cloudUrl, authFile, embedCliBin
|
|
2811
|
-
const warning = await upsertCodexMcpRuntimeConfig(
|
|
1832
|
+
if (existing.code === 0 && codexMcpAlreadyRegistered(existing.stdout, bridgePath, cloudUrl, authFile, embedCliBin)) {
|
|
1833
|
+
const warning = await upsertCodexMcpRuntimeConfig(bridgePath);
|
|
2812
1834
|
if (warning) {
|
|
2813
1835
|
return { registered: true, warning };
|
|
2814
1836
|
}
|
|
@@ -2822,152 +1844,31 @@ async function maybeRegisterCodexMcp(parsed, targetRoot, targetPath) {
|
|
|
2822
1844
|
"--env",
|
|
2823
1845
|
`EMBED_CLOUD_API_URL=${cloudUrl}`,
|
|
2824
1846
|
"--env",
|
|
2825
|
-
`EMBED_AUTH_FILE=${authFile}
|
|
2826
|
-
"--env",
|
|
2827
|
-
`EMBED_MCP_BRIDGE_PATH=${bridgePath}`
|
|
1847
|
+
`EMBED_AUTH_FILE=${authFile}`
|
|
2828
1848
|
];
|
|
2829
1849
|
if (embedCliBin) {
|
|
2830
1850
|
args.push("--env", `EMBED_CLI_BIN=${embedCliBin}`);
|
|
2831
1851
|
}
|
|
2832
|
-
args.push("--",
|
|
1852
|
+
args.push("--", process.execPath, bridgePath);
|
|
2833
1853
|
const addResult = await runLocalProcess(codexBin, args);
|
|
2834
1854
|
if (addResult.code !== 0) {
|
|
2835
1855
|
return {
|
|
2836
1856
|
registered: false,
|
|
2837
|
-
hint:
|
|
1857
|
+
hint: `Codex plugin installed. Register manually with: codex mcp add embed-labs -- ${process.execPath} ${bridgePath}`,
|
|
2838
1858
|
warning: `codex mcp add failed: ${addResult.stderr.trim() || addResult.stdout.trim() || `exit ${addResult.code}`}`
|
|
2839
1859
|
};
|
|
2840
1860
|
}
|
|
2841
|
-
const warning = await upsertCodexMcpRuntimeConfig(
|
|
1861
|
+
const warning = await upsertCodexMcpRuntimeConfig(bridgePath);
|
|
2842
1862
|
return warning ? { registered: true, warning } : { registered: true };
|
|
2843
1863
|
}
|
|
2844
|
-
|
|
2845
|
-
const explicitTarget = Boolean(stringFlag(parsed, "target") || stringFlag(parsed, "codex-target"));
|
|
2846
|
-
if (explicitTarget && process.env.EMBED_CODEX_MARKETPLACE_REGISTER !== "1") {
|
|
2847
|
-
return {
|
|
2848
|
-
registered: false,
|
|
2849
|
-
warning: "Codex plugin marketplace entry was not registered because a custom target was used. Set EMBED_CODEX_MARKETPLACE_REGISTER=1 to register it anyway."
|
|
2850
|
-
};
|
|
2851
|
-
}
|
|
2852
|
-
if (resolve(targetRoot) !== resolve(defaultCodexPluginRoot()) && process.env.EMBED_CODEX_MARKETPLACE_REGISTER !== "1") {
|
|
2853
|
-
return {
|
|
2854
|
-
registered: false,
|
|
2855
|
-
warning: "Codex plugin marketplace entry was not registered because the install target is not the default Codex plugin root."
|
|
2856
|
-
};
|
|
2857
|
-
}
|
|
2858
|
-
const marketplacePath = defaultCodexLocalMarketplaceRoot();
|
|
2859
|
-
const marketplacePluginPath = join(marketplacePath, "plugins", CODEX_PLUGIN_NAME);
|
|
2860
|
-
try {
|
|
2861
|
-
if (!await pathExists(join(targetPath, ".codex-plugin", "plugin.json"))) {
|
|
2862
|
-
return {
|
|
2863
|
-
registered: false,
|
|
2864
|
-
warning: `Codex plugin manifest was not found at ${join(targetPath, ".codex-plugin", "plugin.json")}.`
|
|
2865
|
-
};
|
|
2866
|
-
}
|
|
2867
|
-
await rm(marketplacePluginPath, { recursive: true, force: true });
|
|
2868
|
-
await mkdir(dirname(marketplacePluginPath), { recursive: true });
|
|
2869
|
-
await cp(targetPath, marketplacePluginPath, { recursive: true });
|
|
2870
|
-
await refreshCodexPluginCache(targetPath);
|
|
2871
|
-
await writeCodexLocalMarketplaceManifest(marketplacePath);
|
|
2872
|
-
const warning = await upsertCodexPluginMarketplaceConfig(marketplacePath);
|
|
2873
|
-
return warning ? { registered: true, marketplacePath, warning } : { registered: true, marketplacePath };
|
|
2874
|
-
}
|
|
2875
|
-
catch (error) {
|
|
2876
|
-
return {
|
|
2877
|
-
registered: false,
|
|
2878
|
-
marketplacePath,
|
|
2879
|
-
warning: `Could not register Codex plugin marketplace entry: ${error instanceof Error ? error.message : String(error)}`
|
|
2880
|
-
};
|
|
2881
|
-
}
|
|
2882
|
-
}
|
|
2883
|
-
function defaultCodexLocalMarketplaceRoot() {
|
|
2884
|
-
return join(defaultCodexHome(), "local-marketplaces", CODEX_PLUGIN_NAME);
|
|
2885
|
-
}
|
|
2886
|
-
async function refreshCodexPluginCache(targetPath) {
|
|
2887
|
-
const manifestPath = join(targetPath, ".codex-plugin", "plugin.json");
|
|
2888
|
-
const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
|
|
2889
|
-
const version = typeof manifest.version === "string" && manifest.version.trim()
|
|
2890
|
-
? manifest.version.trim()
|
|
2891
|
-
: "local";
|
|
2892
|
-
const cachePluginRoot = join(defaultCodexPluginRoot(), "cache", CODEX_MARKETPLACE_NAME, CODEX_PLUGIN_NAME);
|
|
2893
|
-
const cacheVersionPath = join(cachePluginRoot, version);
|
|
2894
|
-
await rm(cachePluginRoot, { recursive: true, force: true });
|
|
2895
|
-
await mkdir(dirname(cacheVersionPath), { recursive: true });
|
|
2896
|
-
await cp(targetPath, cacheVersionPath, { recursive: true });
|
|
2897
|
-
}
|
|
2898
|
-
async function writeCodexLocalMarketplaceManifest(marketplacePath) {
|
|
2899
|
-
const manifestPath = join(marketplacePath, ".agents", "plugins", "marketplace.json");
|
|
2900
|
-
const manifest = {
|
|
2901
|
-
name: CODEX_MARKETPLACE_NAME,
|
|
2902
|
-
interface: {
|
|
2903
|
-
displayName: "Embed Labs"
|
|
2904
|
-
},
|
|
2905
|
-
plugins: [
|
|
2906
|
-
{
|
|
2907
|
-
name: CODEX_PLUGIN_NAME,
|
|
2908
|
-
source: {
|
|
2909
|
-
source: "local",
|
|
2910
|
-
path: `./plugins/${CODEX_PLUGIN_NAME}`
|
|
2911
|
-
},
|
|
2912
|
-
policy: {
|
|
2913
|
-
installation: "AVAILABLE",
|
|
2914
|
-
authentication: "ON_USE"
|
|
2915
|
-
},
|
|
2916
|
-
category: "Developer Tools"
|
|
2917
|
-
}
|
|
2918
|
-
]
|
|
2919
|
-
};
|
|
2920
|
-
await mkdir(dirname(manifestPath), { recursive: true });
|
|
2921
|
-
await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
|
|
2922
|
-
}
|
|
2923
|
-
async function upsertCodexPluginMarketplaceConfig(marketplacePath) {
|
|
2924
|
-
const configPath = codexConfigPath();
|
|
2925
|
-
try {
|
|
2926
|
-
await mkdir(dirname(configPath), { recursive: true });
|
|
2927
|
-
let text = "";
|
|
2928
|
-
try {
|
|
2929
|
-
text = await readFile(configPath, "utf8");
|
|
2930
|
-
}
|
|
2931
|
-
catch {
|
|
2932
|
-
text = "";
|
|
2933
|
-
}
|
|
2934
|
-
const original = text;
|
|
2935
|
-
const cleaned = removeLegacyCodexConfigTables(text).text;
|
|
2936
|
-
let updated = upsertTomlTableKeys(cleaned, `marketplaces.${CODEX_MARKETPLACE_NAME}`, {
|
|
2937
|
-
source_type: tomlString("local"),
|
|
2938
|
-
source: tomlString(marketplacePath),
|
|
2939
|
-
last_updated: tomlString(new Date().toISOString().replace(/\.\d{3}Z$/, "Z"))
|
|
2940
|
-
});
|
|
2941
|
-
updated = upsertTomlTableKeys(updated, `plugins."${CODEX_PLUGIN_NAME}@${CODEX_MARKETPLACE_NAME}"`, {
|
|
2942
|
-
enabled: "true"
|
|
2943
|
-
});
|
|
2944
|
-
if (updated !== original) {
|
|
2945
|
-
await writeFile(configPath, updated, "utf8");
|
|
2946
|
-
}
|
|
2947
|
-
return undefined;
|
|
2948
|
-
}
|
|
2949
|
-
catch (error) {
|
|
2950
|
-
return `${configPath} could not be updated with the Embed Labs plugin marketplace entry: ${error instanceof Error ? error.message : String(error)}`;
|
|
2951
|
-
}
|
|
2952
|
-
}
|
|
2953
|
-
async function resolveEmbedCliMcpLauncher(embedCliBin) {
|
|
2954
|
-
const explicit = embedCliBin?.trim();
|
|
2955
|
-
if (explicit) {
|
|
2956
|
-
return { command: explicit, args: ["mcp", "start"] };
|
|
2957
|
-
}
|
|
2958
|
-
const pathBin = await resolveExecutableOnPath("embedlabs") || await resolveExecutableOnPath("embed");
|
|
2959
|
-
if (pathBin) {
|
|
2960
|
-
return { command: pathBin, args: ["mcp", "start"] };
|
|
2961
|
-
}
|
|
2962
|
-
return { command: process.execPath, args: [join(CLI_MODULE_DIR, "index.js"), "mcp", "start"] };
|
|
2963
|
-
}
|
|
2964
|
-
function codexMcpAlreadyRegistered(stdout, bridgePath, cloudUrl, authFile, embedCliBin, mcpCommand, mcpArgs) {
|
|
1864
|
+
function codexMcpAlreadyRegistered(stdout, bridgePath, cloudUrl, authFile, embedCliBin) {
|
|
2965
1865
|
try {
|
|
2966
1866
|
const parsed = JSON.parse(stdout);
|
|
2967
1867
|
const transport = parsed.transport;
|
|
2968
|
-
if (transport?.type !== "stdio")
|
|
1868
|
+
if (transport?.type !== "stdio" || transport.command !== process.execPath)
|
|
1869
|
+
return false;
|
|
1870
|
+
if (!transport.args?.includes(bridgePath))
|
|
2969
1871
|
return false;
|
|
2970
|
-
const args = transport.args || [];
|
|
2971
1872
|
const env = transport.env || {};
|
|
2972
1873
|
if (env.EMBED_CLOUD_API_URL !== cloudUrl)
|
|
2973
1874
|
return false;
|
|
@@ -2975,16 +1876,13 @@ function codexMcpAlreadyRegistered(stdout, bridgePath, cloudUrl, authFile, embed
|
|
|
2975
1876
|
return false;
|
|
2976
1877
|
if (embedCliBin && env.EMBED_CLI_BIN !== embedCliBin)
|
|
2977
1878
|
return false;
|
|
2978
|
-
|
|
2979
|
-
const cliMcp = transport.command === mcpCommand
|
|
2980
|
-
&& mcpArgs.every((arg, index) => args[index] === arg);
|
|
2981
|
-
return directBridge || cliMcp;
|
|
1879
|
+
return true;
|
|
2982
1880
|
}
|
|
2983
1881
|
catch {
|
|
2984
1882
|
return false;
|
|
2985
1883
|
}
|
|
2986
1884
|
}
|
|
2987
|
-
async function upsertCodexMcpRuntimeConfig(
|
|
1885
|
+
async function upsertCodexMcpRuntimeConfig(bridgePath) {
|
|
2988
1886
|
const configPath = join(process.env.CODEX_HOME?.trim() || join(homedir(), ".codex"), "config.toml");
|
|
2989
1887
|
try {
|
|
2990
1888
|
await mkdir(dirname(configPath), { recursive: true });
|
|
@@ -2996,8 +1894,8 @@ async function upsertCodexMcpRuntimeConfig(command, args) {
|
|
|
2996
1894
|
text = "";
|
|
2997
1895
|
}
|
|
2998
1896
|
const updated = upsertTomlTableKeys(text, "mcp_servers.embed-labs", {
|
|
2999
|
-
command: tomlString(
|
|
3000
|
-
args: `[${
|
|
1897
|
+
command: tomlString(process.execPath),
|
|
1898
|
+
args: `[${tomlString(bridgePath)}]`,
|
|
3001
1899
|
startup_timeout_sec: "120"
|
|
3002
1900
|
});
|
|
3003
1901
|
if (updated !== text) {
|
|
@@ -3074,229 +1972,11 @@ async function resolveExecutableOnPath(name) {
|
|
|
3074
1972
|
}
|
|
3075
1973
|
return undefined;
|
|
3076
1974
|
}
|
|
3077
|
-
async function runBridgeStart(parsed) {
|
|
3078
|
-
const host = stringFlag(parsed, "host");
|
|
3079
|
-
const port = numberFlag(parsed, "port");
|
|
3080
|
-
const bridge = await resolveBridgeLauncher();
|
|
3081
|
-
const args = [...bridge.args];
|
|
3082
|
-
if (host) {
|
|
3083
|
-
args.push("--host", host);
|
|
3084
|
-
}
|
|
3085
|
-
if (port !== undefined) {
|
|
3086
|
-
args.push("--port", String(port));
|
|
3087
|
-
}
|
|
3088
|
-
const env = {
|
|
3089
|
-
...process.env,
|
|
3090
|
-
...(host ? { EMBED_BRIDGE_HOST: host } : {}),
|
|
3091
|
-
...(port !== undefined ? { EMBED_BRIDGE_PORT: String(port) } : {})
|
|
3092
|
-
};
|
|
3093
|
-
const child = spawn(bridge.command, args, {
|
|
3094
|
-
stdio: "inherit",
|
|
3095
|
-
env
|
|
3096
|
-
});
|
|
3097
|
-
const forwardSignal = (signal) => {
|
|
3098
|
-
if (!child.killed) {
|
|
3099
|
-
child.kill(signal);
|
|
3100
|
-
}
|
|
3101
|
-
};
|
|
3102
|
-
process.once("SIGINT", forwardSignal);
|
|
3103
|
-
process.once("SIGTERM", forwardSignal);
|
|
3104
|
-
return await new Promise((resolveCode) => {
|
|
3105
|
-
child.on("error", (error) => {
|
|
3106
|
-
process.off("SIGINT", forwardSignal);
|
|
3107
|
-
process.off("SIGTERM", forwardSignal);
|
|
3108
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
3109
|
-
resolveCode(1);
|
|
3110
|
-
});
|
|
3111
|
-
child.on("close", (code, signal) => {
|
|
3112
|
-
process.off("SIGINT", forwardSignal);
|
|
3113
|
-
process.off("SIGTERM", forwardSignal);
|
|
3114
|
-
if (signal === "SIGINT" || signal === "SIGTERM") {
|
|
3115
|
-
resolveCode(0);
|
|
3116
|
-
}
|
|
3117
|
-
else {
|
|
3118
|
-
resolveCode(code ?? 0);
|
|
3119
|
-
}
|
|
3120
|
-
});
|
|
3121
|
-
});
|
|
3122
|
-
}
|
|
3123
|
-
async function runMcpStart(parsed) {
|
|
3124
|
-
const unknownFlag = firstUnknownFlag(parsed, ["bridge-path"]);
|
|
3125
|
-
const unexpected = parsed.command.slice(2);
|
|
3126
|
-
if (unknownFlag || unexpected.length > 0) {
|
|
3127
|
-
console.error(unknownFlag ? `Unknown flag --${unknownFlag}. ${MCP_START_USAGE}` : MCP_START_USAGE);
|
|
3128
|
-
return 2;
|
|
3129
|
-
}
|
|
3130
|
-
const bridge = await resolveMcpBridgeLauncher(stringFlag(parsed, "bridge-path"));
|
|
3131
|
-
if (!bridge) {
|
|
3132
|
-
console.error([
|
|
3133
|
-
"Embed Labs MCP bridge was not found.",
|
|
3134
|
-
"Run npm run build in the source checkout, reinstall the embedlabs npm package, or set EMBED_MCP_BRIDGE_PATH to the bridge script.",
|
|
3135
|
-
"Expected command shape for MCP clients: embedlabs mcp start"
|
|
3136
|
-
].join("\n"));
|
|
3137
|
-
return 1;
|
|
3138
|
-
}
|
|
3139
|
-
const child = spawn(bridge.command, bridge.args, {
|
|
3140
|
-
stdio: "inherit",
|
|
3141
|
-
env: {
|
|
3142
|
-
...process.env,
|
|
3143
|
-
EMBED_CLIENT_KIND: process.env.EMBED_CLIENT_KIND || "mcp_client",
|
|
3144
|
-
EMBED_MCP_BRIDGE_PATH: bridge.bridgePath
|
|
3145
|
-
}
|
|
3146
|
-
});
|
|
3147
|
-
const forwardSignal = (signal) => {
|
|
3148
|
-
if (!child.killed) {
|
|
3149
|
-
child.kill(signal);
|
|
3150
|
-
}
|
|
3151
|
-
};
|
|
3152
|
-
process.once("SIGINT", forwardSignal);
|
|
3153
|
-
process.once("SIGTERM", forwardSignal);
|
|
3154
|
-
return await new Promise((resolveCode) => {
|
|
3155
|
-
child.on("error", (error) => {
|
|
3156
|
-
process.off("SIGINT", forwardSignal);
|
|
3157
|
-
process.off("SIGTERM", forwardSignal);
|
|
3158
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
3159
|
-
resolveCode(1);
|
|
3160
|
-
});
|
|
3161
|
-
child.on("close", (code, signal) => {
|
|
3162
|
-
process.off("SIGINT", forwardSignal);
|
|
3163
|
-
process.off("SIGTERM", forwardSignal);
|
|
3164
|
-
if (signal === "SIGINT" || signal === "SIGTERM") {
|
|
3165
|
-
resolveCode(0);
|
|
3166
|
-
}
|
|
3167
|
-
else {
|
|
3168
|
-
resolveCode(code ?? 0);
|
|
3169
|
-
}
|
|
3170
|
-
});
|
|
3171
|
-
});
|
|
3172
|
-
}
|
|
3173
|
-
async function resolveMcpBridgeLauncher(overridePath) {
|
|
3174
|
-
const explicitPath = overridePath?.trim() || process.env.EMBED_MCP_BRIDGE_PATH?.trim();
|
|
3175
|
-
const candidates = [
|
|
3176
|
-
explicitPath ? resolve(explicitPath) : "",
|
|
3177
|
-
join(CLI_MODULE_DIR, "embed-labs-mcp-bridge.mjs"),
|
|
3178
|
-
sourceCheckoutPath("platform_plugins", "codex_plugin", "plugins", "embed-labs", "scripts", "embed-labs-mcp-bridge.mjs"),
|
|
3179
|
-
join(defaultCodexPluginRoot(), CODEX_PLUGIN_NAME, "scripts", "embed-labs-mcp-bridge.mjs")
|
|
3180
|
-
].filter(Boolean);
|
|
3181
|
-
for (const candidate of candidates) {
|
|
3182
|
-
try {
|
|
3183
|
-
await access(candidate, constants.R_OK);
|
|
3184
|
-
return { command: process.execPath, args: [candidate], bridgePath: candidate };
|
|
3185
|
-
}
|
|
3186
|
-
catch {
|
|
3187
|
-
// Keep looking.
|
|
3188
|
-
}
|
|
3189
|
-
}
|
|
3190
|
-
return undefined;
|
|
3191
|
-
}
|
|
3192
|
-
async function resolveBridgeLauncher() {
|
|
3193
|
-
const explicitBinary = process.env.EMBED_LOCAL_BRIDGE_BINARY?.trim();
|
|
3194
|
-
if (explicitBinary) {
|
|
3195
|
-
try {
|
|
3196
|
-
await access(explicitBinary, constants.X_OK);
|
|
3197
|
-
return { command: explicitBinary, args: [] };
|
|
3198
|
-
}
|
|
3199
|
-
catch {
|
|
3200
|
-
// Fall through so the package launcher can print its clearer repair message.
|
|
3201
|
-
}
|
|
3202
|
-
}
|
|
3203
|
-
const pathBinary = await resolveExecutableOnPath(process.platform === "win32" ? "embed-local-bridge.cmd" : "embed-local-bridge");
|
|
3204
|
-
if (pathBinary) {
|
|
3205
|
-
return { command: pathBinary, args: [] };
|
|
3206
|
-
}
|
|
3207
|
-
const packageLauncher = await resolveBridgePackageLauncher();
|
|
3208
|
-
if (packageLauncher) {
|
|
3209
|
-
return { command: process.execPath, args: [packageLauncher] };
|
|
3210
|
-
}
|
|
3211
|
-
return {
|
|
3212
|
-
command: process.execPath,
|
|
3213
|
-
args: [resolve(SOURCE_CHECKOUT_ROOT, "packages", "local-bridge", "dist", "index.js")]
|
|
3214
|
-
};
|
|
3215
|
-
}
|
|
3216
|
-
async function resolveBridgePackageLauncher() {
|
|
3217
|
-
const candidates = [];
|
|
3218
|
-
try {
|
|
3219
|
-
const packageJson = require.resolve("@embed-labs/local-bridge/package.json");
|
|
3220
|
-
candidates.push(join(dirname(packageJson), "dist", "index.js"));
|
|
3221
|
-
}
|
|
3222
|
-
catch {
|
|
3223
|
-
// Source checkout fallback below.
|
|
3224
|
-
}
|
|
3225
|
-
candidates.push(resolve(SOURCE_CHECKOUT_ROOT, "packages", "local-bridge", "dist", "index.js"));
|
|
3226
|
-
for (const candidate of candidates) {
|
|
3227
|
-
try {
|
|
3228
|
-
await access(candidate, constants.R_OK);
|
|
3229
|
-
return candidate;
|
|
3230
|
-
}
|
|
3231
|
-
catch {
|
|
3232
|
-
// Keep looking.
|
|
3233
|
-
}
|
|
3234
|
-
}
|
|
3235
|
-
return undefined;
|
|
3236
|
-
}
|
|
3237
1975
|
function defaultOpenCodeRoot() {
|
|
3238
|
-
return
|
|
3239
|
-
}
|
|
3240
|
-
function globalOpenCodeRoot() {
|
|
3241
|
-
return join(homedir(), ".config", "opencode");
|
|
3242
|
-
}
|
|
3243
|
-
function isGlobalOpenCodeRoot(targetRoot) {
|
|
3244
|
-
return resolve(targetRoot) === resolve(globalOpenCodeRoot());
|
|
3245
|
-
}
|
|
3246
|
-
async function ensureOpenCodeGlobalPluginConfig() {
|
|
3247
|
-
const configPath = join(globalOpenCodeRoot(), "opencode.json");
|
|
3248
|
-
let existing = {};
|
|
3249
|
-
try {
|
|
3250
|
-
const parsed = JSON.parse(await readFile(configPath, "utf8"));
|
|
3251
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
3252
|
-
existing = parsed;
|
|
3253
|
-
}
|
|
3254
|
-
}
|
|
3255
|
-
catch {
|
|
3256
|
-
existing = {};
|
|
3257
|
-
}
|
|
3258
|
-
const configured = Array.isArray(existing.plugin)
|
|
3259
|
-
? existing.plugin.filter((item) => typeof item === "string")
|
|
3260
|
-
: Array.isArray(existing.plugins)
|
|
3261
|
-
? existing.plugins.filter((item) => typeof item === "string")
|
|
3262
|
-
: [];
|
|
3263
|
-
const removed = configured.filter(isLegacyOpenCodePluginConfigEntry);
|
|
3264
|
-
const cleaned = configured.filter((item) => !isLegacyOpenCodePluginConfigEntry(item));
|
|
3265
|
-
if (!cleaned.includes("embed-labs")) {
|
|
3266
|
-
cleaned.push("embed-labs");
|
|
3267
|
-
}
|
|
3268
|
-
await writeFile(configPath, `${JSON.stringify({
|
|
3269
|
-
...existing,
|
|
3270
|
-
plugin: cleaned,
|
|
3271
|
-
plugins: undefined
|
|
3272
|
-
}, null, 2)}\n`, "utf8");
|
|
3273
|
-
return removed;
|
|
3274
|
-
}
|
|
3275
|
-
function isLegacyOpenCodePluginConfigEntry(item) {
|
|
3276
|
-
const normalized = item.trim().replace(/\\/g, "/");
|
|
3277
|
-
const pathOnly = normalized.split(/[?#]/, 1)[0] || normalized;
|
|
3278
|
-
return normalized === "dbt-agent"
|
|
3279
|
-
|| normalized === "Dbt Agent"
|
|
3280
|
-
|| normalized === "deve"
|
|
3281
|
-
|| normalized === "development-board-toolchain"
|
|
3282
|
-
|| normalized === "development-board-toolchain-dev"
|
|
3283
|
-
|| normalized === "./plugins/deve"
|
|
3284
|
-
|| normalized === "./plugins/deve.js"
|
|
3285
|
-
|| /(?:^|\/)plugins\/deve(?:\.js)?$/.test(pathOnly)
|
|
3286
|
-
|| /(?:^|\/)plugins\/dbt-agent(?:\.js)?$/.test(pathOnly)
|
|
3287
|
-
|| normalized === "./plugins/development-board-toolchain"
|
|
3288
|
-
|| normalized === "./plugins/development-board-toolchain.js"
|
|
3289
|
-
|| normalized === "./plugins/development-board-toolchain-dev"
|
|
3290
|
-
|| normalized === "./plugins/development-board-toolchain-dev.js"
|
|
3291
|
-
|| pathOnly.endsWith("/plugins/development-board-toolchain")
|
|
3292
|
-
|| pathOnly.endsWith("/plugins/development-board-toolchain.js")
|
|
3293
|
-
|| pathOnly.endsWith("/plugins/development-board-toolchain-dev")
|
|
3294
|
-
|| pathOnly.endsWith("/plugins/development-board-toolchain-dev.js")
|
|
3295
|
-
|| normalized.includes("dbt-agent")
|
|
3296
|
-
|| normalized.includes("development-board-toolchain");
|
|
1976
|
+
return join(process.cwd(), ".opencode");
|
|
3297
1977
|
}
|
|
3298
1978
|
async function openCodeDuplicatePluginWarning(targetRoot) {
|
|
3299
|
-
const globalRoot =
|
|
1979
|
+
const globalRoot = join(homedir(), ".config", "opencode");
|
|
3300
1980
|
if (resolve(targetRoot) === resolve(globalRoot))
|
|
3301
1981
|
return undefined;
|
|
3302
1982
|
const configPath = join(globalRoot, "opencode.json");
|
|
@@ -3328,21 +2008,6 @@ async function localPluginVersion(kind) {
|
|
|
3328
2008
|
return undefined;
|
|
3329
2009
|
}
|
|
3330
2010
|
}
|
|
3331
|
-
async function installedCodexPluginVersion(pluginPath) {
|
|
3332
|
-
return await readPackageVersion(join(pluginPath, ".codex-plugin", "plugin.json"));
|
|
3333
|
-
}
|
|
3334
|
-
async function installedOpenCodePluginVersion(targetRoot) {
|
|
3335
|
-
return await readPackageVersion(join(targetRoot, "node_modules", "embed-labs", "package.json"));
|
|
3336
|
-
}
|
|
3337
|
-
async function readPackageVersion(filePath) {
|
|
3338
|
-
try {
|
|
3339
|
-
const parsed = JSON.parse(await readFile(filePath, "utf8"));
|
|
3340
|
-
return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version.trim() : undefined;
|
|
3341
|
-
}
|
|
3342
|
-
catch {
|
|
3343
|
-
return undefined;
|
|
3344
|
-
}
|
|
3345
|
-
}
|
|
3346
2011
|
async function localPluginSourcesAvailable() {
|
|
3347
2012
|
return await pathExists(sourceCheckoutPath("platform_plugins", "codex_plugin", "plugins", "embed-labs", ".codex-plugin", "plugin.json"))
|
|
3348
2013
|
&& await pathExists(sourceCheckoutPath("platform_plugins", "opencode_plugin", "package.json"));
|
|
@@ -3361,8 +2026,7 @@ async function pathExists(pathValue) {
|
|
|
3361
2026
|
}
|
|
3362
2027
|
async function runLocalProcess(command, args) {
|
|
3363
2028
|
return await new Promise((resolveProcess) => {
|
|
3364
|
-
const
|
|
3365
|
-
const child = spawn(launcher.command, launcher.args, {
|
|
2029
|
+
const child = spawn(command, args, {
|
|
3366
2030
|
cwd: process.cwd(),
|
|
3367
2031
|
env: process.env,
|
|
3368
2032
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -3385,50 +2049,6 @@ async function runLocalProcess(command, args) {
|
|
|
3385
2049
|
});
|
|
3386
2050
|
});
|
|
3387
2051
|
}
|
|
3388
|
-
function npmCommand() {
|
|
3389
|
-
return platform() === "win32" ? "npm.cmd" : "npm";
|
|
3390
|
-
}
|
|
3391
|
-
function localProcessLauncher(command, args) {
|
|
3392
|
-
if (platform() !== "win32" || !/\.(?:cmd|bat)$/i.test(command)) {
|
|
3393
|
-
return { command, args };
|
|
3394
|
-
}
|
|
3395
|
-
return {
|
|
3396
|
-
command: process.env.ComSpec || "cmd.exe",
|
|
3397
|
-
args: ["/d", "/s", "/c", windowsCommandLine([command, ...args])]
|
|
3398
|
-
};
|
|
3399
|
-
}
|
|
3400
|
-
function windowsCommandLine(args) {
|
|
3401
|
-
return args.map(windowsQuoteArg).join(" ");
|
|
3402
|
-
}
|
|
3403
|
-
function windowsQuoteArg(arg) {
|
|
3404
|
-
if (arg.length > 0 && !/[\s"]/u.test(arg)) {
|
|
3405
|
-
return arg;
|
|
3406
|
-
}
|
|
3407
|
-
let quoted = "\"";
|
|
3408
|
-
let backslashes = 0;
|
|
3409
|
-
for (const char of arg) {
|
|
3410
|
-
if (char === "\\") {
|
|
3411
|
-
backslashes += 1;
|
|
3412
|
-
continue;
|
|
3413
|
-
}
|
|
3414
|
-
if (char === "\"") {
|
|
3415
|
-
quoted += "\\".repeat((backslashes * 2) + 1);
|
|
3416
|
-
quoted += "\"";
|
|
3417
|
-
backslashes = 0;
|
|
3418
|
-
continue;
|
|
3419
|
-
}
|
|
3420
|
-
if (backslashes > 0) {
|
|
3421
|
-
quoted += "\\".repeat(backslashes);
|
|
3422
|
-
backslashes = 0;
|
|
3423
|
-
}
|
|
3424
|
-
quoted += char;
|
|
3425
|
-
}
|
|
3426
|
-
if (backslashes > 0) {
|
|
3427
|
-
quoted += "\\".repeat(backslashes * 2);
|
|
3428
|
-
}
|
|
3429
|
-
quoted += "\"";
|
|
3430
|
-
return quoted;
|
|
3431
|
-
}
|
|
3432
2052
|
async function parseErrorResponse(response) {
|
|
3433
2053
|
const text = await response.text();
|
|
3434
2054
|
if (!text.trim()) {
|
|
@@ -3446,65 +2066,19 @@ async function parseErrorResponse(response) {
|
|
|
3446
2066
|
return undefined;
|
|
3447
2067
|
}
|
|
3448
2068
|
async function cloudAuthToken() {
|
|
3449
|
-
return (await cloudAuthConfig()).token;
|
|
3450
|
-
}
|
|
3451
|
-
async function cloudAuthConfig() {
|
|
3452
2069
|
const envToken = process.env.EMBED_API_TOKEN?.trim();
|
|
3453
2070
|
if (envToken) {
|
|
3454
|
-
|
|
3455
|
-
return {
|
|
3456
|
-
...fileConfig,
|
|
3457
|
-
token: envToken,
|
|
3458
|
-
profile: process.env.EMBED_AUTH_PROFILE ?? fileConfig.profile ?? "default",
|
|
3459
|
-
source: "env"
|
|
3460
|
-
};
|
|
2071
|
+
return envToken;
|
|
3461
2072
|
}
|
|
3462
|
-
const fileConfig = await readLocalAuthFile();
|
|
3463
|
-
return {
|
|
3464
|
-
...fileConfig,
|
|
3465
|
-
token: fileConfig.token?.trim() || undefined,
|
|
3466
|
-
profile: fileConfig.profile ?? "default",
|
|
3467
|
-
source: fileConfig.token ? "file" : undefined
|
|
3468
|
-
};
|
|
3469
|
-
}
|
|
3470
|
-
async function readLocalAuthFile() {
|
|
3471
2073
|
try {
|
|
3472
2074
|
const parsed = JSON.parse(await readFile(DEFAULT_AUTH_FILE, "utf8"));
|
|
3473
|
-
|
|
2075
|
+
const fileToken = typeof parsed.token === "string" ? parsed.token.trim() : "";
|
|
2076
|
+
return fileToken || undefined;
|
|
3474
2077
|
}
|
|
3475
2078
|
catch {
|
|
3476
|
-
return
|
|
2079
|
+
return undefined;
|
|
3477
2080
|
}
|
|
3478
2081
|
}
|
|
3479
|
-
function normalizeLocalAuthFile(parsed) {
|
|
3480
|
-
const device = isJsonObject(parsed.device) ? parsed.device : undefined;
|
|
3481
|
-
const normalizedDevice = device && typeof device.device_id === "string" && typeof device.fingerprint_hash === "string" && typeof device.private_key_pem === "string"
|
|
3482
|
-
? {
|
|
3483
|
-
device_id: device.device_id,
|
|
3484
|
-
fingerprint_hash: device.fingerprint_hash,
|
|
3485
|
-
private_key_pem: device.private_key_pem,
|
|
3486
|
-
public_key_pem: typeof device.public_key_pem === "string" ? device.public_key_pem : undefined,
|
|
3487
|
-
label: typeof device.label === "string" ? device.label : undefined,
|
|
3488
|
-
platform: typeof device.platform === "string" ? device.platform : undefined,
|
|
3489
|
-
arch: typeof device.arch === "string" ? device.arch : undefined,
|
|
3490
|
-
hostname_hash: typeof device.hostname_hash === "string" ? device.hostname_hash : undefined,
|
|
3491
|
-
registered_at: typeof device.registered_at === "string" ? device.registered_at : undefined
|
|
3492
|
-
}
|
|
3493
|
-
: undefined;
|
|
3494
|
-
return {
|
|
3495
|
-
profile: typeof parsed.profile === "string" ? parsed.profile : undefined,
|
|
3496
|
-
token: typeof parsed.token === "string" ? parsed.token.trim() : undefined,
|
|
3497
|
-
updated_at: typeof parsed.updated_at === "string" ? parsed.updated_at : undefined,
|
|
3498
|
-
account_id: typeof parsed.account_id === "string" ? parsed.account_id : undefined,
|
|
3499
|
-
api_key_id: typeof parsed.api_key_id === "string" ? parsed.api_key_id : undefined,
|
|
3500
|
-
device: normalizedDevice
|
|
3501
|
-
};
|
|
3502
|
-
}
|
|
3503
|
-
async function writeLocalAuthFile(config) {
|
|
3504
|
-
await mkdir(dirname(DEFAULT_AUTH_FILE), { recursive: true });
|
|
3505
|
-
await writeFile(DEFAULT_AUTH_FILE, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
3506
|
-
await chmod(DEFAULT_AUTH_FILE, 0o600).catch(() => undefined);
|
|
3507
|
-
}
|
|
3508
2082
|
function serviceBaseUrl(url) {
|
|
3509
2083
|
return url.replace(/\/+$/, "");
|
|
3510
2084
|
}
|
|
@@ -3527,16 +2101,9 @@ async function naturalLanguageQuery(parsed) {
|
|
|
3527
2101
|
}
|
|
3528
2102
|
const text = parsed.command.slice(1).join(" ").trim();
|
|
3529
2103
|
if (!text) {
|
|
3530
|
-
return { response: fail("invalid_args", QUERY_USAGE, { remediation: "Try: embed query \"
|
|
2104
|
+
return { response: fail("invalid_args", QUERY_USAGE, { remediation: "Try: embed query \"查一下我的额度\"." }) };
|
|
3531
2105
|
}
|
|
3532
2106
|
const normalized = text.toLowerCase();
|
|
3533
|
-
if (isQuotaOrBillingIntent(normalized)) {
|
|
3534
|
-
return {
|
|
3535
|
-
response: fail("quota_not_supported", "当前 Embed Labs 服务器只提供 MCP 服务,不提供额度、充值、余额、账本或存储配额功能。", {
|
|
3536
|
-
remediation: "请使用 embedlabs auth status 查看登录和设备绑定状态,或在 MCP 客户端中直接调用开发板、工具链、知识库和本地硬件相关工具。"
|
|
3537
|
-
})
|
|
3538
|
-
};
|
|
3539
|
-
}
|
|
3540
2107
|
const accountId = await queryAccountId(parsed);
|
|
3541
2108
|
const needsAccount = queryNeedsAccount(normalized);
|
|
3542
2109
|
if (needsAccount && !accountId.ok) {
|
|
@@ -3555,7 +2122,7 @@ async function naturalLanguageQuery(parsed) {
|
|
|
3555
2122
|
if (!amountUsd) {
|
|
3556
2123
|
return {
|
|
3557
2124
|
response: fail("amount_required", "Recharge requests need an amount.", {
|
|
3558
|
-
remediation: "
|
|
2125
|
+
remediation: "Try: embed query \"用 USDC 链上充值 100 美元\"."
|
|
3559
2126
|
})
|
|
3560
2127
|
};
|
|
3561
2128
|
}
|
|
@@ -3648,8 +2215,13 @@ async function naturalLanguageQuery(parsed) {
|
|
|
3648
2215
|
response: fail("query_intent_unknown", "I could not map that request to a stable CLI action yet.", {
|
|
3649
2216
|
remediation: [
|
|
3650
2217
|
"Supported examples:",
|
|
2218
|
+
"embed query \"查一下我的额度\"",
|
|
2219
|
+
"embed query \"用 USDC 链上充值 100 美元\"",
|
|
2220
|
+
"embed query \"看一下 token 用量\"",
|
|
2221
|
+
"embed query \"看一下磁盘空间\"",
|
|
3651
2222
|
"embed query \"列出我的 API key\"",
|
|
3652
2223
|
"embed query \"有哪些开发板模板\"",
|
|
2224
|
+
"embed query \"当前可以用哪些模型\"",
|
|
3653
2225
|
"embed query \"本地有哪些工具能力\""
|
|
3654
2226
|
].join("\n")
|
|
3655
2227
|
})
|
|
@@ -3862,10 +2434,6 @@ function agentRunToolInputs(parsed) {
|
|
|
3862
2434
|
if (remotePath.error) {
|
|
3863
2435
|
return remotePath.error;
|
|
3864
2436
|
}
|
|
3865
|
-
const runCommand = optionalTrimmedStringFlag(parsed, "run-command");
|
|
3866
|
-
if (runCommand.error) {
|
|
3867
|
-
return runCommand.error;
|
|
3868
|
-
}
|
|
3869
2437
|
const user = optionalTrimmedStringFlag(parsed, "user");
|
|
3870
2438
|
if (user.error) {
|
|
3871
2439
|
return user.error;
|
|
@@ -3919,7 +2487,6 @@ function agentRunToolInputs(parsed) {
|
|
|
3919
2487
|
user: user.value,
|
|
3920
2488
|
artifact_path: artifact.value,
|
|
3921
2489
|
remote_path: remotePath.value,
|
|
3922
|
-
run_command: runCommand.value,
|
|
3923
2490
|
run: booleanFlag(parsed, "run") || undefined,
|
|
3924
2491
|
timeout_seconds: timeout.value
|
|
3925
2492
|
});
|
|
@@ -4000,15 +2567,13 @@ async function queryAccountId(parsed) {
|
|
|
4000
2567
|
return { ok: true, value: auth.data.account_id };
|
|
4001
2568
|
}
|
|
4002
2569
|
function queryNeedsAccount(normalized) {
|
|
4003
|
-
return isApiKeyIntent(normalized);
|
|
4004
|
-
}
|
|
4005
|
-
function isQuotaOrBillingIntent(normalized) {
|
|
4006
2570
|
return isRechargeIntent(normalized)
|
|
4007
2571
|
|| isBalanceIntent(normalized)
|
|
4008
2572
|
|| isUsageIntent(normalized)
|
|
4009
2573
|
|| isBillingStatementIntent(normalized)
|
|
4010
2574
|
|| isStorageSettlementIntent(normalized)
|
|
4011
|
-
|| isStorageIntent(normalized)
|
|
2575
|
+
|| isStorageIntent(normalized)
|
|
2576
|
+
|| isApiKeyIntent(normalized);
|
|
4012
2577
|
}
|
|
4013
2578
|
function isRechargeIntent(normalized) {
|
|
4014
2579
|
return /(充值|充钱|续费|购买额度|买额度|top\s*up|recharge|add credits|buy credits)/i.test(normalized);
|
|
@@ -4854,48 +3419,7 @@ function boardKnowledgeFileRequest(parsed) {
|
|
|
4854
3419
|
templateId,
|
|
4855
3420
|
source: source.value,
|
|
4856
3421
|
path: knowledgePath.value,
|
|
4857
|
-
outputPath: outputPath.value
|
|
4858
|
-
};
|
|
4859
|
-
}
|
|
4860
|
-
function boardKnowledgeSearchRequest(parsed) {
|
|
4861
|
-
const unknownFlag = firstUnknownFlag(parsed, ["json", "query", "source", "limit"]);
|
|
4862
|
-
if (unknownFlag) {
|
|
4863
|
-
return `Unknown flag --${unknownFlag}. ${BOARD_KNOWLEDGE_SEARCH_USAGE}`;
|
|
4864
|
-
}
|
|
4865
|
-
const templateId = parsed.command[3]?.trim();
|
|
4866
|
-
if (!templateId) {
|
|
4867
|
-
return BOARD_KNOWLEDGE_SEARCH_USAGE;
|
|
4868
|
-
}
|
|
4869
|
-
const extra = parsed.command.slice(4);
|
|
4870
|
-
if (extra.length > 0) {
|
|
4871
|
-
return `Unexpected argument: ${extra[0]}. ${BOARD_KNOWLEDGE_SEARCH_USAGE}`;
|
|
4872
|
-
}
|
|
4873
|
-
const query = optionalTrimmedStringFlag(parsed, "query");
|
|
4874
|
-
if (query.error) {
|
|
4875
|
-
return query.error;
|
|
4876
|
-
}
|
|
4877
|
-
if (!query.value) {
|
|
4878
|
-
return BOARD_KNOWLEDGE_SEARCH_USAGE;
|
|
4879
|
-
}
|
|
4880
|
-
const source = optionalTrimmedStringFlag(parsed, "source");
|
|
4881
|
-
if (source.error) {
|
|
4882
|
-
return source.error;
|
|
4883
|
-
}
|
|
4884
|
-
if (source.value && !["board_pack", "build_template", "registry"].includes(source.value)) {
|
|
4885
|
-
return BOARD_KNOWLEDGE_SEARCH_USAGE;
|
|
4886
|
-
}
|
|
4887
|
-
const limit = optionalPositiveIntegerFlag(parsed, "limit");
|
|
4888
|
-
if (limit.error) {
|
|
4889
|
-
return limit.error;
|
|
4890
|
-
}
|
|
4891
|
-
if (limit.value !== undefined && limit.value > 10) {
|
|
4892
|
-
return "--limit must be between 1 and 10.";
|
|
4893
|
-
}
|
|
4894
|
-
return {
|
|
4895
|
-
templateId,
|
|
4896
|
-
query: query.value,
|
|
4897
|
-
source: source.value,
|
|
4898
|
-
limit: limit.value ?? 5
|
|
3422
|
+
outputPath: outputPath.value
|
|
4899
3423
|
};
|
|
4900
3424
|
}
|
|
4901
3425
|
function toolCallRequest(parsed) {
|
|
@@ -4945,7 +3469,7 @@ function isTaishanPiDeployCommand(parsed) {
|
|
|
4945
3469
|
|| area === "deploy";
|
|
4946
3470
|
}
|
|
4947
3471
|
function boardDeployTaishanPiRequest(parsed) {
|
|
4948
|
-
const unknownFlag = firstUnknownFlag(parsed, ["json", "host", "user", "artifact", "artifact-path", "remote-path", "run
|
|
3472
|
+
const unknownFlag = firstUnknownFlag(parsed, ["json", "host", "user", "artifact", "artifact-path", "remote-path", "run", "timeout", "approve", "approved"]);
|
|
4949
3473
|
if (unknownFlag) {
|
|
4950
3474
|
return `Unknown flag --${unknownFlag}. ${BOARD_DEPLOY_TAISHANPI_USAGE}`;
|
|
4951
3475
|
}
|
|
@@ -4987,10 +3511,6 @@ function boardDeployTaishanPiRequest(parsed) {
|
|
|
4987
3511
|
if (remotePath.error) {
|
|
4988
3512
|
return remotePath.error;
|
|
4989
3513
|
}
|
|
4990
|
-
const runCommand = optionalTrimmedStringFlag(parsed, "run-command");
|
|
4991
|
-
if (runCommand.error) {
|
|
4992
|
-
return runCommand.error;
|
|
4993
|
-
}
|
|
4994
3514
|
const timeout = optionalNonNegativeIntegerFlag(parsed, "timeout");
|
|
4995
3515
|
if (timeout.error) {
|
|
4996
3516
|
return timeout.error;
|
|
@@ -5005,7 +3525,6 @@ function boardDeployTaishanPiRequest(parsed) {
|
|
|
5005
3525
|
user: user.value,
|
|
5006
3526
|
artifact_path: artifact.value,
|
|
5007
3527
|
remote_path: remotePath.value,
|
|
5008
|
-
run_command: runCommand.value,
|
|
5009
3528
|
run: booleanFlag(parsed, "run") || undefined,
|
|
5010
3529
|
timeout_seconds: timeout.value,
|
|
5011
3530
|
approved: approval.value || undefined
|
|
@@ -5018,272 +3537,30 @@ async function authLogin(parsed) {
|
|
|
5018
3537
|
return fail("invalid_args", "Usage: embed auth login --token <token> [--profile default] [--json]");
|
|
5019
3538
|
}
|
|
5020
3539
|
const updatedAt = new Date().toISOString();
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
if (!registration.ok) {
|
|
5025
|
-
return fail(registration.error.code, registration.error.message, {
|
|
5026
|
-
remediation: registration.error.remediation,
|
|
5027
|
-
details: registration.error.details
|
|
5028
|
-
});
|
|
5029
|
-
}
|
|
5030
|
-
const device = {
|
|
5031
|
-
device_id: registration.data.device.device_id,
|
|
5032
|
-
fingerprint_hash: localDevice.device.fingerprint_hash,
|
|
5033
|
-
private_key_pem: localDevice.device.private_key_pem,
|
|
5034
|
-
public_key_pem: localDevice.device.public_key_pem,
|
|
5035
|
-
label: registration.data.device.label ?? localDevice.device.label,
|
|
5036
|
-
platform: registration.data.device.platform ?? localDevice.device.platform,
|
|
5037
|
-
arch: registration.data.device.arch ?? localDevice.device.arch,
|
|
5038
|
-
hostname_hash: registration.data.device.hostname_hash ?? localDevice.device.hostname_hash,
|
|
5039
|
-
registered_at: registration.data.device.first_seen_at
|
|
5040
|
-
};
|
|
5041
|
-
await writeLocalAuthFile({
|
|
5042
|
-
profile,
|
|
5043
|
-
token: token.trim(),
|
|
5044
|
-
updated_at: updatedAt,
|
|
5045
|
-
account_id: registration.data.device.account_id,
|
|
5046
|
-
api_key_id: registration.data.device.api_key_id,
|
|
5047
|
-
device
|
|
5048
|
-
});
|
|
5049
|
-
return ok({
|
|
5050
|
-
authenticated: true,
|
|
5051
|
-
profile,
|
|
5052
|
-
source: "file",
|
|
5053
|
-
updated_at: updatedAt,
|
|
5054
|
-
account_id: registration.data.device.account_id,
|
|
5055
|
-
api_key_id: registration.data.device.api_key_id,
|
|
5056
|
-
device_id: device.device_id,
|
|
5057
|
-
device_fingerprint_hash: device.fingerprint_hash,
|
|
5058
|
-
device_label: device.label,
|
|
5059
|
-
device_registered_at: device.registered_at,
|
|
5060
|
-
device_private_key_configured: true
|
|
5061
|
-
});
|
|
3540
|
+
await mkdir(dirname(DEFAULT_AUTH_FILE), { recursive: true });
|
|
3541
|
+
await writeFile(DEFAULT_AUTH_FILE, `${JSON.stringify({ profile, token, updated_at: updatedAt }, null, 2)}\n`, "utf8");
|
|
3542
|
+
return ok({ authenticated: true, profile, source: "file", updated_at: updatedAt });
|
|
5062
3543
|
}
|
|
5063
3544
|
async function authStatus() {
|
|
5064
|
-
|
|
5065
|
-
const file = await readLocalAuthFile();
|
|
5066
|
-
const deviceIntegrity = file.device
|
|
5067
|
-
? (await validateLocalDeviceIntegrity(file.device)).ok ? "ok" : "failed"
|
|
5068
|
-
: "unbound";
|
|
5069
|
-
if (envToken) {
|
|
3545
|
+
if (process.env.EMBED_API_TOKEN?.trim()) {
|
|
5070
3546
|
return {
|
|
5071
3547
|
authenticated: true,
|
|
5072
3548
|
profile: process.env.EMBED_AUTH_PROFILE ?? "default",
|
|
5073
|
-
source: "env"
|
|
5074
|
-
account_id: file.account_id,
|
|
5075
|
-
api_key_id: file.api_key_id,
|
|
5076
|
-
device_id: file.device?.device_id,
|
|
5077
|
-
device_fingerprint_hash: file.device?.fingerprint_hash,
|
|
5078
|
-
device_label: file.device?.label,
|
|
5079
|
-
device_registered_at: file.device?.registered_at,
|
|
5080
|
-
device_private_key_configured: Boolean(file.device?.private_key_pem),
|
|
5081
|
-
device_integrity: deviceIntegrity
|
|
3549
|
+
source: "env"
|
|
5082
3550
|
};
|
|
5083
3551
|
}
|
|
5084
|
-
return {
|
|
5085
|
-
authenticated: Boolean(file.token?.trim()),
|
|
5086
|
-
profile: file.profile ?? "default",
|
|
5087
|
-
source: file.token ? "file" : undefined,
|
|
5088
|
-
updated_at: file.updated_at,
|
|
5089
|
-
account_id: file.account_id,
|
|
5090
|
-
api_key_id: file.api_key_id,
|
|
5091
|
-
device_id: file.device?.device_id,
|
|
5092
|
-
device_fingerprint_hash: file.device?.fingerprint_hash,
|
|
5093
|
-
device_label: file.device?.label,
|
|
5094
|
-
device_registered_at: file.device?.registered_at,
|
|
5095
|
-
device_private_key_configured: Boolean(file.device?.private_key_pem),
|
|
5096
|
-
device_integrity: deviceIntegrity
|
|
5097
|
-
};
|
|
5098
|
-
}
|
|
5099
|
-
async function authDeviceStatus(parsed) {
|
|
5100
|
-
const unknownFlag = firstUnknownFlag(parsed, ["json"]);
|
|
5101
|
-
const unexpected = parsed.command.slice(3);
|
|
5102
|
-
if (unknownFlag || unexpected.length > 0) {
|
|
5103
|
-
return fail("invalid_args", unknownFlag ? `Unknown flag --${unknownFlag}. ${AUTH_DEVICE_STATUS_USAGE}` : AUTH_DEVICE_STATUS_USAGE);
|
|
5104
|
-
}
|
|
5105
|
-
const local = await authStatus();
|
|
5106
|
-
const remote = local.authenticated ? await cloudGet("/v1/me/devices") : undefined;
|
|
5107
|
-
if (remote && !remote.ok) {
|
|
5108
|
-
return ok({ local });
|
|
5109
|
-
}
|
|
5110
|
-
return ok({ local, remote: remote?.data });
|
|
5111
|
-
}
|
|
5112
|
-
async function authDeviceList(parsed) {
|
|
5113
|
-
const unknownFlag = firstUnknownFlag(parsed, ["json"]);
|
|
5114
|
-
const unexpected = parsed.command.slice(3);
|
|
5115
|
-
if (unknownFlag || unexpected.length > 0) {
|
|
5116
|
-
return fail("invalid_args", unknownFlag ? `Unknown flag --${unknownFlag}. ${AUTH_DEVICE_LIST_USAGE}` : AUTH_DEVICE_LIST_USAGE);
|
|
5117
|
-
}
|
|
5118
|
-
return await cloudGet("/v1/me/devices");
|
|
5119
|
-
}
|
|
5120
|
-
async function authDeviceRevoke(parsed) {
|
|
5121
|
-
const unknownFlag = firstUnknownFlag(parsed, ["json"]);
|
|
5122
|
-
if (unknownFlag) {
|
|
5123
|
-
return fail("invalid_args", `Unknown flag --${unknownFlag}. ${AUTH_DEVICE_REVOKE_USAGE}`);
|
|
5124
|
-
}
|
|
5125
|
-
const id = commandId(parsed, 3, "device_id", AUTH_DEVICE_REVOKE_USAGE);
|
|
5126
|
-
if (!id.ok) {
|
|
5127
|
-
return fail("invalid_args", id.error);
|
|
5128
|
-
}
|
|
5129
|
-
return await cloudPost(`/v1/me/devices/${encodeURIComponent(id.value)}/revoke`, {});
|
|
5130
|
-
}
|
|
5131
|
-
async function authDeviceRename(parsed) {
|
|
5132
|
-
const unknownFlag = firstUnknownFlag(parsed, ["json", "label"]);
|
|
5133
|
-
if (unknownFlag) {
|
|
5134
|
-
return fail("invalid_args", `Unknown flag --${unknownFlag}. ${AUTH_DEVICE_RENAME_USAGE}`);
|
|
5135
|
-
}
|
|
5136
|
-
const id = commandId(parsed, 3, "device_id", AUTH_DEVICE_RENAME_USAGE);
|
|
5137
|
-
if (!id.ok) {
|
|
5138
|
-
return fail("invalid_args", id.error);
|
|
5139
|
-
}
|
|
5140
|
-
const label = stringFlag(parsed, "label");
|
|
5141
|
-
if (!label?.trim()) {
|
|
5142
|
-
return fail("invalid_args", AUTH_DEVICE_RENAME_USAGE);
|
|
5143
|
-
}
|
|
5144
|
-
const updated = await cloudPost(`/v1/me/devices/${encodeURIComponent(id.value)}`, { label: label.trim() });
|
|
5145
|
-
if (updated.ok) {
|
|
5146
|
-
const auth = await readLocalAuthFile();
|
|
5147
|
-
if (auth.device?.device_id === updated.data.device_id) {
|
|
5148
|
-
await writeLocalAuthFile({ ...auth, device: { ...auth.device, label: updated.data.label } });
|
|
5149
|
-
}
|
|
5150
|
-
}
|
|
5151
|
-
return updated;
|
|
5152
|
-
}
|
|
5153
|
-
async function buildLocalDeviceAuth(parsed, existing) {
|
|
5154
|
-
const fingerprint = await localHardwareFingerprint();
|
|
5155
|
-
const keyPair = existing?.fingerprint_hash === fingerprint.fingerprint_hash && existing.private_key_pem && existing.public_key_pem
|
|
5156
|
-
? { privateKeyPem: existing.private_key_pem, publicKeyPem: existing.public_key_pem }
|
|
5157
|
-
: generateLocalDeviceKeyPair();
|
|
5158
|
-
const label = stringFlag(parsed, "label")?.trim()
|
|
5159
|
-
|| existing?.label
|
|
5160
|
-
|| `${fingerprint.platform} ${fingerprint.arch}`;
|
|
5161
|
-
const device = {
|
|
5162
|
-
device_id: existing?.fingerprint_hash === fingerprint.fingerprint_hash ? existing.device_id : "",
|
|
5163
|
-
fingerprint_hash: fingerprint.fingerprint_hash,
|
|
5164
|
-
private_key_pem: keyPair.privateKeyPem,
|
|
5165
|
-
public_key_pem: keyPair.publicKeyPem,
|
|
5166
|
-
label,
|
|
5167
|
-
platform: fingerprint.platform,
|
|
5168
|
-
arch: fingerprint.arch,
|
|
5169
|
-
hostname_hash: fingerprint.hostname_hash,
|
|
5170
|
-
registered_at: existing?.registered_at
|
|
5171
|
-
};
|
|
5172
|
-
return {
|
|
5173
|
-
device,
|
|
5174
|
-
registration: {
|
|
5175
|
-
fingerprint_hash: fingerprint.fingerprint_hash,
|
|
5176
|
-
public_key: keyPair.publicKeyPem,
|
|
5177
|
-
label,
|
|
5178
|
-
platform: fingerprint.platform,
|
|
5179
|
-
arch: fingerprint.arch,
|
|
5180
|
-
hostname_hash: fingerprint.hostname_hash,
|
|
5181
|
-
client_name: EMBED_CLIENT_NAME,
|
|
5182
|
-
client_version: EMBED_CLIENT_VERSION,
|
|
5183
|
-
metadata: {
|
|
5184
|
-
fingerprint_version: "v1",
|
|
5185
|
-
fingerprint_source: fingerprint.source
|
|
5186
|
-
}
|
|
5187
|
-
}
|
|
5188
|
-
};
|
|
5189
|
-
}
|
|
5190
|
-
function generateLocalDeviceKeyPair() {
|
|
5191
|
-
const { privateKey, publicKey } = generateKeyPairSync("ed25519");
|
|
5192
|
-
return {
|
|
5193
|
-
privateKeyPem: privateKey.export({ type: "pkcs8", format: "pem" }).toString(),
|
|
5194
|
-
publicKeyPem: publicKey.export({ type: "spki", format: "pem" }).toString()
|
|
5195
|
-
};
|
|
5196
|
-
}
|
|
5197
|
-
async function registerLocalDevice(token, body) {
|
|
5198
3552
|
try {
|
|
5199
|
-
const
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
3553
|
+
const parsed = JSON.parse(await readFile(DEFAULT_AUTH_FILE, "utf8"));
|
|
3554
|
+
return {
|
|
3555
|
+
authenticated: typeof parsed.token === "string" && parsed.token.trim().length > 0,
|
|
3556
|
+
profile: typeof parsed.profile === "string" ? parsed.profile : "default",
|
|
3557
|
+
source: "file",
|
|
3558
|
+
updated_at: typeof parsed.updated_at === "string" ? parsed.updated_at : undefined
|
|
5203
3559
|
};
|
|
5204
|
-
addCloudRequestSignature(headers, "POST", "/v1/me/devices/register", bodyText, token);
|
|
5205
|
-
const response = await fetch(`${serviceBaseUrl(DEFAULT_CLOUD_API_URL)}/v1/me/devices/register`, {
|
|
5206
|
-
method: "POST",
|
|
5207
|
-
headers,
|
|
5208
|
-
body: bodyText
|
|
5209
|
-
});
|
|
5210
|
-
const parsed = await response.json();
|
|
5211
|
-
return enrichCloudAuthFailure(parsed, true);
|
|
5212
|
-
}
|
|
5213
|
-
catch (error) {
|
|
5214
|
-
return fail("cloud_api_unreachable", error instanceof Error ? error.message : String(error), {
|
|
5215
|
-
remediation: `Check that embed cloud-api is running at ${DEFAULT_CLOUD_API_URL}. Start it with: npm run cloud-api`
|
|
5216
|
-
});
|
|
5217
|
-
}
|
|
5218
|
-
}
|
|
5219
|
-
async function localHardwareFingerprint() {
|
|
5220
|
-
cachedLocalHardwareFingerprint ??= localHardwareFingerprintUncached();
|
|
5221
|
-
return await cachedLocalHardwareFingerprint;
|
|
5222
|
-
}
|
|
5223
|
-
async function localHardwareFingerprintUncached() {
|
|
5224
|
-
const platformName = platform();
|
|
5225
|
-
const archName = arch();
|
|
5226
|
-
const raw = await localHardwareId(platformName);
|
|
5227
|
-
const fingerprintHash = createHash("sha256")
|
|
5228
|
-
.update(`embed-labs:device:v1:${platformName}:${raw.value}`)
|
|
5229
|
-
.digest("hex");
|
|
5230
|
-
const hostnameHash = createHash("sha256")
|
|
5231
|
-
.update(`embed-labs:hostname:v1:${hostname()}`)
|
|
5232
|
-
.digest("hex");
|
|
5233
|
-
return {
|
|
5234
|
-
fingerprint_hash: fingerprintHash,
|
|
5235
|
-
platform: platformName,
|
|
5236
|
-
arch: archName,
|
|
5237
|
-
hostname_hash: hostnameHash,
|
|
5238
|
-
source: raw.source
|
|
5239
|
-
};
|
|
5240
|
-
}
|
|
5241
|
-
async function localHardwareId(platformName) {
|
|
5242
|
-
if (platformName === "darwin") {
|
|
5243
|
-
const result = await runLocalProcess("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"]);
|
|
5244
|
-
const match = /"IOPlatformUUID"\s*=\s*"([^"]+)"/.exec(result.stdout);
|
|
5245
|
-
if (match?.[1]) {
|
|
5246
|
-
return { value: match[1], source: "macos_ioplatformuuid" };
|
|
5247
|
-
}
|
|
5248
|
-
}
|
|
5249
|
-
if (platformName === "win32") {
|
|
5250
|
-
const result = await runLocalProcess("reg", ["query", "HKLM\\SOFTWARE\\Microsoft\\Cryptography", "/v", "MachineGuid"]);
|
|
5251
|
-
const match = /MachineGuid\s+REG_\w+\s+([^\r\n]+)/.exec(result.stdout);
|
|
5252
|
-
if (match?.[1]?.trim()) {
|
|
5253
|
-
return { value: match[1].trim(), source: "windows_machineguid" };
|
|
5254
|
-
}
|
|
5255
|
-
}
|
|
5256
|
-
if (platformName === "linux") {
|
|
5257
|
-
for (const pathValue of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
5258
|
-
try {
|
|
5259
|
-
const value = (await readFile(pathValue, "utf8")).trim();
|
|
5260
|
-
if (value) {
|
|
5261
|
-
return { value, source: `linux:${pathValue}` };
|
|
5262
|
-
}
|
|
5263
|
-
}
|
|
5264
|
-
catch {
|
|
5265
|
-
// Try the next stable machine id location.
|
|
5266
|
-
}
|
|
5267
|
-
}
|
|
5268
|
-
}
|
|
5269
|
-
const generated = await localGeneratedInstallId();
|
|
5270
|
-
return { value: generated, source: "generated_install_id" };
|
|
5271
|
-
}
|
|
5272
|
-
async function localGeneratedInstallId() {
|
|
5273
|
-
try {
|
|
5274
|
-
const parsed = JSON.parse(await readFile(DEFAULT_DEVICE_FILE, "utf8"));
|
|
5275
|
-
if (typeof parsed.generated_install_id === "string" && parsed.generated_install_id.trim()) {
|
|
5276
|
-
return parsed.generated_install_id.trim();
|
|
5277
|
-
}
|
|
5278
3560
|
}
|
|
5279
3561
|
catch {
|
|
5280
|
-
|
|
3562
|
+
return { authenticated: false, profile: "default" };
|
|
5281
3563
|
}
|
|
5282
|
-
const generated = `install_${randomBytes(24).toString("base64url")}`;
|
|
5283
|
-
await mkdir(dirname(DEFAULT_DEVICE_FILE), { recursive: true });
|
|
5284
|
-
await writeFile(DEFAULT_DEVICE_FILE, `${JSON.stringify({ generated_install_id: generated, created_at: new Date().toISOString() }, null, 2)}\n`, "utf8");
|
|
5285
|
-
await chmod(DEFAULT_DEVICE_FILE, 0o600).catch(() => undefined);
|
|
5286
|
-
return generated;
|
|
5287
3564
|
}
|
|
5288
3565
|
function accountCreateBody(parsed) {
|
|
5289
3566
|
const unknownFlag = firstUnknownFlag(parsed, ["json", "email", "display-name"]);
|
|
@@ -5439,80 +3716,6 @@ function usageRecordBody(parsed) {
|
|
|
5439
3716
|
created_at: createdAtResult.value
|
|
5440
3717
|
});
|
|
5441
3718
|
}
|
|
5442
|
-
function mcpToolEventBody(parsed) {
|
|
5443
|
-
const unknownFlag = firstUnknownFlag(parsed, [
|
|
5444
|
-
"json",
|
|
5445
|
-
"account",
|
|
5446
|
-
"account-id",
|
|
5447
|
-
"tool",
|
|
5448
|
-
"client",
|
|
5449
|
-
"mode",
|
|
5450
|
-
"server-model-used",
|
|
5451
|
-
"success",
|
|
5452
|
-
"local-device-id",
|
|
5453
|
-
"local_device_id",
|
|
5454
|
-
"request-id",
|
|
5455
|
-
"duration-ms",
|
|
5456
|
-
"input-summary",
|
|
5457
|
-
"output-summary"
|
|
5458
|
-
]);
|
|
5459
|
-
if (unknownFlag) {
|
|
5460
|
-
return `Unknown flag --${unknownFlag}. ${MCP_TOOL_EVENT_USAGE}`;
|
|
5461
|
-
}
|
|
5462
|
-
const extra = parsed.command.slice(2);
|
|
5463
|
-
if (extra.length > 0) {
|
|
5464
|
-
return `Unexpected argument: ${extra[0]}. ${MCP_TOOL_EVENT_USAGE}`;
|
|
5465
|
-
}
|
|
5466
|
-
const toolResult = optionalTrimmedStringFlag(parsed, "tool");
|
|
5467
|
-
if (toolResult.error)
|
|
5468
|
-
return toolResult.error;
|
|
5469
|
-
if (!toolResult.value)
|
|
5470
|
-
return MCP_TOOL_EVENT_USAGE;
|
|
5471
|
-
const accountResult = optionalTrimmedStringAliasFlag(parsed, ["account", "account-id"], "account or account-id");
|
|
5472
|
-
if (accountResult.error)
|
|
5473
|
-
return accountResult.error;
|
|
5474
|
-
const clientResult = optionalTrimmedStringFlag(parsed, "client");
|
|
5475
|
-
if (clientResult.error)
|
|
5476
|
-
return clientResult.error;
|
|
5477
|
-
const modeResult = optionalTrimmedStringFlag(parsed, "mode");
|
|
5478
|
-
if (modeResult.error)
|
|
5479
|
-
return modeResult.error;
|
|
5480
|
-
const localDeviceResult = optionalTrimmedStringAliasFlag(parsed, ["local-device-id", "local_device_id"], "local-device-id");
|
|
5481
|
-
if (localDeviceResult.error)
|
|
5482
|
-
return localDeviceResult.error;
|
|
5483
|
-
const requestIdResult = optionalTrimmedStringFlag(parsed, "request-id");
|
|
5484
|
-
if (requestIdResult.error)
|
|
5485
|
-
return requestIdResult.error;
|
|
5486
|
-
const inputSummaryResult = optionalTrimmedStringFlag(parsed, "input-summary");
|
|
5487
|
-
if (inputSummaryResult.error)
|
|
5488
|
-
return inputSummaryResult.error;
|
|
5489
|
-
const outputSummaryResult = optionalTrimmedStringFlag(parsed, "output-summary");
|
|
5490
|
-
if (outputSummaryResult.error)
|
|
5491
|
-
return outputSummaryResult.error;
|
|
5492
|
-
const durationResult = optionalNonNegativeIntegerFlag(parsed, "duration-ms");
|
|
5493
|
-
if (durationResult.error)
|
|
5494
|
-
return durationResult.error;
|
|
5495
|
-
const serverModelUsed = optionalBooleanFlag(parsed, "server-model-used");
|
|
5496
|
-
if (typeof serverModelUsed === "string")
|
|
5497
|
-
return serverModelUsed;
|
|
5498
|
-
const success = optionalBooleanFlag(parsed, "success");
|
|
5499
|
-
if (typeof success === "string")
|
|
5500
|
-
return success;
|
|
5501
|
-
return compactBody({
|
|
5502
|
-
account_id: accountResult.value,
|
|
5503
|
-
tool_name: toolResult.value,
|
|
5504
|
-
client: clientResult.value,
|
|
5505
|
-
mode: modeResult.value,
|
|
5506
|
-
local_device_id: localDeviceResult.value,
|
|
5507
|
-
server_model_used: serverModelUsed,
|
|
5508
|
-
success,
|
|
5509
|
-
request_id: requestIdResult.value,
|
|
5510
|
-
duration_ms: durationResult.value,
|
|
5511
|
-
input_summary: inputSummaryResult.value,
|
|
5512
|
-
output_summary: outputSummaryResult.value,
|
|
5513
|
-
metadata: localDeviceResult.value ? { local_device_id: localDeviceResult.value } : undefined
|
|
5514
|
-
});
|
|
5515
|
-
}
|
|
5516
3719
|
function usageSummaryRequest(parsed) {
|
|
5517
3720
|
const unknownFlag = firstUnknownFlag(parsed, ["json", "account", "account-id", "api-key-id", "from", "to"]);
|
|
5518
3721
|
if (unknownFlag) {
|
|
@@ -5848,29 +4051,6 @@ function billingSnapshotListRequest(parsed) {
|
|
|
5848
4051
|
}
|
|
5849
4052
|
return { path: `/v1/accounts/${encodeURIComponent(accountResult.value)}/billing/snapshots` };
|
|
5850
4053
|
}
|
|
5851
|
-
function localToolchainListRequest(parsed, usage = LOCAL_TOOLCHAIN_LIST_USAGE) {
|
|
5852
|
-
const unknownFlag = firstUnknownFlag(parsed, ["json", "board", "board-id", "channel", "metadata-root", "install-root"]);
|
|
5853
|
-
if (unknownFlag) {
|
|
5854
|
-
return `Unknown flag --${unknownFlag}. ${usage}`;
|
|
5855
|
-
}
|
|
5856
|
-
const extra = parsed.command.slice(3);
|
|
5857
|
-
if (extra.length > 0) {
|
|
5858
|
-
return `Unexpected argument: ${extra[0]}. ${usage}`;
|
|
5859
|
-
}
|
|
5860
|
-
const board = optionalTrimmedStringAliasFlag(parsed, ["board", "board-id"], "board or board-id");
|
|
5861
|
-
if (board.error)
|
|
5862
|
-
return board.error;
|
|
5863
|
-
const channel = optionalTrimmedStringFlag(parsed, "channel");
|
|
5864
|
-
if (channel.error)
|
|
5865
|
-
return channel.error;
|
|
5866
|
-
const metadataRoot = optionalTrimmedStringFlag(parsed, "metadata-root");
|
|
5867
|
-
if (metadataRoot.error)
|
|
5868
|
-
return metadataRoot.error;
|
|
5869
|
-
const installRoot = optionalTrimmedStringFlag(parsed, "install-root");
|
|
5870
|
-
if (installRoot.error)
|
|
5871
|
-
return installRoot.error;
|
|
5872
|
-
return { boardId: board.value, channel: channel.value, metadataRoot: metadataRoot.value, installRoot: installRoot.value };
|
|
5873
|
-
}
|
|
5874
4054
|
function localToolchainLatestRequest(parsed) {
|
|
5875
4055
|
const unknownFlag = firstUnknownFlag(parsed, ["json", "board", "board-id", "channel", "metadata-root"]);
|
|
5876
4056
|
if (unknownFlag) {
|
|
@@ -5891,32 +4071,6 @@ function localToolchainLatestRequest(parsed) {
|
|
|
5891
4071
|
return metadataRoot.error;
|
|
5892
4072
|
return { boardId: board.value, channel: channel.value, metadataRoot: metadataRoot.value };
|
|
5893
4073
|
}
|
|
5894
|
-
function localWslInstallRequest(parsed) {
|
|
5895
|
-
const unknownFlag = firstUnknownFlag(parsed, ["json", "distribution", "distro", "no-launch", "web-download", "timeout-ms"]);
|
|
5896
|
-
if (unknownFlag) {
|
|
5897
|
-
return `Unknown flag --${unknownFlag}. ${LOCAL_WSL_INSTALL_USAGE}`;
|
|
5898
|
-
}
|
|
5899
|
-
const extra = parsed.command.slice(3);
|
|
5900
|
-
if (extra.length > 0) {
|
|
5901
|
-
return `Unexpected argument: ${extra[0]}. ${LOCAL_WSL_INSTALL_USAGE}`;
|
|
5902
|
-
}
|
|
5903
|
-
const distribution = stringFlag(parsed, "distribution") ?? stringFlag(parsed, "distro");
|
|
5904
|
-
const noLaunch = optionalBooleanFlag(parsed, "no-launch");
|
|
5905
|
-
if (typeof noLaunch === "string")
|
|
5906
|
-
return noLaunch;
|
|
5907
|
-
const webDownload = optionalBooleanFlag(parsed, "web-download");
|
|
5908
|
-
if (typeof webDownload === "string")
|
|
5909
|
-
return webDownload;
|
|
5910
|
-
const timeoutMs = optionalIntegerFlag(parsed, "timeout-ms", 1_000, 3_600_000);
|
|
5911
|
-
if (timeoutMs.error)
|
|
5912
|
-
return timeoutMs.error;
|
|
5913
|
-
return {
|
|
5914
|
-
distribution,
|
|
5915
|
-
noLaunch,
|
|
5916
|
-
webDownload,
|
|
5917
|
-
timeoutMs: timeoutMs.value
|
|
5918
|
-
};
|
|
5919
|
-
}
|
|
5920
4074
|
function localToolchainCurrentRequest(parsed) {
|
|
5921
4075
|
const unknownFlag = firstUnknownFlag(parsed, ["json", "install-root"]);
|
|
5922
4076
|
if (unknownFlag) {
|
|
@@ -5932,7 +4086,7 @@ function localToolchainCurrentRequest(parsed) {
|
|
|
5932
4086
|
return { installRoot: installRoot.value };
|
|
5933
4087
|
}
|
|
5934
4088
|
function localToolchainInstallRequest(parsed) {
|
|
5935
|
-
const unknownFlag = firstUnknownFlag(parsed, ["json", "board", "board-id", "channel", "metadata-root", "source-url", "source-release-root", "install-root", "
|
|
4089
|
+
const unknownFlag = firstUnknownFlag(parsed, ["json", "board", "board-id", "channel", "metadata-root", "source-url", "source-release-root", "install-root", "force"]);
|
|
5936
4090
|
if (unknownFlag) {
|
|
5937
4091
|
return `Unknown flag --${unknownFlag}. ${LOCAL_TOOLCHAIN_INSTALL_USAGE}`;
|
|
5938
4092
|
}
|
|
@@ -5961,9 +4115,6 @@ function localToolchainInstallRequest(parsed) {
|
|
|
5961
4115
|
const installRoot = optionalTrimmedStringFlag(parsed, "install-root");
|
|
5962
4116
|
if (installRoot.error)
|
|
5963
4117
|
return installRoot.error;
|
|
5964
|
-
const mode = optionalTrimmedStringFlag(parsed, "mode");
|
|
5965
|
-
if (mode.error)
|
|
5966
|
-
return mode.error;
|
|
5967
4118
|
return {
|
|
5968
4119
|
boardId: board.value,
|
|
5969
4120
|
channel: channel.value,
|
|
@@ -5971,32 +4122,11 @@ function localToolchainInstallRequest(parsed) {
|
|
|
5971
4122
|
sourceUrl: sourceUrl.value,
|
|
5972
4123
|
sourceReleaseRoot: sourceReleaseRoot.value,
|
|
5973
4124
|
installRoot: installRoot.value,
|
|
5974
|
-
mode: mode.value,
|
|
5975
4125
|
force: booleanFlag(parsed, "force")
|
|
5976
4126
|
};
|
|
5977
4127
|
}
|
|
5978
|
-
function localToolchainUninstallRequest(parsed) {
|
|
5979
|
-
const unknownFlag = firstUnknownFlag(parsed, ["json", "board", "board-id", "install-root", "yes", "force"]);
|
|
5980
|
-
if (unknownFlag) {
|
|
5981
|
-
return `Unknown flag --${unknownFlag}. ${LOCAL_TOOLCHAIN_UNINSTALL_USAGE}`;
|
|
5982
|
-
}
|
|
5983
|
-
const extra = parsed.command.slice(3);
|
|
5984
|
-
if (extra.length > 0) {
|
|
5985
|
-
return `Unexpected argument: ${extra[0]}. ${LOCAL_TOOLCHAIN_UNINSTALL_USAGE}`;
|
|
5986
|
-
}
|
|
5987
|
-
const board = optionalTrimmedStringAliasFlag(parsed, ["board", "board-id"], "board or board-id");
|
|
5988
|
-
if (board.error)
|
|
5989
|
-
return board.error;
|
|
5990
|
-
if (!board.value) {
|
|
5991
|
-
return LOCAL_TOOLCHAIN_UNINSTALL_USAGE;
|
|
5992
|
-
}
|
|
5993
|
-
const installRoot = optionalTrimmedStringFlag(parsed, "install-root");
|
|
5994
|
-
if (installRoot.error)
|
|
5995
|
-
return installRoot.error;
|
|
5996
|
-
return { boardId: board.value, installRoot: installRoot.value };
|
|
5997
|
-
}
|
|
5998
4128
|
function localToolchainValidateRequest(parsed) {
|
|
5999
|
-
const unknownFlag = firstUnknownFlag(parsed, ["json", "
|
|
4129
|
+
const unknownFlag = firstUnknownFlag(parsed, ["json", "release-root"]);
|
|
6000
4130
|
if (unknownFlag) {
|
|
6001
4131
|
return `Unknown flag --${unknownFlag}. ${LOCAL_TOOLCHAIN_VALIDATE_USAGE}`;
|
|
6002
4132
|
}
|
|
@@ -6008,15 +4138,7 @@ function localToolchainValidateRequest(parsed) {
|
|
|
6008
4138
|
if (releaseRoot.error) {
|
|
6009
4139
|
return releaseRoot.error;
|
|
6010
4140
|
}
|
|
6011
|
-
|
|
6012
|
-
if (board.error) {
|
|
6013
|
-
return board.error;
|
|
6014
|
-
}
|
|
6015
|
-
const mode = optionalTrimmedStringFlag(parsed, "mode");
|
|
6016
|
-
if (mode.error) {
|
|
6017
|
-
return mode.error;
|
|
6018
|
-
}
|
|
6019
|
-
return { releaseRoot: releaseRoot.value, mode: mode.value, boardId: board.value };
|
|
4141
|
+
return { releaseRoot: releaseRoot.value };
|
|
6020
4142
|
}
|
|
6021
4143
|
function localCompileTaishanPiRequest(parsed, auth) {
|
|
6022
4144
|
const unknownFlag = firstUnknownFlag(parsed, ["json", "source", "output", "release-root", "account", "account-id"]);
|
|
@@ -6101,9 +4223,7 @@ function localToolchainAuthContext(auth, accountId) {
|
|
|
6101
4223
|
authenticated: auth.authenticated,
|
|
6102
4224
|
profile: auth.profile,
|
|
6103
4225
|
source: auth.source,
|
|
6104
|
-
account_id: accountId
|
|
6105
|
-
api_key_id: auth.api_key_id,
|
|
6106
|
-
device_id: auth.device_id
|
|
4226
|
+
account_id: accountId
|
|
6107
4227
|
};
|
|
6108
4228
|
}
|
|
6109
4229
|
function usageEventsRequest(parsed) {
|
|
@@ -6513,35 +4633,6 @@ function renderPluginList(result) {
|
|
|
6513
4633
|
`install="${plugin.install_command}"`
|
|
6514
4634
|
].filter(Boolean).join(" ")).join("\n");
|
|
6515
4635
|
}
|
|
6516
|
-
function renderPluginUpdateCheck(result) {
|
|
6517
|
-
const lines = [
|
|
6518
|
-
`release_url=${result.release_url}`,
|
|
6519
|
-
result.latest_version ? `latest_version=${result.latest_version}` : ""
|
|
6520
|
-
].filter(Boolean);
|
|
6521
|
-
if (result.release_notes.length > 0) {
|
|
6522
|
-
lines.push("release_notes:");
|
|
6523
|
-
for (const note of result.release_notes) {
|
|
6524
|
-
lines.push(` - ${note}`);
|
|
6525
|
-
}
|
|
6526
|
-
}
|
|
6527
|
-
for (const plugin of result.plugins) {
|
|
6528
|
-
lines.push("");
|
|
6529
|
-
lines.push(`${plugin.display_name} (${plugin.id})`);
|
|
6530
|
-
lines.push(` installed=${plugin.installed}`);
|
|
6531
|
-
lines.push(` installed_version=${plugin.installed_version ?? "none"}`);
|
|
6532
|
-
lines.push(` latest_version=${plugin.latest_version ?? "unknown"}`);
|
|
6533
|
-
lines.push(` update_available=${plugin.update_available}`);
|
|
6534
|
-
lines.push(` target=${plugin.target_path}`);
|
|
6535
|
-
lines.push(` update_command=${plugin.update_command}`);
|
|
6536
|
-
if (plugin.release_file) {
|
|
6537
|
-
lines.push(` release_file=${plugin.release_file}`);
|
|
6538
|
-
}
|
|
6539
|
-
for (const note of plugin.notes) {
|
|
6540
|
-
lines.push(` note=${note}`);
|
|
6541
|
-
}
|
|
6542
|
-
}
|
|
6543
|
-
return lines.join("\n");
|
|
6544
|
-
}
|
|
6545
4636
|
function renderPluginInstall(result) {
|
|
6546
4637
|
const lines = ["Installed plugins:"];
|
|
6547
4638
|
for (const item of result.installed) {
|
|
@@ -6563,15 +4654,6 @@ function renderPluginInstall(result) {
|
|
|
6563
4654
|
if (item.mcp_warning) {
|
|
6564
4655
|
lines.push(` warning=${item.mcp_warning}`);
|
|
6565
4656
|
}
|
|
6566
|
-
if (item.marketplace_registered !== undefined) {
|
|
6567
|
-
lines.push(` codex_marketplace_registered=${item.marketplace_registered}`);
|
|
6568
|
-
}
|
|
6569
|
-
if (item.marketplace_path) {
|
|
6570
|
-
lines.push(` codex_marketplace=${item.marketplace_path}`);
|
|
6571
|
-
}
|
|
6572
|
-
if (item.marketplace_warning) {
|
|
6573
|
-
lines.push(` warning=${item.marketplace_warning}`);
|
|
6574
|
-
}
|
|
6575
4657
|
}
|
|
6576
4658
|
return lines.join("\n");
|
|
6577
4659
|
}
|
|
@@ -6603,53 +4685,9 @@ function renderAgentRunResult(result) {
|
|
|
6603
4685
|
return lines.join("\n");
|
|
6604
4686
|
}
|
|
6605
4687
|
function renderAuthStatus(status) {
|
|
6606
|
-
|
|
6607
|
-
|
|
6608
|
-
|
|
6609
|
-
return [
|
|
6610
|
-
`Authenticated profile=${status.profile}${status.source ? ` source=${status.source}` : ""}`,
|
|
6611
|
-
status.account_id ? `account=${status.account_id}` : "",
|
|
6612
|
-
status.api_key_id ? `api_key=${status.api_key_id}` : "",
|
|
6613
|
-
status.device_id ? `device=${status.device_id}` : "device=not_registered",
|
|
6614
|
-
status.device_label ? `device_label=${status.device_label}` : "",
|
|
6615
|
-
status.device_integrity ? `device_integrity=${status.device_integrity}` : "",
|
|
6616
|
-
status.device_private_key_configured === false ? "device_private_key=missing" : ""
|
|
6617
|
-
].filter(Boolean).join("\n");
|
|
6618
|
-
}
|
|
6619
|
-
function renderAuthDeviceStatus(status) {
|
|
6620
|
-
const lines = [renderAuthStatus(status.local)];
|
|
6621
|
-
if (status.remote) {
|
|
6622
|
-
const activeCount = status.remote.devices.filter((device) => device.status === "active").length;
|
|
6623
|
-
lines.push(`remote_devices=${activeCount}/${status.remote.device_limit}`);
|
|
6624
|
-
const localDevice = status.local.device_id
|
|
6625
|
-
? status.remote.devices.find((device) => device.device_id === status.local.device_id)
|
|
6626
|
-
: undefined;
|
|
6627
|
-
if (localDevice) {
|
|
6628
|
-
lines.push(`remote_current=${renderAuthDevice(localDevice)}`);
|
|
6629
|
-
}
|
|
6630
|
-
}
|
|
6631
|
-
return lines.join("\n");
|
|
6632
|
-
}
|
|
6633
|
-
function renderAuthDeviceList(result) {
|
|
6634
|
-
if (result.devices.length === 0) {
|
|
6635
|
-
return `No registered devices. device_limit=${result.device_limit}`;
|
|
6636
|
-
}
|
|
6637
|
-
return [
|
|
6638
|
-
`device_limit=${result.device_limit}`,
|
|
6639
|
-
...result.devices.map(renderAuthDevice)
|
|
6640
|
-
].join("\n");
|
|
6641
|
-
}
|
|
6642
|
-
function renderAuthDevice(device) {
|
|
6643
|
-
return [
|
|
6644
|
-
`${device.device_id} account=${device.account_id}`,
|
|
6645
|
-
device.api_key_id ? `api_key=${device.api_key_id}` : "",
|
|
6646
|
-
`status=${device.status}`,
|
|
6647
|
-
device.label ? `label=${device.label}` : "",
|
|
6648
|
-
device.platform ? `platform=${device.platform}` : "",
|
|
6649
|
-
device.arch ? `arch=${device.arch}` : "",
|
|
6650
|
-
`last_seen_at=${device.last_seen_at}`,
|
|
6651
|
-
device.revoked_at ? `revoked_at=${device.revoked_at}` : ""
|
|
6652
|
-
].filter(Boolean).join(" ");
|
|
4688
|
+
return status.authenticated
|
|
4689
|
+
? `Authenticated profile=${status.profile}${status.source ? ` source=${status.source}` : ""}`
|
|
4690
|
+
: `Not authenticated profile=${status.profile}`;
|
|
6653
4691
|
}
|
|
6654
4692
|
function renderAccount(account) {
|
|
6655
4693
|
return [
|
|
@@ -6699,20 +4737,6 @@ function renderUsageRecord(record) {
|
|
|
6699
4737
|
`created_at=${record.created_at}`
|
|
6700
4738
|
].filter(Boolean).join(" ");
|
|
6701
4739
|
}
|
|
6702
|
-
function renderMcpToolEvent(event) {
|
|
6703
|
-
return [
|
|
6704
|
-
`${event.event_id} tool=${event.tool_name}`,
|
|
6705
|
-
event.account_id ? `account=${event.account_id}` : "",
|
|
6706
|
-
event.api_key_id ? `api_key=${event.api_key_id}` : "",
|
|
6707
|
-
`client=${event.client}`,
|
|
6708
|
-
`mode=${event.mode}`,
|
|
6709
|
-
`server_model_used=${event.server_model_used}`,
|
|
6710
|
-
`success=${event.success}`,
|
|
6711
|
-
event.request_id ? `request=${event.request_id}` : "",
|
|
6712
|
-
event.duration_ms !== undefined ? `duration_ms=${event.duration_ms}` : "",
|
|
6713
|
-
`created_at=${event.created_at}`
|
|
6714
|
-
].filter(Boolean).join(" ");
|
|
6715
|
-
}
|
|
6716
4740
|
function renderUsageSummary(summary) {
|
|
6717
4741
|
const lines = [
|
|
6718
4742
|
summary.account_id ? `account=${summary.account_id}` : "",
|
|
@@ -7024,20 +5048,6 @@ function renderBoardKnowledge(data) {
|
|
|
7024
5048
|
`title=${file.title}`
|
|
7025
5049
|
].join(" ")).join("\n");
|
|
7026
5050
|
}
|
|
7027
|
-
function renderBoardKnowledgeSearch(data) {
|
|
7028
|
-
const result = data;
|
|
7029
|
-
const matches = Array.isArray(result.matches) ? result.matches : [];
|
|
7030
|
-
if (matches.length === 0) {
|
|
7031
|
-
return "No matching board knowledge snippets.";
|
|
7032
|
-
}
|
|
7033
|
-
return matches.map((match, index) => [
|
|
7034
|
-
`#${index + 1}`,
|
|
7035
|
-
`${match.source}:${match.path}`,
|
|
7036
|
-
`score=${match.score}`,
|
|
7037
|
-
`title=${match.title}`,
|
|
7038
|
-
`excerpt=${match.excerpt.replace(/\s+/g, " ").trim()}`
|
|
7039
|
-
].join(" ")).join("\n");
|
|
7040
|
-
}
|
|
7041
5051
|
function renderBoardKnowledgeFile(file) {
|
|
7042
5052
|
return [
|
|
7043
5053
|
`template=${file.template_id}`,
|
|
@@ -7250,194 +5260,6 @@ function renderBuildWorkspaceSourcePatch(result) {
|
|
|
7250
5260
|
}
|
|
7251
5261
|
return lines.join("\n");
|
|
7252
5262
|
}
|
|
7253
|
-
function renderLocalToolchainList(result) {
|
|
7254
|
-
const installedCount = result.environments.filter((environment) => !!environment.installed).length;
|
|
7255
|
-
const availableCount = result.environments.filter((environment) => environment.status === "available").length;
|
|
7256
|
-
const updateCount = result.environments.filter((environment) => environment.status === "update_available").length;
|
|
7257
|
-
const lines = [
|
|
7258
|
-
`Local development environments: ${result.environments.length}`,
|
|
7259
|
-
`installed=${installedCount} available=${availableCount} updates=${updateCount}`,
|
|
7260
|
-
`host=${result.host}`,
|
|
7261
|
-
`channel=${result.channel}`,
|
|
7262
|
-
result.metadata_source === "local_override" ? `metadata_override=${result.metadata_root}` : "metadata=production/built-in",
|
|
7263
|
-
`install_root=${result.install_root}`,
|
|
7264
|
-
`registry=${result.registry_path}`
|
|
7265
|
-
];
|
|
7266
|
-
for (const environment of result.environments) {
|
|
7267
|
-
lines.push("");
|
|
7268
|
-
lines.push(`${environment.display_name} (${environment.board_id})`);
|
|
7269
|
-
lines.push(` status=${localToolchainStatusLabel(environment.status)}`);
|
|
7270
|
-
lines.push(` latest=${environment.latest.version}`);
|
|
7271
|
-
if (environment.installed) {
|
|
7272
|
-
lines.push(` installed=${environment.installed.version ?? "unknown"} mode=${environment.installed.mode ?? "unknown"}`);
|
|
7273
|
-
if (environment.installed.release_root) {
|
|
7274
|
-
lines.push(` release_root=${environment.installed.release_root}`);
|
|
7275
|
-
}
|
|
7276
|
-
}
|
|
7277
|
-
lines.push(` install_modes=${environment.install_modes.join(",")}`);
|
|
7278
|
-
if (environment.execution) {
|
|
7279
|
-
lines.push(` execution=${environment.execution.kind} supported=${environment.execution.supported}`);
|
|
7280
|
-
if (environment.execution.required_host) {
|
|
7281
|
-
lines.push(` execution_required_host=${environment.execution.required_host}`);
|
|
7282
|
-
}
|
|
7283
|
-
if (environment.execution.actual_host) {
|
|
7284
|
-
lines.push(` execution_actual_host=${environment.execution.actual_host}`);
|
|
7285
|
-
}
|
|
7286
|
-
if (environment.execution.reason) {
|
|
7287
|
-
lines.push(` execution_reason=${environment.execution.reason}`);
|
|
7288
|
-
}
|
|
7289
|
-
}
|
|
7290
|
-
lines.push(` install_command=${environment.install_command}`);
|
|
7291
|
-
if (environment.status === "update_available") {
|
|
7292
|
-
lines.push(` update_command=${environment.update_command}`);
|
|
7293
|
-
}
|
|
7294
|
-
if (environment.components?.length) {
|
|
7295
|
-
lines.push(` component_catalog=${localToolchainComponentSummary(environment.components)}`);
|
|
7296
|
-
if (environment.latest.default_mode) {
|
|
7297
|
-
lines.push(` default_mode_download=${environment.latest.default_mode}: ${localToolchainComponentSummaryForMode(environment.components, environment.latest.default_mode)}`);
|
|
7298
|
-
}
|
|
7299
|
-
if (environment.installed?.mode && environment.installed.mode !== environment.latest.default_mode) {
|
|
7300
|
-
lines.push(` installed_mode_components=${environment.installed.mode}: ${localToolchainComponentSummaryForMode(environment.components, environment.installed.mode)}`);
|
|
7301
|
-
}
|
|
7302
|
-
lines.push(` mode_downloads=${localToolchainModeSummaries(environment.install_modes, environment.components).join("; ")}`);
|
|
7303
|
-
lines.push(` package_groups=${localToolchainComponentGroups(environment.components).join(", ")}`);
|
|
7304
|
-
lines.push(` detail_command=embedlabs local toolchain latest --board ${environment.board_id}${result.channel === "stable" ? "" : ` --channel ${result.channel}`}`);
|
|
7305
|
-
}
|
|
7306
|
-
if (environment.notes.length > 0) {
|
|
7307
|
-
for (const note of environment.notes) {
|
|
7308
|
-
lines.push(` note=${note}`);
|
|
7309
|
-
}
|
|
7310
|
-
}
|
|
7311
|
-
}
|
|
7312
|
-
return lines.join("\n");
|
|
7313
|
-
}
|
|
7314
|
-
function localToolchainStatusLabel(status) {
|
|
7315
|
-
if (status === "installed")
|
|
7316
|
-
return "installed/已安装";
|
|
7317
|
-
if (status === "available")
|
|
7318
|
-
return "available/可安装";
|
|
7319
|
-
if (status === "update_available")
|
|
7320
|
-
return "update_available/可更新";
|
|
7321
|
-
if (status === "unsupported_host")
|
|
7322
|
-
return "unsupported_host/当前系统暂不支持";
|
|
7323
|
-
return status;
|
|
7324
|
-
}
|
|
7325
|
-
function renderWindowsWslStatus(result) {
|
|
7326
|
-
const lines = [
|
|
7327
|
-
"Windows WSL2 status",
|
|
7328
|
-
`host=${result.host}`,
|
|
7329
|
-
`applicable=${result.applicable}`,
|
|
7330
|
-
`wsl_available=${result.wsl_available}`,
|
|
7331
|
-
`usable=${result.usable}`,
|
|
7332
|
-
`taishanpi_execution=${result.taishanpi_execution.status} supported=${result.taishanpi_execution.supported}`,
|
|
7333
|
-
`taishanpi_required_host=${result.taishanpi_execution.required_host}`,
|
|
7334
|
-
result.taishanpi_execution.actual_host ? `taishanpi_actual_host=${result.taishanpi_execution.actual_host}` : "",
|
|
7335
|
-
`taishanpi_reason=${result.taishanpi_execution.reason}`,
|
|
7336
|
-
`checked_at=${result.checked_at}`,
|
|
7337
|
-
`status_command=${result.commands.status}`,
|
|
7338
|
-
`list_command=${result.commands.list}`,
|
|
7339
|
-
`list_online_command=${result.commands.list_online}`,
|
|
7340
|
-
`install_command=${result.commands.install_ubuntu}`
|
|
7341
|
-
].filter(Boolean);
|
|
7342
|
-
if (result.distributions.length > 0) {
|
|
7343
|
-
lines.push("distributions:");
|
|
7344
|
-
for (const distro of result.distributions) {
|
|
7345
|
-
lines.push(` ${distro.default ? "*" : "-"} ${distro.name} state=${distro.state ?? "unknown"} version=${distro.version ?? "unknown"}`);
|
|
7346
|
-
}
|
|
7347
|
-
}
|
|
7348
|
-
if (result.online_distributions.length > 0) {
|
|
7349
|
-
lines.push("online_distributions:");
|
|
7350
|
-
for (const distro of result.online_distributions) {
|
|
7351
|
-
lines.push(` ${distro.default ? "*" : "-"} ${distro.name}${distro.friendly_name ? ` (${distro.friendly_name})` : ""}`);
|
|
7352
|
-
}
|
|
7353
|
-
}
|
|
7354
|
-
for (const note of result.notes) {
|
|
7355
|
-
lines.push(`note=${note}`);
|
|
7356
|
-
}
|
|
7357
|
-
return lines.join("\n");
|
|
7358
|
-
}
|
|
7359
|
-
function renderWindowsWslInstall(result) {
|
|
7360
|
-
const lines = [
|
|
7361
|
-
"Windows WSL2 install",
|
|
7362
|
-
`host=${result.host}`,
|
|
7363
|
-
`command=${result.command.join(" ")}`,
|
|
7364
|
-
`exit_code=${result.exit_code}`,
|
|
7365
|
-
`usable_after=${result.status_after.usable}`
|
|
7366
|
-
];
|
|
7367
|
-
if (result.stdout_tail.length > 0) {
|
|
7368
|
-
lines.push("stdout_tail:");
|
|
7369
|
-
for (const line of result.stdout_tail) {
|
|
7370
|
-
lines.push(` ${line}`);
|
|
7371
|
-
}
|
|
7372
|
-
}
|
|
7373
|
-
if (result.stderr_tail.length > 0) {
|
|
7374
|
-
lines.push("stderr_tail:");
|
|
7375
|
-
for (const line of result.stderr_tail) {
|
|
7376
|
-
lines.push(` ${line}`);
|
|
7377
|
-
}
|
|
7378
|
-
}
|
|
7379
|
-
for (const note of result.notes) {
|
|
7380
|
-
lines.push(`note=${note}`);
|
|
7381
|
-
}
|
|
7382
|
-
return lines.join("\n");
|
|
7383
|
-
}
|
|
7384
|
-
function localToolchainComponentSummary(components) {
|
|
7385
|
-
const totalBytes = components.reduce((total, component) => total + component.size_bytes, 0);
|
|
7386
|
-
return `${components.length} components, ${formatByteSize(totalBytes)}`;
|
|
7387
|
-
}
|
|
7388
|
-
function localToolchainComponentSummaryForMode(components, mode) {
|
|
7389
|
-
return localToolchainComponentSummary(localToolchainComponentsForMode(components, mode));
|
|
7390
|
-
}
|
|
7391
|
-
function localToolchainModeSummaries(modes, components) {
|
|
7392
|
-
return modes.map((mode) => `${mode}=${localToolchainComponentSummaryForMode(components, mode)}`);
|
|
7393
|
-
}
|
|
7394
|
-
function localToolchainComponentsForMode(components, mode) {
|
|
7395
|
-
return components.filter((component) => {
|
|
7396
|
-
if (!component.install_modes?.length) {
|
|
7397
|
-
return true;
|
|
7398
|
-
}
|
|
7399
|
-
return component.install_modes.includes(mode);
|
|
7400
|
-
});
|
|
7401
|
-
}
|
|
7402
|
-
function localToolchainComponentGroups(components) {
|
|
7403
|
-
const groups = new Set();
|
|
7404
|
-
for (const component of components) {
|
|
7405
|
-
const text = `${component.id} ${component.role ?? ""}`.toLowerCase();
|
|
7406
|
-
if (text.includes("arm-none-eabi") || text.includes("bare-metal") || text.includes("compiler"))
|
|
7407
|
-
groups.add("compiler/ARM 裸机编译器");
|
|
7408
|
-
if (text.includes("pico-sdk") || text.includes("sdk-core"))
|
|
7409
|
-
groups.add("pico-sdk/C/C++ SDK");
|
|
7410
|
-
if (text.includes("sysroot") || text.includes("cross"))
|
|
7411
|
-
groups.add("sysroot/交叉运行库");
|
|
7412
|
-
if (text.includes("qt"))
|
|
7413
|
-
groups.add("qt/Qt 应用支持");
|
|
7414
|
-
if (text.includes("rockchip") || text.includes("boot") || text.includes("resource"))
|
|
7415
|
-
groups.add("boot-flash/启动与烧写工具");
|
|
7416
|
-
if (text.includes("image") || text.includes("rootfs"))
|
|
7417
|
-
groups.add("images/镜像资源");
|
|
7418
|
-
if (text.includes("initial-firmware"))
|
|
7419
|
-
groups.add("initial-firmware/初始化镜像");
|
|
7420
|
-
if (text.includes("rp2350-monitor"))
|
|
7421
|
-
groups.add("rp2350-monitor/可选硬件监控镜像");
|
|
7422
|
-
if (text.includes("meta"))
|
|
7423
|
-
groups.add("metadata/知识与脚本元数据");
|
|
7424
|
-
}
|
|
7425
|
-
return groups.size > 0 ? [...groups] : ["runtime/运行时工具"];
|
|
7426
|
-
}
|
|
7427
|
-
function formatByteSize(bytes) {
|
|
7428
|
-
if (!Number.isFinite(bytes) || bytes < 0) {
|
|
7429
|
-
return "unknown size";
|
|
7430
|
-
}
|
|
7431
|
-
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
7432
|
-
let value = bytes;
|
|
7433
|
-
let unit = 0;
|
|
7434
|
-
while (value >= 1024 && unit < units.length - 1) {
|
|
7435
|
-
value /= 1024;
|
|
7436
|
-
unit += 1;
|
|
7437
|
-
}
|
|
7438
|
-
const fixed = unit === 0 || value >= 10 ? value.toFixed(0) : value.toFixed(1);
|
|
7439
|
-
return `${fixed} ${units[unit]}`;
|
|
7440
|
-
}
|
|
7441
5263
|
function renderLocalToolchainLatest(result) {
|
|
7442
5264
|
const lines = [
|
|
7443
5265
|
`board=${result.board_id}`,
|
|
@@ -7445,26 +5267,11 @@ function renderLocalToolchainLatest(result) {
|
|
|
7445
5267
|
`version=${result.version}`,
|
|
7446
5268
|
`host=${result.host}`,
|
|
7447
5269
|
result.metadata_root ? `metadata_root=${result.metadata_root}` : "metadata=built-in",
|
|
7448
|
-
result.download
|
|
5270
|
+
result.download ? `download=${result.download.mirror_kind}:${result.download.source_url}` : "",
|
|
7449
5271
|
result.download?.archive ? `archive_sha256=${result.download.archive.sha256}` : "",
|
|
7450
5272
|
result.download?.archive ? `archive_size_bytes=${result.download.archive.size_bytes}` : "",
|
|
7451
|
-
result.download?.components?.length ? `components=${result.download.components.length}` : "",
|
|
7452
|
-
result.download?.default_mode ? `default_mode=${result.download.default_mode}` : "",
|
|
7453
5273
|
result.download_error ? `download_error=${result.download_error}` : ""
|
|
7454
5274
|
].filter(Boolean);
|
|
7455
|
-
if (result.download?.components?.length) {
|
|
7456
|
-
const modes = [...new Set(result.download.components.flatMap((component) => component.install_modes ?? ["all"]))].filter((mode) => mode !== "all");
|
|
7457
|
-
if (result.download.default_mode) {
|
|
7458
|
-
lines.push(`default_mode_download=${result.download.default_mode}: ${localToolchainDownloadComponentSummaryForMode(result.download.components, result.download.default_mode)}`);
|
|
7459
|
-
}
|
|
7460
|
-
if (modes.length > 0) {
|
|
7461
|
-
lines.push(`mode_downloads=${modes.map((mode) => `${mode}=${localToolchainDownloadComponentSummaryForMode(result.download?.components ?? [], mode)}`).join("; ")}`);
|
|
7462
|
-
}
|
|
7463
|
-
lines.push("download_components:");
|
|
7464
|
-
for (const component of result.download.components) {
|
|
7465
|
-
lines.push(` ${component.id}@${component.version} modes=${component.install_modes?.join(",") || "all"} size=${formatByteSize(component.archive.size_bytes)} file=${component.archive.file}`);
|
|
7466
|
-
}
|
|
7467
|
-
}
|
|
7468
5275
|
if (result.packages.length > 0) {
|
|
7469
5276
|
lines.push("packages:");
|
|
7470
5277
|
for (const pkg of result.packages) {
|
|
@@ -7473,11 +5280,6 @@ function renderLocalToolchainLatest(result) {
|
|
|
7473
5280
|
}
|
|
7474
5281
|
return lines.join("\n");
|
|
7475
5282
|
}
|
|
7476
|
-
function localToolchainDownloadComponentSummaryForMode(components, mode) {
|
|
7477
|
-
const selected = localToolchainComponentsForMode(components ?? [], mode);
|
|
7478
|
-
const totalBytes = selected.reduce((total, component) => total + component.archive.size_bytes, 0);
|
|
7479
|
-
return `${selected.length} components, ${formatByteSize(totalBytes)}`;
|
|
7480
|
-
}
|
|
7481
5283
|
function renderLocalToolchainCurrent(result) {
|
|
7482
5284
|
if (!result.installed) {
|
|
7483
5285
|
return [
|
|
@@ -7492,7 +5294,6 @@ function renderLocalToolchainCurrent(result) {
|
|
|
7492
5294
|
`board=${result.board_id}`,
|
|
7493
5295
|
result.version ? `version=${result.version}` : "",
|
|
7494
5296
|
result.channel ? `channel=${result.channel}` : "",
|
|
7495
|
-
result.mode ? `mode=${result.mode}` : "",
|
|
7496
5297
|
result.release_root ? `release_root=${result.release_root}` : "",
|
|
7497
5298
|
`install_root=${result.install_root}`,
|
|
7498
5299
|
`registry=${result.registry_path}`
|
|
@@ -7505,21 +5306,13 @@ function renderLocalToolchainInstall(result) {
|
|
|
7505
5306
|
`version=${result.version}`,
|
|
7506
5307
|
`channel=${result.channel}`,
|
|
7507
5308
|
`host=${result.host}`,
|
|
7508
|
-
`mode=${result.mode}`,
|
|
7509
5309
|
`install_root=${result.install_root}`,
|
|
7510
5310
|
`release_root=${result.release_root}`,
|
|
7511
5311
|
`registry=${result.registry_path}`,
|
|
7512
5312
|
`source=${result.source.kind}:${result.source.value}`,
|
|
7513
5313
|
result.source.downloaded_path ? `downloaded=${result.source.downloaded_path}` : "",
|
|
7514
|
-
result.source.components?.length ? `components=${result.source.components.length}` : "",
|
|
7515
5314
|
`validation=${result.validation.ok ? "ok" : "failed"}`
|
|
7516
5315
|
].filter(Boolean);
|
|
7517
|
-
if (result.source.components?.length) {
|
|
7518
|
-
lines.push("installed_components:");
|
|
7519
|
-
for (const component of result.source.components) {
|
|
7520
|
-
lines.push(` ${component.id}@${component.version} ${component.mirror_kind || ""} bytes=${component.size_bytes}`);
|
|
7521
|
-
}
|
|
7522
|
-
}
|
|
7523
5316
|
if (result.installed_paths.length > 0) {
|
|
7524
5317
|
lines.push("installed_paths:");
|
|
7525
5318
|
for (const installedPath of result.installed_paths) {
|
|
@@ -7534,50 +5327,16 @@ function renderLocalToolchainInstall(result) {
|
|
|
7534
5327
|
}
|
|
7535
5328
|
return lines.join("\n");
|
|
7536
5329
|
}
|
|
7537
|
-
function renderLocalToolchainUninstall(result) {
|
|
7538
|
-
const lines = [
|
|
7539
|
-
result.removed ? "Local toolchain uninstalled." : "Local toolchain was not installed.",
|
|
7540
|
-
`board=${result.board_id}`,
|
|
7541
|
-
`install_root=${result.install_root}`,
|
|
7542
|
-
`registry=${result.registry_path}`,
|
|
7543
|
-
`removed_registry_entry=${result.removed_registry_entry}`,
|
|
7544
|
-
`observed_at=${result.observed_at}`
|
|
7545
|
-
];
|
|
7546
|
-
if (result.removed_paths.length > 0) {
|
|
7547
|
-
lines.push("removed_paths:");
|
|
7548
|
-
for (const removedPath of result.removed_paths) {
|
|
7549
|
-
lines.push(` ${removedPath}`);
|
|
7550
|
-
}
|
|
7551
|
-
}
|
|
7552
|
-
if (result.remaining_installed_boards.length > 0) {
|
|
7553
|
-
lines.push(`remaining_installed_boards=${result.remaining_installed_boards.join(",")}`);
|
|
7554
|
-
}
|
|
7555
|
-
return lines.join("\n");
|
|
7556
|
-
}
|
|
7557
5330
|
function renderLocalToolchainValidation(result) {
|
|
7558
5331
|
const lines = [
|
|
7559
5332
|
result.ok ? "Local toolchain ready." : "Local toolchain not ready.",
|
|
7560
5333
|
`board=${result.board_id}`,
|
|
7561
|
-
`mode=${result.mode}`,
|
|
7562
5334
|
`host=${result.host.platform}/${result.host.arch}`,
|
|
7563
|
-
`release_root=${result.release_root}
|
|
7564
|
-
`summary=${result.summary_for_user}`
|
|
5335
|
+
`release_root=${result.release_root}`
|
|
7565
5336
|
];
|
|
7566
|
-
if (!result.ok && result.missing_groups.length > 0) {
|
|
7567
|
-
lines.push(`missing_groups=${result.missing_groups.join(", ")}`);
|
|
7568
|
-
}
|
|
7569
|
-
if (result.repair_command) {
|
|
7570
|
-
lines.push(`repair_command=${result.repair_command}`);
|
|
7571
|
-
}
|
|
7572
5337
|
for (const check of result.checked_paths) {
|
|
7573
5338
|
lines.push(`${check.exists ? "ok" : "missing"} ${check.label}: ${check.path}`);
|
|
7574
5339
|
}
|
|
7575
|
-
if (result.path_leaks.length > 0) {
|
|
7576
|
-
lines.push("path_leaks:");
|
|
7577
|
-
for (const leak of result.path_leaks) {
|
|
7578
|
-
lines.push(` ${leak.label}: ${leak.path} contains ${leak.forbidden}`);
|
|
7579
|
-
}
|
|
7580
|
-
}
|
|
7581
5340
|
if (result.notes.length > 0) {
|
|
7582
5341
|
lines.push("notes:");
|
|
7583
5342
|
for (const note of result.notes) {
|
|
@@ -8026,22 +5785,6 @@ function stringFlag(parsed, name) {
|
|
|
8026
5785
|
function booleanFlag(parsed, name) {
|
|
8027
5786
|
return parsed.flags[name] === true;
|
|
8028
5787
|
}
|
|
8029
|
-
function optionalBooleanFlag(parsed, name) {
|
|
8030
|
-
const values = flagValues(parsed, name);
|
|
8031
|
-
if (values.length === 0)
|
|
8032
|
-
return undefined;
|
|
8033
|
-
const value = values[values.length - 1];
|
|
8034
|
-
if (value === true)
|
|
8035
|
-
return true;
|
|
8036
|
-
if (typeof value !== "string")
|
|
8037
|
-
return `--${name} must be true or false.`;
|
|
8038
|
-
const normalized = value.trim().toLowerCase();
|
|
8039
|
-
if (["1", "true", "yes", "y", "on"].includes(normalized))
|
|
8040
|
-
return true;
|
|
8041
|
-
if (["0", "false", "no", "n", "off"].includes(normalized))
|
|
8042
|
-
return false;
|
|
8043
|
-
return `--${name} must be true or false.`;
|
|
8044
|
-
}
|
|
8045
5788
|
function switchFlag(parsed, name) {
|
|
8046
5789
|
const values = flagValues(parsed, name);
|
|
8047
5790
|
for (const value of values) {
|
|
@@ -8205,24 +5948,6 @@ function optionalPositiveIntegerFlag(parsed, name) {
|
|
|
8205
5948
|
}
|
|
8206
5949
|
return { value };
|
|
8207
5950
|
}
|
|
8208
|
-
function optionalIntegerFlag(parsed, name, min, max) {
|
|
8209
|
-
const values = flagValues(parsed, name);
|
|
8210
|
-
if (values.length === 0) {
|
|
8211
|
-
return {};
|
|
8212
|
-
}
|
|
8213
|
-
if (values.some((value) => typeof value !== "string")) {
|
|
8214
|
-
return { error: `--${name} requires a value.` };
|
|
8215
|
-
}
|
|
8216
|
-
const raw = values[values.length - 1].trim();
|
|
8217
|
-
if (!/^-?\d+$/.test(raw)) {
|
|
8218
|
-
return { error: `--${name} must be an integer.` };
|
|
8219
|
-
}
|
|
8220
|
-
const value = Number(raw);
|
|
8221
|
-
if (!Number.isSafeInteger(value) || value < min || value > max) {
|
|
8222
|
-
return { error: `--${name} must be an integer from ${min} through ${max}.` };
|
|
8223
|
-
}
|
|
8224
|
-
return { value };
|
|
8225
|
-
}
|
|
8226
5951
|
function jsonObjectListFlag(parsed, name) {
|
|
8227
5952
|
const objects = [];
|
|
8228
5953
|
for (const raw of flagValues(parsed, name)) {
|
|
@@ -8340,17 +6065,12 @@ async function waitForever() {
|
|
|
8340
6065
|
});
|
|
8341
6066
|
return 0;
|
|
8342
6067
|
}
|
|
8343
|
-
function quotaAndBillingDisabled() {
|
|
8344
|
-
return fail("quota_not_supported", "当前 Embed Labs 服务器只提供 MCP 服务,不提供额度、充值、余额、账本、存储配额或用量计费功能。", {
|
|
8345
|
-
remediation: "请使用 auth、plugin、board、local toolchain、mcp start、device 和 Local Bridge 相关命令。"
|
|
8346
|
-
});
|
|
8347
|
-
}
|
|
8348
6068
|
function printHelp() {
|
|
8349
6069
|
printCliHelp(`embed CLI
|
|
8350
6070
|
|
|
8351
6071
|
Usage:
|
|
8352
6072
|
embed <command> [options]
|
|
8353
|
-
embed query "
|
|
6073
|
+
embed query "查一下我的额度"
|
|
8354
6074
|
embed help getting-started
|
|
8355
6075
|
embed help commands
|
|
8356
6076
|
|
|
@@ -8360,57 +6080,68 @@ Main workflow:
|
|
|
8360
6080
|
2. Sign in with a token:
|
|
8361
6081
|
embed auth login --token <token>
|
|
8362
6082
|
# or set EMBED_API_TOKEN / create an account API key for automation.
|
|
8363
|
-
3.
|
|
6083
|
+
3. Inspect server model routing:
|
|
8364
6084
|
embed plugin install codex
|
|
8365
6085
|
embed plugin install opencode
|
|
8366
|
-
embed
|
|
6086
|
+
embed service modes
|
|
6087
|
+
embed model list
|
|
6088
|
+
embed model default
|
|
8367
6089
|
4. Run a natural-language local tool loop:
|
|
8368
6090
|
embed agent run --prompt "验证开发板状态"
|
|
8369
6091
|
5. Validate or use the local TaishanPi toolchain:
|
|
8370
|
-
embed local toolchain list
|
|
8371
|
-
embed local toolchain installed
|
|
8372
6092
|
embed local toolchain latest
|
|
8373
6093
|
embed local toolchain install
|
|
8374
|
-
embed local toolchain uninstall --board pico2w-rp2350-monitor
|
|
8375
6094
|
embed local toolchain validate
|
|
8376
6095
|
embed local compile taishanpi --source ./main.c --output ./.embed-labs/build/main
|
|
8377
6096
|
embed local build qt-smoke --build-dir ./.embed-labs/build/qt-smoke
|
|
8378
|
-
|
|
8379
|
-
6. Query board knowledge and method metadata:
|
|
6097
|
+
6. Pick a cloud build template:
|
|
8380
6098
|
embed board registry list
|
|
8381
6099
|
embed board methods taishanpi-1m-rk3566
|
|
8382
6100
|
embed board knowledge taishanpi-1m-rk3566
|
|
8383
|
-
embed board knowledge search taishanpi-1m-rk3566 --query "UART pinout"
|
|
8384
6101
|
embed build template list
|
|
8385
6102
|
embed build template show <template_id>
|
|
8386
|
-
7.
|
|
6103
|
+
7. Provision and populate a build workspace:
|
|
6104
|
+
embed build workspace provision --account <account_id> --project <project_id> --template <template_id>
|
|
6105
|
+
embed build resource lease create --workspace <workspace_id> --execution-mode cloud_worker
|
|
6106
|
+
embed build workspace source put <workspace_id> --file ./main.c:src/main.c
|
|
6107
|
+
embed build workspace source list <workspace_id>
|
|
6108
|
+
embed build workspace source get <workspace_id> --path src/main.c --output ./main.c
|
|
6109
|
+
embed build workspace source search <workspace_id> --query init --glob "**/*.c"
|
|
6110
|
+
embed build workspace source patch <workspace_id> --patch ./fix.patch
|
|
6111
|
+
embed build workspace release <workspace_id> --dry-run
|
|
6112
|
+
8. Generate application source on the server and follow artifacts:
|
|
6113
|
+
embed build application generate --workspace <workspace_id> --prompt "Create a minimal Linux app" --provider bai --model gpt-5.2
|
|
6114
|
+
embed build application compile --workspace <workspace_id> --source app/generated.c --execution-mode docker_worker
|
|
6115
|
+
embed build image generate --workspace <workspace_id> --prompt "Generate a minimal TaishanPi image"
|
|
8387
6116
|
embed build image boot-logo --logo ./logo.png --board taishanpi --variant 1M-RK3566 --output ./boot-logo-package.json
|
|
8388
6117
|
embed image boot-logo compose --package ./boot-logo-package.json --base-image ./boot.img --output ./boot-logo.img
|
|
8389
6118
|
embed cloud task artifacts <task_id>
|
|
8390
6119
|
embed artifact download <artifact_id> --output ./artifact.bin
|
|
6120
|
+
9. Check credits or create a recharge QR:
|
|
6121
|
+
embed billing balance --account <account_id>
|
|
6122
|
+
embed billing tokens --account <account_id>
|
|
6123
|
+
embed billing ledger --account <account_id>
|
|
6124
|
+
embed billing storage --account <account_id>
|
|
6125
|
+
embed billing storage settle --account <account_id> --dry-run
|
|
6126
|
+
embed billing recharge create --account <account_id> --amount-usd 10 --provider onchain --qr
|
|
6127
|
+
|
|
8391
6128
|
Local hardware:
|
|
8392
6129
|
embed bridge start
|
|
8393
|
-
embed mcp start
|
|
8394
6130
|
embed agent run --prompt "验证开发板状态"
|
|
8395
6131
|
embed agent run --prompt "部署泰山派应用" --host 198.19.77.2 --artifact ./artifact.bin --remote-path /userdata/embed-labs/apps/app --approve --run
|
|
8396
6132
|
embed agent run --prompt "部署生成的泰山派应用" --host 198.19.77.2 --artifact-task <task_id> --remote-path /userdata/embed-labs/apps/app --approve --run
|
|
8397
6133
|
embed tool list
|
|
8398
|
-
embed tool call device.probe --input-json '{"host":"198.19.77.2","ports":[22]}'
|
|
6134
|
+
embed tool call device.probe --input-json '{"host":"198.19.77.2","ports":[22,15301]}'
|
|
8399
6135
|
embed tool call wifi.scan --input-json '{"host":"198.19.77.2","user":"root"}'
|
|
8400
|
-
embed tool call rp2350.monitor.spi.transfer --input-json '{"hex":"a55a3cc3"}' --approve
|
|
8401
6136
|
embed tool call chip.temperature --input-json '{"host":"198.19.77.2","user":"root"}'
|
|
8402
6137
|
embed tool call qml.runtime.status --input-json '{"host":"198.19.77.2","user":"root","port":18130}'
|
|
8403
6138
|
embed device list
|
|
8404
|
-
embed local toolchain list
|
|
8405
|
-
embed local toolchain installed
|
|
8406
6139
|
embed local toolchain latest
|
|
8407
6140
|
embed local toolchain current
|
|
8408
6141
|
embed local toolchain install
|
|
8409
|
-
embed local toolchain uninstall --board pico2w-rp2350-monitor
|
|
8410
6142
|
embed local toolchain validate
|
|
8411
6143
|
embed local compile taishanpi --source ./main.c --output ./.embed-labs/build/main
|
|
8412
6144
|
embed local build qt-smoke --build-dir ./.embed-labs/build/qt-smoke
|
|
8413
|
-
embed local build qt-smoke --source ./taishanpi-app --target-name app --build-dir ./.embed-labs/build/app
|
|
8414
6145
|
embed image boot-logo compose --package ./boot-logo-package.json --base-image ./boot.img --output ./boot-logo.img
|
|
8415
6146
|
embed deploy taishanpi --host 198.19.77.2 --artifact ./artifact.bin --approve --run
|
|
8416
6147
|
embed flash plan --board <rp2350|taishanpi>
|
|
@@ -8421,14 +6152,16 @@ Help:
|
|
|
8421
6152
|
|
|
8422
6153
|
Environment:
|
|
8423
6154
|
EMBED_BRIDGE_URL=http://127.0.0.1:18083
|
|
8424
|
-
EMBED_CLOUD_API_URL=
|
|
6155
|
+
EMBED_CLOUD_API_URL=http://127.0.0.1:18100
|
|
8425
6156
|
EMBED_API_TOKEN=<token>
|
|
8426
6157
|
CODEX_HOME=~/.codex
|
|
8427
6158
|
|
|
8428
6159
|
Natural language:
|
|
8429
|
-
embed query "
|
|
8430
|
-
embed query "
|
|
8431
|
-
embed query "
|
|
6160
|
+
embed query "查一下我的额度"
|
|
6161
|
+
embed query "用 USDC 链上充值 100 美元"
|
|
6162
|
+
embed query "看一下 token 用量"
|
|
6163
|
+
embed query "看一下磁盘空间"
|
|
6164
|
+
embed query "结算磁盘空间扣费"
|
|
8432
6165
|
`);
|
|
8433
6166
|
}
|
|
8434
6167
|
function printHelpTopic(topic) {
|
|
@@ -8474,21 +6207,20 @@ Install local AI client plugins explicitly:
|
|
|
8474
6207
|
embed plugin list
|
|
8475
6208
|
embed plugin install codex
|
|
8476
6209
|
embed plugin install opencode
|
|
8477
|
-
embed plugin update check
|
|
8478
|
-
embed plugin update all
|
|
8479
6210
|
|
|
8480
|
-
|
|
6211
|
+
Cloud build path:
|
|
8481
6212
|
|
|
6213
|
+
embed service modes
|
|
6214
|
+
embed model list
|
|
6215
|
+
embed model default
|
|
8482
6216
|
embed agent run --prompt "验证开发板状态"
|
|
8483
6217
|
embed local toolchain validate
|
|
8484
6218
|
embed local compile taishanpi --source ./main.c --output ./.embed-labs/build/main
|
|
8485
6219
|
embed local build qt-smoke --build-dir ./.embed-labs/build/qt-smoke
|
|
8486
|
-
embed local build qt-smoke --source ./taishanpi-app --target-name app --build-dir ./.embed-labs/build/app
|
|
8487
6220
|
embed board registry list
|
|
8488
6221
|
embed board registry show taishanpi-1m-rk3566
|
|
8489
6222
|
embed board methods taishanpi-1m-rk3566
|
|
8490
6223
|
embed board knowledge taishanpi-1m-rk3566
|
|
8491
|
-
embed board knowledge search taishanpi-1m-rk3566 --query "UART pinout"
|
|
8492
6224
|
embed build template list
|
|
8493
6225
|
embed build template show <template_id>
|
|
8494
6226
|
embed build workspace provision --account <account_id> --project <project_id> --template <template_id>
|
|
@@ -8499,6 +6231,9 @@ Local MCP service path:
|
|
|
8499
6231
|
embed build workspace source search <workspace_id> --query init --glob "**/*.c"
|
|
8500
6232
|
embed build workspace source patch <workspace_id> --patch ./fix.patch
|
|
8501
6233
|
embed build workspace release <workspace_id> --dry-run
|
|
6234
|
+
embed build application generate --workspace <workspace_id> --prompt "Create a minimal Linux app" --provider bai --model gpt-5.2
|
|
6235
|
+
embed build application compile --workspace <workspace_id> --source app/generated.c --execution-mode docker_worker
|
|
6236
|
+
embed build image generate --workspace <workspace_id> --prompt "Generate a minimal TaishanPi image"
|
|
8502
6237
|
embed build image boot-logo --logo ./logo.png --board taishanpi --variant 1M-RK3566 --output ./boot-logo-package.json
|
|
8503
6238
|
embed image boot-logo compose --package ./boot-logo-package.json --base-image ./boot.img --output ./boot-logo.img
|
|
8504
6239
|
embed cloud task status <task_id>
|
|
@@ -8507,18 +6242,29 @@ Local MCP service path:
|
|
|
8507
6242
|
|
|
8508
6243
|
Natural language shortcuts:
|
|
8509
6244
|
|
|
8510
|
-
embed query "
|
|
8511
|
-
embed query "
|
|
8512
|
-
embed query "
|
|
6245
|
+
embed query "查一下我的额度"
|
|
6246
|
+
embed query "用 USDC 链上充值 100 美元"
|
|
6247
|
+
embed query "看一下 token 用量"
|
|
6248
|
+
embed query "看一下磁盘空间"
|
|
6249
|
+
|
|
6250
|
+
Billing path:
|
|
6251
|
+
|
|
6252
|
+
embed billing balance --account <account_id>
|
|
6253
|
+
embed billing tokens --account <account_id>
|
|
6254
|
+
embed billing ledger --account <account_id>
|
|
6255
|
+
embed billing storage --account <account_id>
|
|
6256
|
+
embed billing storage settle --account <account_id> --dry-run
|
|
6257
|
+
embed billing recharge create --account <account_id> --amount-usd 10 --provider onchain --qr
|
|
6258
|
+
embed billing recharge submit-tx <recharge_session_id> --tx-hash <hash>
|
|
6259
|
+
embed billing recharge list --account <account_id>
|
|
8513
6260
|
|
|
8514
6261
|
Local hardware path:
|
|
8515
6262
|
|
|
8516
6263
|
embed bridge start
|
|
8517
|
-
embed mcp start
|
|
8518
6264
|
embed run "验证开发板状态"
|
|
8519
6265
|
embed tool list
|
|
8520
6266
|
embed tool call debug.tools.scan
|
|
8521
|
-
embed tool call device.probe --input-json '{"host":"198.19.77.2","ports":[22]}'
|
|
6267
|
+
embed tool call device.probe --input-json '{"host":"198.19.77.2","ports":[22,15301]}'
|
|
8522
6268
|
embed device list
|
|
8523
6269
|
embed deploy taishanpi --host 198.19.77.2 --artifact ./artifact.bin --approve --run
|
|
8524
6270
|
embed flash plan --board <rp2350|taishanpi> --artifact ./artifact.bin
|
|
@@ -8534,14 +6280,11 @@ Usage:
|
|
|
8534
6280
|
embed query <natural language request> [--account <account_id>] [--qr] [--json]
|
|
8535
6281
|
embed bridge start [--host 127.0.0.1] [--port 18083]
|
|
8536
6282
|
embed bridge status [--json]
|
|
8537
|
-
embed mcp start [--bridge-path <path>]
|
|
8538
6283
|
embed auth login --token <token> [--profile default] [--json]
|
|
8539
6284
|
embed auth status [--json]
|
|
8540
6285
|
embed auth logout [--json]
|
|
8541
6286
|
embed plugin list [--release-dir <dir>] [--release-url <url>] [--json]
|
|
8542
6287
|
embed plugin install <codex|opencode|all> [--release-dir <dir>] [--release-url <url>] [--target <dir>] [--codex-target <dir>] [--opencode-target <dir>] [--force] [--json]
|
|
8543
|
-
embed plugin update check [--release-url <url>] [--target <dir>] [--codex-target <dir>] [--opencode-target <dir>] [--json]
|
|
8544
|
-
embed plugin update <codex|opencode|all> [--release-url <url>] [--target <dir>] [--codex-target <dir>] [--opencode-target <dir>] [--json]
|
|
8545
6288
|
embed service modes [--json]
|
|
8546
6289
|
embed model list [--json]
|
|
8547
6290
|
embed model default [--json]
|
|
@@ -8549,7 +6292,6 @@ Usage:
|
|
|
8549
6292
|
embed board registry show <template_id> [--json]
|
|
8550
6293
|
embed board methods <template_id> [--json]
|
|
8551
6294
|
embed board knowledge <template_id> [--json]
|
|
8552
|
-
embed board knowledge search <template_id> --query <text> [--source board_pack|build_template|registry] [--limit 5] [--json]
|
|
8553
6295
|
embed board knowledge file <template_id> --source board_pack|build_template|registry --path <relative_path> [--output <local_path>] [--json]
|
|
8554
6296
|
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]
|
|
8555
6297
|
embed run <natural language request> [--provider stub|openai|bai|cc|claude-code] [--approve] [--json]
|
|
@@ -8560,6 +6302,23 @@ Usage:
|
|
|
8560
6302
|
embed account keys create --account <account_id> [--name <name>] [--json]
|
|
8561
6303
|
embed account keys list --account <account_id> [--json]
|
|
8562
6304
|
embed account keys revoke <api_key_id> [--json]
|
|
6305
|
+
embed usage record [--api-key <key>|--api-key-id <api_key_id>] --model <model> --input-tokens <n> --output-tokens <n> [--provider <name>] [--operation <name>] [--task <task_id>] [--request-id <id>] [--json]
|
|
6306
|
+
embed usage summary --account <account_id>|--api-key-id <api_key_id> [--from <iso>] [--to <iso>] [--json]
|
|
6307
|
+
embed usage events --account <account_id> [--api-key-id <api_key_id>] [--from <iso>] [--to <iso>] [--limit 100] [--json]
|
|
6308
|
+
embed billing statement --account <account_id> [--from <iso>] [--to <iso>] [--json]
|
|
6309
|
+
embed billing balance --account <account_id> [--json]
|
|
6310
|
+
embed billing tokens --account <account_id> [--json]
|
|
6311
|
+
embed billing ledger --account <account_id> [--json]
|
|
6312
|
+
embed billing storage --account <account_id> [--json]
|
|
6313
|
+
embed billing storage settle --account <account_id> [--dry-run] [--json]
|
|
6314
|
+
embed billing recharge create --account <account_id> --amount-usd <amount> [--provider mock|stripe|onchain] [--chain <chain>] [--token <symbol>] [--success-url <url>] [--cancel-url <url>] [--qr] [--json]
|
|
6315
|
+
embed billing recharge list --account <account_id> [--json]
|
|
6316
|
+
embed billing recharge show <recharge_session_id> [--json]
|
|
6317
|
+
embed billing recharge submit-tx <recharge_session_id> --tx-hash <hash> [--json]
|
|
6318
|
+
embed billing recharge confirm <recharge_session_id> [--json]
|
|
6319
|
+
embed billing snapshot create --account <account_id> [--from <iso>] [--to <iso>] [--json]
|
|
6320
|
+
embed billing snapshot list --account <account_id> [--json]
|
|
6321
|
+
embed billing snapshot show <billing_snapshot_id> [--json]
|
|
8563
6322
|
embed service modes [--json]
|
|
8564
6323
|
embed build template list [--json]
|
|
8565
6324
|
embed build template show <template_id> [--json]
|
|
@@ -8581,16 +6340,13 @@ Usage:
|
|
|
8581
6340
|
embed build image generate --workspace <workspace_id> --prompt <request> [--account <account_id>] [--image-profile <profile_id>] [--provider stub|openai|bai|cc|claude-code] [--model <model>] [--execution-mode cloud_worker|dry_run] [--worker-pool <pool>] [--json]
|
|
8582
6341
|
embed build image boot-logo --logo <local_image> [--account <account_id>] [--project <project_id>] [--board taishanpi] [--variant 1M-RK3566] [--kernel-logo <local_image>] [--rotate -90] [--scale 100] [--output <package.json>] [--json]
|
|
8583
6342
|
embed image boot-logo compose --package <boot-logo-package.json> --base-image <boot.img|image.img> --output <image> [--manifest <manifest.json>] [--force] [--json]
|
|
8584
|
-
embed local toolchain
|
|
8585
|
-
embed local toolchain installed [--board taishanpi-1m-rk3566|pico2w-rp2350-monitor|coloreasypico2-rp2350-monitor] [--channel stable] [--metadata-root <path>] [--install-root <path>] [--json]
|
|
8586
|
-
embed local toolchain latest [--board taishanpi-1m-rk3566|pico2w-rp2350-monitor|coloreasypico2-rp2350-monitor] [--channel stable] [--metadata-root <path>] [--json]
|
|
6343
|
+
embed local toolchain latest [--board taishanpi-1m-rk3566] [--channel stable] [--metadata-root <path>] [--json]
|
|
8587
6344
|
embed local toolchain current [--install-root <path>] [--json]
|
|
8588
|
-
embed local toolchain install [--board taishanpi-1m-rk3566
|
|
6345
|
+
embed local toolchain install [--board taishanpi-1m-rk3566] [--channel stable] [--metadata-root <path>] [--source-url <tar.gz-url>|--source-release-root <path>] [--install-root <path>] [--force] [--json]
|
|
8589
6346
|
Defaults to the production download channel at download.embedboard.com.
|
|
8590
|
-
embed local toolchain
|
|
8591
|
-
embed local toolchain validate [--board taishanpi-1m-rk3566|pico2w-rp2350-monitor|coloreasypico2-rp2350-monitor] [--release-root <path>] [--mode minimal|runtime|compile|qt|firmware|full|images] [--json]
|
|
6347
|
+
embed local toolchain validate [--release-root <path>] [--json]
|
|
8592
6348
|
embed local compile taishanpi --source <main.c|main.cpp> --output <artifact> [--release-root <path>] [--account <account_id>] [--json]
|
|
8593
|
-
embed local build qt-smoke --build-dir <dir> [--source <qt-
|
|
6349
|
+
embed local build qt-smoke --build-dir <dir> [--source <qt-smoke-dir>] [--release-root <path>] [--account <account_id>] [--json]
|
|
8594
6350
|
embed debug tools [--json]
|
|
8595
6351
|
embed tool list [--json]
|
|
8596
6352
|
embed tool call wifi.scan --input-json '{"host":"198.19.77.2","user":"root"}' [--json]
|
|
@@ -8599,18 +6355,6 @@ Usage:
|
|
|
8599
6355
|
embed tool call chip.temperature --input-json '{"host":"198.19.77.2","user":"root"}' [--json]
|
|
8600
6356
|
embed tool call qml.runtime.status --input-json '{"host":"198.19.77.2","user":"root","port":18130}' [--json]
|
|
8601
6357
|
embed tool call qml.runtime.start --input-json '{"host":"198.19.77.2","user":"root","port":18130}' [--json]
|
|
8602
|
-
embed tool call rp2350.monitor.capabilities [--json]
|
|
8603
|
-
embed tool call rp2350.monitor.status [--json]
|
|
8604
|
-
embed tool call rp2350.monitor.gpio.read --input-json '{"pins":[16,17]}' --approve [--json]
|
|
8605
|
-
embed tool call rp2350.monitor.gpio.write --input-json '{"pin":16,"level":true}' --approve [--json]
|
|
8606
|
-
embed tool call rp2350.monitor.uart.write --input-json '{"baud":115200,"text":"hello","line_ending":"lf"}' --approve [--json]
|
|
8607
|
-
embed tool call rp2350.monitor.i2c.transfer --input-json '{"address":"0x50","write":"00","read_len":4}' --approve [--json]
|
|
8608
|
-
embed tool call rp2350.monitor.spi.transfer --input-json '{"hex":"a55a3cc3"}' --approve [--json]
|
|
8609
|
-
embed tool call rp2350.monitor.logic.capture --input-json '{"pin_base":16,"pin_count":4,"sample_rate":1000000,"samples":4096}' --approve [--json]
|
|
8610
|
-
embed tool call rp2350.monitor.logic.decode --input-json '{"input_path":".embed-labs/rp2350-monitor/captures/logic.jsonl","decoder":"summary"}' [--json]
|
|
8611
|
-
embed tool call rp2350.monitor.wifi.manage --input-json '{"action":"scan"}' --approve [--json]
|
|
8612
|
-
embed tool call rp2350.monitor.probe.debug --input-json '{"action":"status"}' --approve [--json]
|
|
8613
|
-
embed tool call rp2350.monitor.operation --input-json '{"action":"logic.stop","params":{}}' --approve [--json]
|
|
8614
6358
|
embed deploy taishanpi --host <ip> --artifact <local_file> --approve [--remote-path /userdata/embed-labs/apps/app] [--run] [--json]
|
|
8615
6359
|
embed board deploy taishanpi --host <ip> --artifact <local_file> --approve [--remote-path /userdata/embed-labs/apps/app] [--run] [--json]
|
|
8616
6360
|
embed device list [--json]
|
|
@@ -8644,7 +6388,7 @@ Usage:
|
|
|
8644
6388
|
|
|
8645
6389
|
Environment:
|
|
8646
6390
|
EMBED_BRIDGE_URL=http://127.0.0.1:18083
|
|
8647
|
-
EMBED_CLOUD_API_URL=
|
|
6391
|
+
EMBED_CLOUD_API_URL=http://127.0.0.1:18100
|
|
8648
6392
|
EMBED_API_TOKEN=<token>
|
|
8649
6393
|
CODEX_HOME=~/.codex
|
|
8650
6394
|
`);
|