@kvell007/embed-labs-cli 0.1.0-alpha.73 → 0.1.0-alpha.74

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