@kvell007/embed-labs-cli 0.1.0-alpha.6 → 0.1.0-alpha.60

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,24 +1,28 @@
1
1
  #!/usr/bin/env node
2
- import { createHash } from "node:crypto";
2
+ import { createHash, createHmac, generateKeyPairSync, randomBytes, sign as signCrypto } from "node:crypto";
3
3
  import { constants } from "node:fs";
4
4
  import { spawn } from "node:child_process";
5
- import { access, cp, mkdir, mkdtemp, readFile, rm, stat, writeFile } from "node:fs/promises";
5
+ import { access, chmod, cp, mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
6
6
  import { createRequire } from "node:module";
7
- import { homedir, tmpdir } from "node:os";
7
+ import { arch, hostname, homedir, platform, tmpdir } from "node:os";
8
8
  import { basename, delimiter, dirname, join, resolve } from "node:path";
9
9
  import { fileURLToPath } from "node:url";
10
- import { startServer } from "@embed-labs/local-bridge";
11
10
  import { composeBootLogoPackage } from "./image-compose.js";
12
- import { buildTaishanPiQtSmoke, compileTaishanPiSingleFile, currentLocalToolchain, installLocalToolchain, latestLocalToolchain, validateLocalToolchain } from "./local-toolchain.js";
11
+ import { buildTaishanPiQtSmoke, compileTaishanPiSingleFile, currentLocalToolchain, installLocalToolchain, latestLocalToolchain, listLocalToolchainEnvironments, uninstallLocalToolchain, validateLocalToolchain, windowsWslInstall, windowsWslStatus } from "./local-toolchain.js";
13
12
  import { fail, ok } from "@embed-labs/protocol";
14
13
  const require = createRequire(import.meta.url);
15
14
  const CLI_MODULE_DIR = dirname(fileURLToPath(import.meta.url));
16
15
  const SOURCE_CHECKOUT_ROOT = resolve(CLI_MODULE_DIR, "..", "..", "..");
17
16
  const qrcodeTerminal = require("qrcode-terminal");
18
17
  const DEFAULT_BRIDGE_URL = process.env.EMBED_BRIDGE_URL ?? "http://127.0.0.1:18083";
19
- const DEFAULT_CLOUD_API_URL = process.env.EMBED_CLOUD_API_URL ?? "http://127.0.0.1:18100";
18
+ const DEFAULT_CLOUD_API_URL = process.env.EMBED_CLOUD_API_URL ?? "https://api.embedboard.com";
20
19
  const DEFAULT_AUTH_FILE = process.env.EMBED_AUTH_FILE ?? ".embed-labs/auth.json";
20
+ const DEFAULT_DEVICE_FILE = process.env.EMBED_DEVICE_FILE ?? join(dirname(DEFAULT_AUTH_FILE), "device.json");
21
+ 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";
21
24
  const DEFAULT_AGENT_ARTIFACT_DIR = process.env.EMBED_AGENT_ARTIFACT_DIR ?? ".embed-labs/artifacts";
25
+ const TOOL_INTEGRITY_RELOGIN_MESSAGE = "工具完整性校验失败,请在当前电脑重新执行 embedlabs auth login --token <key>";
22
26
  const DOCTOR_USAGE = "Usage: embed doctor [--json]";
23
27
  const DOCTOR_HTTP_TIMEOUT_MS = 8000;
24
28
  const MIN_NODE_MAJOR = 20;
@@ -27,7 +31,19 @@ const DEVICE_PROBE_USAGE = "Usage: embed device probe --host <host> --ports 22,1
27
31
  const QUERY_USAGE = "Usage: embed query <natural language request> [--account <account_id>] [--qr] [--json]";
28
32
  const DEFAULT_PLUGIN_RELEASE_URL = process.env.EMBED_PLUGIN_RELEASE_URL?.trim() || "https://api.embedboard.com/plugin-releases/agent-plugins/latest";
29
33
  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 LEGACY_CODEX_PLUGIN_NAMES = [
37
+ "dbt-agent",
38
+ "Dbt Agent",
39
+ "development-board-toolchain",
40
+ "development-board-toolchain-dev",
41
+ "deve"
42
+ ];
43
+ const LEGACY_CODEX_MARKETPLACE_NAMES = new Set(["embed-labs-plugins", "plugins", "Plugins", "deve"]);
30
44
  const PLUGIN_INSTALL_USAGE = "Usage: embed plugin install <codex|opencode|all> [--release-dir <dir>] [--release-url <url>] [--target <dir>] [--codex-target <dir>] [--opencode-target <dir>] [--force] [--json]";
45
+ const PLUGIN_UPDATE_CHECK_USAGE = "Usage: embed plugin update check [--release-url <url>] [--target <dir>] [--codex-target <dir>] [--opencode-target <dir>] [--json]";
46
+ const PLUGIN_UPDATE_USAGE = "Usage: embed plugin update <codex|opencode|all> [--release-url <url>] [--target <dir>] [--codex-target <dir>] [--opencode-target <dir>] [--json]";
31
47
  const CLOUD_TASK_ARTIFACTS_USAGE = "Usage: embed cloud task artifacts <task_id> [--json]";
32
48
  const CLOUD_TASK_EVIDENCE_USAGE = "Usage: embed cloud task evidence <task_id> [--json]";
33
49
  const ARTIFACT_STATUS_USAGE = "Usage: embed artifact status <artifact_id> [--json]";
@@ -81,24 +97,36 @@ const BUILD_IMAGE_BOOT_LOGO_USAGE = "Usage: embed build image boot-logo --logo <
81
97
  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]";
82
98
  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]";
83
99
  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]";
84
- const LOCAL_TOOLCHAIN_LATEST_USAGE = "Usage: embed local toolchain latest [--board taishanpi-1m-rk3566] [--channel stable] [--metadata-root <path>] [--json]";
100
+ const LOCAL_TOOLCHAIN_LIST_USAGE = "Usage: embed local toolchain list [--board taishanpi-1m-rk3566|pico2w-rp2350-monitor|coloreasypico2-rp2350-monitor] [--channel stable] [--metadata-root <path>] [--install-root <path>] [--json]";
101
+ 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]";
102
+ 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]";
85
103
  const LOCAL_TOOLCHAIN_CURRENT_USAGE = "Usage: embed local toolchain current [--install-root <path>] [--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]";
104
+ const LOCAL_TOOLCHAIN_INSTALL_USAGE = "Usage: embed local toolchain install [--board taishanpi-1m-rk3566|pico2w-rp2350-monitor|coloreasypico2-rp2350-monitor] [--channel stable] [--metadata-root <path>] [--source-url <tar.gz-url>|--source-release-root <path>] [--install-root <path>] [--mode minimal|runtime|compile|qt|firmware|full|images] [--force] [--json]\nDefault source: the production download channel at download.embedboard.com.";
105
+ const LOCAL_TOOLCHAIN_UNINSTALL_USAGE = "Usage: embed local toolchain uninstall --board taishanpi-1m-rk3566|pico2w-rp2350-monitor|coloreasypico2-rp2350-monitor [--install-root <path>] [--json]";
106
+ 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]";
107
+ const LOCAL_WSL_STATUS_USAGE = "Usage: embed local wsl status [--json]";
108
+ const LOCAL_WSL_INSTALL_USAGE = "Usage: embed local wsl install [--distribution Ubuntu] [--no-launch true|false] [--web-download true|false] [--timeout-ms 600000] [--json]";
88
109
  const LOCAL_COMPILE_TAISHANPI_USAGE = "Usage: embed local compile taishanpi --source <main.c|main.cpp> --output <artifact> [--release-root <path>] [--account <account_id>] [--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]";
110
+ const LOCAL_BUILD_QT_SMOKE_USAGE = "Usage: embed local build qt-smoke --build-dir <dir> [--source <qt-cmake-dir>] [--target-name <executable>] [--release-root <path>] [--account <account_id>] [--json]";
111
+ const AUTH_DEVICE_STATUS_USAGE = "Usage: embed auth device status [--json]";
112
+ const AUTH_DEVICE_LIST_USAGE = "Usage: embed auth device list [--json]";
113
+ const AUTH_DEVICE_REVOKE_USAGE = "Usage: embed auth device revoke <device_id> [--json]";
114
+ const AUTH_DEVICE_RENAME_USAGE = "Usage: embed auth device rename <device_id> --label <name> [--json]";
90
115
  const BOARD_REGISTRY_LIST_USAGE = "Usage: embed board registry list [--json]";
91
116
  const BOARD_REGISTRY_SHOW_USAGE = "Usage: embed board registry show <template_id> [--json]";
92
117
  const BOARD_METHODS_USAGE = "Usage: embed board methods <template_id> [--json]";
93
118
  const BOARD_KNOWLEDGE_USAGE = "Usage: embed board knowledge <template_id> [--json]";
119
+ const BOARD_KNOWLEDGE_SEARCH_USAGE = "Usage: embed board knowledge search <template_id> --query <text> [--source board_pack|build_template|registry] [--limit 5] [--json]";
94
120
  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]";
95
121
  const MODEL_LIST_USAGE = "Usage: embed model list [--json]";
96
122
  const MODEL_DEFAULT_USAGE = "Usage: embed model default [--json]";
97
123
  const SERVICE_MODES_USAGE = "Usage: embed service modes [--json]";
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]";
124
+ 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-command <cmd>] [--run] [--approve] [--json]";
125
+ let cachedLocalHardwareFingerprint;
99
126
  const TOOL_LIST_USAGE = "Usage: embed tool list [--json]";
100
127
  const TOOL_CALL_USAGE = "Usage: embed tool call <capability_id> [--input-json '<json>'] [--approve] [--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]";
128
+ const MCP_TOOL_EVENT_USAGE = "Usage: embed mcp log --tool <tool_name> [--client codex|opencode] [--mode local_ai|server_ai] [--local-device-id <id>] [--server-model-used true|false] [--success true|false] [--request-id <id>] [--duration-ms <ms>] [--input-summary <text>] [--output-summary <text>] [--json]";
129
+ 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]";
102
130
  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]";
103
131
  const TASK_STATES = new Set([
104
132
  "created",
@@ -147,11 +175,7 @@ async function main(argv) {
147
175
  return output(parsed, await bridgePost("/v1/board/taishanpi/deploy", request), renderBoardDeployResult);
148
176
  }
149
177
  if (area === "bridge" && action === "start") {
150
- startServer({
151
- host: stringFlag(parsed, "host"),
152
- port: numberFlag(parsed, "port")
153
- });
154
- return await waitForever();
178
+ return await runBridgeStart(parsed);
155
179
  }
156
180
  if (area === "bridge" && action === "status") {
157
181
  return output(parsed, await bridgeGet("/healthz"), renderBridgeStatus);
@@ -216,6 +240,17 @@ async function main(argv) {
216
240
  return output(parsed, await cloudGet(`/v1/board-registry/templates/${encodeURIComponent(idResult.value)}/methods`), renderBoardMethods);
217
241
  }
218
242
  if (action === "knowledge") {
243
+ if (parsed.command[2] === "search") {
244
+ const request = boardKnowledgeSearchRequest(parsed);
245
+ if (typeof request === "string") {
246
+ return output(parsed, fail("invalid_args", request), undefined, 2);
247
+ }
248
+ const params = new URLSearchParams({ q: request.query, limit: String(request.limit) });
249
+ if (request.source) {
250
+ params.set("source", request.source);
251
+ }
252
+ return output(parsed, await cloudGet(`/v1/board-registry/templates/${encodeURIComponent(request.templateId)}/knowledge-search?${params.toString()}`), renderBoardKnowledgeSearch);
253
+ }
219
254
  if (parsed.command[2] === "file") {
220
255
  const request = boardKnowledgeFileRequest(parsed);
221
256
  if (typeof request === "string") {
@@ -241,6 +276,7 @@ async function main(argv) {
241
276
  BOARD_REGISTRY_SHOW_USAGE,
242
277
  BOARD_METHODS_USAGE,
243
278
  BOARD_KNOWLEDGE_USAGE,
279
+ BOARD_KNOWLEDGE_SEARCH_USAGE,
244
280
  BOARD_KNOWLEDGE_FILE_USAGE
245
281
  ].join("\n")), undefined, 2);
246
282
  }
@@ -279,7 +315,18 @@ async function main(argv) {
279
315
  const result = await pluginInstall(parsed);
280
316
  return output(parsed, result, renderPluginInstall, result.ok ? 0 : 2);
281
317
  }
282
- return output(parsed, fail("invalid_args", [PLUGIN_LIST_USAGE, PLUGIN_INSTALL_USAGE].join("\n")), undefined, 2);
318
+ if (action === "update") {
319
+ if (parsed.command[2] === "check") {
320
+ const result = await pluginUpdateCheck(parsed);
321
+ return output(parsed, result, renderPluginUpdateCheck, result.ok ? 0 : 2);
322
+ }
323
+ if (["codex", "opencode", "all"].includes(parsed.command[2] ?? "")) {
324
+ const result = await pluginUpdate(parsed);
325
+ return output(parsed, result, renderPluginInstall, result.ok ? 0 : 2);
326
+ }
327
+ return output(parsed, fail("invalid_args", [PLUGIN_UPDATE_CHECK_USAGE, PLUGIN_UPDATE_USAGE].join("\n")), undefined, 2);
328
+ }
329
+ return output(parsed, fail("invalid_args", [PLUGIN_LIST_USAGE, PLUGIN_INSTALL_USAGE, PLUGIN_UPDATE_CHECK_USAGE, PLUGIN_UPDATE_USAGE].join("\n")), undefined, 2);
283
330
  }
284
331
  if (area === "auth" && action === "login") {
285
332
  const result = await authLogin(parsed);
@@ -288,6 +335,31 @@ async function main(argv) {
288
335
  if (area === "auth" && action === "status") {
289
336
  return output(parsed, ok(await authStatus()), renderAuthStatus);
290
337
  }
338
+ if (area === "auth" && action === "device") {
339
+ const deviceAction = parsed.command[2] ?? "status";
340
+ if (deviceAction === "status") {
341
+ const result = await authDeviceStatus(parsed);
342
+ return output(parsed, result, renderAuthDeviceStatus, result.ok ? 0 : 2);
343
+ }
344
+ if (deviceAction === "list") {
345
+ const result = await authDeviceList(parsed);
346
+ return output(parsed, result, renderAuthDeviceList, result.ok ? 0 : 2);
347
+ }
348
+ if (deviceAction === "revoke") {
349
+ const result = await authDeviceRevoke(parsed);
350
+ return output(parsed, result, renderAuthDevice, result.ok ? 0 : 2);
351
+ }
352
+ if (deviceAction === "rename") {
353
+ const result = await authDeviceRename(parsed);
354
+ return output(parsed, result, renderAuthDevice, result.ok ? 0 : 2);
355
+ }
356
+ return output(parsed, fail("invalid_args", [
357
+ AUTH_DEVICE_STATUS_USAGE,
358
+ AUTH_DEVICE_LIST_USAGE,
359
+ AUTH_DEVICE_REVOKE_USAGE,
360
+ AUTH_DEVICE_RENAME_USAGE
361
+ ].join("\n")), undefined, 2);
362
+ }
291
363
  if (area === "auth" && action === "logout") {
292
364
  await rm(DEFAULT_AUTH_FILE, { force: true });
293
365
  return output(parsed, ok(await authStatus()), renderAuthStatus);
@@ -367,6 +439,16 @@ async function main(argv) {
367
439
  USAGE_EVENTS_USAGE
368
440
  ].join("\n")), undefined, 2);
369
441
  }
442
+ if (area === "mcp") {
443
+ if (action === "log" || action === "tool-event") {
444
+ const body = mcpToolEventBody(parsed);
445
+ if (typeof body === "string") {
446
+ return output(parsed, fail("invalid_args", body), undefined, 2);
447
+ }
448
+ return output(parsed, await cloudPost("/v1/mcp/tool-events", body), renderMcpToolEvent);
449
+ }
450
+ return output(parsed, fail("invalid_args", MCP_TOOL_EVENT_USAGE), undefined, 2);
451
+ }
370
452
  if (area === "billing") {
371
453
  if (action === "statement") {
372
454
  const request = billingStatementRequest(parsed);
@@ -506,6 +588,20 @@ async function main(argv) {
506
588
  return output(parsed, fail("invalid_args", [IMAGE_BOOT_LOGO_COMPOSE_USAGE, IMAGE_DTB_COMPOSE_USAGE].join("\n")), undefined, 2);
507
589
  }
508
590
  if (area === "local") {
591
+ if (action === "toolchain" && parsed.command[2] === "list") {
592
+ const request = localToolchainListRequest(parsed);
593
+ if (typeof request === "string") {
594
+ return output(parsed, fail("invalid_args", request), undefined, 2);
595
+ }
596
+ return output(parsed, ok(await listLocalToolchainEnvironments(request)), renderLocalToolchainList);
597
+ }
598
+ if (action === "toolchain" && parsed.command[2] === "installed") {
599
+ const request = localToolchainListRequest(parsed, LOCAL_TOOLCHAIN_INSTALLED_USAGE);
600
+ if (typeof request === "string") {
601
+ return output(parsed, fail("invalid_args", request), undefined, 2);
602
+ }
603
+ return output(parsed, ok(await listLocalToolchainEnvironments({ ...request, installedOnly: true })), renderLocalToolchainList);
604
+ }
509
605
  if (action === "toolchain" && parsed.command[2] === "latest") {
510
606
  const request = localToolchainLatestRequest(parsed);
511
607
  if (typeof request === "string") {
@@ -527,12 +623,34 @@ async function main(argv) {
527
623
  }
528
624
  return output(parsed, ok(await installLocalToolchain(request)), renderLocalToolchainInstall);
529
625
  }
626
+ if (action === "toolchain" && (parsed.command[2] === "uninstall" || parsed.command[2] === "remove")) {
627
+ const request = localToolchainUninstallRequest(parsed);
628
+ if (typeof request === "string") {
629
+ return output(parsed, fail("invalid_args", request), undefined, 2);
630
+ }
631
+ return output(parsed, ok(await uninstallLocalToolchain(request)), renderLocalToolchainUninstall);
632
+ }
530
633
  if (action === "toolchain" && parsed.command[2] === "validate") {
531
634
  const request = localToolchainValidateRequest(parsed);
532
635
  if (typeof request === "string") {
533
636
  return output(parsed, fail("invalid_args", request), undefined, 2);
534
637
  }
535
- return output(parsed, ok(await validateLocalToolchain(request.releaseRoot)), renderLocalToolchainValidation);
638
+ return output(parsed, ok(await validateLocalToolchain(request)), renderLocalToolchainValidation);
639
+ }
640
+ if (action === "wsl" && parsed.command[2] === "status") {
641
+ const unknownFlag = firstUnknownFlag(parsed, ["json"]);
642
+ const extra = parsed.command.slice(3);
643
+ if (unknownFlag || extra.length > 0) {
644
+ return output(parsed, fail("invalid_args", unknownFlag ? `Unknown flag --${unknownFlag}. ${LOCAL_WSL_STATUS_USAGE}` : LOCAL_WSL_STATUS_USAGE), undefined, 2);
645
+ }
646
+ return output(parsed, ok(await windowsWslStatus()), renderWindowsWslStatus);
647
+ }
648
+ if (action === "wsl" && parsed.command[2] === "install") {
649
+ const request = localWslInstallRequest(parsed);
650
+ if (typeof request === "string") {
651
+ return output(parsed, fail("invalid_args", request), undefined, 2);
652
+ }
653
+ return output(parsed, ok(await windowsWslInstall(request)), renderWindowsWslInstall);
536
654
  }
537
655
  if (action === "compile" && parsed.command[2] === "taishanpi") {
538
656
  const request = localCompileTaishanPiRequest(parsed, await authStatus());
@@ -549,10 +667,15 @@ async function main(argv) {
549
667
  return output(parsed, ok(await buildTaishanPiQtSmoke(request)), renderLocalCompileResult);
550
668
  }
551
669
  return output(parsed, fail("invalid_args", [
670
+ LOCAL_TOOLCHAIN_LIST_USAGE,
671
+ LOCAL_TOOLCHAIN_INSTALLED_USAGE,
552
672
  LOCAL_TOOLCHAIN_LATEST_USAGE,
553
673
  LOCAL_TOOLCHAIN_CURRENT_USAGE,
554
674
  LOCAL_TOOLCHAIN_INSTALL_USAGE,
675
+ LOCAL_TOOLCHAIN_UNINSTALL_USAGE,
555
676
  LOCAL_TOOLCHAIN_VALIDATE_USAGE,
677
+ LOCAL_WSL_STATUS_USAGE,
678
+ LOCAL_WSL_INSTALL_USAGE,
556
679
  LOCAL_COMPILE_TAISHANPI_USAGE,
557
680
  LOCAL_BUILD_QT_SMOKE_USAGE
558
681
  ].join("\n")), undefined, 2);
@@ -984,11 +1107,18 @@ async function main(argv) {
984
1107
  return output(parsed, fail("unknown_command", `Unknown command: ${parsed.command.join(" ")}`), undefined, 2);
985
1108
  }
986
1109
  catch (error) {
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`
1110
+ const message = error instanceof Error ? error.message : String(error);
1111
+ return output(parsed, fail("command_failed", message, {
1112
+ remediation: commandFailureRemediation(message)
989
1113
  }), undefined, 1);
990
1114
  }
991
1115
  }
1116
+ function commandFailureRemediation(message) {
1117
+ if (message.includes("TaishanPi") && message.includes("WSL2")) {
1118
+ return "Run: embedlabs local wsl status";
1119
+ }
1120
+ return `Check that embed-local-bridge is running at ${DEFAULT_BRIDGE_URL}. Start it with: embed bridge start`;
1121
+ }
992
1122
  function parseArgs(argv) {
993
1123
  const command = [];
994
1124
  const flags = {};
@@ -1098,6 +1228,7 @@ function flashBody(parsed, includeApproval) {
1098
1228
  variant_id: stringFlag(parsed, "variant"),
1099
1229
  hardware_profile_id: stringFlag(parsed, "hardware-profile"),
1100
1230
  profile_id: stringFlag(parsed, "profile"),
1231
+ local_device_id: stringFlag(parsed, "local-device-id") ?? stringFlag(parsed, "device-id"),
1101
1232
  image_dir: stringFlag(parsed, "image-dir"),
1102
1233
  artifact_path: stringFlag(parsed, "artifact"),
1103
1234
  target_volume_path: stringFlag(parsed, "target-volume")
@@ -1126,11 +1257,11 @@ async function doctor() {
1126
1257
  const bridgeHealth = await apiDoctorCheck("bridge_health", "Local Bridge health", `${bridgeBaseUrl}/healthz`, "bridge_unreachable", `Local Bridge is unreachable at ${bridgeBaseUrl}.`, renderHealthSummary, healthStatus);
1127
1258
  checks.push(bridgeHealth);
1128
1259
  if (isUsableDoctorCheck(bridgeHealth)) {
1129
- checks.push(await apiDoctorCheck("device_scan", "Device scan", `${bridgeBaseUrl}/v1/devices`, "bridge_unreachable", `Local Bridge is unreachable at ${bridgeBaseUrl}.`, renderDeviceScanSummary, warningIfWarnings));
1260
+ checks.push(await apiDoctorCheck("device_scan", "Device inventory", `${bridgeBaseUrl}/v1/devices`, "bridge_unreachable", `Local Bridge is unreachable at ${bridgeBaseUrl}.`, renderDeviceScanSummary, warningIfWarnings));
1130
1261
  checks.push(await apiDoctorCheck("debug_tools", "Debug tool scan", `${bridgeBaseUrl}/v1/debug/tools`, "bridge_unreachable", `Local Bridge is unreachable at ${bridgeBaseUrl}.`, renderDebugToolScanSummary, warningIfWarnings));
1131
1262
  }
1132
1263
  else {
1133
- checks.push(dependentDoctorCheck("device_scan", "Device scan", `${bridgeBaseUrl}/v1/devices`, "Device scan requires a reachable Local Bridge."));
1264
+ checks.push(dependentDoctorCheck("device_scan", "Device inventory", `${bridgeBaseUrl}/v1/devices`, "Device inventory requires a reachable Local Bridge."));
1134
1265
  checks.push(dependentDoctorCheck("debug_tools", "Debug tool scan", `${bridgeBaseUrl}/v1/debug/tools`, "Debug tool scan requires a reachable Local Bridge."));
1135
1266
  }
1136
1267
  checks.push(await apiDoctorCheck("cloud_api_health", "Cloud API health", `${cloudBaseUrl}/healthz`, "cloud_api_unreachable", `Cloud API is unreachable at ${cloudBaseUrl}.`, renderHealthSummary, healthStatus));
@@ -1189,7 +1320,8 @@ function authDoctorCheck(status) {
1189
1320
  : {
1190
1321
  code: "auth_not_ready",
1191
1322
  message: "No CLI auth token is configured.",
1192
- remediation: "Run: embed auth login --token <token>"
1323
+ remediation: cloudAuthSetupRemediation(),
1324
+ details: cloudAuthSetupDetails()
1193
1325
  }
1194
1326
  };
1195
1327
  }
@@ -1322,7 +1454,7 @@ function warningIfWarnings(data) {
1322
1454
  }
1323
1455
  function renderDeviceScanSummary(result) {
1324
1456
  const warningText = result.warnings?.length ? ` ${result.warnings.length} warning(s).` : "";
1325
- return `Device scan completed: ${result.devices.length} device(s), ${result.usb.length} USB item(s), ${result.serial.length} serial port(s).${warningText}`;
1457
+ return `Device inventory snapshot: ${result.devices.length} device(s), ${result.usb.length} USB item(s), ${result.serial.length} serial port(s).${warningText}`;
1326
1458
  }
1327
1459
  function renderDebugToolScanSummary(result) {
1328
1460
  const available = result.tools.filter((tool) => tool.available).length;
@@ -1340,16 +1472,142 @@ function isApiResponse(value) {
1340
1472
  return isJsonObject(error) && typeof error.code === "string" && typeof error.message === "string";
1341
1473
  }
1342
1474
  async function bridgeGet(path) {
1343
- const response = await fetch(`${DEFAULT_BRIDGE_URL}${path}`);
1344
- return await response.json();
1475
+ return await bridgeRequest("GET", path);
1345
1476
  }
1346
1477
  async function bridgePost(path, body) {
1347
- const response = await fetch(`${DEFAULT_BRIDGE_URL}${path}`, {
1348
- method: "POST",
1349
- headers: { "content-type": "application/json" },
1350
- body: JSON.stringify(body)
1478
+ return await bridgeRequest("POST", path, body);
1479
+ }
1480
+ async function bridgeRequest(method, path, body) {
1481
+ const bodyText = body === undefined ? "" : JSON.stringify(body);
1482
+ const makeRequest = async () => {
1483
+ const response = await fetch(`${DEFAULT_BRIDGE_URL}${path}`, {
1484
+ method,
1485
+ headers: bridgeHeaders(method, path, method === "POST" ? bodyText : "", method === "POST" ? { "content-type": "application/json" } : {}),
1486
+ body: method === "POST" ? bodyText : undefined
1487
+ });
1488
+ return await response.json();
1489
+ };
1490
+ try {
1491
+ return await makeRequest();
1492
+ }
1493
+ catch (error) {
1494
+ if (!isBridgeConnectionFailure(error)) {
1495
+ throw error;
1496
+ }
1497
+ const started = await ensureBridgeStartedForRequest();
1498
+ if (!started.ok) {
1499
+ return started;
1500
+ }
1501
+ return await makeRequest();
1502
+ }
1503
+ }
1504
+ function bridgeHeaders(method, path, bodyText, base = {}) {
1505
+ const token = process.env.EMBED_BRIDGE_TOKEN?.trim();
1506
+ if (!token) {
1507
+ return base;
1508
+ }
1509
+ const headers = {
1510
+ ...base,
1511
+ authorization: `Bearer ${token}`
1512
+ };
1513
+ addBridgeRequestSignature(headers, method, path, bodyText, token);
1514
+ return headers;
1515
+ }
1516
+ function addBridgeRequestSignature(headers, method, pathWithQuery, bodyText, token) {
1517
+ if (process.env.EMBED_BRIDGE_SIGNING === "0") {
1518
+ return;
1519
+ }
1520
+ const timestamp = String(Math.floor(Date.now() / 1000));
1521
+ const nonce = randomBytes(16).toString("hex");
1522
+ const bodySha256 = createHash("sha256").update(bodyText).digest("hex");
1523
+ const keyId = createHash("sha256").update(token).digest("hex").slice(0, 16);
1524
+ const canonical = cloudRequestCanonicalString(method, pathWithQuery, timestamp, nonce, bodySha256);
1525
+ headers["x-embed-key-id"] = keyId;
1526
+ headers["x-embed-timestamp"] = timestamp;
1527
+ headers["x-embed-nonce"] = nonce;
1528
+ headers["x-embed-body-sha256"] = bodySha256;
1529
+ headers["x-embed-signature"] = createHmac("sha256", token).update(canonical).digest("hex");
1530
+ }
1531
+ function isBridgeConnectionFailure(error) {
1532
+ const message = error instanceof Error ? error.message : String(error);
1533
+ return message.includes("fetch failed") ||
1534
+ message.includes("ECONNREFUSED") ||
1535
+ message.includes("ECONNRESET") ||
1536
+ message.includes("UND_ERR_SOCKET");
1537
+ }
1538
+ async function ensureBridgeStartedForRequest() {
1539
+ if (process.env.EMBED_BRIDGE_AUTO_START === "0") {
1540
+ return fail("bridge_unavailable", `embed-local-bridge is not running at ${DEFAULT_BRIDGE_URL}.`, {
1541
+ remediation: `Start it with: embed bridge start`
1542
+ });
1543
+ }
1544
+ let bridgeURL;
1545
+ try {
1546
+ bridgeURL = new URL(DEFAULT_BRIDGE_URL);
1547
+ }
1548
+ catch {
1549
+ return fail("bridge_url_invalid", `EMBED_BRIDGE_URL is not a valid URL: ${DEFAULT_BRIDGE_URL}`);
1550
+ }
1551
+ if (!isLocalBridgeURL(bridgeURL)) {
1552
+ return fail("bridge_unavailable", `embed-local-bridge is not reachable at ${DEFAULT_BRIDGE_URL}.`, {
1553
+ remediation: `Start the bridge for that host, or set EMBED_BRIDGE_URL to a local bridge URL.`
1554
+ });
1555
+ }
1556
+ const launcher = await resolveBridgeLauncher();
1557
+ const host = bridgeURL.hostname === "::1" ? "::1" : bridgeURL.hostname || "127.0.0.1";
1558
+ const port = bridgeURL.port || "18083";
1559
+ const env = {
1560
+ ...process.env,
1561
+ EMBED_BRIDGE_HOST: host,
1562
+ EMBED_BRIDGE_PORT: port
1563
+ };
1564
+ const child = spawn(launcher.command, [...launcher.args, "--host", host, "--port", port], {
1565
+ cwd: process.cwd(),
1566
+ detached: true,
1567
+ stdio: "ignore",
1568
+ env
1569
+ });
1570
+ child.unref();
1571
+ const ready = await waitForBridgeHealth(bridgeURL, 8000);
1572
+ if (!ready.ok) {
1573
+ return ready;
1574
+ }
1575
+ return ok({
1576
+ started: true,
1577
+ bridge_url: DEFAULT_BRIDGE_URL,
1578
+ command: launcher.command
1579
+ });
1580
+ }
1581
+ function isLocalBridgeURL(url) {
1582
+ const host = url.hostname.toLowerCase();
1583
+ return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
1584
+ }
1585
+ async function waitForBridgeHealth(bridgeURL, timeoutMs) {
1586
+ const deadline = Date.now() + timeoutMs;
1587
+ let lastError = "";
1588
+ while (Date.now() < deadline) {
1589
+ try {
1590
+ const response = await fetch(new URL("/healthz", bridgeURL), {
1591
+ headers: bridgeHeaders("GET", "/healthz", "")
1592
+ });
1593
+ const parsed = await response.json();
1594
+ if (parsed.ok) {
1595
+ return parsed;
1596
+ }
1597
+ lastError = parsed.error?.message ?? `HTTP ${response.status}`;
1598
+ }
1599
+ catch (error) {
1600
+ lastError = error instanceof Error ? error.message : String(error);
1601
+ }
1602
+ await delay(100);
1603
+ }
1604
+ return fail("bridge_start_failed", `embed-local-bridge did not become healthy at ${DEFAULT_BRIDGE_URL}.`, {
1605
+ remediation: `Run embed bridge start in a separate terminal and retry.`,
1606
+ details: { last_error: lastError }
1351
1607
  });
1352
- return await response.json();
1608
+ }
1609
+ function delay(ms) {
1610
+ return new Promise((resolveDelay) => setTimeout(resolveDelay, ms));
1353
1611
  }
1354
1612
  async function cloudGet(path) {
1355
1613
  return await cloudRequest("GET", path);
@@ -1360,16 +1618,23 @@ async function cloudPost(path, body) {
1360
1618
  async function cloudDownloadArtifact(artifactId, outputPath) {
1361
1619
  try {
1362
1620
  const headers = {};
1363
- const token = await cloudAuthToken();
1364
- if (token) {
1365
- headers.authorization = `Bearer ${token}`;
1621
+ const auth = await cloudAuthConfig();
1622
+ if (auth.token) {
1623
+ if (auth.device) {
1624
+ const integrity = await validateLocalDeviceIntegrity(auth.device);
1625
+ if (!integrity.ok) {
1626
+ return integrity;
1627
+ }
1628
+ }
1629
+ headers.authorization = `Bearer ${auth.token}`;
1630
+ addCloudRequestSignature(headers, "GET", `/v1/artifacts/${encodeURIComponent(artifactId)}/download`, "", auth.token, auth.device);
1366
1631
  }
1367
1632
  const response = await fetch(`${serviceBaseUrl(DEFAULT_CLOUD_API_URL)}/v1/artifacts/${encodeURIComponent(artifactId)}/download`, {
1368
1633
  headers: Object.keys(headers).length > 0 ? headers : undefined
1369
1634
  });
1370
1635
  if (!response.ok) {
1371
1636
  const parsed = await parseErrorResponse(response);
1372
- return parsed ?? fail("artifact_download_failed", `Artifact download failed with HTTP ${response.status}.`);
1637
+ return parsed ? enrichCloudAuthFailure(parsed, Boolean(auth.token)) : fail("artifact_download_failed", `Artifact download failed with HTTP ${response.status}.`);
1373
1638
  }
1374
1639
  const bytes = Buffer.from(await response.arrayBuffer());
1375
1640
  const expectedSha256 = response.headers.get("x-embed-artifact-sha256")?.trim();
@@ -1398,19 +1663,28 @@ async function cloudDownloadArtifact(artifactId, outputPath) {
1398
1663
  async function cloudRequest(method, path, body) {
1399
1664
  try {
1400
1665
  const headers = {};
1401
- if (body !== undefined) {
1666
+ const bodyText = body === undefined ? "" : JSON.stringify(body);
1667
+ if (bodyText) {
1402
1668
  headers["content-type"] = "application/json";
1403
1669
  }
1404
- const token = await cloudAuthToken();
1405
- if (token) {
1406
- headers.authorization = `Bearer ${token}`;
1670
+ const auth = await cloudAuthConfig();
1671
+ if (auth.token) {
1672
+ if (auth.device) {
1673
+ const integrity = await validateLocalDeviceIntegrity(auth.device);
1674
+ if (!integrity.ok) {
1675
+ return integrity;
1676
+ }
1677
+ }
1678
+ headers.authorization = `Bearer ${auth.token}`;
1679
+ addCloudRequestSignature(headers, method, path, bodyText, auth.token, auth.device);
1407
1680
  }
1408
1681
  const response = await fetch(`${serviceBaseUrl(DEFAULT_CLOUD_API_URL)}${path}`, {
1409
1682
  method,
1410
1683
  headers: Object.keys(headers).length > 0 ? headers : undefined,
1411
- body: body === undefined ? undefined : JSON.stringify(body)
1684
+ body: body === undefined ? undefined : bodyText
1412
1685
  });
1413
- return await response.json();
1686
+ const parsed = await response.json();
1687
+ return enrichCloudAuthFailure(parsed, Boolean(auth.token));
1414
1688
  }
1415
1689
  catch (error) {
1416
1690
  return fail("cloud_api_unreachable", error instanceof Error ? error.message : String(error), {
@@ -1418,6 +1692,129 @@ async function cloudRequest(method, path, body) {
1418
1692
  });
1419
1693
  }
1420
1694
  }
1695
+ async function validateLocalDeviceIntegrity(device) {
1696
+ const current = await localHardwareFingerprint();
1697
+ if (current.fingerprint_hash === device.fingerprint_hash) {
1698
+ return ok(undefined);
1699
+ }
1700
+ return fail("tool_integrity_check_failed", TOOL_INTEGRITY_RELOGIN_MESSAGE, {
1701
+ remediation: [
1702
+ "当前 Embed Labs CLI/插件配置绑定的电脑与本机硬件唯一码不一致。",
1703
+ TOOL_INTEGRITY_RELOGIN_MESSAGE,
1704
+ "如果账号设备数量已达上限,请先在原电脑或用户后台撤销旧设备。"
1705
+ ].join("\n"),
1706
+ details: {
1707
+ expected_fingerprint_hash: device.fingerprint_hash,
1708
+ current_fingerprint_hash: current.fingerprint_hash,
1709
+ platform: current.platform,
1710
+ arch: current.arch,
1711
+ fingerprint_source: current.source
1712
+ }
1713
+ });
1714
+ }
1715
+ function enrichCloudAuthFailure(response, hadToken) {
1716
+ if (response.ok) {
1717
+ return response;
1718
+ }
1719
+ if (response.error.code.startsWith("device_") || response.error.code.startsWith("request_signature_")) {
1720
+ return fail(response.error.code, response.error.message, {
1721
+ remediation: [
1722
+ "This computer is not fully registered for the configured Embed Labs API Token.",
1723
+ "Run: embedlabs auth login --token <your_token>",
1724
+ "Then verify with: embedlabs auth device status",
1725
+ "If the account already has too many devices, revoke one with: embedlabs auth device revoke <device_id>"
1726
+ ].join("\n"),
1727
+ details: response.error.details
1728
+ });
1729
+ }
1730
+ if (response.error.code !== "unauthorized") {
1731
+ return response;
1732
+ }
1733
+ if (!hadToken) {
1734
+ 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.", {
1735
+ remediation: cloudAuthSetupRemediation(),
1736
+ details: cloudAuthSetupDetails()
1737
+ });
1738
+ }
1739
+ 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.", {
1740
+ remediation: cloudAuthSetupRemediation(),
1741
+ details: cloudAuthSetupDetails()
1742
+ });
1743
+ }
1744
+ function cloudAuthSetupRemediation() {
1745
+ return [
1746
+ `1. Open ${DEFAULT_DASHBOARD_URL} and register or sign in.`,
1747
+ "2. Create or copy your Embed Labs API Token from the user dashboard.",
1748
+ "3. Run: embedlabs auth login --token <your_token>",
1749
+ "4. For automation, set: EMBED_API_TOKEN=<your_token>",
1750
+ "5. Verify with: embedlabs auth status"
1751
+ ].join("\n");
1752
+ }
1753
+ function cloudAuthSetupDetails() {
1754
+ return {
1755
+ dashboard_url: DEFAULT_DASHBOARD_URL,
1756
+ login_command: "embedlabs auth login --token <your_token>",
1757
+ env_var: "EMBED_API_TOKEN",
1758
+ auth_status_command: "embedlabs auth status",
1759
+ auth_file: DEFAULT_AUTH_FILE
1760
+ };
1761
+ }
1762
+ function addCloudRequestSignature(headers, method, pathWithQuery, bodyText, token, device) {
1763
+ if (process.env.EMBED_CLOUD_API_SIGNING === "0") {
1764
+ return;
1765
+ }
1766
+ const timestamp = String(Math.floor(Date.now() / 1000));
1767
+ const nonce = randomBytes(16).toString("hex");
1768
+ const bodySha256 = createHash("sha256").update(bodyText).digest("hex");
1769
+ const keyId = createHash("sha256").update(token).digest("hex").slice(0, 16);
1770
+ const canonical = device
1771
+ ? cloudRequestCanonicalStringV2(method, pathWithQuery, timestamp, nonce, bodySha256, device, EMBED_CLIENT_NAME, EMBED_CLIENT_VERSION)
1772
+ : cloudRequestCanonicalString(method, pathWithQuery, timestamp, nonce, bodySha256);
1773
+ headers["x-embed-key-id"] = keyId;
1774
+ headers["x-embed-timestamp"] = timestamp;
1775
+ headers["x-embed-nonce"] = nonce;
1776
+ headers["x-embed-body-sha256"] = bodySha256;
1777
+ if (device) {
1778
+ headers["x-embed-signature-version"] = "v2";
1779
+ headers["x-embed-device-id"] = device.device_id;
1780
+ headers["x-embed-device-fingerprint-sha256"] = device.fingerprint_hash;
1781
+ headers["x-embed-client-name"] = EMBED_CLIENT_NAME;
1782
+ headers["x-embed-client-version"] = EMBED_CLIENT_VERSION;
1783
+ headers["x-embed-device-signature"] = signCrypto(null, Buffer.from(canonical), device.private_key_pem).toString("base64url");
1784
+ }
1785
+ headers["x-embed-signature"] = createHmac("sha256", token).update(canonical).digest("hex");
1786
+ }
1787
+ function cloudRequestCanonicalString(method, pathWithQuery, timestamp, nonce, bodySha256) {
1788
+ return [
1789
+ method.toUpperCase(),
1790
+ normalizeCloudPathForSignature(pathWithQuery),
1791
+ timestamp,
1792
+ nonce,
1793
+ bodySha256
1794
+ ].join("\n");
1795
+ }
1796
+ function cloudRequestCanonicalStringV2(method, pathWithQuery, timestamp, nonce, bodySha256, device, clientName, clientVersion) {
1797
+ return [
1798
+ method.toUpperCase(),
1799
+ normalizeCloudPathForSignature(pathWithQuery),
1800
+ timestamp,
1801
+ nonce,
1802
+ bodySha256,
1803
+ device.device_id,
1804
+ device.fingerprint_hash,
1805
+ clientName,
1806
+ clientVersion
1807
+ ].join("\n");
1808
+ }
1809
+ function normalizeCloudPathForSignature(pathWithQuery) {
1810
+ try {
1811
+ const parsed = new URL(pathWithQuery, "http://embed.local");
1812
+ return `${parsed.pathname}${parsed.search}`;
1813
+ }
1814
+ catch {
1815
+ return pathWithQuery.startsWith("/") ? pathWithQuery : `/${pathWithQuery}`;
1816
+ }
1817
+ }
1421
1818
  async function pluginList(parsed) {
1422
1819
  const releaseDir = stringFlag(parsed, "release-dir");
1423
1820
  const manifest = releaseDir ? await readPluginReleaseManifest(releaseDir) : undefined;
@@ -1517,13 +1914,112 @@ async function pluginInstall(parsed) {
1517
1914
  await rm(tempDir, { recursive: true, force: true });
1518
1915
  }
1519
1916
  }
1917
+ async function pluginUpdateCheck(parsed) {
1918
+ const unknownFlag = firstUnknownFlag(parsed, ["release-url", "target", "codex-target", "opencode-target", "json"]);
1919
+ if (unknownFlag) {
1920
+ return fail("invalid_args", `Unknown flag --${unknownFlag}. ${PLUGIN_UPDATE_CHECK_USAGE}`);
1921
+ }
1922
+ const unexpected = parsed.command.slice(3);
1923
+ if (unexpected.length > 0) {
1924
+ return fail("invalid_args", `Unexpected argument: ${unexpected[0]}. ${PLUGIN_UPDATE_CHECK_USAGE}`);
1925
+ }
1926
+ const remoteManifest = await fetchRemotePluginManifest(parsed);
1927
+ if (!remoteManifest.ok) {
1928
+ return remoteManifest;
1929
+ }
1930
+ const manifest = remoteManifest.data;
1931
+ const codexPackage = manifest.packages?.find((item) => item.id === "codex-embed-labs");
1932
+ const opencodePackage = manifest.packages?.find((item) => item.id === "opencode-embed-labs");
1933
+ const codexTarget = join(codexPluginTargetRoot(parsed, true), CODEX_PLUGIN_NAME);
1934
+ const openCodeTarget = openCodePluginTargetRoot(parsed, true);
1935
+ return ok({
1936
+ release_url: pluginReleaseBaseUrl(parsed),
1937
+ latest_version: manifest.version,
1938
+ release_notes: normalizedReleaseNotes(manifest.release_notes),
1939
+ plugins: [
1940
+ await pluginUpdateItem({
1941
+ id: "codex",
1942
+ displayName: "Embed Labs Codex plugin",
1943
+ targetPath: codexTarget,
1944
+ installedVersion: await installedCodexPluginVersion(codexTarget),
1945
+ latestVersion: codexPackage?.version ?? manifest.version,
1946
+ releaseFile: codexPackage?.file,
1947
+ updateCommand: "embedlabs plugin update codex"
1948
+ }),
1949
+ await pluginUpdateItem({
1950
+ id: "opencode",
1951
+ displayName: "Embed Labs OpenCode plugin",
1952
+ targetPath: openCodeTarget,
1953
+ installedVersion: await installedOpenCodePluginVersion(openCodeTarget),
1954
+ latestVersion: opencodePackage?.version ?? manifest.version,
1955
+ releaseFile: opencodePackage?.file,
1956
+ updateCommand: "embedlabs plugin update opencode"
1957
+ })
1958
+ ]
1959
+ });
1960
+ }
1961
+ function normalizedReleaseNotes(notes) {
1962
+ if (!Array.isArray(notes)) {
1963
+ return [];
1964
+ }
1965
+ return notes
1966
+ .filter((item) => typeof item === "string" && item.trim().length > 0)
1967
+ .map((item) => item.trim());
1968
+ }
1969
+ async function pluginUpdate(parsed) {
1970
+ const unknownFlag = firstUnknownFlag(parsed, ["release-url", "target", "codex-target", "opencode-target", "json"]);
1971
+ if (unknownFlag) {
1972
+ return fail("invalid_args", `Unknown flag --${unknownFlag}. ${PLUGIN_UPDATE_USAGE}`);
1973
+ }
1974
+ const target = parsed.command[2];
1975
+ if (!target || !["codex", "opencode", "all"].includes(target)) {
1976
+ return fail("invalid_args", PLUGIN_UPDATE_USAGE);
1977
+ }
1978
+ const unexpected = parsed.command.slice(3);
1979
+ if (unexpected.length > 0) {
1980
+ return fail("invalid_args", `Unexpected argument: ${unexpected[0]}. ${PLUGIN_UPDATE_USAGE}`);
1981
+ }
1982
+ const installParsed = {
1983
+ ...parsed,
1984
+ command: ["plugin", "install", target],
1985
+ flags: { ...parsed.flags, force: true }
1986
+ };
1987
+ return await pluginInstall(installParsed);
1988
+ }
1989
+ async function pluginUpdateItem(input) {
1990
+ const installed = !!input.installedVersion;
1991
+ const updateAvailable = !!input.latestVersion && input.installedVersion !== input.latestVersion;
1992
+ const notes = [];
1993
+ if (!installed) {
1994
+ notes.push("Plugin is not installed in the selected target.");
1995
+ }
1996
+ else if (updateAvailable) {
1997
+ notes.push("A newer plugin release is available. Run the update command, then restart Codex/OpenCode.");
1998
+ }
1999
+ else {
2000
+ notes.push("Plugin is up to date for the selected release channel.");
2001
+ }
2002
+ return {
2003
+ id: input.id,
2004
+ display_name: input.displayName,
2005
+ installed,
2006
+ installed_version: input.installedVersion,
2007
+ latest_version: input.latestVersion,
2008
+ update_available: updateAvailable,
2009
+ target_path: input.targetPath,
2010
+ release_file: input.releaseFile,
2011
+ update_command: input.updateCommand,
2012
+ notes
2013
+ };
2014
+ }
1520
2015
  async function installCodexPlugin(parsed, context) {
1521
2016
  const source = await resolveCodexPluginSource(context);
1522
2017
  if (!source.ok) {
1523
2018
  return source;
1524
2019
  }
1525
2020
  const targetRoot = codexPluginTargetRoot(parsed, context.installingAll);
1526
- const targetPath = join(targetRoot, "embed-labs");
2021
+ const targetPath = join(targetRoot, CODEX_PLUGIN_NAME);
2022
+ const legacyCleanup = await cleanupLegacyCodexPluginRemnants(targetRoot);
1527
2023
  if (await pathExists(targetPath) && !booleanFlag(parsed, "force")) {
1528
2024
  return fail("plugin_already_installed", `Codex plugin already exists at ${targetPath}.`, {
1529
2025
  remediation: "Pass --force to replace it, or pass --codex-target/--target to install into a different directory."
@@ -1533,16 +2029,24 @@ async function installCodexPlugin(parsed, context) {
1533
2029
  await mkdir(targetRoot, { recursive: true });
1534
2030
  await cp(source.data.sourcePath, targetPath, { recursive: true });
1535
2031
  const mcpRegistration = await maybeRegisterCodexMcp(parsed, targetRoot, targetPath);
2032
+ const marketplaceRegistration = await maybeRegisterCodexMarketplace(parsed, targetRoot, targetPath);
1536
2033
  return ok({
1537
2034
  id: "codex",
1538
2035
  target_path: targetPath,
1539
2036
  source: source.data.sourceLabel,
1540
2037
  version: source.data.version,
1541
2038
  command_hint: mcpRegistration.registered
1542
- ? "Codex MCP was registered. Start a new Codex session to reload tools."
2039
+ ? (marketplaceRegistration.registered
2040
+ ? "Codex MCP and plugin marketplace entry were registered. Fully restart Codex to reload @Embed Labs."
2041
+ : "Codex MCP was registered. Start a new Codex session to reload tools.")
1543
2042
  : mcpRegistration.hint,
2043
+ warning: legacyCodexCleanupWarning(legacyCleanup),
1544
2044
  mcp_registered: mcpRegistration.registered,
1545
- mcp_warning: mcpRegistration.warning
2045
+ mcp_warning: mcpRegistration.warning,
2046
+ marketplace_registered: marketplaceRegistration.registered,
2047
+ marketplace_path: marketplaceRegistration.marketplacePath,
2048
+ marketplace_warning: marketplaceRegistration.warning,
2049
+ cleanup: legacyCleanup
1546
2050
  });
1547
2051
  }
1548
2052
  async function installOpenCodePlugin(parsed, context) {
@@ -1551,8 +2055,10 @@ async function installOpenCodePlugin(parsed, context) {
1551
2055
  return source;
1552
2056
  }
1553
2057
  const targetRoot = openCodePluginTargetRoot(parsed, context.installingAll);
1554
- const wrapperPath = join(targetRoot, "plugins", "development-board-toolchain.js");
1555
- if (await pathExists(wrapperPath) && !booleanFlag(parsed, "force")) {
2058
+ const globalInstall = isGlobalOpenCodeRoot(targetRoot);
2059
+ const wrapperPath = join(targetRoot, "plugins", "embed-labs.js");
2060
+ const legacyCleanup = await cleanupLegacyOpenCodePluginRemnants(targetRoot, globalInstall);
2061
+ if (!globalInstall && await pathExists(wrapperPath) && !booleanFlag(parsed, "force")) {
1556
2062
  return fail("plugin_already_installed", `OpenCode plugin wrapper already exists at ${wrapperPath}.`, {
1557
2063
  remediation: "Pass --force to replace it, or pass --opencode-target/--target to install into a different directory."
1558
2064
  });
@@ -1578,15 +2084,25 @@ async function installOpenCodePlugin(parsed, context) {
1578
2084
  });
1579
2085
  }
1580
2086
  await ensureOpenCodeInstallPackageJson(targetRoot);
1581
- await writeFile(wrapperPath, `export { default, DevelopmentBoardToolchainPlugin } from "embed-labs";\n`, "utf8");
2087
+ if (globalInstall) {
2088
+ await rm(wrapperPath, { force: true });
2089
+ legacyCleanup.legacy_removed_config_entries?.push(...await ensureOpenCodeGlobalPluginConfig());
2090
+ }
2091
+ else {
2092
+ await writeFile(wrapperPath, `export { default, DevelopmentBoardToolchainPlugin } from "embed-labs";\n`, "utf8");
2093
+ }
1582
2094
  const duplicateWarning = await openCodeDuplicatePluginWarning(targetRoot);
2095
+ const cleanupWarning = legacyOpenCodeCleanupWarning(legacyCleanup);
1583
2096
  return ok({
1584
2097
  id: "opencode",
1585
2098
  target_path: targetRoot,
1586
2099
  source: source.data.sourceLabel,
1587
2100
  version: source.data.version,
1588
- command_hint: "Start OpenCode from the project containing this .opencode directory.",
1589
- warning: duplicateWarning
2101
+ command_hint: globalInstall
2102
+ ? "Restart OpenCode so the global embed-labs package plugin is reloaded."
2103
+ : "Start OpenCode from the project containing this .opencode directory.",
2104
+ warning: combineWarnings(cleanupWarning, duplicateWarning),
2105
+ cleanup: legacyCleanup
1590
2106
  });
1591
2107
  }
1592
2108
  async function resolveCodexPluginSource(context) {
@@ -1800,7 +2316,458 @@ function openCodePluginTargetRoot(parsed, installingAll) {
1800
2316
  return resolve(target ?? defaultOpenCodeRoot());
1801
2317
  }
1802
2318
  function defaultCodexPluginRoot() {
1803
- return join(process.env.CODEX_HOME?.trim() || join(homedir(), ".codex"), "plugins");
2319
+ return join(defaultCodexHome(), "plugins");
2320
+ }
2321
+ function defaultCodexHome() {
2322
+ return resolve(process.env.CODEX_HOME?.trim() || join(homedir(), ".codex"));
2323
+ }
2324
+ function codexConfigPath() {
2325
+ return join(defaultCodexHome(), "config.toml");
2326
+ }
2327
+ async function cleanupLegacyCodexPluginRemnants(targetRoot) {
2328
+ const removedPaths = [];
2329
+ const removedConfigTables = [];
2330
+ const warnings = [];
2331
+ const stoppedProcesses = await stopLegacyCodexPluginProcesses(warnings);
2332
+ const legacyPaths = [
2333
+ join(targetRoot, "cache", CODEX_MARKETPLACE_NAME, CODEX_PLUGIN_NAME)
2334
+ ];
2335
+ for (const marketplaceName of LEGACY_CODEX_MARKETPLACE_NAMES) {
2336
+ legacyPaths.push(join(targetRoot, "cache", marketplaceName, CODEX_PLUGIN_NAME));
2337
+ }
2338
+ for (const pluginName of LEGACY_CODEX_PLUGIN_NAMES) {
2339
+ legacyPaths.push(join(targetRoot, pluginName));
2340
+ legacyPaths.push(join(targetRoot, "cache", pluginName));
2341
+ for (const marketplaceName of [CODEX_MARKETPLACE_NAME, ...LEGACY_CODEX_MARKETPLACE_NAMES]) {
2342
+ legacyPaths.push(join(targetRoot, "cache", marketplaceName, pluginName));
2343
+ }
2344
+ }
2345
+ legacyPaths.push(...await discoverLegacyCodexCachePaths(targetRoot));
2346
+ if (resolve(targetRoot) === resolve(defaultCodexPluginRoot())) {
2347
+ 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"));
2348
+ legacyPaths.push(...legacyCodexLocalMarketplacePaths(), ...await discoverLegacyHomeAgentsMarketplacePaths(warnings), ...await discoverLegacyCodexProjectMarketplacePaths(warnings), ...legacyDevelopmentBoardRuntimePluginPaths());
2349
+ }
2350
+ for (const candidate of legacyPaths) {
2351
+ try {
2352
+ if (await pathExists(candidate)) {
2353
+ await rm(candidate, { recursive: true, force: true });
2354
+ removedPaths.push(candidate);
2355
+ }
2356
+ }
2357
+ catch (error) {
2358
+ warnings.push(`Could not remove ${candidate}: ${error instanceof Error ? error.message : String(error)}`);
2359
+ }
2360
+ }
2361
+ if (resolve(targetRoot) === resolve(defaultCodexPluginRoot())) {
2362
+ const configPath = codexConfigPath();
2363
+ try {
2364
+ if (await pathExists(configPath)) {
2365
+ const current = await readFile(configPath, "utf8");
2366
+ const updated = removeLegacyCodexConfigTables(current);
2367
+ if (updated.text !== current) {
2368
+ await writeFile(configPath, updated.text, "utf8");
2369
+ }
2370
+ removedConfigTables.push(...updated.removedTables);
2371
+ }
2372
+ }
2373
+ catch (error) {
2374
+ warnings.push(`Could not update ${configPath}: ${error instanceof Error ? error.message : String(error)}`);
2375
+ }
2376
+ }
2377
+ const removedHistoryEntries = await cleanupLegacyCodexTextState(warnings);
2378
+ return {
2379
+ legacy_removed_paths: Array.from(new Set(removedPaths)),
2380
+ legacy_removed_config_tables: removedConfigTables,
2381
+ legacy_removed_history_entries: removedHistoryEntries,
2382
+ legacy_stopped_processes: stoppedProcesses,
2383
+ ...(warnings.length > 0 ? { warnings } : {})
2384
+ };
2385
+ }
2386
+ async function stopLegacyCodexPluginProcesses(warnings) {
2387
+ if (process.platform === "win32")
2388
+ return 0;
2389
+ try {
2390
+ const ps = await runLocalProcess("ps", ["-axo", "pid=,command="]);
2391
+ if (ps.code !== 0)
2392
+ return 0;
2393
+ let stopped = 0;
2394
+ for (const line of ps.stdout.split("\n")) {
2395
+ const match = /^\s*(\d+)\s+(.+)$/.exec(line);
2396
+ if (!match)
2397
+ continue;
2398
+ const pid = Number(match[1]);
2399
+ const command = match[2] || "";
2400
+ if (!isLegacyCodexPluginProcess(command))
2401
+ continue;
2402
+ try {
2403
+ process.kill(pid, "SIGTERM");
2404
+ stopped += 1;
2405
+ }
2406
+ catch (error) {
2407
+ warnings.push(`Could not stop legacy Codex plugin process ${pid}: ${error instanceof Error ? error.message : String(error)}`);
2408
+ }
2409
+ }
2410
+ return stopped;
2411
+ }
2412
+ catch (error) {
2413
+ warnings.push(`Could not scan legacy Codex plugin processes: ${error instanceof Error ? error.message : String(error)}`);
2414
+ return 0;
2415
+ }
2416
+ }
2417
+ function isLegacyCodexPluginProcess(command) {
2418
+ const trimmed = command.trim();
2419
+ return /^\/.*\/dbt-agent-mcp-bridge(?:\s|$)/.test(trimmed)
2420
+ || /^dbt-agent-mcp-bridge(?:\s|$)/.test(trimmed)
2421
+ || legacyDevelopmentBoardRuntimeProcessPatterns().some((pattern) => pattern.test(trimmed));
2422
+ }
2423
+ function legacyDevelopmentBoardRuntimeProcessPatterns() {
2424
+ const home = escapeRegExp(homedir());
2425
+ return [
2426
+ new RegExp(`^${home}/Library/development-board-toolchain/agent/bin/dbt-agentd(?:\\s|$)`),
2427
+ new RegExp(`^${home}/Library/Application Support/development-board-toolchain/agent/bin/dbt-agentd(?:\\s|$)`),
2428
+ new RegExp(`^${home}/Library/development-board-toolchain/runtime/dbtctl\\s+status(?:\\s|$)`),
2429
+ new RegExp(`^${home}/Library/Application Support/development-board-toolchain/runtime/dbtctl\\s+status(?:\\s|$)`),
2430
+ new RegExp(`^${home}/.*?/DBT-Agent\\.app/Contents/MacOS/DBT-Agent(?:\\s|$)`)
2431
+ ];
2432
+ }
2433
+ function legacyDevelopmentBoardRuntimePluginPaths() {
2434
+ return [
2435
+ join(homedir(), "Library", "development-board-toolchain", "runtime", "editor_plugins"),
2436
+ join(homedir(), "Library", "development-board-toolchain", "runtime", "opencode_plugin"),
2437
+ join(homedir(), "Library", "Application Support", "development-board-toolchain", "runtime", "editor_plugins"),
2438
+ join(homedir(), "Library", "Application Support", "development-board-toolchain", "runtime", "opencode_plugin")
2439
+ ];
2440
+ }
2441
+ function legacyCodexLocalMarketplacePaths() {
2442
+ return Array.from(LEGACY_CODEX_MARKETPLACE_NAMES)
2443
+ .filter((name) => name !== CODEX_MARKETPLACE_NAME)
2444
+ .map((name) => join(defaultCodexHome(), "local-marketplaces", name));
2445
+ }
2446
+ async function discoverLegacyHomeAgentsMarketplacePaths(warnings) {
2447
+ const paths = [];
2448
+ const pluginRoot = join(homedir(), ".agents", "plugins");
2449
+ try {
2450
+ const entries = await readdir(pluginRoot, { withFileTypes: true });
2451
+ for (const entry of entries) {
2452
+ if (!entry.isFile() || !entry.name.startsWith("marketplace.json"))
2453
+ continue;
2454
+ const filePath = join(pluginRoot, entry.name);
2455
+ try {
2456
+ const current = await readFile(filePath, "utf8");
2457
+ if (isLegacyHomeAgentsMarketplace(current)) {
2458
+ paths.push(filePath);
2459
+ }
2460
+ }
2461
+ catch (error) {
2462
+ warnings.push(`Could not inspect legacy Codex home marketplace ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
2463
+ }
2464
+ }
2465
+ }
2466
+ catch {
2467
+ return paths;
2468
+ }
2469
+ return paths;
2470
+ }
2471
+ function isLegacyHomeAgentsMarketplace(text) {
2472
+ return marketplaceTextHasLegacyCodexPlugin(text);
2473
+ }
2474
+ async function discoverLegacyCodexProjectMarketplacePaths(warnings) {
2475
+ const configPath = codexConfigPath();
2476
+ let text = "";
2477
+ try {
2478
+ text = await readFile(configPath, "utf8");
2479
+ }
2480
+ catch {
2481
+ return [];
2482
+ }
2483
+ const paths = new Set();
2484
+ for (const projectPath of legacyCodexProjectPathsFromConfig(text)) {
2485
+ for (const candidate of [
2486
+ join(projectPath, ".agents", "plugins", "marketplace.json"),
2487
+ join(projectPath, "platform_plugin", ".agents", "plugins", "marketplace.json"),
2488
+ join(projectPath, "platform_plugins", "codex_plugin", ".agents", "plugins", "marketplace.json")
2489
+ ]) {
2490
+ try {
2491
+ if (!await pathExists(candidate))
2492
+ continue;
2493
+ const current = await readFile(candidate, "utf8");
2494
+ if (marketplaceTextHasLegacyCodexPlugin(current)) {
2495
+ paths.add(candidate);
2496
+ }
2497
+ }
2498
+ catch (error) {
2499
+ warnings.push(`Could not inspect legacy Codex project marketplace ${candidate}: ${error instanceof Error ? error.message : String(error)}`);
2500
+ }
2501
+ }
2502
+ }
2503
+ return Array.from(paths);
2504
+ }
2505
+ function legacyCodexProjectPathsFromConfig(text) {
2506
+ const paths = [];
2507
+ const lines = text.match(/[^\n]*\n|[^\n]+$/g) ?? [];
2508
+ for (const line of lines) {
2509
+ const table = parseTomlTableHeader(line);
2510
+ const match = table ? /^projects\."([^"]+)"$/.exec(table) : undefined;
2511
+ if (match?.[1] && /DBT-Agent-Project|development-board-toolchain|dbt-agent/i.test(match[1])) {
2512
+ paths.push(match[1].replace(/\\"/g, '"'));
2513
+ }
2514
+ }
2515
+ return paths;
2516
+ }
2517
+ function marketplaceTextHasLegacyCodexPlugin(text) {
2518
+ try {
2519
+ const parsed = JSON.parse(text);
2520
+ const marketplaceName = typeof parsed.name === "string" ? parsed.name : "";
2521
+ const marketplaceDisplayName = typeof parsed.interface?.displayName === "string" ? parsed.interface.displayName : "";
2522
+ const marketplaceLooksLegacy = legacyTextHasCodexPluginResidue(marketplaceName)
2523
+ || legacyTextHasCodexPluginResidue(marketplaceDisplayName)
2524
+ || LEGACY_CODEX_MARKETPLACE_NAMES.has(marketplaceName);
2525
+ return (parsed.plugins ?? []).some((plugin) => {
2526
+ const values = [
2527
+ plugin.name,
2528
+ plugin.category,
2529
+ plugin.source?.path,
2530
+ plugin.interface?.displayName
2531
+ ].filter((value) => typeof value === "string");
2532
+ return values.some(legacyTextHasCodexPluginResidue) || marketplaceLooksLegacy && values.some((value) => /embed-labs|dbt|development-board/i.test(value));
2533
+ });
2534
+ }
2535
+ catch {
2536
+ return legacyTextHasCodexPluginResidue(text);
2537
+ }
2538
+ }
2539
+ async function discoverLegacyCodexCachePaths(targetRoot) {
2540
+ const paths = [];
2541
+ const cacheRoot = join(targetRoot, "cache");
2542
+ try {
2543
+ const marketplaces = await readdir(cacheRoot, { withFileTypes: true });
2544
+ for (const entry of marketplaces) {
2545
+ if (!entry.isDirectory())
2546
+ continue;
2547
+ if (LEGACY_CODEX_MARKETPLACE_NAMES.has(entry.name)) {
2548
+ paths.push(join(cacheRoot, entry.name, CODEX_PLUGIN_NAME));
2549
+ }
2550
+ for (const pluginName of LEGACY_CODEX_PLUGIN_NAMES) {
2551
+ paths.push(join(cacheRoot, entry.name, pluginName));
2552
+ }
2553
+ }
2554
+ }
2555
+ catch {
2556
+ return paths;
2557
+ }
2558
+ return paths;
2559
+ }
2560
+ async function cleanupLegacyCodexTextState(warnings) {
2561
+ let removed = 0;
2562
+ removed += await cleanupLegacyCodexTextFile(join(defaultCodexHome(), "history.jsonl"), warnings);
2563
+ removed += await cleanupLegacyCodexTextFile(join(defaultCodexHome(), "session_index.jsonl"), warnings);
2564
+ removed += await cleanupLegacyCodexTextFile(join(defaultCodexHome(), "rules", "default.rules"), warnings);
2565
+ return removed;
2566
+ }
2567
+ async function cleanupLegacyCodexTextFile(filePath, warnings) {
2568
+ try {
2569
+ if (!await pathExists(filePath))
2570
+ return 0;
2571
+ const current = await readFile(filePath, "utf8");
2572
+ const lines = current.split("\n");
2573
+ let removed = 0;
2574
+ const kept = lines.filter((line) => {
2575
+ if (!line)
2576
+ return true;
2577
+ if (isLegacyCodexHistoryMention(line)) {
2578
+ removed += 1;
2579
+ return false;
2580
+ }
2581
+ return true;
2582
+ });
2583
+ if (removed > 0) {
2584
+ await writeFile(filePath, kept.join("\n"), "utf8");
2585
+ }
2586
+ return removed;
2587
+ }
2588
+ catch (error) {
2589
+ warnings.push(`Could not clean Codex legacy text state ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
2590
+ return 0;
2591
+ }
2592
+ }
2593
+ function isLegacyCodexHistoryMention(line) {
2594
+ return line.includes("plugin://dbt-agent@plugins")
2595
+ || line.includes("plugin://dbt-agent@embed-labs")
2596
+ || line.includes("development-board-toolchain-dev")
2597
+ || line.includes("development-board-toolchain")
2598
+ || /plugin:\/\/Dbt Agent@/i.test(line)
2599
+ || /plugin:\/\/deve@/i.test(line)
2600
+ || /dbt-agent/i.test(line);
2601
+ }
2602
+ function removeLegacyCodexConfigTables(text) {
2603
+ const lines = text.match(/[^\n]*\n|[^\n]+$/g) ?? [];
2604
+ const output = [];
2605
+ const removedTables = [];
2606
+ let skipping = false;
2607
+ for (const line of lines) {
2608
+ const table = parseTomlTableHeader(line);
2609
+ if (table) {
2610
+ skipping = isLegacyCodexConfigTable(table);
2611
+ if (skipping) {
2612
+ removedTables.push(table);
2613
+ continue;
2614
+ }
2615
+ }
2616
+ if (!skipping) {
2617
+ output.push(line);
2618
+ }
2619
+ }
2620
+ return { text: output.join("").replace(/\n{3,}/g, "\n\n"), removedTables };
2621
+ }
2622
+ function parseTomlTableHeader(line) {
2623
+ const match = /^\s*\[([^\]]+)\]\s*(?:#.*)?$/.exec(line);
2624
+ return match?.[1]?.trim();
2625
+ }
2626
+ function isLegacyCodexConfigTable(table) {
2627
+ return /^plugins\."dbt-agent@[^"]+"$/.test(table)
2628
+ || /^plugins\."Dbt Agent@[^"]+"$/i.test(table)
2629
+ || /^plugins\."deve@[^"]+"$/i.test(table)
2630
+ || isLegacyEmbedLabsCodexMarketplaceConfigTable(table)
2631
+ || table === "mcp_servers.dbt-agent"
2632
+ || table.startsWith("mcp_servers.dbt-agent.")
2633
+ || table === 'mcp_servers."dbt-agent"'
2634
+ || table.startsWith('mcp_servers."dbt-agent".')
2635
+ || table === "mcp_servers.deve"
2636
+ || table.startsWith("mcp_servers.deve.")
2637
+ || /^projects\."[^"]*\/DBT-Agent-Project(?:\/[^"]*)?"$/.test(table);
2638
+ }
2639
+ function isLegacyEmbedLabsCodexMarketplaceConfigTable(table) {
2640
+ for (const marketplaceName of LEGACY_CODEX_MARKETPLACE_NAMES) {
2641
+ if (table === `marketplaces.${marketplaceName}`) {
2642
+ return true;
2643
+ }
2644
+ if (table === `plugins."${CODEX_PLUGIN_NAME}@${marketplaceName}"`) {
2645
+ return true;
2646
+ }
2647
+ }
2648
+ return false;
2649
+ }
2650
+ function legacyTextHasCodexPluginResidue(value) {
2651
+ return /dbt-agent|Dbt Agent|development-board-toolchain|development-board-toolchain-dev/i.test(value)
2652
+ || value === "deve"
2653
+ || value.replace(/\\/g, "/").includes("/plugins/deve")
2654
+ || value.includes("plugin://deve@");
2655
+ }
2656
+ function legacyCodexCleanupWarning(cleanup) {
2657
+ const parts = [];
2658
+ if (cleanup.legacy_removed_paths.length > 0) {
2659
+ parts.push(`removed ${cleanup.legacy_removed_paths.length} stale/legacy Codex plugin path(s)`);
2660
+ }
2661
+ if (cleanup.legacy_removed_config_tables?.length) {
2662
+ parts.push(`removed ${cleanup.legacy_removed_config_tables.length} legacy Codex config table(s)`);
2663
+ }
2664
+ if (cleanup.legacy_removed_history_entries) {
2665
+ parts.push(`removed ${cleanup.legacy_removed_history_entries} legacy Codex text-state mention(s)`);
2666
+ }
2667
+ if (cleanup.legacy_stopped_processes) {
2668
+ parts.push(`stopped ${cleanup.legacy_stopped_processes} legacy Codex plugin process(es)`);
2669
+ }
2670
+ if (cleanup.warnings?.length) {
2671
+ parts.push(`cleanup warning(s): ${cleanup.warnings.join("; ")}`);
2672
+ }
2673
+ return parts.length > 0 ? `Codex plugin cleanup: ${parts.join(", ")}.` : undefined;
2674
+ }
2675
+ async function cleanupLegacyOpenCodePluginRemnants(targetRoot, globalInstall) {
2676
+ const removedPaths = [];
2677
+ const warnings = [];
2678
+ const legacyPaths = [
2679
+ join(targetRoot, "plugins", "development-board-toolchain.js"),
2680
+ join(targetRoot, "plugins", "development-board-toolchain-dev.js"),
2681
+ join(targetRoot, "plugins", "dbt-agent.js"),
2682
+ join(targetRoot, "plugins", "Dbt Agent.js"),
2683
+ join(targetRoot, "plugins", "deve.js"),
2684
+ join(targetRoot, "plugins", "deve"),
2685
+ join(targetRoot, "node_modules", "development-board-toolchain"),
2686
+ join(targetRoot, "node_modules", "development-board-toolchain-dev"),
2687
+ join(targetRoot, "node_modules", "dbt-agent")
2688
+ ];
2689
+ if (globalInstall) {
2690
+ legacyPaths.push(join(targetRoot, "plugins", "embed-labs.js"));
2691
+ legacyPaths.push(...await discoverLegacyOpenCodeBackupPaths(targetRoot, warnings));
2692
+ legacyPaths.push(...await discoverLegacyOpenCodePluginCachePaths(targetRoot, warnings));
2693
+ }
2694
+ for (const candidate of legacyPaths) {
2695
+ try {
2696
+ if (await pathExists(candidate)) {
2697
+ await rm(candidate, { recursive: true, force: true });
2698
+ removedPaths.push(candidate);
2699
+ }
2700
+ }
2701
+ catch (error) {
2702
+ warnings.push(`Could not remove ${candidate}: ${error instanceof Error ? error.message : String(error)}`);
2703
+ }
2704
+ }
2705
+ return {
2706
+ legacy_removed_paths: removedPaths,
2707
+ legacy_removed_config_entries: [],
2708
+ ...(warnings.length > 0 ? { warnings } : {})
2709
+ };
2710
+ }
2711
+ async function discoverLegacyOpenCodeBackupPaths(targetRoot, warnings) {
2712
+ const paths = [];
2713
+ try {
2714
+ const entries = await readdir(targetRoot, { withFileTypes: true });
2715
+ for (const entry of entries) {
2716
+ if (!entry.isFile() || !entry.name.includes(".bak"))
2717
+ continue;
2718
+ const filePath = join(targetRoot, entry.name);
2719
+ try {
2720
+ const current = await readFile(filePath, "utf8");
2721
+ if (legacyTextHasCodexPluginResidue(current)) {
2722
+ paths.push(filePath);
2723
+ }
2724
+ }
2725
+ catch (error) {
2726
+ warnings.push(`Could not inspect legacy OpenCode backup ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
2727
+ }
2728
+ }
2729
+ }
2730
+ catch {
2731
+ return paths;
2732
+ }
2733
+ return paths;
2734
+ }
2735
+ async function discoverLegacyOpenCodePluginCachePaths(targetRoot, warnings) {
2736
+ const paths = [];
2737
+ const cacheRoot = join(targetRoot, ".embed-labs", "plugin-cache");
2738
+ try {
2739
+ const entries = await readdir(cacheRoot, { withFileTypes: true });
2740
+ for (const entry of entries) {
2741
+ if (!entry.isFile())
2742
+ continue;
2743
+ if (/^(embed-labs|embed-labs-opencode-plugin)-\d+\.\d+\.\d+\.tgz$/.test(entry.name)) {
2744
+ paths.push(join(cacheRoot, entry.name));
2745
+ }
2746
+ }
2747
+ }
2748
+ catch (error) {
2749
+ if (error.code !== "ENOENT") {
2750
+ warnings.push(`Could not inspect OpenCode plugin cache ${cacheRoot}: ${error instanceof Error ? error.message : String(error)}`);
2751
+ }
2752
+ }
2753
+ return paths;
2754
+ }
2755
+ function legacyOpenCodeCleanupWarning(cleanup) {
2756
+ const parts = [];
2757
+ if (cleanup.legacy_removed_paths.length > 0) {
2758
+ parts.push(`removed ${cleanup.legacy_removed_paths.length} legacy OpenCode plugin path(s)`);
2759
+ }
2760
+ if (cleanup.legacy_removed_config_entries?.length) {
2761
+ parts.push(`removed ${cleanup.legacy_removed_config_entries.length} legacy OpenCode config entry(s)`);
2762
+ }
2763
+ if (cleanup.warnings?.length) {
2764
+ parts.push(`cleanup warning(s): ${cleanup.warnings.join("; ")}`);
2765
+ }
2766
+ return parts.length > 0 ? `Legacy OpenCode cleanup: ${parts.join(", ")}.` : undefined;
2767
+ }
2768
+ function combineWarnings(...warnings) {
2769
+ const actual = warnings.filter((warning) => Boolean(warning));
2770
+ return actual.length > 0 ? actual.join(" ") : undefined;
1804
2771
  }
1805
2772
  async function maybeRegisterCodexMcp(parsed, targetRoot, targetPath) {
1806
2773
  const explicitTarget = Boolean(stringFlag(parsed, "target") || stringFlag(parsed, "codex-target"));
@@ -1861,6 +2828,115 @@ async function maybeRegisterCodexMcp(parsed, targetRoot, targetPath) {
1861
2828
  const warning = await upsertCodexMcpRuntimeConfig(bridgePath);
1862
2829
  return warning ? { registered: true, warning } : { registered: true };
1863
2830
  }
2831
+ async function maybeRegisterCodexMarketplace(parsed, targetRoot, targetPath) {
2832
+ const explicitTarget = Boolean(stringFlag(parsed, "target") || stringFlag(parsed, "codex-target"));
2833
+ if (explicitTarget && process.env.EMBED_CODEX_MARKETPLACE_REGISTER !== "1") {
2834
+ return {
2835
+ registered: false,
2836
+ warning: "Codex plugin marketplace entry was not registered because a custom target was used. Set EMBED_CODEX_MARKETPLACE_REGISTER=1 to register it anyway."
2837
+ };
2838
+ }
2839
+ if (resolve(targetRoot) !== resolve(defaultCodexPluginRoot()) && process.env.EMBED_CODEX_MARKETPLACE_REGISTER !== "1") {
2840
+ return {
2841
+ registered: false,
2842
+ warning: "Codex plugin marketplace entry was not registered because the install target is not the default Codex plugin root."
2843
+ };
2844
+ }
2845
+ const marketplacePath = defaultCodexLocalMarketplaceRoot();
2846
+ const marketplacePluginPath = join(marketplacePath, "plugins", CODEX_PLUGIN_NAME);
2847
+ try {
2848
+ if (!await pathExists(join(targetPath, ".codex-plugin", "plugin.json"))) {
2849
+ return {
2850
+ registered: false,
2851
+ warning: `Codex plugin manifest was not found at ${join(targetPath, ".codex-plugin", "plugin.json")}.`
2852
+ };
2853
+ }
2854
+ await rm(marketplacePluginPath, { recursive: true, force: true });
2855
+ await mkdir(dirname(marketplacePluginPath), { recursive: true });
2856
+ await cp(targetPath, marketplacePluginPath, { recursive: true });
2857
+ await refreshCodexPluginCache(targetPath);
2858
+ await writeCodexLocalMarketplaceManifest(marketplacePath);
2859
+ const warning = await upsertCodexPluginMarketplaceConfig(marketplacePath);
2860
+ return warning ? { registered: true, marketplacePath, warning } : { registered: true, marketplacePath };
2861
+ }
2862
+ catch (error) {
2863
+ return {
2864
+ registered: false,
2865
+ marketplacePath,
2866
+ warning: `Could not register Codex plugin marketplace entry: ${error instanceof Error ? error.message : String(error)}`
2867
+ };
2868
+ }
2869
+ }
2870
+ function defaultCodexLocalMarketplaceRoot() {
2871
+ return join(defaultCodexHome(), "local-marketplaces", CODEX_PLUGIN_NAME);
2872
+ }
2873
+ async function refreshCodexPluginCache(targetPath) {
2874
+ const manifestPath = join(targetPath, ".codex-plugin", "plugin.json");
2875
+ const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
2876
+ const version = typeof manifest.version === "string" && manifest.version.trim()
2877
+ ? manifest.version.trim()
2878
+ : "local";
2879
+ const cachePluginRoot = join(defaultCodexPluginRoot(), "cache", CODEX_MARKETPLACE_NAME, CODEX_PLUGIN_NAME);
2880
+ const cacheVersionPath = join(cachePluginRoot, version);
2881
+ await rm(cachePluginRoot, { recursive: true, force: true });
2882
+ await mkdir(dirname(cacheVersionPath), { recursive: true });
2883
+ await cp(targetPath, cacheVersionPath, { recursive: true });
2884
+ }
2885
+ async function writeCodexLocalMarketplaceManifest(marketplacePath) {
2886
+ const manifestPath = join(marketplacePath, ".agents", "plugins", "marketplace.json");
2887
+ const manifest = {
2888
+ name: CODEX_MARKETPLACE_NAME,
2889
+ interface: {
2890
+ displayName: "Embed Labs"
2891
+ },
2892
+ plugins: [
2893
+ {
2894
+ name: CODEX_PLUGIN_NAME,
2895
+ source: {
2896
+ source: "local",
2897
+ path: `./plugins/${CODEX_PLUGIN_NAME}`
2898
+ },
2899
+ policy: {
2900
+ installation: "AVAILABLE",
2901
+ authentication: "ON_USE"
2902
+ },
2903
+ category: "Developer Tools"
2904
+ }
2905
+ ]
2906
+ };
2907
+ await mkdir(dirname(manifestPath), { recursive: true });
2908
+ await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
2909
+ }
2910
+ async function upsertCodexPluginMarketplaceConfig(marketplacePath) {
2911
+ const configPath = codexConfigPath();
2912
+ try {
2913
+ await mkdir(dirname(configPath), { recursive: true });
2914
+ let text = "";
2915
+ try {
2916
+ text = await readFile(configPath, "utf8");
2917
+ }
2918
+ catch {
2919
+ text = "";
2920
+ }
2921
+ const original = text;
2922
+ const cleaned = removeLegacyCodexConfigTables(text).text;
2923
+ let updated = upsertTomlTableKeys(cleaned, `marketplaces.${CODEX_MARKETPLACE_NAME}`, {
2924
+ source_type: tomlString("local"),
2925
+ source: tomlString(marketplacePath),
2926
+ last_updated: tomlString(new Date().toISOString().replace(/\.\d{3}Z$/, "Z"))
2927
+ });
2928
+ updated = upsertTomlTableKeys(updated, `plugins."${CODEX_PLUGIN_NAME}@${CODEX_MARKETPLACE_NAME}"`, {
2929
+ enabled: "true"
2930
+ });
2931
+ if (updated !== original) {
2932
+ await writeFile(configPath, updated, "utf8");
2933
+ }
2934
+ return undefined;
2935
+ }
2936
+ catch (error) {
2937
+ return `${configPath} could not be updated with the Embed Labs plugin marketplace entry: ${error instanceof Error ? error.message : String(error)}`;
2938
+ }
2939
+ }
1864
2940
  function codexMcpAlreadyRegistered(stdout, bridgePath, cloudUrl, authFile, embedCliBin) {
1865
2941
  try {
1866
2942
  const parsed = JSON.parse(stdout);
@@ -1972,11 +3048,160 @@ async function resolveExecutableOnPath(name) {
1972
3048
  }
1973
3049
  return undefined;
1974
3050
  }
3051
+ async function runBridgeStart(parsed) {
3052
+ const host = stringFlag(parsed, "host");
3053
+ const port = numberFlag(parsed, "port");
3054
+ const bridge = await resolveBridgeLauncher();
3055
+ const args = [...bridge.args];
3056
+ if (host) {
3057
+ args.push("--host", host);
3058
+ }
3059
+ if (port !== undefined) {
3060
+ args.push("--port", String(port));
3061
+ }
3062
+ const env = {
3063
+ ...process.env,
3064
+ ...(host ? { EMBED_BRIDGE_HOST: host } : {}),
3065
+ ...(port !== undefined ? { EMBED_BRIDGE_PORT: String(port) } : {})
3066
+ };
3067
+ const child = spawn(bridge.command, args, {
3068
+ stdio: "inherit",
3069
+ env
3070
+ });
3071
+ const forwardSignal = (signal) => {
3072
+ if (!child.killed) {
3073
+ child.kill(signal);
3074
+ }
3075
+ };
3076
+ process.once("SIGINT", forwardSignal);
3077
+ process.once("SIGTERM", forwardSignal);
3078
+ return await new Promise((resolveCode) => {
3079
+ child.on("error", (error) => {
3080
+ process.off("SIGINT", forwardSignal);
3081
+ process.off("SIGTERM", forwardSignal);
3082
+ console.error(error instanceof Error ? error.message : String(error));
3083
+ resolveCode(1);
3084
+ });
3085
+ child.on("close", (code, signal) => {
3086
+ process.off("SIGINT", forwardSignal);
3087
+ process.off("SIGTERM", forwardSignal);
3088
+ if (signal === "SIGINT" || signal === "SIGTERM") {
3089
+ resolveCode(0);
3090
+ }
3091
+ else {
3092
+ resolveCode(code ?? 0);
3093
+ }
3094
+ });
3095
+ });
3096
+ }
3097
+ async function resolveBridgeLauncher() {
3098
+ const explicitBinary = process.env.EMBED_LOCAL_BRIDGE_BINARY?.trim();
3099
+ if (explicitBinary) {
3100
+ try {
3101
+ await access(explicitBinary, constants.X_OK);
3102
+ return { command: explicitBinary, args: [] };
3103
+ }
3104
+ catch {
3105
+ // Fall through so the package launcher can print its clearer repair message.
3106
+ }
3107
+ }
3108
+ const pathBinary = await resolveExecutableOnPath(process.platform === "win32" ? "embed-local-bridge.cmd" : "embed-local-bridge");
3109
+ if (pathBinary) {
3110
+ return { command: pathBinary, args: [] };
3111
+ }
3112
+ const packageLauncher = await resolveBridgePackageLauncher();
3113
+ if (packageLauncher) {
3114
+ return { command: process.execPath, args: [packageLauncher] };
3115
+ }
3116
+ return {
3117
+ command: process.execPath,
3118
+ args: [resolve(SOURCE_CHECKOUT_ROOT, "packages", "local-bridge", "dist", "index.js")]
3119
+ };
3120
+ }
3121
+ async function resolveBridgePackageLauncher() {
3122
+ const candidates = [];
3123
+ try {
3124
+ const packageJson = require.resolve("@embed-labs/local-bridge/package.json");
3125
+ candidates.push(join(dirname(packageJson), "dist", "index.js"));
3126
+ }
3127
+ catch {
3128
+ // Source checkout fallback below.
3129
+ }
3130
+ candidates.push(resolve(SOURCE_CHECKOUT_ROOT, "packages", "local-bridge", "dist", "index.js"));
3131
+ for (const candidate of candidates) {
3132
+ try {
3133
+ await access(candidate, constants.R_OK);
3134
+ return candidate;
3135
+ }
3136
+ catch {
3137
+ // Keep looking.
3138
+ }
3139
+ }
3140
+ return undefined;
3141
+ }
1975
3142
  function defaultOpenCodeRoot() {
1976
- return join(process.cwd(), ".opencode");
3143
+ return globalOpenCodeRoot();
3144
+ }
3145
+ function globalOpenCodeRoot() {
3146
+ return join(homedir(), ".config", "opencode");
3147
+ }
3148
+ function isGlobalOpenCodeRoot(targetRoot) {
3149
+ return resolve(targetRoot) === resolve(globalOpenCodeRoot());
3150
+ }
3151
+ async function ensureOpenCodeGlobalPluginConfig() {
3152
+ const configPath = join(globalOpenCodeRoot(), "opencode.json");
3153
+ let existing = {};
3154
+ try {
3155
+ const parsed = JSON.parse(await readFile(configPath, "utf8"));
3156
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
3157
+ existing = parsed;
3158
+ }
3159
+ }
3160
+ catch {
3161
+ existing = {};
3162
+ }
3163
+ const configured = Array.isArray(existing.plugin)
3164
+ ? existing.plugin.filter((item) => typeof item === "string")
3165
+ : Array.isArray(existing.plugins)
3166
+ ? existing.plugins.filter((item) => typeof item === "string")
3167
+ : [];
3168
+ const removed = configured.filter(isLegacyOpenCodePluginConfigEntry);
3169
+ const cleaned = configured.filter((item) => !isLegacyOpenCodePluginConfigEntry(item));
3170
+ if (!cleaned.includes("embed-labs")) {
3171
+ cleaned.push("embed-labs");
3172
+ }
3173
+ await writeFile(configPath, `${JSON.stringify({
3174
+ ...existing,
3175
+ plugin: cleaned,
3176
+ plugins: undefined
3177
+ }, null, 2)}\n`, "utf8");
3178
+ return removed;
3179
+ }
3180
+ function isLegacyOpenCodePluginConfigEntry(item) {
3181
+ const normalized = item.trim().replace(/\\/g, "/");
3182
+ const pathOnly = normalized.split(/[?#]/, 1)[0] || normalized;
3183
+ return normalized === "dbt-agent"
3184
+ || normalized === "Dbt Agent"
3185
+ || normalized === "deve"
3186
+ || normalized === "development-board-toolchain"
3187
+ || normalized === "development-board-toolchain-dev"
3188
+ || normalized === "./plugins/deve"
3189
+ || normalized === "./plugins/deve.js"
3190
+ || /(?:^|\/)plugins\/deve(?:\.js)?$/.test(pathOnly)
3191
+ || /(?:^|\/)plugins\/dbt-agent(?:\.js)?$/.test(pathOnly)
3192
+ || normalized === "./plugins/development-board-toolchain"
3193
+ || normalized === "./plugins/development-board-toolchain.js"
3194
+ || normalized === "./plugins/development-board-toolchain-dev"
3195
+ || normalized === "./plugins/development-board-toolchain-dev.js"
3196
+ || pathOnly.endsWith("/plugins/development-board-toolchain")
3197
+ || pathOnly.endsWith("/plugins/development-board-toolchain.js")
3198
+ || pathOnly.endsWith("/plugins/development-board-toolchain-dev")
3199
+ || pathOnly.endsWith("/plugins/development-board-toolchain-dev.js")
3200
+ || normalized.includes("dbt-agent")
3201
+ || normalized.includes("development-board-toolchain");
1977
3202
  }
1978
3203
  async function openCodeDuplicatePluginWarning(targetRoot) {
1979
- const globalRoot = join(homedir(), ".config", "opencode");
3204
+ const globalRoot = globalOpenCodeRoot();
1980
3205
  if (resolve(targetRoot) === resolve(globalRoot))
1981
3206
  return undefined;
1982
3207
  const configPath = join(globalRoot, "opencode.json");
@@ -2008,6 +3233,21 @@ async function localPluginVersion(kind) {
2008
3233
  return undefined;
2009
3234
  }
2010
3235
  }
3236
+ async function installedCodexPluginVersion(pluginPath) {
3237
+ return await readPackageVersion(join(pluginPath, ".codex-plugin", "plugin.json"));
3238
+ }
3239
+ async function installedOpenCodePluginVersion(targetRoot) {
3240
+ return await readPackageVersion(join(targetRoot, "node_modules", "embed-labs", "package.json"));
3241
+ }
3242
+ async function readPackageVersion(filePath) {
3243
+ try {
3244
+ const parsed = JSON.parse(await readFile(filePath, "utf8"));
3245
+ return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version.trim() : undefined;
3246
+ }
3247
+ catch {
3248
+ return undefined;
3249
+ }
3250
+ }
2011
3251
  async function localPluginSourcesAvailable() {
2012
3252
  return await pathExists(sourceCheckoutPath("platform_plugins", "codex_plugin", "plugins", "embed-labs", ".codex-plugin", "plugin.json"))
2013
3253
  && await pathExists(sourceCheckoutPath("platform_plugins", "opencode_plugin", "package.json"));
@@ -2066,19 +3306,65 @@ async function parseErrorResponse(response) {
2066
3306
  return undefined;
2067
3307
  }
2068
3308
  async function cloudAuthToken() {
3309
+ return (await cloudAuthConfig()).token;
3310
+ }
3311
+ async function cloudAuthConfig() {
2069
3312
  const envToken = process.env.EMBED_API_TOKEN?.trim();
2070
3313
  if (envToken) {
2071
- return envToken;
3314
+ const fileConfig = await readLocalAuthFile();
3315
+ return {
3316
+ ...fileConfig,
3317
+ token: envToken,
3318
+ profile: process.env.EMBED_AUTH_PROFILE ?? fileConfig.profile ?? "default",
3319
+ source: "env"
3320
+ };
2072
3321
  }
3322
+ const fileConfig = await readLocalAuthFile();
3323
+ return {
3324
+ ...fileConfig,
3325
+ token: fileConfig.token?.trim() || undefined,
3326
+ profile: fileConfig.profile ?? "default",
3327
+ source: fileConfig.token ? "file" : undefined
3328
+ };
3329
+ }
3330
+ async function readLocalAuthFile() {
2073
3331
  try {
2074
3332
  const parsed = JSON.parse(await readFile(DEFAULT_AUTH_FILE, "utf8"));
2075
- const fileToken = typeof parsed.token === "string" ? parsed.token.trim() : "";
2076
- return fileToken || undefined;
3333
+ return normalizeLocalAuthFile(parsed);
2077
3334
  }
2078
3335
  catch {
2079
- return undefined;
3336
+ return {};
2080
3337
  }
2081
3338
  }
3339
+ function normalizeLocalAuthFile(parsed) {
3340
+ const device = isJsonObject(parsed.device) ? parsed.device : undefined;
3341
+ const normalizedDevice = device && typeof device.device_id === "string" && typeof device.fingerprint_hash === "string" && typeof device.private_key_pem === "string"
3342
+ ? {
3343
+ device_id: device.device_id,
3344
+ fingerprint_hash: device.fingerprint_hash,
3345
+ private_key_pem: device.private_key_pem,
3346
+ public_key_pem: typeof device.public_key_pem === "string" ? device.public_key_pem : undefined,
3347
+ label: typeof device.label === "string" ? device.label : undefined,
3348
+ platform: typeof device.platform === "string" ? device.platform : undefined,
3349
+ arch: typeof device.arch === "string" ? device.arch : undefined,
3350
+ hostname_hash: typeof device.hostname_hash === "string" ? device.hostname_hash : undefined,
3351
+ registered_at: typeof device.registered_at === "string" ? device.registered_at : undefined
3352
+ }
3353
+ : undefined;
3354
+ return {
3355
+ profile: typeof parsed.profile === "string" ? parsed.profile : undefined,
3356
+ token: typeof parsed.token === "string" ? parsed.token.trim() : undefined,
3357
+ updated_at: typeof parsed.updated_at === "string" ? parsed.updated_at : undefined,
3358
+ account_id: typeof parsed.account_id === "string" ? parsed.account_id : undefined,
3359
+ api_key_id: typeof parsed.api_key_id === "string" ? parsed.api_key_id : undefined,
3360
+ device: normalizedDevice
3361
+ };
3362
+ }
3363
+ async function writeLocalAuthFile(config) {
3364
+ await mkdir(dirname(DEFAULT_AUTH_FILE), { recursive: true });
3365
+ await writeFile(DEFAULT_AUTH_FILE, `${JSON.stringify(config, null, 2)}\n`, "utf8");
3366
+ await chmod(DEFAULT_AUTH_FILE, 0o600).catch(() => undefined);
3367
+ }
2082
3368
  function serviceBaseUrl(url) {
2083
3369
  return url.replace(/\/+$/, "");
2084
3370
  }
@@ -2434,6 +3720,10 @@ function agentRunToolInputs(parsed) {
2434
3720
  if (remotePath.error) {
2435
3721
  return remotePath.error;
2436
3722
  }
3723
+ const runCommand = optionalTrimmedStringFlag(parsed, "run-command");
3724
+ if (runCommand.error) {
3725
+ return runCommand.error;
3726
+ }
2437
3727
  const user = optionalTrimmedStringFlag(parsed, "user");
2438
3728
  if (user.error) {
2439
3729
  return user.error;
@@ -2487,6 +3777,7 @@ function agentRunToolInputs(parsed) {
2487
3777
  user: user.value,
2488
3778
  artifact_path: artifact.value,
2489
3779
  remote_path: remotePath.value,
3780
+ run_command: runCommand.value,
2490
3781
  run: booleanFlag(parsed, "run") || undefined,
2491
3782
  timeout_seconds: timeout.value
2492
3783
  });
@@ -3411,15 +4702,56 @@ function boardKnowledgeFileRequest(parsed) {
3411
4702
  if (!knowledgePath.value) {
3412
4703
  return BOARD_KNOWLEDGE_FILE_USAGE;
3413
4704
  }
3414
- const outputPath = optionalTrimmedStringFlag(parsed, "output");
3415
- if (outputPath.error) {
3416
- return outputPath.error;
4705
+ const outputPath = optionalTrimmedStringFlag(parsed, "output");
4706
+ if (outputPath.error) {
4707
+ return outputPath.error;
4708
+ }
4709
+ return {
4710
+ templateId,
4711
+ source: source.value,
4712
+ path: knowledgePath.value,
4713
+ outputPath: outputPath.value
4714
+ };
4715
+ }
4716
+ function boardKnowledgeSearchRequest(parsed) {
4717
+ const unknownFlag = firstUnknownFlag(parsed, ["json", "query", "source", "limit"]);
4718
+ if (unknownFlag) {
4719
+ return `Unknown flag --${unknownFlag}. ${BOARD_KNOWLEDGE_SEARCH_USAGE}`;
4720
+ }
4721
+ const templateId = parsed.command[3]?.trim();
4722
+ if (!templateId) {
4723
+ return BOARD_KNOWLEDGE_SEARCH_USAGE;
4724
+ }
4725
+ const extra = parsed.command.slice(4);
4726
+ if (extra.length > 0) {
4727
+ return `Unexpected argument: ${extra[0]}. ${BOARD_KNOWLEDGE_SEARCH_USAGE}`;
4728
+ }
4729
+ const query = optionalTrimmedStringFlag(parsed, "query");
4730
+ if (query.error) {
4731
+ return query.error;
4732
+ }
4733
+ if (!query.value) {
4734
+ return BOARD_KNOWLEDGE_SEARCH_USAGE;
4735
+ }
4736
+ const source = optionalTrimmedStringFlag(parsed, "source");
4737
+ if (source.error) {
4738
+ return source.error;
4739
+ }
4740
+ if (source.value && !["board_pack", "build_template", "registry"].includes(source.value)) {
4741
+ return BOARD_KNOWLEDGE_SEARCH_USAGE;
4742
+ }
4743
+ const limit = optionalPositiveIntegerFlag(parsed, "limit");
4744
+ if (limit.error) {
4745
+ return limit.error;
4746
+ }
4747
+ if (limit.value !== undefined && limit.value > 10) {
4748
+ return "--limit must be between 1 and 10.";
3417
4749
  }
3418
4750
  return {
3419
4751
  templateId,
4752
+ query: query.value,
3420
4753
  source: source.value,
3421
- path: knowledgePath.value,
3422
- outputPath: outputPath.value
4754
+ limit: limit.value ?? 5
3423
4755
  };
3424
4756
  }
3425
4757
  function toolCallRequest(parsed) {
@@ -3469,7 +4801,7 @@ function isTaishanPiDeployCommand(parsed) {
3469
4801
  || area === "deploy";
3470
4802
  }
3471
4803
  function boardDeployTaishanPiRequest(parsed) {
3472
- const unknownFlag = firstUnknownFlag(parsed, ["json", "host", "user", "artifact", "artifact-path", "remote-path", "run", "timeout", "approve", "approved"]);
4804
+ const unknownFlag = firstUnknownFlag(parsed, ["json", "host", "user", "artifact", "artifact-path", "remote-path", "run-command", "run", "timeout", "approve", "approved"]);
3473
4805
  if (unknownFlag) {
3474
4806
  return `Unknown flag --${unknownFlag}. ${BOARD_DEPLOY_TAISHANPI_USAGE}`;
3475
4807
  }
@@ -3511,6 +4843,10 @@ function boardDeployTaishanPiRequest(parsed) {
3511
4843
  if (remotePath.error) {
3512
4844
  return remotePath.error;
3513
4845
  }
4846
+ const runCommand = optionalTrimmedStringFlag(parsed, "run-command");
4847
+ if (runCommand.error) {
4848
+ return runCommand.error;
4849
+ }
3514
4850
  const timeout = optionalNonNegativeIntegerFlag(parsed, "timeout");
3515
4851
  if (timeout.error) {
3516
4852
  return timeout.error;
@@ -3525,6 +4861,7 @@ function boardDeployTaishanPiRequest(parsed) {
3525
4861
  user: user.value,
3526
4862
  artifact_path: artifact.value,
3527
4863
  remote_path: remotePath.value,
4864
+ run_command: runCommand.value,
3528
4865
  run: booleanFlag(parsed, "run") || undefined,
3529
4866
  timeout_seconds: timeout.value,
3530
4867
  approved: approval.value || undefined
@@ -3537,30 +4874,272 @@ async function authLogin(parsed) {
3537
4874
  return fail("invalid_args", "Usage: embed auth login --token <token> [--profile default] [--json]");
3538
4875
  }
3539
4876
  const updatedAt = new Date().toISOString();
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 });
4877
+ const current = await readLocalAuthFile();
4878
+ const localDevice = await buildLocalDeviceAuth(parsed, current.device);
4879
+ const registration = await registerLocalDevice(token.trim(), localDevice.registration);
4880
+ if (!registration.ok) {
4881
+ return fail(registration.error.code, registration.error.message, {
4882
+ remediation: registration.error.remediation,
4883
+ details: registration.error.details
4884
+ });
4885
+ }
4886
+ const device = {
4887
+ device_id: registration.data.device.device_id,
4888
+ fingerprint_hash: localDevice.device.fingerprint_hash,
4889
+ private_key_pem: localDevice.device.private_key_pem,
4890
+ public_key_pem: localDevice.device.public_key_pem,
4891
+ label: registration.data.device.label ?? localDevice.device.label,
4892
+ platform: registration.data.device.platform ?? localDevice.device.platform,
4893
+ arch: registration.data.device.arch ?? localDevice.device.arch,
4894
+ hostname_hash: registration.data.device.hostname_hash ?? localDevice.device.hostname_hash,
4895
+ registered_at: registration.data.device.first_seen_at
4896
+ };
4897
+ await writeLocalAuthFile({
4898
+ profile,
4899
+ token: token.trim(),
4900
+ updated_at: updatedAt,
4901
+ account_id: registration.data.device.account_id,
4902
+ api_key_id: registration.data.device.api_key_id,
4903
+ device
4904
+ });
4905
+ return ok({
4906
+ authenticated: true,
4907
+ profile,
4908
+ source: "file",
4909
+ updated_at: updatedAt,
4910
+ account_id: registration.data.device.account_id,
4911
+ api_key_id: registration.data.device.api_key_id,
4912
+ device_id: device.device_id,
4913
+ device_fingerprint_hash: device.fingerprint_hash,
4914
+ device_label: device.label,
4915
+ device_registered_at: device.registered_at,
4916
+ device_private_key_configured: true
4917
+ });
3543
4918
  }
3544
4919
  async function authStatus() {
3545
- if (process.env.EMBED_API_TOKEN?.trim()) {
4920
+ const envToken = process.env.EMBED_API_TOKEN?.trim();
4921
+ const file = await readLocalAuthFile();
4922
+ const deviceIntegrity = file.device
4923
+ ? (await validateLocalDeviceIntegrity(file.device)).ok ? "ok" : "failed"
4924
+ : "unbound";
4925
+ if (envToken) {
3546
4926
  return {
3547
4927
  authenticated: true,
3548
4928
  profile: process.env.EMBED_AUTH_PROFILE ?? "default",
3549
- source: "env"
4929
+ source: "env",
4930
+ account_id: file.account_id,
4931
+ api_key_id: file.api_key_id,
4932
+ device_id: file.device?.device_id,
4933
+ device_fingerprint_hash: file.device?.fingerprint_hash,
4934
+ device_label: file.device?.label,
4935
+ device_registered_at: file.device?.registered_at,
4936
+ device_private_key_configured: Boolean(file.device?.private_key_pem),
4937
+ device_integrity: deviceIntegrity
3550
4938
  };
3551
4939
  }
4940
+ return {
4941
+ authenticated: Boolean(file.token?.trim()),
4942
+ profile: file.profile ?? "default",
4943
+ source: file.token ? "file" : undefined,
4944
+ updated_at: file.updated_at,
4945
+ account_id: file.account_id,
4946
+ api_key_id: file.api_key_id,
4947
+ device_id: file.device?.device_id,
4948
+ device_fingerprint_hash: file.device?.fingerprint_hash,
4949
+ device_label: file.device?.label,
4950
+ device_registered_at: file.device?.registered_at,
4951
+ device_private_key_configured: Boolean(file.device?.private_key_pem),
4952
+ device_integrity: deviceIntegrity
4953
+ };
4954
+ }
4955
+ async function authDeviceStatus(parsed) {
4956
+ const unknownFlag = firstUnknownFlag(parsed, ["json"]);
4957
+ const unexpected = parsed.command.slice(3);
4958
+ if (unknownFlag || unexpected.length > 0) {
4959
+ return fail("invalid_args", unknownFlag ? `Unknown flag --${unknownFlag}. ${AUTH_DEVICE_STATUS_USAGE}` : AUTH_DEVICE_STATUS_USAGE);
4960
+ }
4961
+ const local = await authStatus();
4962
+ const remote = local.authenticated ? await cloudGet("/v1/me/devices") : undefined;
4963
+ if (remote && !remote.ok) {
4964
+ return ok({ local });
4965
+ }
4966
+ return ok({ local, remote: remote?.data });
4967
+ }
4968
+ async function authDeviceList(parsed) {
4969
+ const unknownFlag = firstUnknownFlag(parsed, ["json"]);
4970
+ const unexpected = parsed.command.slice(3);
4971
+ if (unknownFlag || unexpected.length > 0) {
4972
+ return fail("invalid_args", unknownFlag ? `Unknown flag --${unknownFlag}. ${AUTH_DEVICE_LIST_USAGE}` : AUTH_DEVICE_LIST_USAGE);
4973
+ }
4974
+ return await cloudGet("/v1/me/devices");
4975
+ }
4976
+ async function authDeviceRevoke(parsed) {
4977
+ const unknownFlag = firstUnknownFlag(parsed, ["json"]);
4978
+ if (unknownFlag) {
4979
+ return fail("invalid_args", `Unknown flag --${unknownFlag}. ${AUTH_DEVICE_REVOKE_USAGE}`);
4980
+ }
4981
+ const id = commandId(parsed, 3, "device_id", AUTH_DEVICE_REVOKE_USAGE);
4982
+ if (!id.ok) {
4983
+ return fail("invalid_args", id.error);
4984
+ }
4985
+ return await cloudPost(`/v1/me/devices/${encodeURIComponent(id.value)}/revoke`, {});
4986
+ }
4987
+ async function authDeviceRename(parsed) {
4988
+ const unknownFlag = firstUnknownFlag(parsed, ["json", "label"]);
4989
+ if (unknownFlag) {
4990
+ return fail("invalid_args", `Unknown flag --${unknownFlag}. ${AUTH_DEVICE_RENAME_USAGE}`);
4991
+ }
4992
+ const id = commandId(parsed, 3, "device_id", AUTH_DEVICE_RENAME_USAGE);
4993
+ if (!id.ok) {
4994
+ return fail("invalid_args", id.error);
4995
+ }
4996
+ const label = stringFlag(parsed, "label");
4997
+ if (!label?.trim()) {
4998
+ return fail("invalid_args", AUTH_DEVICE_RENAME_USAGE);
4999
+ }
5000
+ const updated = await cloudPost(`/v1/me/devices/${encodeURIComponent(id.value)}`, { label: label.trim() });
5001
+ if (updated.ok) {
5002
+ const auth = await readLocalAuthFile();
5003
+ if (auth.device?.device_id === updated.data.device_id) {
5004
+ await writeLocalAuthFile({ ...auth, device: { ...auth.device, label: updated.data.label } });
5005
+ }
5006
+ }
5007
+ return updated;
5008
+ }
5009
+ async function buildLocalDeviceAuth(parsed, existing) {
5010
+ const fingerprint = await localHardwareFingerprint();
5011
+ const keyPair = existing?.fingerprint_hash === fingerprint.fingerprint_hash && existing.private_key_pem && existing.public_key_pem
5012
+ ? { privateKeyPem: existing.private_key_pem, publicKeyPem: existing.public_key_pem }
5013
+ : generateLocalDeviceKeyPair();
5014
+ const label = stringFlag(parsed, "label")?.trim()
5015
+ || existing?.label
5016
+ || `${fingerprint.platform} ${fingerprint.arch}`;
5017
+ const device = {
5018
+ device_id: existing?.fingerprint_hash === fingerprint.fingerprint_hash ? existing.device_id : "",
5019
+ fingerprint_hash: fingerprint.fingerprint_hash,
5020
+ private_key_pem: keyPair.privateKeyPem,
5021
+ public_key_pem: keyPair.publicKeyPem,
5022
+ label,
5023
+ platform: fingerprint.platform,
5024
+ arch: fingerprint.arch,
5025
+ hostname_hash: fingerprint.hostname_hash,
5026
+ registered_at: existing?.registered_at
5027
+ };
5028
+ return {
5029
+ device,
5030
+ registration: {
5031
+ fingerprint_hash: fingerprint.fingerprint_hash,
5032
+ public_key: keyPair.publicKeyPem,
5033
+ label,
5034
+ platform: fingerprint.platform,
5035
+ arch: fingerprint.arch,
5036
+ hostname_hash: fingerprint.hostname_hash,
5037
+ client_name: EMBED_CLIENT_NAME,
5038
+ client_version: EMBED_CLIENT_VERSION,
5039
+ metadata: {
5040
+ fingerprint_version: "v1",
5041
+ fingerprint_source: fingerprint.source
5042
+ }
5043
+ }
5044
+ };
5045
+ }
5046
+ function generateLocalDeviceKeyPair() {
5047
+ const { privateKey, publicKey } = generateKeyPairSync("ed25519");
5048
+ return {
5049
+ privateKeyPem: privateKey.export({ type: "pkcs8", format: "pem" }).toString(),
5050
+ publicKeyPem: publicKey.export({ type: "spki", format: "pem" }).toString()
5051
+ };
5052
+ }
5053
+ async function registerLocalDevice(token, body) {
3552
5054
  try {
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
5055
+ const bodyText = JSON.stringify(body);
5056
+ const headers = {
5057
+ "content-type": "application/json",
5058
+ authorization: `Bearer ${token}`
3559
5059
  };
5060
+ addCloudRequestSignature(headers, "POST", "/v1/me/devices/register", bodyText, token);
5061
+ const response = await fetch(`${serviceBaseUrl(DEFAULT_CLOUD_API_URL)}/v1/me/devices/register`, {
5062
+ method: "POST",
5063
+ headers,
5064
+ body: bodyText
5065
+ });
5066
+ const parsed = await response.json();
5067
+ return enrichCloudAuthFailure(parsed, true);
5068
+ }
5069
+ catch (error) {
5070
+ return fail("cloud_api_unreachable", error instanceof Error ? error.message : String(error), {
5071
+ remediation: `Check that embed cloud-api is running at ${DEFAULT_CLOUD_API_URL}. Start it with: npm run cloud-api`
5072
+ });
5073
+ }
5074
+ }
5075
+ async function localHardwareFingerprint() {
5076
+ cachedLocalHardwareFingerprint ??= localHardwareFingerprintUncached();
5077
+ return await cachedLocalHardwareFingerprint;
5078
+ }
5079
+ async function localHardwareFingerprintUncached() {
5080
+ const platformName = platform();
5081
+ const archName = arch();
5082
+ const raw = await localHardwareId(platformName);
5083
+ const fingerprintHash = createHash("sha256")
5084
+ .update(`embed-labs:device:v1:${platformName}:${raw.value}`)
5085
+ .digest("hex");
5086
+ const hostnameHash = createHash("sha256")
5087
+ .update(`embed-labs:hostname:v1:${hostname()}`)
5088
+ .digest("hex");
5089
+ return {
5090
+ fingerprint_hash: fingerprintHash,
5091
+ platform: platformName,
5092
+ arch: archName,
5093
+ hostname_hash: hostnameHash,
5094
+ source: raw.source
5095
+ };
5096
+ }
5097
+ async function localHardwareId(platformName) {
5098
+ if (platformName === "darwin") {
5099
+ const result = await runLocalProcess("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"]);
5100
+ const match = /"IOPlatformUUID"\s*=\s*"([^"]+)"/.exec(result.stdout);
5101
+ if (match?.[1]) {
5102
+ return { value: match[1], source: "macos_ioplatformuuid" };
5103
+ }
5104
+ }
5105
+ if (platformName === "win32") {
5106
+ const result = await runLocalProcess("reg", ["query", "HKLM\\SOFTWARE\\Microsoft\\Cryptography", "/v", "MachineGuid"]);
5107
+ const match = /MachineGuid\s+REG_\w+\s+([^\r\n]+)/.exec(result.stdout);
5108
+ if (match?.[1]?.trim()) {
5109
+ return { value: match[1].trim(), source: "windows_machineguid" };
5110
+ }
5111
+ }
5112
+ if (platformName === "linux") {
5113
+ for (const pathValue of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
5114
+ try {
5115
+ const value = (await readFile(pathValue, "utf8")).trim();
5116
+ if (value) {
5117
+ return { value, source: `linux:${pathValue}` };
5118
+ }
5119
+ }
5120
+ catch {
5121
+ // Try the next stable machine id location.
5122
+ }
5123
+ }
5124
+ }
5125
+ const generated = await localGeneratedInstallId();
5126
+ return { value: generated, source: "generated_install_id" };
5127
+ }
5128
+ async function localGeneratedInstallId() {
5129
+ try {
5130
+ const parsed = JSON.parse(await readFile(DEFAULT_DEVICE_FILE, "utf8"));
5131
+ if (typeof parsed.generated_install_id === "string" && parsed.generated_install_id.trim()) {
5132
+ return parsed.generated_install_id.trim();
5133
+ }
3560
5134
  }
3561
5135
  catch {
3562
- return { authenticated: false, profile: "default" };
5136
+ // Fall through and create a local-only fallback id.
3563
5137
  }
5138
+ const generated = `install_${randomBytes(24).toString("base64url")}`;
5139
+ await mkdir(dirname(DEFAULT_DEVICE_FILE), { recursive: true });
5140
+ await writeFile(DEFAULT_DEVICE_FILE, `${JSON.stringify({ generated_install_id: generated, created_at: new Date().toISOString() }, null, 2)}\n`, "utf8");
5141
+ await chmod(DEFAULT_DEVICE_FILE, 0o600).catch(() => undefined);
5142
+ return generated;
3564
5143
  }
3565
5144
  function accountCreateBody(parsed) {
3566
5145
  const unknownFlag = firstUnknownFlag(parsed, ["json", "email", "display-name"]);
@@ -3716,6 +5295,80 @@ function usageRecordBody(parsed) {
3716
5295
  created_at: createdAtResult.value
3717
5296
  });
3718
5297
  }
5298
+ function mcpToolEventBody(parsed) {
5299
+ const unknownFlag = firstUnknownFlag(parsed, [
5300
+ "json",
5301
+ "account",
5302
+ "account-id",
5303
+ "tool",
5304
+ "client",
5305
+ "mode",
5306
+ "server-model-used",
5307
+ "success",
5308
+ "local-device-id",
5309
+ "local_device_id",
5310
+ "request-id",
5311
+ "duration-ms",
5312
+ "input-summary",
5313
+ "output-summary"
5314
+ ]);
5315
+ if (unknownFlag) {
5316
+ return `Unknown flag --${unknownFlag}. ${MCP_TOOL_EVENT_USAGE}`;
5317
+ }
5318
+ const extra = parsed.command.slice(2);
5319
+ if (extra.length > 0) {
5320
+ return `Unexpected argument: ${extra[0]}. ${MCP_TOOL_EVENT_USAGE}`;
5321
+ }
5322
+ const toolResult = optionalTrimmedStringFlag(parsed, "tool");
5323
+ if (toolResult.error)
5324
+ return toolResult.error;
5325
+ if (!toolResult.value)
5326
+ return MCP_TOOL_EVENT_USAGE;
5327
+ const accountResult = optionalTrimmedStringAliasFlag(parsed, ["account", "account-id"], "account or account-id");
5328
+ if (accountResult.error)
5329
+ return accountResult.error;
5330
+ const clientResult = optionalTrimmedStringFlag(parsed, "client");
5331
+ if (clientResult.error)
5332
+ return clientResult.error;
5333
+ const modeResult = optionalTrimmedStringFlag(parsed, "mode");
5334
+ if (modeResult.error)
5335
+ return modeResult.error;
5336
+ const localDeviceResult = optionalTrimmedStringAliasFlag(parsed, ["local-device-id", "local_device_id"], "local-device-id");
5337
+ if (localDeviceResult.error)
5338
+ return localDeviceResult.error;
5339
+ const requestIdResult = optionalTrimmedStringFlag(parsed, "request-id");
5340
+ if (requestIdResult.error)
5341
+ return requestIdResult.error;
5342
+ const inputSummaryResult = optionalTrimmedStringFlag(parsed, "input-summary");
5343
+ if (inputSummaryResult.error)
5344
+ return inputSummaryResult.error;
5345
+ const outputSummaryResult = optionalTrimmedStringFlag(parsed, "output-summary");
5346
+ if (outputSummaryResult.error)
5347
+ return outputSummaryResult.error;
5348
+ const durationResult = optionalNonNegativeIntegerFlag(parsed, "duration-ms");
5349
+ if (durationResult.error)
5350
+ return durationResult.error;
5351
+ const serverModelUsed = optionalBooleanFlag(parsed, "server-model-used");
5352
+ if (typeof serverModelUsed === "string")
5353
+ return serverModelUsed;
5354
+ const success = optionalBooleanFlag(parsed, "success");
5355
+ if (typeof success === "string")
5356
+ return success;
5357
+ return compactBody({
5358
+ account_id: accountResult.value,
5359
+ tool_name: toolResult.value,
5360
+ client: clientResult.value,
5361
+ mode: modeResult.value,
5362
+ local_device_id: localDeviceResult.value,
5363
+ server_model_used: serverModelUsed,
5364
+ success,
5365
+ request_id: requestIdResult.value,
5366
+ duration_ms: durationResult.value,
5367
+ input_summary: inputSummaryResult.value,
5368
+ output_summary: outputSummaryResult.value,
5369
+ metadata: localDeviceResult.value ? { local_device_id: localDeviceResult.value } : undefined
5370
+ });
5371
+ }
3719
5372
  function usageSummaryRequest(parsed) {
3720
5373
  const unknownFlag = firstUnknownFlag(parsed, ["json", "account", "account-id", "api-key-id", "from", "to"]);
3721
5374
  if (unknownFlag) {
@@ -4051,6 +5704,29 @@ function billingSnapshotListRequest(parsed) {
4051
5704
  }
4052
5705
  return { path: `/v1/accounts/${encodeURIComponent(accountResult.value)}/billing/snapshots` };
4053
5706
  }
5707
+ function localToolchainListRequest(parsed, usage = LOCAL_TOOLCHAIN_LIST_USAGE) {
5708
+ const unknownFlag = firstUnknownFlag(parsed, ["json", "board", "board-id", "channel", "metadata-root", "install-root"]);
5709
+ if (unknownFlag) {
5710
+ return `Unknown flag --${unknownFlag}. ${usage}`;
5711
+ }
5712
+ const extra = parsed.command.slice(3);
5713
+ if (extra.length > 0) {
5714
+ return `Unexpected argument: ${extra[0]}. ${usage}`;
5715
+ }
5716
+ const board = optionalTrimmedStringAliasFlag(parsed, ["board", "board-id"], "board or board-id");
5717
+ if (board.error)
5718
+ return board.error;
5719
+ const channel = optionalTrimmedStringFlag(parsed, "channel");
5720
+ if (channel.error)
5721
+ return channel.error;
5722
+ const metadataRoot = optionalTrimmedStringFlag(parsed, "metadata-root");
5723
+ if (metadataRoot.error)
5724
+ return metadataRoot.error;
5725
+ const installRoot = optionalTrimmedStringFlag(parsed, "install-root");
5726
+ if (installRoot.error)
5727
+ return installRoot.error;
5728
+ return { boardId: board.value, channel: channel.value, metadataRoot: metadataRoot.value, installRoot: installRoot.value };
5729
+ }
4054
5730
  function localToolchainLatestRequest(parsed) {
4055
5731
  const unknownFlag = firstUnknownFlag(parsed, ["json", "board", "board-id", "channel", "metadata-root"]);
4056
5732
  if (unknownFlag) {
@@ -4071,6 +5747,32 @@ function localToolchainLatestRequest(parsed) {
4071
5747
  return metadataRoot.error;
4072
5748
  return { boardId: board.value, channel: channel.value, metadataRoot: metadataRoot.value };
4073
5749
  }
5750
+ function localWslInstallRequest(parsed) {
5751
+ const unknownFlag = firstUnknownFlag(parsed, ["json", "distribution", "distro", "no-launch", "web-download", "timeout-ms"]);
5752
+ if (unknownFlag) {
5753
+ return `Unknown flag --${unknownFlag}. ${LOCAL_WSL_INSTALL_USAGE}`;
5754
+ }
5755
+ const extra = parsed.command.slice(3);
5756
+ if (extra.length > 0) {
5757
+ return `Unexpected argument: ${extra[0]}. ${LOCAL_WSL_INSTALL_USAGE}`;
5758
+ }
5759
+ const distribution = stringFlag(parsed, "distribution") ?? stringFlag(parsed, "distro");
5760
+ const noLaunch = optionalBooleanFlag(parsed, "no-launch");
5761
+ if (typeof noLaunch === "string")
5762
+ return noLaunch;
5763
+ const webDownload = optionalBooleanFlag(parsed, "web-download");
5764
+ if (typeof webDownload === "string")
5765
+ return webDownload;
5766
+ const timeoutMs = optionalIntegerFlag(parsed, "timeout-ms", 1_000, 3_600_000);
5767
+ if (timeoutMs.error)
5768
+ return timeoutMs.error;
5769
+ return {
5770
+ distribution,
5771
+ noLaunch,
5772
+ webDownload,
5773
+ timeoutMs: timeoutMs.value
5774
+ };
5775
+ }
4074
5776
  function localToolchainCurrentRequest(parsed) {
4075
5777
  const unknownFlag = firstUnknownFlag(parsed, ["json", "install-root"]);
4076
5778
  if (unknownFlag) {
@@ -4086,7 +5788,7 @@ function localToolchainCurrentRequest(parsed) {
4086
5788
  return { installRoot: installRoot.value };
4087
5789
  }
4088
5790
  function localToolchainInstallRequest(parsed) {
4089
- const unknownFlag = firstUnknownFlag(parsed, ["json", "board", "board-id", "channel", "metadata-root", "source-url", "source-release-root", "install-root", "force"]);
5791
+ const unknownFlag = firstUnknownFlag(parsed, ["json", "board", "board-id", "channel", "metadata-root", "source-url", "source-release-root", "install-root", "mode", "force"]);
4090
5792
  if (unknownFlag) {
4091
5793
  return `Unknown flag --${unknownFlag}. ${LOCAL_TOOLCHAIN_INSTALL_USAGE}`;
4092
5794
  }
@@ -4115,6 +5817,9 @@ function localToolchainInstallRequest(parsed) {
4115
5817
  const installRoot = optionalTrimmedStringFlag(parsed, "install-root");
4116
5818
  if (installRoot.error)
4117
5819
  return installRoot.error;
5820
+ const mode = optionalTrimmedStringFlag(parsed, "mode");
5821
+ if (mode.error)
5822
+ return mode.error;
4118
5823
  return {
4119
5824
  boardId: board.value,
4120
5825
  channel: channel.value,
@@ -4122,11 +5827,32 @@ function localToolchainInstallRequest(parsed) {
4122
5827
  sourceUrl: sourceUrl.value,
4123
5828
  sourceReleaseRoot: sourceReleaseRoot.value,
4124
5829
  installRoot: installRoot.value,
5830
+ mode: mode.value,
4125
5831
  force: booleanFlag(parsed, "force")
4126
5832
  };
4127
5833
  }
5834
+ function localToolchainUninstallRequest(parsed) {
5835
+ const unknownFlag = firstUnknownFlag(parsed, ["json", "board", "board-id", "install-root"]);
5836
+ if (unknownFlag) {
5837
+ return `Unknown flag --${unknownFlag}. ${LOCAL_TOOLCHAIN_UNINSTALL_USAGE}`;
5838
+ }
5839
+ const extra = parsed.command.slice(3);
5840
+ if (extra.length > 0) {
5841
+ return `Unexpected argument: ${extra[0]}. ${LOCAL_TOOLCHAIN_UNINSTALL_USAGE}`;
5842
+ }
5843
+ const board = optionalTrimmedStringAliasFlag(parsed, ["board", "board-id"], "board or board-id");
5844
+ if (board.error)
5845
+ return board.error;
5846
+ if (!board.value) {
5847
+ return LOCAL_TOOLCHAIN_UNINSTALL_USAGE;
5848
+ }
5849
+ const installRoot = optionalTrimmedStringFlag(parsed, "install-root");
5850
+ if (installRoot.error)
5851
+ return installRoot.error;
5852
+ return { boardId: board.value, installRoot: installRoot.value };
5853
+ }
4128
5854
  function localToolchainValidateRequest(parsed) {
4129
- const unknownFlag = firstUnknownFlag(parsed, ["json", "release-root"]);
5855
+ const unknownFlag = firstUnknownFlag(parsed, ["json", "board", "board-id", "release-root", "mode"]);
4130
5856
  if (unknownFlag) {
4131
5857
  return `Unknown flag --${unknownFlag}. ${LOCAL_TOOLCHAIN_VALIDATE_USAGE}`;
4132
5858
  }
@@ -4138,7 +5864,15 @@ function localToolchainValidateRequest(parsed) {
4138
5864
  if (releaseRoot.error) {
4139
5865
  return releaseRoot.error;
4140
5866
  }
4141
- return { releaseRoot: releaseRoot.value };
5867
+ const board = optionalTrimmedStringAliasFlag(parsed, ["board", "board-id"], "board or board-id");
5868
+ if (board.error) {
5869
+ return board.error;
5870
+ }
5871
+ const mode = optionalTrimmedStringFlag(parsed, "mode");
5872
+ if (mode.error) {
5873
+ return mode.error;
5874
+ }
5875
+ return { releaseRoot: releaseRoot.value, mode: mode.value, boardId: board.value };
4142
5876
  }
4143
5877
  function localCompileTaishanPiRequest(parsed, auth) {
4144
5878
  const unknownFlag = firstUnknownFlag(parsed, ["json", "source", "output", "release-root", "account", "account-id"]);
@@ -4223,7 +5957,9 @@ function localToolchainAuthContext(auth, accountId) {
4223
5957
  authenticated: auth.authenticated,
4224
5958
  profile: auth.profile,
4225
5959
  source: auth.source,
4226
- account_id: accountId
5960
+ account_id: accountId,
5961
+ api_key_id: auth.api_key_id,
5962
+ device_id: auth.device_id
4227
5963
  };
4228
5964
  }
4229
5965
  function usageEventsRequest(parsed) {
@@ -4633,6 +6369,35 @@ function renderPluginList(result) {
4633
6369
  `install="${plugin.install_command}"`
4634
6370
  ].filter(Boolean).join(" ")).join("\n");
4635
6371
  }
6372
+ function renderPluginUpdateCheck(result) {
6373
+ const lines = [
6374
+ `release_url=${result.release_url}`,
6375
+ result.latest_version ? `latest_version=${result.latest_version}` : ""
6376
+ ].filter(Boolean);
6377
+ if (result.release_notes.length > 0) {
6378
+ lines.push("release_notes:");
6379
+ for (const note of result.release_notes) {
6380
+ lines.push(` - ${note}`);
6381
+ }
6382
+ }
6383
+ for (const plugin of result.plugins) {
6384
+ lines.push("");
6385
+ lines.push(`${plugin.display_name} (${plugin.id})`);
6386
+ lines.push(` installed=${plugin.installed}`);
6387
+ lines.push(` installed_version=${plugin.installed_version ?? "none"}`);
6388
+ lines.push(` latest_version=${plugin.latest_version ?? "unknown"}`);
6389
+ lines.push(` update_available=${plugin.update_available}`);
6390
+ lines.push(` target=${plugin.target_path}`);
6391
+ lines.push(` update_command=${plugin.update_command}`);
6392
+ if (plugin.release_file) {
6393
+ lines.push(` release_file=${plugin.release_file}`);
6394
+ }
6395
+ for (const note of plugin.notes) {
6396
+ lines.push(` note=${note}`);
6397
+ }
6398
+ }
6399
+ return lines.join("\n");
6400
+ }
4636
6401
  function renderPluginInstall(result) {
4637
6402
  const lines = ["Installed plugins:"];
4638
6403
  for (const item of result.installed) {
@@ -4654,6 +6419,15 @@ function renderPluginInstall(result) {
4654
6419
  if (item.mcp_warning) {
4655
6420
  lines.push(` warning=${item.mcp_warning}`);
4656
6421
  }
6422
+ if (item.marketplace_registered !== undefined) {
6423
+ lines.push(` codex_marketplace_registered=${item.marketplace_registered}`);
6424
+ }
6425
+ if (item.marketplace_path) {
6426
+ lines.push(` codex_marketplace=${item.marketplace_path}`);
6427
+ }
6428
+ if (item.marketplace_warning) {
6429
+ lines.push(` warning=${item.marketplace_warning}`);
6430
+ }
4657
6431
  }
4658
6432
  return lines.join("\n");
4659
6433
  }
@@ -4685,9 +6459,53 @@ function renderAgentRunResult(result) {
4685
6459
  return lines.join("\n");
4686
6460
  }
4687
6461
  function renderAuthStatus(status) {
4688
- return status.authenticated
4689
- ? `Authenticated profile=${status.profile}${status.source ? ` source=${status.source}` : ""}`
4690
- : `Not authenticated profile=${status.profile}`;
6462
+ if (!status.authenticated) {
6463
+ return `Not authenticated profile=${status.profile}`;
6464
+ }
6465
+ return [
6466
+ `Authenticated profile=${status.profile}${status.source ? ` source=${status.source}` : ""}`,
6467
+ status.account_id ? `account=${status.account_id}` : "",
6468
+ status.api_key_id ? `api_key=${status.api_key_id}` : "",
6469
+ status.device_id ? `device=${status.device_id}` : "device=not_registered",
6470
+ status.device_label ? `device_label=${status.device_label}` : "",
6471
+ status.device_integrity ? `device_integrity=${status.device_integrity}` : "",
6472
+ status.device_private_key_configured === false ? "device_private_key=missing" : ""
6473
+ ].filter(Boolean).join("\n");
6474
+ }
6475
+ function renderAuthDeviceStatus(status) {
6476
+ const lines = [renderAuthStatus(status.local)];
6477
+ if (status.remote) {
6478
+ const activeCount = status.remote.devices.filter((device) => device.status === "active").length;
6479
+ lines.push(`remote_devices=${activeCount}/${status.remote.device_limit}`);
6480
+ const localDevice = status.local.device_id
6481
+ ? status.remote.devices.find((device) => device.device_id === status.local.device_id)
6482
+ : undefined;
6483
+ if (localDevice) {
6484
+ lines.push(`remote_current=${renderAuthDevice(localDevice)}`);
6485
+ }
6486
+ }
6487
+ return lines.join("\n");
6488
+ }
6489
+ function renderAuthDeviceList(result) {
6490
+ if (result.devices.length === 0) {
6491
+ return `No registered devices. device_limit=${result.device_limit}`;
6492
+ }
6493
+ return [
6494
+ `device_limit=${result.device_limit}`,
6495
+ ...result.devices.map(renderAuthDevice)
6496
+ ].join("\n");
6497
+ }
6498
+ function renderAuthDevice(device) {
6499
+ return [
6500
+ `${device.device_id} account=${device.account_id}`,
6501
+ device.api_key_id ? `api_key=${device.api_key_id}` : "",
6502
+ `status=${device.status}`,
6503
+ device.label ? `label=${device.label}` : "",
6504
+ device.platform ? `platform=${device.platform}` : "",
6505
+ device.arch ? `arch=${device.arch}` : "",
6506
+ `last_seen_at=${device.last_seen_at}`,
6507
+ device.revoked_at ? `revoked_at=${device.revoked_at}` : ""
6508
+ ].filter(Boolean).join(" ");
4691
6509
  }
4692
6510
  function renderAccount(account) {
4693
6511
  return [
@@ -4737,6 +6555,20 @@ function renderUsageRecord(record) {
4737
6555
  `created_at=${record.created_at}`
4738
6556
  ].filter(Boolean).join(" ");
4739
6557
  }
6558
+ function renderMcpToolEvent(event) {
6559
+ return [
6560
+ `${event.event_id} tool=${event.tool_name}`,
6561
+ event.account_id ? `account=${event.account_id}` : "",
6562
+ event.api_key_id ? `api_key=${event.api_key_id}` : "",
6563
+ `client=${event.client}`,
6564
+ `mode=${event.mode}`,
6565
+ `server_model_used=${event.server_model_used}`,
6566
+ `success=${event.success}`,
6567
+ event.request_id ? `request=${event.request_id}` : "",
6568
+ event.duration_ms !== undefined ? `duration_ms=${event.duration_ms}` : "",
6569
+ `created_at=${event.created_at}`
6570
+ ].filter(Boolean).join(" ");
6571
+ }
4740
6572
  function renderUsageSummary(summary) {
4741
6573
  const lines = [
4742
6574
  summary.account_id ? `account=${summary.account_id}` : "",
@@ -5048,6 +6880,20 @@ function renderBoardKnowledge(data) {
5048
6880
  `title=${file.title}`
5049
6881
  ].join(" ")).join("\n");
5050
6882
  }
6883
+ function renderBoardKnowledgeSearch(data) {
6884
+ const result = data;
6885
+ const matches = Array.isArray(result.matches) ? result.matches : [];
6886
+ if (matches.length === 0) {
6887
+ return "No matching board knowledge snippets.";
6888
+ }
6889
+ return matches.map((match, index) => [
6890
+ `#${index + 1}`,
6891
+ `${match.source}:${match.path}`,
6892
+ `score=${match.score}`,
6893
+ `title=${match.title}`,
6894
+ `excerpt=${match.excerpt.replace(/\s+/g, " ").trim()}`
6895
+ ].join(" ")).join("\n");
6896
+ }
5051
6897
  function renderBoardKnowledgeFile(file) {
5052
6898
  return [
5053
6899
  `template=${file.template_id}`,
@@ -5260,6 +7106,194 @@ function renderBuildWorkspaceSourcePatch(result) {
5260
7106
  }
5261
7107
  return lines.join("\n");
5262
7108
  }
7109
+ function renderLocalToolchainList(result) {
7110
+ const installedCount = result.environments.filter((environment) => !!environment.installed).length;
7111
+ const availableCount = result.environments.filter((environment) => environment.status === "available").length;
7112
+ const updateCount = result.environments.filter((environment) => environment.status === "update_available").length;
7113
+ const lines = [
7114
+ `Local development environments: ${result.environments.length}`,
7115
+ `installed=${installedCount} available=${availableCount} updates=${updateCount}`,
7116
+ `host=${result.host}`,
7117
+ `channel=${result.channel}`,
7118
+ result.metadata_source === "local_override" ? `metadata_override=${result.metadata_root}` : "metadata=production/built-in",
7119
+ `install_root=${result.install_root}`,
7120
+ `registry=${result.registry_path}`
7121
+ ];
7122
+ for (const environment of result.environments) {
7123
+ lines.push("");
7124
+ lines.push(`${environment.display_name} (${environment.board_id})`);
7125
+ lines.push(` status=${localToolchainStatusLabel(environment.status)}`);
7126
+ lines.push(` latest=${environment.latest.version}`);
7127
+ if (environment.installed) {
7128
+ lines.push(` installed=${environment.installed.version ?? "unknown"} mode=${environment.installed.mode ?? "unknown"}`);
7129
+ if (environment.installed.release_root) {
7130
+ lines.push(` release_root=${environment.installed.release_root}`);
7131
+ }
7132
+ }
7133
+ lines.push(` install_modes=${environment.install_modes.join(",")}`);
7134
+ if (environment.execution) {
7135
+ lines.push(` execution=${environment.execution.kind} supported=${environment.execution.supported}`);
7136
+ if (environment.execution.required_host) {
7137
+ lines.push(` execution_required_host=${environment.execution.required_host}`);
7138
+ }
7139
+ if (environment.execution.actual_host) {
7140
+ lines.push(` execution_actual_host=${environment.execution.actual_host}`);
7141
+ }
7142
+ if (environment.execution.reason) {
7143
+ lines.push(` execution_reason=${environment.execution.reason}`);
7144
+ }
7145
+ }
7146
+ lines.push(` install_command=${environment.install_command}`);
7147
+ if (environment.status === "update_available") {
7148
+ lines.push(` update_command=${environment.update_command}`);
7149
+ }
7150
+ if (environment.components?.length) {
7151
+ lines.push(` component_catalog=${localToolchainComponentSummary(environment.components)}`);
7152
+ if (environment.latest.default_mode) {
7153
+ lines.push(` default_mode_download=${environment.latest.default_mode}: ${localToolchainComponentSummaryForMode(environment.components, environment.latest.default_mode)}`);
7154
+ }
7155
+ if (environment.installed?.mode && environment.installed.mode !== environment.latest.default_mode) {
7156
+ lines.push(` installed_mode_components=${environment.installed.mode}: ${localToolchainComponentSummaryForMode(environment.components, environment.installed.mode)}`);
7157
+ }
7158
+ lines.push(` mode_downloads=${localToolchainModeSummaries(environment.install_modes, environment.components).join("; ")}`);
7159
+ lines.push(` package_groups=${localToolchainComponentGroups(environment.components).join(", ")}`);
7160
+ lines.push(` detail_command=embedlabs local toolchain latest --board ${environment.board_id}`);
7161
+ }
7162
+ if (environment.notes.length > 0) {
7163
+ for (const note of environment.notes) {
7164
+ lines.push(` note=${note}`);
7165
+ }
7166
+ }
7167
+ }
7168
+ return lines.join("\n");
7169
+ }
7170
+ function localToolchainStatusLabel(status) {
7171
+ if (status === "installed")
7172
+ return "installed/已安装";
7173
+ if (status === "available")
7174
+ return "available/可安装";
7175
+ if (status === "update_available")
7176
+ return "update_available/可更新";
7177
+ if (status === "unsupported_host")
7178
+ return "unsupported_host/当前系统暂不支持";
7179
+ return status;
7180
+ }
7181
+ function renderWindowsWslStatus(result) {
7182
+ const lines = [
7183
+ "Windows WSL2 status",
7184
+ `host=${result.host}`,
7185
+ `applicable=${result.applicable}`,
7186
+ `wsl_available=${result.wsl_available}`,
7187
+ `usable=${result.usable}`,
7188
+ `taishanpi_execution=${result.taishanpi_execution.status} supported=${result.taishanpi_execution.supported}`,
7189
+ `taishanpi_required_host=${result.taishanpi_execution.required_host}`,
7190
+ result.taishanpi_execution.actual_host ? `taishanpi_actual_host=${result.taishanpi_execution.actual_host}` : "",
7191
+ `taishanpi_reason=${result.taishanpi_execution.reason}`,
7192
+ `checked_at=${result.checked_at}`,
7193
+ `status_command=${result.commands.status}`,
7194
+ `list_command=${result.commands.list}`,
7195
+ `list_online_command=${result.commands.list_online}`,
7196
+ `install_command=${result.commands.install_ubuntu}`
7197
+ ].filter(Boolean);
7198
+ if (result.distributions.length > 0) {
7199
+ lines.push("distributions:");
7200
+ for (const distro of result.distributions) {
7201
+ lines.push(` ${distro.default ? "*" : "-"} ${distro.name} state=${distro.state ?? "unknown"} version=${distro.version ?? "unknown"}`);
7202
+ }
7203
+ }
7204
+ if (result.online_distributions.length > 0) {
7205
+ lines.push("online_distributions:");
7206
+ for (const distro of result.online_distributions) {
7207
+ lines.push(` ${distro.default ? "*" : "-"} ${distro.name}${distro.friendly_name ? ` (${distro.friendly_name})` : ""}`);
7208
+ }
7209
+ }
7210
+ for (const note of result.notes) {
7211
+ lines.push(`note=${note}`);
7212
+ }
7213
+ return lines.join("\n");
7214
+ }
7215
+ function renderWindowsWslInstall(result) {
7216
+ const lines = [
7217
+ "Windows WSL2 install",
7218
+ `host=${result.host}`,
7219
+ `command=${result.command.join(" ")}`,
7220
+ `exit_code=${result.exit_code}`,
7221
+ `usable_after=${result.status_after.usable}`
7222
+ ];
7223
+ if (result.stdout_tail.length > 0) {
7224
+ lines.push("stdout_tail:");
7225
+ for (const line of result.stdout_tail) {
7226
+ lines.push(` ${line}`);
7227
+ }
7228
+ }
7229
+ if (result.stderr_tail.length > 0) {
7230
+ lines.push("stderr_tail:");
7231
+ for (const line of result.stderr_tail) {
7232
+ lines.push(` ${line}`);
7233
+ }
7234
+ }
7235
+ for (const note of result.notes) {
7236
+ lines.push(`note=${note}`);
7237
+ }
7238
+ return lines.join("\n");
7239
+ }
7240
+ function localToolchainComponentSummary(components) {
7241
+ const totalBytes = components.reduce((total, component) => total + component.size_bytes, 0);
7242
+ return `${components.length} components, ${formatByteSize(totalBytes)}`;
7243
+ }
7244
+ function localToolchainComponentSummaryForMode(components, mode) {
7245
+ return localToolchainComponentSummary(localToolchainComponentsForMode(components, mode));
7246
+ }
7247
+ function localToolchainModeSummaries(modes, components) {
7248
+ return modes.map((mode) => `${mode}=${localToolchainComponentSummaryForMode(components, mode)}`);
7249
+ }
7250
+ function localToolchainComponentsForMode(components, mode) {
7251
+ return components.filter((component) => {
7252
+ if (!component.install_modes?.length) {
7253
+ return true;
7254
+ }
7255
+ return component.install_modes.includes(mode);
7256
+ });
7257
+ }
7258
+ function localToolchainComponentGroups(components) {
7259
+ const groups = new Set();
7260
+ for (const component of components) {
7261
+ const text = `${component.id} ${component.role ?? ""}`.toLowerCase();
7262
+ if (text.includes("arm-none-eabi") || text.includes("bare-metal") || text.includes("compiler"))
7263
+ groups.add("compiler/ARM 裸机编译器");
7264
+ if (text.includes("pico-sdk") || text.includes("sdk-core"))
7265
+ groups.add("pico-sdk/C/C++ SDK");
7266
+ if (text.includes("sysroot") || text.includes("cross"))
7267
+ groups.add("sysroot/交叉运行库");
7268
+ if (text.includes("qt"))
7269
+ groups.add("qt/Qt 应用支持");
7270
+ if (text.includes("rockchip") || text.includes("boot") || text.includes("resource"))
7271
+ groups.add("boot-flash/启动与烧写工具");
7272
+ if (text.includes("image") || text.includes("rootfs"))
7273
+ groups.add("images/镜像资源");
7274
+ if (text.includes("initial-firmware"))
7275
+ groups.add("initial-firmware/初始化镜像");
7276
+ if (text.includes("rp2350-monitor"))
7277
+ groups.add("rp2350-monitor/可选硬件监控镜像");
7278
+ if (text.includes("meta"))
7279
+ groups.add("metadata/知识与脚本元数据");
7280
+ }
7281
+ return groups.size > 0 ? [...groups] : ["runtime/运行时工具"];
7282
+ }
7283
+ function formatByteSize(bytes) {
7284
+ if (!Number.isFinite(bytes) || bytes < 0) {
7285
+ return "unknown size";
7286
+ }
7287
+ const units = ["B", "KB", "MB", "GB", "TB"];
7288
+ let value = bytes;
7289
+ let unit = 0;
7290
+ while (value >= 1024 && unit < units.length - 1) {
7291
+ value /= 1024;
7292
+ unit += 1;
7293
+ }
7294
+ const fixed = unit === 0 || value >= 10 ? value.toFixed(0) : value.toFixed(1);
7295
+ return `${fixed} ${units[unit]}`;
7296
+ }
5263
7297
  function renderLocalToolchainLatest(result) {
5264
7298
  const lines = [
5265
7299
  `board=${result.board_id}`,
@@ -5267,11 +7301,26 @@ function renderLocalToolchainLatest(result) {
5267
7301
  `version=${result.version}`,
5268
7302
  `host=${result.host}`,
5269
7303
  result.metadata_root ? `metadata_root=${result.metadata_root}` : "metadata=built-in",
5270
- result.download ? `download=${result.download.mirror_kind}:${result.download.source_url}` : "",
5271
- result.download ? `archive_sha256=${result.download.archive.sha256}` : "",
5272
- result.download ? `archive_size_bytes=${result.download.archive.size_bytes}` : "",
7304
+ result.download?.source_url ? `download=${result.download.mirror_kind}:${result.download.source_url}` : "",
7305
+ result.download?.archive ? `archive_sha256=${result.download.archive.sha256}` : "",
7306
+ result.download?.archive ? `archive_size_bytes=${result.download.archive.size_bytes}` : "",
7307
+ result.download?.components?.length ? `components=${result.download.components.length}` : "",
7308
+ result.download?.default_mode ? `default_mode=${result.download.default_mode}` : "",
5273
7309
  result.download_error ? `download_error=${result.download_error}` : ""
5274
7310
  ].filter(Boolean);
7311
+ if (result.download?.components?.length) {
7312
+ const modes = [...new Set(result.download.components.flatMap((component) => component.install_modes ?? ["all"]))].filter((mode) => mode !== "all");
7313
+ if (result.download.default_mode) {
7314
+ lines.push(`default_mode_download=${result.download.default_mode}: ${localToolchainDownloadComponentSummaryForMode(result.download.components, result.download.default_mode)}`);
7315
+ }
7316
+ if (modes.length > 0) {
7317
+ lines.push(`mode_downloads=${modes.map((mode) => `${mode}=${localToolchainDownloadComponentSummaryForMode(result.download?.components ?? [], mode)}`).join("; ")}`);
7318
+ }
7319
+ lines.push("download_components:");
7320
+ for (const component of result.download.components) {
7321
+ lines.push(` ${component.id}@${component.version} modes=${component.install_modes?.join(",") || "all"} size=${formatByteSize(component.archive.size_bytes)} file=${component.archive.file}`);
7322
+ }
7323
+ }
5275
7324
  if (result.packages.length > 0) {
5276
7325
  lines.push("packages:");
5277
7326
  for (const pkg of result.packages) {
@@ -5280,6 +7329,11 @@ function renderLocalToolchainLatest(result) {
5280
7329
  }
5281
7330
  return lines.join("\n");
5282
7331
  }
7332
+ function localToolchainDownloadComponentSummaryForMode(components, mode) {
7333
+ const selected = localToolchainComponentsForMode(components ?? [], mode);
7334
+ const totalBytes = selected.reduce((total, component) => total + component.archive.size_bytes, 0);
7335
+ return `${selected.length} components, ${formatByteSize(totalBytes)}`;
7336
+ }
5283
7337
  function renderLocalToolchainCurrent(result) {
5284
7338
  if (!result.installed) {
5285
7339
  return [
@@ -5294,6 +7348,7 @@ function renderLocalToolchainCurrent(result) {
5294
7348
  `board=${result.board_id}`,
5295
7349
  result.version ? `version=${result.version}` : "",
5296
7350
  result.channel ? `channel=${result.channel}` : "",
7351
+ result.mode ? `mode=${result.mode}` : "",
5297
7352
  result.release_root ? `release_root=${result.release_root}` : "",
5298
7353
  `install_root=${result.install_root}`,
5299
7354
  `registry=${result.registry_path}`
@@ -5306,13 +7361,21 @@ function renderLocalToolchainInstall(result) {
5306
7361
  `version=${result.version}`,
5307
7362
  `channel=${result.channel}`,
5308
7363
  `host=${result.host}`,
7364
+ `mode=${result.mode}`,
5309
7365
  `install_root=${result.install_root}`,
5310
7366
  `release_root=${result.release_root}`,
5311
7367
  `registry=${result.registry_path}`,
5312
7368
  `source=${result.source.kind}:${result.source.value}`,
5313
7369
  result.source.downloaded_path ? `downloaded=${result.source.downloaded_path}` : "",
7370
+ result.source.components?.length ? `components=${result.source.components.length}` : "",
5314
7371
  `validation=${result.validation.ok ? "ok" : "failed"}`
5315
7372
  ].filter(Boolean);
7373
+ if (result.source.components?.length) {
7374
+ lines.push("installed_components:");
7375
+ for (const component of result.source.components) {
7376
+ lines.push(` ${component.id}@${component.version} ${component.mirror_kind || ""} bytes=${component.size_bytes}`);
7377
+ }
7378
+ }
5316
7379
  if (result.installed_paths.length > 0) {
5317
7380
  lines.push("installed_paths:");
5318
7381
  for (const installedPath of result.installed_paths) {
@@ -5327,16 +7390,50 @@ function renderLocalToolchainInstall(result) {
5327
7390
  }
5328
7391
  return lines.join("\n");
5329
7392
  }
7393
+ function renderLocalToolchainUninstall(result) {
7394
+ const lines = [
7395
+ result.removed ? "Local toolchain uninstalled." : "Local toolchain was not installed.",
7396
+ `board=${result.board_id}`,
7397
+ `install_root=${result.install_root}`,
7398
+ `registry=${result.registry_path}`,
7399
+ `removed_registry_entry=${result.removed_registry_entry}`,
7400
+ `observed_at=${result.observed_at}`
7401
+ ];
7402
+ if (result.removed_paths.length > 0) {
7403
+ lines.push("removed_paths:");
7404
+ for (const removedPath of result.removed_paths) {
7405
+ lines.push(` ${removedPath}`);
7406
+ }
7407
+ }
7408
+ if (result.remaining_installed_boards.length > 0) {
7409
+ lines.push(`remaining_installed_boards=${result.remaining_installed_boards.join(",")}`);
7410
+ }
7411
+ return lines.join("\n");
7412
+ }
5330
7413
  function renderLocalToolchainValidation(result) {
5331
7414
  const lines = [
5332
7415
  result.ok ? "Local toolchain ready." : "Local toolchain not ready.",
5333
7416
  `board=${result.board_id}`,
7417
+ `mode=${result.mode}`,
5334
7418
  `host=${result.host.platform}/${result.host.arch}`,
5335
- `release_root=${result.release_root}`
7419
+ `release_root=${result.release_root}`,
7420
+ `summary=${result.summary_for_user}`
5336
7421
  ];
7422
+ if (!result.ok && result.missing_groups.length > 0) {
7423
+ lines.push(`missing_groups=${result.missing_groups.join(", ")}`);
7424
+ }
7425
+ if (result.repair_command) {
7426
+ lines.push(`repair_command=${result.repair_command}`);
7427
+ }
5337
7428
  for (const check of result.checked_paths) {
5338
7429
  lines.push(`${check.exists ? "ok" : "missing"} ${check.label}: ${check.path}`);
5339
7430
  }
7431
+ if (result.path_leaks.length > 0) {
7432
+ lines.push("path_leaks:");
7433
+ for (const leak of result.path_leaks) {
7434
+ lines.push(` ${leak.label}: ${leak.path} contains ${leak.forbidden}`);
7435
+ }
7436
+ }
5340
7437
  if (result.notes.length > 0) {
5341
7438
  lines.push("notes:");
5342
7439
  for (const note of result.notes) {
@@ -5785,6 +7882,22 @@ function stringFlag(parsed, name) {
5785
7882
  function booleanFlag(parsed, name) {
5786
7883
  return parsed.flags[name] === true;
5787
7884
  }
7885
+ function optionalBooleanFlag(parsed, name) {
7886
+ const values = flagValues(parsed, name);
7887
+ if (values.length === 0)
7888
+ return undefined;
7889
+ const value = values[values.length - 1];
7890
+ if (value === true)
7891
+ return true;
7892
+ if (typeof value !== "string")
7893
+ return `--${name} must be true or false.`;
7894
+ const normalized = value.trim().toLowerCase();
7895
+ if (["1", "true", "yes", "y", "on"].includes(normalized))
7896
+ return true;
7897
+ if (["0", "false", "no", "n", "off"].includes(normalized))
7898
+ return false;
7899
+ return `--${name} must be true or false.`;
7900
+ }
5788
7901
  function switchFlag(parsed, name) {
5789
7902
  const values = flagValues(parsed, name);
5790
7903
  for (const value of values) {
@@ -5948,6 +8061,24 @@ function optionalPositiveIntegerFlag(parsed, name) {
5948
8061
  }
5949
8062
  return { value };
5950
8063
  }
8064
+ function optionalIntegerFlag(parsed, name, min, max) {
8065
+ const values = flagValues(parsed, name);
8066
+ if (values.length === 0) {
8067
+ return {};
8068
+ }
8069
+ if (values.some((value) => typeof value !== "string")) {
8070
+ return { error: `--${name} requires a value.` };
8071
+ }
8072
+ const raw = values[values.length - 1].trim();
8073
+ if (!/^-?\d+$/.test(raw)) {
8074
+ return { error: `--${name} must be an integer.` };
8075
+ }
8076
+ const value = Number(raw);
8077
+ if (!Number.isSafeInteger(value) || value < min || value > max) {
8078
+ return { error: `--${name} must be an integer from ${min} through ${max}.` };
8079
+ }
8080
+ return { value };
8081
+ }
5951
8082
  function jsonObjectListFlag(parsed, name) {
5952
8083
  const objects = [];
5953
8084
  for (const raw of flagValues(parsed, name)) {
@@ -6083,41 +8214,35 @@ Main workflow:
6083
8214
  3. Inspect server model routing:
6084
8215
  embed plugin install codex
6085
8216
  embed plugin install opencode
8217
+ embed plugin update check
6086
8218
  embed service modes
6087
8219
  embed model list
6088
8220
  embed model default
6089
8221
  4. Run a natural-language local tool loop:
6090
8222
  embed agent run --prompt "验证开发板状态"
6091
8223
  5. Validate or use the local TaishanPi toolchain:
8224
+ embed local toolchain list
8225
+ embed local toolchain installed
6092
8226
  embed local toolchain latest
6093
8227
  embed local toolchain install
8228
+ embed local toolchain uninstall --board pico2w-rp2350-monitor
6094
8229
  embed local toolchain validate
6095
8230
  embed local compile taishanpi --source ./main.c --output ./.embed-labs/build/main
6096
8231
  embed local build qt-smoke --build-dir ./.embed-labs/build/qt-smoke
6097
- 6. Pick a cloud build template:
8232
+ embed local build qt-smoke --source ./taishanpi-app --target-name app --build-dir ./.embed-labs/build/app
8233
+ 6. Query board knowledge and method metadata:
6098
8234
  embed board registry list
6099
8235
  embed board methods taishanpi-1m-rk3566
6100
8236
  embed board knowledge taishanpi-1m-rk3566
8237
+ embed board knowledge search taishanpi-1m-rk3566 --query "UART pinout"
6101
8238
  embed build template list
6102
8239
  embed build template show <template_id>
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"
8240
+ 7. Generate small image packages and compose locally:
6116
8241
  embed build image boot-logo --logo ./logo.png --board taishanpi --variant 1M-RK3566 --output ./boot-logo-package.json
6117
8242
  embed image boot-logo compose --package ./boot-logo-package.json --base-image ./boot.img --output ./boot-logo.img
6118
8243
  embed cloud task artifacts <task_id>
6119
8244
  embed artifact download <artifact_id> --output ./artifact.bin
6120
- 9. Check credits or create a recharge QR:
8245
+ 8. Check credits or create a recharge QR:
6121
8246
  embed billing balance --account <account_id>
6122
8247
  embed billing tokens --account <account_id>
6123
8248
  embed billing ledger --account <account_id>
@@ -6131,17 +8256,22 @@ Local hardware:
6131
8256
  embed agent run --prompt "部署泰山派应用" --host 198.19.77.2 --artifact ./artifact.bin --remote-path /userdata/embed-labs/apps/app --approve --run
6132
8257
  embed agent run --prompt "部署生成的泰山派应用" --host 198.19.77.2 --artifact-task <task_id> --remote-path /userdata/embed-labs/apps/app --approve --run
6133
8258
  embed tool list
6134
- embed tool call device.probe --input-json '{"host":"198.19.77.2","ports":[22,15301]}'
8259
+ embed tool call device.probe --input-json '{"host":"198.19.77.2","ports":[22]}'
6135
8260
  embed tool call wifi.scan --input-json '{"host":"198.19.77.2","user":"root"}'
8261
+ embed tool call rp2350.monitor.spi.transfer --input-json '{"hex":"a55a3cc3"}' --approve
6136
8262
  embed tool call chip.temperature --input-json '{"host":"198.19.77.2","user":"root"}'
6137
8263
  embed tool call qml.runtime.status --input-json '{"host":"198.19.77.2","user":"root","port":18130}'
6138
8264
  embed device list
8265
+ embed local toolchain list
8266
+ embed local toolchain installed
6139
8267
  embed local toolchain latest
6140
8268
  embed local toolchain current
6141
8269
  embed local toolchain install
8270
+ embed local toolchain uninstall --board pico2w-rp2350-monitor
6142
8271
  embed local toolchain validate
6143
8272
  embed local compile taishanpi --source ./main.c --output ./.embed-labs/build/main
6144
8273
  embed local build qt-smoke --build-dir ./.embed-labs/build/qt-smoke
8274
+ embed local build qt-smoke --source ./taishanpi-app --target-name app --build-dir ./.embed-labs/build/app
6145
8275
  embed image boot-logo compose --package ./boot-logo-package.json --base-image ./boot.img --output ./boot-logo.img
6146
8276
  embed deploy taishanpi --host 198.19.77.2 --artifact ./artifact.bin --approve --run
6147
8277
  embed flash plan --board <rp2350|taishanpi>
@@ -6152,7 +8282,7 @@ Help:
6152
8282
 
6153
8283
  Environment:
6154
8284
  EMBED_BRIDGE_URL=http://127.0.0.1:18083
6155
- EMBED_CLOUD_API_URL=http://127.0.0.1:18100
8285
+ EMBED_CLOUD_API_URL=https://api.embedboard.com
6156
8286
  EMBED_API_TOKEN=<token>
6157
8287
  CODEX_HOME=~/.codex
6158
8288
 
@@ -6207,6 +8337,8 @@ Install local AI client plugins explicitly:
6207
8337
  embed plugin list
6208
8338
  embed plugin install codex
6209
8339
  embed plugin install opencode
8340
+ embed plugin update check
8341
+ embed plugin update all
6210
8342
 
6211
8343
  Cloud build path:
6212
8344
 
@@ -6217,10 +8349,12 @@ Cloud build path:
6217
8349
  embed local toolchain validate
6218
8350
  embed local compile taishanpi --source ./main.c --output ./.embed-labs/build/main
6219
8351
  embed local build qt-smoke --build-dir ./.embed-labs/build/qt-smoke
8352
+ embed local build qt-smoke --source ./taishanpi-app --target-name app --build-dir ./.embed-labs/build/app
6220
8353
  embed board registry list
6221
8354
  embed board registry show taishanpi-1m-rk3566
6222
8355
  embed board methods taishanpi-1m-rk3566
6223
8356
  embed board knowledge taishanpi-1m-rk3566
8357
+ embed board knowledge search taishanpi-1m-rk3566 --query "UART pinout"
6224
8358
  embed build template list
6225
8359
  embed build template show <template_id>
6226
8360
  embed build workspace provision --account <account_id> --project <project_id> --template <template_id>
@@ -6231,9 +8365,6 @@ Cloud build path:
6231
8365
  embed build workspace source search <workspace_id> --query init --glob "**/*.c"
6232
8366
  embed build workspace source patch <workspace_id> --patch ./fix.patch
6233
8367
  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"
6237
8368
  embed build image boot-logo --logo ./logo.png --board taishanpi --variant 1M-RK3566 --output ./boot-logo-package.json
6238
8369
  embed image boot-logo compose --package ./boot-logo-package.json --base-image ./boot.img --output ./boot-logo.img
6239
8370
  embed cloud task status <task_id>
@@ -6264,7 +8395,7 @@ Local hardware path:
6264
8395
  embed run "验证开发板状态"
6265
8396
  embed tool list
6266
8397
  embed tool call debug.tools.scan
6267
- embed tool call device.probe --input-json '{"host":"198.19.77.2","ports":[22,15301]}'
8398
+ embed tool call device.probe --input-json '{"host":"198.19.77.2","ports":[22]}'
6268
8399
  embed device list
6269
8400
  embed deploy taishanpi --host 198.19.77.2 --artifact ./artifact.bin --approve --run
6270
8401
  embed flash plan --board <rp2350|taishanpi> --artifact ./artifact.bin
@@ -6285,6 +8416,8 @@ Usage:
6285
8416
  embed auth logout [--json]
6286
8417
  embed plugin list [--release-dir <dir>] [--release-url <url>] [--json]
6287
8418
  embed plugin install <codex|opencode|all> [--release-dir <dir>] [--release-url <url>] [--target <dir>] [--codex-target <dir>] [--opencode-target <dir>] [--force] [--json]
8419
+ embed plugin update check [--release-url <url>] [--target <dir>] [--codex-target <dir>] [--opencode-target <dir>] [--json]
8420
+ embed plugin update <codex|opencode|all> [--release-url <url>] [--target <dir>] [--codex-target <dir>] [--opencode-target <dir>] [--json]
6288
8421
  embed service modes [--json]
6289
8422
  embed model list [--json]
6290
8423
  embed model default [--json]
@@ -6292,6 +8425,7 @@ Usage:
6292
8425
  embed board registry show <template_id> [--json]
6293
8426
  embed board methods <template_id> [--json]
6294
8427
  embed board knowledge <template_id> [--json]
8428
+ embed board knowledge search <template_id> --query <text> [--source board_pack|build_template|registry] [--limit 5] [--json]
6295
8429
  embed board knowledge file <template_id> --source board_pack|build_template|registry --path <relative_path> [--output <local_path>] [--json]
6296
8430
  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]
6297
8431
  embed run <natural language request> [--provider stub|openai|bai|cc|claude-code] [--approve] [--json]
@@ -6340,13 +8474,16 @@ Usage:
6340
8474
  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]
6341
8475
  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]
6342
8476
  embed image boot-logo compose --package <boot-logo-package.json> --base-image <boot.img|image.img> --output <image> [--manifest <manifest.json>] [--force] [--json]
6343
- embed local toolchain latest [--board taishanpi-1m-rk3566] [--channel stable] [--metadata-root <path>] [--json]
8477
+ embed local toolchain list [--board taishanpi-1m-rk3566|pico2w-rp2350-monitor|coloreasypico2-rp2350-monitor] [--channel stable] [--metadata-root <path>] [--install-root <path>] [--json]
8478
+ embed local toolchain installed [--board taishanpi-1m-rk3566|pico2w-rp2350-monitor|coloreasypico2-rp2350-monitor] [--channel stable] [--metadata-root <path>] [--install-root <path>] [--json]
8479
+ embed local toolchain latest [--board taishanpi-1m-rk3566|pico2w-rp2350-monitor|coloreasypico2-rp2350-monitor] [--channel stable] [--metadata-root <path>] [--json]
6344
8480
  embed local toolchain current [--install-root <path>] [--json]
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]
8481
+ embed local toolchain install [--board taishanpi-1m-rk3566|pico2w-rp2350-monitor|coloreasypico2-rp2350-monitor] [--channel stable] [--metadata-root <path>] [--source-url <tar.gz-url>|--source-release-root <path>] [--install-root <path>] [--mode minimal|runtime|compile|qt|firmware|full|images] [--force] [--json]
6346
8482
  Defaults to the production download channel at download.embedboard.com.
6347
- embed local toolchain validate [--release-root <path>] [--json]
8483
+ embed local toolchain uninstall --board taishanpi-1m-rk3566|pico2w-rp2350-monitor|coloreasypico2-rp2350-monitor [--install-root <path>] [--json]
8484
+ 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]
6348
8485
  embed local compile taishanpi --source <main.c|main.cpp> --output <artifact> [--release-root <path>] [--account <account_id>] [--json]
6349
- embed local build qt-smoke --build-dir <dir> [--source <qt-smoke-dir>] [--release-root <path>] [--account <account_id>] [--json]
8486
+ embed local build qt-smoke --build-dir <dir> [--source <qt-cmake-dir>] [--target-name <executable>] [--release-root <path>] [--account <account_id>] [--json]
6350
8487
  embed debug tools [--json]
6351
8488
  embed tool list [--json]
6352
8489
  embed tool call wifi.scan --input-json '{"host":"198.19.77.2","user":"root"}' [--json]
@@ -6355,6 +8492,18 @@ Usage:
6355
8492
  embed tool call chip.temperature --input-json '{"host":"198.19.77.2","user":"root"}' [--json]
6356
8493
  embed tool call qml.runtime.status --input-json '{"host":"198.19.77.2","user":"root","port":18130}' [--json]
6357
8494
  embed tool call qml.runtime.start --input-json '{"host":"198.19.77.2","user":"root","port":18130}' [--json]
8495
+ embed tool call rp2350.monitor.capabilities [--json]
8496
+ embed tool call rp2350.monitor.status [--json]
8497
+ embed tool call rp2350.monitor.gpio.read --input-json '{"pins":[16,17]}' --approve [--json]
8498
+ embed tool call rp2350.monitor.gpio.write --input-json '{"pin":16,"level":true}' --approve [--json]
8499
+ embed tool call rp2350.monitor.uart.write --input-json '{"baud":115200,"text":"hello","line_ending":"lf"}' --approve [--json]
8500
+ embed tool call rp2350.monitor.i2c.transfer --input-json '{"address":"0x50","write":"00","read_len":4}' --approve [--json]
8501
+ embed tool call rp2350.monitor.spi.transfer --input-json '{"hex":"a55a3cc3"}' --approve [--json]
8502
+ embed tool call rp2350.monitor.logic.capture --input-json '{"pin_base":16,"pin_count":4,"sample_rate":1000000,"samples":4096}' --approve [--json]
8503
+ embed tool call rp2350.monitor.logic.decode --input-json '{"input_path":".embed-labs/rp2350-monitor/captures/logic.jsonl","decoder":"summary"}' [--json]
8504
+ embed tool call rp2350.monitor.wifi.manage --input-json '{"action":"scan"}' --approve [--json]
8505
+ embed tool call rp2350.monitor.probe.debug --input-json '{"action":"status"}' --approve [--json]
8506
+ embed tool call rp2350.monitor.operation --input-json '{"action":"logic.stop","params":{}}' --approve [--json]
6358
8507
  embed deploy taishanpi --host <ip> --artifact <local_file> --approve [--remote-path /userdata/embed-labs/apps/app] [--run] [--json]
6359
8508
  embed board deploy taishanpi --host <ip> --artifact <local_file> --approve [--remote-path /userdata/embed-labs/apps/app] [--run] [--json]
6360
8509
  embed device list [--json]
@@ -6388,7 +8537,7 @@ Usage:
6388
8537
 
6389
8538
  Environment:
6390
8539
  EMBED_BRIDGE_URL=http://127.0.0.1:18083
6391
- EMBED_CLOUD_API_URL=http://127.0.0.1:18100
8540
+ EMBED_CLOUD_API_URL=https://api.embedboard.com
6392
8541
  EMBED_API_TOKEN=<token>
6393
8542
  CODEX_HOME=~/.codex
6394
8543
  `);