@kvell007/embed-labs-cli 0.1.0-alpha.1 → 0.1.0-alpha.11

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,13 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  import { createHash } from "node:crypto";
3
+ import { constants } from "node:fs";
3
4
  import { spawn } from "node:child_process";
4
- import { cp, mkdir, mkdtemp, readFile, rm, stat, writeFile } from "node:fs/promises";
5
+ import { access, cp, mkdir, mkdtemp, readFile, rm, stat, writeFile } from "node:fs/promises";
5
6
  import { createRequire } from "node:module";
6
7
  import { homedir, tmpdir } from "node:os";
7
- import { basename, dirname, join, resolve } from "node:path";
8
+ import { basename, delimiter, dirname, join, resolve } from "node:path";
9
+ import { fileURLToPath } from "node:url";
8
10
  import { startServer } from "@embed-labs/local-bridge";
11
+ import { composeBootLogoPackage } from "./image-compose.js";
12
+ import { buildTaishanPiQtSmoke, compileTaishanPiSingleFile, currentLocalToolchain, installLocalToolchain, latestLocalToolchain, validateLocalToolchain } from "./local-toolchain.js";
9
13
  import { fail, ok } from "@embed-labs/protocol";
10
14
  const require = createRequire(import.meta.url);
15
+ const CLI_MODULE_DIR = dirname(fileURLToPath(import.meta.url));
16
+ const SOURCE_CHECKOUT_ROOT = resolve(CLI_MODULE_DIR, "..", "..", "..");
11
17
  const qrcodeTerminal = require("qrcode-terminal");
12
18
  const DEFAULT_BRIDGE_URL = process.env.EMBED_BRIDGE_URL ?? "http://127.0.0.1:18083";
13
19
  const DEFAULT_CLOUD_API_URL = process.env.EMBED_CLOUD_API_URL ?? "http://127.0.0.1:18100";
@@ -71,6 +77,16 @@ const BUILD_APPLICATION_STUB_USAGE = "Usage: embed build application stub --work
71
77
  const BUILD_APPLICATION_GENERATE_USAGE = "Usage: embed build application generate --workspace <workspace_id> --prompt <request> [--account <account_id>] [--target <source_path>] [--provider stub|openai|bai|cc|claude-code] [--model <model>] [--json]";
72
78
  const BUILD_APPLICATION_COMPILE_USAGE = "Usage: embed build application compile --workspace <workspace_id> [--account <account_id>] [--source <source_path>] [--execution-mode docker_worker|server_direct|dry_run] [--compiler auto|cc|gcc|clang|aarch64-linux-gnu-gcc] [--json]";
73
79
  const BUILD_IMAGE_GENERATE_USAGE = "Usage: 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]";
80
+ const BUILD_IMAGE_BOOT_LOGO_USAGE = "Usage: 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]";
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]";
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]";
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]";
84
+ const LOCAL_TOOLCHAIN_LATEST_USAGE = "Usage: embed local toolchain latest [--board taishanpi-1m-rk3566] [--channel stable] [--metadata-root <path>] [--json]";
85
+ const LOCAL_TOOLCHAIN_CURRENT_USAGE = "Usage: embed local toolchain current [--install-root <path>] [--json]";
86
+ const LOCAL_TOOLCHAIN_INSTALL_USAGE = "Usage: embed local toolchain install [--board taishanpi-1m-rk3566] [--channel stable] [--metadata-root <path>] [--source-url <tar.gz-url>|--source-release-root <path>] [--install-root <path>] [--force] [--json]\nDefault source: the production download channel at download.embedboard.com.";
87
+ const LOCAL_TOOLCHAIN_VALIDATE_USAGE = "Usage: embed local toolchain validate [--release-root <path>] [--json]";
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]";
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]";
74
90
  const BOARD_REGISTRY_LIST_USAGE = "Usage: embed board registry list [--json]";
75
91
  const BOARD_REGISTRY_SHOW_USAGE = "Usage: embed board registry show <template_id> [--json]";
76
92
  const BOARD_METHODS_USAGE = "Usage: embed board methods <template_id> [--json]";
@@ -79,9 +95,10 @@ const BOARD_KNOWLEDGE_FILE_USAGE = "Usage: embed board knowledge file <template_
79
95
  const MODEL_LIST_USAGE = "Usage: embed model list [--json]";
80
96
  const MODEL_DEFAULT_USAGE = "Usage: embed model default [--json]";
81
97
  const SERVICE_MODES_USAGE = "Usage: embed service modes [--json]";
82
- 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 4] [--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]";
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]";
83
99
  const TOOL_LIST_USAGE = "Usage: embed tool list [--json]";
84
100
  const TOOL_CALL_USAGE = "Usage: embed tool call <capability_id> [--input-json '<json>'] [--approve] [--json]";
101
+ const MCP_TOOL_EVENT_USAGE = "Usage: embed mcp log --tool <tool_name> [--client codex|opencode] [--mode local_ai|server_ai] [--server-model-used true|false] [--success true|false] [--request-id <id>] [--duration-ms <ms>] [--input-summary <text>] [--output-summary <text>] [--json]";
85
102
  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]";
86
103
  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]";
87
104
  const TASK_STATES = new Set([
@@ -351,6 +368,16 @@ async function main(argv) {
351
368
  USAGE_EVENTS_USAGE
352
369
  ].join("\n")), undefined, 2);
353
370
  }
371
+ if (area === "mcp") {
372
+ if (action === "log" || action === "tool-event") {
373
+ const body = mcpToolEventBody(parsed);
374
+ if (typeof body === "string") {
375
+ return output(parsed, fail("invalid_args", body), undefined, 2);
376
+ }
377
+ return output(parsed, await cloudPost("/v1/mcp/tool-events", body), renderMcpToolEvent);
378
+ }
379
+ return output(parsed, fail("invalid_args", MCP_TOOL_EVENT_USAGE), undefined, 2);
380
+ }
354
381
  if (area === "billing") {
355
382
  if (action === "statement") {
356
383
  const request = billingStatementRequest(parsed);
@@ -472,6 +499,75 @@ async function main(argv) {
472
499
  BILLING_SNAPSHOT_SHOW_USAGE
473
500
  ].join("\n")), undefined, 2);
474
501
  }
502
+ if (area === "image") {
503
+ if ((action === "boot-logo" && parsed.command[2] === "compose") || (action === "compose" && parsed.command[2] === "boot-logo")) {
504
+ const request = imageBootLogoComposeRequest(parsed, IMAGE_BOOT_LOGO_COMPOSE_USAGE);
505
+ if (typeof request === "string") {
506
+ return output(parsed, fail("invalid_args", request), undefined, 2);
507
+ }
508
+ return output(parsed, ok(await composeBootLogoPackage(request)), renderBootLogoComposeResult);
509
+ }
510
+ if ((action === "dtb" && parsed.command[2] === "compose") || (action === "compose" && parsed.command[2] === "dtb")) {
511
+ const request = imageBootLogoComposeRequest(parsed, IMAGE_DTB_COMPOSE_USAGE);
512
+ if (typeof request === "string") {
513
+ return output(parsed, fail("invalid_args", request), undefined, 2);
514
+ }
515
+ return output(parsed, ok(await composeBootLogoPackage(request)), renderBootLogoComposeResult);
516
+ }
517
+ return output(parsed, fail("invalid_args", [IMAGE_BOOT_LOGO_COMPOSE_USAGE, IMAGE_DTB_COMPOSE_USAGE].join("\n")), undefined, 2);
518
+ }
519
+ if (area === "local") {
520
+ if (action === "toolchain" && parsed.command[2] === "latest") {
521
+ const request = localToolchainLatestRequest(parsed);
522
+ if (typeof request === "string") {
523
+ return output(parsed, fail("invalid_args", request), undefined, 2);
524
+ }
525
+ return output(parsed, ok(await latestLocalToolchain(request)), renderLocalToolchainLatest);
526
+ }
527
+ if (action === "toolchain" && parsed.command[2] === "current") {
528
+ const request = localToolchainCurrentRequest(parsed);
529
+ if (typeof request === "string") {
530
+ return output(parsed, fail("invalid_args", request), undefined, 2);
531
+ }
532
+ return output(parsed, ok(await currentLocalToolchain(request.installRoot)), renderLocalToolchainCurrent);
533
+ }
534
+ if (action === "toolchain" && parsed.command[2] === "install") {
535
+ const request = localToolchainInstallRequest(parsed);
536
+ if (typeof request === "string") {
537
+ return output(parsed, fail("invalid_args", request), undefined, 2);
538
+ }
539
+ return output(parsed, ok(await installLocalToolchain(request)), renderLocalToolchainInstall);
540
+ }
541
+ if (action === "toolchain" && parsed.command[2] === "validate") {
542
+ const request = localToolchainValidateRequest(parsed);
543
+ if (typeof request === "string") {
544
+ return output(parsed, fail("invalid_args", request), undefined, 2);
545
+ }
546
+ return output(parsed, ok(await validateLocalToolchain(request.releaseRoot)), renderLocalToolchainValidation);
547
+ }
548
+ if (action === "compile" && parsed.command[2] === "taishanpi") {
549
+ const request = localCompileTaishanPiRequest(parsed, await authStatus());
550
+ if (typeof request === "string") {
551
+ return output(parsed, fail("invalid_args", request), undefined, 2);
552
+ }
553
+ return output(parsed, ok(await compileTaishanPiSingleFile(request)), renderLocalCompileResult);
554
+ }
555
+ if (action === "build" && parsed.command[2] === "qt-smoke") {
556
+ const request = localBuildQtSmokeRequest(parsed, await authStatus());
557
+ if (typeof request === "string") {
558
+ return output(parsed, fail("invalid_args", request), undefined, 2);
559
+ }
560
+ return output(parsed, ok(await buildTaishanPiQtSmoke(request)), renderLocalCompileResult);
561
+ }
562
+ return output(parsed, fail("invalid_args", [
563
+ LOCAL_TOOLCHAIN_LATEST_USAGE,
564
+ LOCAL_TOOLCHAIN_CURRENT_USAGE,
565
+ LOCAL_TOOLCHAIN_INSTALL_USAGE,
566
+ LOCAL_TOOLCHAIN_VALIDATE_USAGE,
567
+ LOCAL_COMPILE_TAISHANPI_USAGE,
568
+ LOCAL_BUILD_QT_SMOKE_USAGE
569
+ ].join("\n")), undefined, 2);
570
+ }
475
571
  if (area === "build") {
476
572
  if (action === "template") {
477
573
  const templateAction = parsed.command[2];
@@ -617,6 +713,44 @@ async function main(argv) {
617
713
  }
618
714
  return output(parsed, await cloudPost("/v1/build/image-generate", body), renderJob);
619
715
  }
716
+ if (action === "image" && (parsed.command[2] === "boot-logo" || parsed.command[2] === "boot-logo-package")) {
717
+ const request = await buildImageBootLogoPackageRequest(parsed);
718
+ if (typeof request === "string") {
719
+ return output(parsed, fail("invalid_args", request), undefined, 2);
720
+ }
721
+ const created = await cloudPost("/v1/build/image/boot-logo-package", request.body);
722
+ if (!created.ok || !request.output_path) {
723
+ return output(parsed, created, renderJob, created.ok ? 0 : 2);
724
+ }
725
+ const packageArtifact = created.data.artifacts?.find((artifact) => artifact.kind === "patch" && artifact.name === "boot-logo-package.json");
726
+ if (!packageArtifact) {
727
+ return output(parsed, fail("artifact_not_found", "Boot-logo package task did not include boot-logo-package.json."), undefined, 2);
728
+ }
729
+ const downloaded = await cloudDownloadArtifact(packageArtifact.artifact_id, request.output_path);
730
+ if (!downloaded.ok) {
731
+ return output(parsed, downloaded, renderArtifactDownload, 2);
732
+ }
733
+ return output(parsed, ok({ task: created.data, downloaded_artifact: downloaded.data }), renderBootLogoPackageResult);
734
+ }
735
+ if (action === "image" && (parsed.command[2] === "dtb" || parsed.command[2] === "dtb-package")) {
736
+ const request = await buildImageDtbPackageRequest(parsed);
737
+ if (typeof request === "string") {
738
+ return output(parsed, fail("invalid_args", request), undefined, 2);
739
+ }
740
+ const created = await cloudPost("/v1/build/image/dtb-package", request.body);
741
+ if (!created.ok || !request.output_path) {
742
+ return output(parsed, created, renderJob, created.ok ? 0 : 2);
743
+ }
744
+ const packageArtifact = created.data.artifacts?.find((artifact) => artifact.kind === "patch" && artifact.name === "dtb-package.json");
745
+ if (!packageArtifact) {
746
+ return output(parsed, fail("artifact_not_found", "DTB package task did not include dtb-package.json."), undefined, 2);
747
+ }
748
+ const downloaded = await cloudDownloadArtifact(packageArtifact.artifact_id, request.output_path);
749
+ if (!downloaded.ok) {
750
+ return output(parsed, downloaded, renderArtifactDownload, 2);
751
+ }
752
+ return output(parsed, ok({ task: created.data, downloaded_artifact: downloaded.data }), renderDtbPackageResult);
753
+ }
620
754
  return output(parsed, fail("invalid_args", [
621
755
  BUILD_TEMPLATE_LIST_USAGE,
622
756
  BUILD_TEMPLATE_SHOW_USAGE,
@@ -633,7 +767,9 @@ async function main(argv) {
633
767
  BUILD_APPLICATION_STUB_USAGE,
634
768
  BUILD_APPLICATION_GENERATE_USAGE,
635
769
  BUILD_APPLICATION_COMPILE_USAGE,
636
- BUILD_IMAGE_GENERATE_USAGE
770
+ BUILD_IMAGE_GENERATE_USAGE,
771
+ BUILD_IMAGE_BOOT_LOGO_USAGE,
772
+ BUILD_IMAGE_DTB_USAGE
637
773
  ].join("\n")), undefined, 2);
638
774
  }
639
775
  if (area === "project" && action === "create") {
@@ -1304,16 +1440,16 @@ async function pluginList(parsed) {
1304
1440
  return remoteManifest;
1305
1441
  }
1306
1442
  const effectiveManifest = manifest?.data ?? remoteManifest?.data;
1307
- const codexPackage = manifest?.data.packages?.find((item) => item.id === "codex-dbt-agent");
1308
- const opencodePackage = manifest?.data.packages?.find((item) => item.id === "opencode-dbt-agent");
1309
- const effectiveCodexPackage = effectiveManifest?.packages?.find((item) => item.id === "codex-dbt-agent") ?? codexPackage;
1310
- const effectiveOpenCodePackage = effectiveManifest?.packages?.find((item) => item.id === "opencode-dbt-agent") ?? opencodePackage;
1443
+ const codexPackage = manifest?.data.packages?.find((item) => item.id === "codex-embed-labs");
1444
+ const opencodePackage = manifest?.data.packages?.find((item) => item.id === "opencode-embed-labs");
1445
+ const effectiveCodexPackage = effectiveManifest?.packages?.find((item) => item.id === "codex-embed-labs") ?? codexPackage;
1446
+ const effectiveOpenCodePackage = effectiveManifest?.packages?.find((item) => item.id === "opencode-embed-labs") ?? opencodePackage;
1311
1447
  const source = releaseDir || remoteManifest ? "release_dir" : "source_checkout";
1312
1448
  return ok({
1313
1449
  plugins: [
1314
1450
  {
1315
1451
  id: "codex",
1316
- display_name: "Embed Labs Cloud Codex plugin",
1452
+ display_name: "Embed Labs Codex plugin",
1317
1453
  source,
1318
1454
  version: effectiveCodexPackage?.version ?? effectiveManifest?.version ?? await localPluginVersion("codex"),
1319
1455
  release_file: effectiveCodexPackage?.file,
@@ -1322,7 +1458,7 @@ async function pluginList(parsed) {
1322
1458
  },
1323
1459
  {
1324
1460
  id: "opencode",
1325
- display_name: "Embed Labs Cloud OpenCode plugin",
1461
+ display_name: "Embed Labs OpenCode plugin",
1326
1462
  source,
1327
1463
  version: effectiveOpenCodePackage?.version ?? effectiveManifest?.version ?? await localPluginVersion("opencode"),
1328
1464
  release_file: effectiveOpenCodePackage?.file,
@@ -1398,7 +1534,7 @@ async function installCodexPlugin(parsed, context) {
1398
1534
  return source;
1399
1535
  }
1400
1536
  const targetRoot = codexPluginTargetRoot(parsed, context.installingAll);
1401
- const targetPath = join(targetRoot, "dbt-agent");
1537
+ const targetPath = join(targetRoot, "embed-labs");
1402
1538
  if (await pathExists(targetPath) && !booleanFlag(parsed, "force")) {
1403
1539
  return fail("plugin_already_installed", `Codex plugin already exists at ${targetPath}.`, {
1404
1540
  remediation: "Pass --force to replace it, or pass --codex-target/--target to install into a different directory."
@@ -1407,12 +1543,17 @@ async function installCodexPlugin(parsed, context) {
1407
1543
  await rm(targetPath, { recursive: true, force: true });
1408
1544
  await mkdir(targetRoot, { recursive: true });
1409
1545
  await cp(source.data.sourcePath, targetPath, { recursive: true });
1546
+ const mcpRegistration = await maybeRegisterCodexMcp(parsed, targetRoot, targetPath);
1410
1547
  return ok({
1411
1548
  id: "codex",
1412
1549
  target_path: targetPath,
1413
1550
  source: source.data.sourceLabel,
1414
1551
  version: source.data.version,
1415
- command_hint: "Restart Codex or reload plugins after installing."
1552
+ command_hint: mcpRegistration.registered
1553
+ ? "Codex MCP was registered. Start a new Codex session to reload tools."
1554
+ : mcpRegistration.hint,
1555
+ mcp_registered: mcpRegistration.registered,
1556
+ mcp_warning: mcpRegistration.warning
1416
1557
  });
1417
1558
  }
1418
1559
  async function installOpenCodePlugin(parsed, context) {
@@ -1448,20 +1589,22 @@ async function installOpenCodePlugin(parsed, context) {
1448
1589
  });
1449
1590
  }
1450
1591
  await ensureOpenCodeInstallPackageJson(targetRoot);
1451
- await writeFile(wrapperPath, `export { default, DevelopmentBoardToolchainPlugin } from "dbt-agent";\n`, "utf8");
1592
+ await writeFile(wrapperPath, `export { default, DevelopmentBoardToolchainPlugin } from "embed-labs";\n`, "utf8");
1593
+ const duplicateWarning = await openCodeDuplicatePluginWarning(targetRoot);
1452
1594
  return ok({
1453
1595
  id: "opencode",
1454
1596
  target_path: targetRoot,
1455
1597
  source: source.data.sourceLabel,
1456
1598
  version: source.data.version,
1457
- command_hint: "Start OpenCode from the project containing this .opencode directory."
1599
+ command_hint: "Start OpenCode from the project containing this .opencode directory.",
1600
+ warning: duplicateWarning
1458
1601
  });
1459
1602
  }
1460
1603
  async function resolveCodexPluginSource(context) {
1461
1604
  if (context.releaseDir) {
1462
- const item = context.manifest?.packages?.find((entry) => entry.id === "codex-dbt-agent");
1605
+ const item = context.manifest?.packages?.find((entry) => entry.id === "codex-embed-labs");
1463
1606
  if (!item?.file) {
1464
- return fail("plugin_release_not_found", `Release manifest in ${context.releaseDir} does not include codex-dbt-agent.`);
1607
+ return fail("plugin_release_not_found", `Release manifest in ${context.releaseDir} does not include codex-embed-labs.`);
1465
1608
  }
1466
1609
  const tarball = resolve(context.releaseDir, item.file);
1467
1610
  const verified = await verifyReleasePackage(tarball, item);
@@ -1476,13 +1619,13 @@ async function resolveCodexPluginSource(context) {
1476
1619
  details: { exit_code: tarResult.code, stderr_tail: tarResult.stderr.split("\n").slice(-20) }
1477
1620
  });
1478
1621
  }
1479
- const sourcePath = join(extractDir, "codex_plugin", "plugins", "dbt-agent");
1622
+ const sourcePath = join(extractDir, "codex_plugin", "plugins", "embed-labs");
1480
1623
  if (!await pathExists(join(sourcePath, ".codex-plugin", "plugin.json"))) {
1481
- return fail("plugin_release_invalid", `Codex plugin release ${tarball} did not contain plugins/dbt-agent/.codex-plugin/plugin.json.`);
1624
+ return fail("plugin_release_invalid", `Codex plugin release ${tarball} did not contain plugins/embed-labs/.codex-plugin/plugin.json.`);
1482
1625
  }
1483
1626
  return ok({ sourcePath, sourceLabel: tarball, version: item.version ?? context.manifest?.version });
1484
1627
  }
1485
- const sourcePath = resolve(process.cwd(), "platform_plugins", "codex_plugin", "plugins", "dbt-agent");
1628
+ const sourcePath = sourceCheckoutPath("platform_plugins", "codex_plugin", "plugins", "embed-labs");
1486
1629
  if (!await pathExists(join(sourcePath, ".codex-plugin", "plugin.json"))) {
1487
1630
  return fail("plugin_source_not_found", "Could not find Codex plugin source in this checkout.", {
1488
1631
  remediation: "Run from the Embed-Labs-Cloud repo root or pass --release-dir pointing to a plugin release directory."
@@ -1492,9 +1635,9 @@ async function resolveCodexPluginSource(context) {
1492
1635
  }
1493
1636
  async function resolveOpenCodePluginSource(context) {
1494
1637
  if (context.releaseDir) {
1495
- const item = context.manifest?.packages?.find((entry) => entry.id === "opencode-dbt-agent");
1638
+ const item = context.manifest?.packages?.find((entry) => entry.id === "opencode-embed-labs");
1496
1639
  if (!item?.file) {
1497
- return fail("plugin_release_not_found", `Release manifest in ${context.releaseDir} does not include opencode-dbt-agent.`);
1640
+ return fail("plugin_release_not_found", `Release manifest in ${context.releaseDir} does not include opencode-embed-labs.`);
1498
1641
  }
1499
1642
  const tarball = resolve(context.releaseDir, item.file);
1500
1643
  const verified = await verifyReleasePackage(tarball, item);
@@ -1503,13 +1646,38 @@ async function resolveOpenCodePluginSource(context) {
1503
1646
  }
1504
1647
  return ok({ packagePath: tarball, sourceLabel: tarball, version: item.version ?? context.manifest?.version });
1505
1648
  }
1506
- const packagePath = resolve(process.cwd(), "platform_plugins", "opencode_plugin");
1649
+ const packagePath = sourceCheckoutPath("platform_plugins", "opencode_plugin");
1507
1650
  if (!await pathExists(join(packagePath, "package.json"))) {
1508
1651
  return fail("plugin_source_not_found", "Could not find OpenCode plugin source in this checkout.", {
1509
1652
  remediation: "Run from the Embed-Labs-Cloud repo root or pass --release-dir pointing to a plugin release directory."
1510
1653
  });
1511
1654
  }
1512
- return ok({ packagePath, sourceLabel: packagePath, version: await localPluginVersion("opencode") });
1655
+ const packed = await runLocalProcess("npm", ["pack", packagePath, "--pack-destination", context.tempDir, "--json"]);
1656
+ if (packed.code !== 0) {
1657
+ return fail("opencode_plugin_pack_failed", "npm pack failed while preparing the OpenCode plugin source package.", {
1658
+ details: {
1659
+ exit_code: packed.code,
1660
+ stdout_tail: packed.stdout.split("\n").slice(-20),
1661
+ stderr_tail: packed.stderr.split("\n").slice(-20)
1662
+ }
1663
+ });
1664
+ }
1665
+ let tarballName = "";
1666
+ try {
1667
+ const parsed = JSON.parse(packed.stdout);
1668
+ tarballName = basename(parsed[0]?.filename || "");
1669
+ }
1670
+ catch {
1671
+ tarballName = "";
1672
+ }
1673
+ if (!tarballName) {
1674
+ return fail("opencode_plugin_pack_failed", "npm pack did not report an OpenCode plugin tarball filename.");
1675
+ }
1676
+ const tarballPath = join(context.tempDir, tarballName);
1677
+ if (!await pathExists(tarballPath)) {
1678
+ return fail("opencode_plugin_pack_failed", `npm pack reported ${tarballName}, but the tarball was not found.`);
1679
+ }
1680
+ return ok({ packagePath: tarballPath, sourceLabel: `${packagePath} -> ${tarballPath}`, version: await localPluginVersion("opencode") });
1513
1681
  }
1514
1682
  async function fetchRemotePluginManifest(parsed) {
1515
1683
  const manifestUrl = `${pluginReleaseBaseUrl(parsed)}/manifest.json`;
@@ -1645,13 +1813,204 @@ function openCodePluginTargetRoot(parsed, installingAll) {
1645
1813
  function defaultCodexPluginRoot() {
1646
1814
  return join(process.env.CODEX_HOME?.trim() || join(homedir(), ".codex"), "plugins");
1647
1815
  }
1816
+ async function maybeRegisterCodexMcp(parsed, targetRoot, targetPath) {
1817
+ const explicitTarget = Boolean(stringFlag(parsed, "target") || stringFlag(parsed, "codex-target"));
1818
+ if (explicitTarget && process.env.EMBED_CODEX_MCP_REGISTER !== "1") {
1819
+ return {
1820
+ registered: false,
1821
+ hint: `Installed into a custom target. Register manually with: codex mcp add embed-labs -- node ${join(targetPath, "scripts", "embed-labs-mcp-bridge.mjs")}`
1822
+ };
1823
+ }
1824
+ const bridgePath = join(targetPath, "scripts", "embed-labs-mcp-bridge.mjs");
1825
+ if (!await pathExists(bridgePath)) {
1826
+ return {
1827
+ registered: false,
1828
+ hint: "Restart Codex or reload plugins after installing.",
1829
+ warning: `Codex MCP bridge was not found at ${bridgePath}.`
1830
+ };
1831
+ }
1832
+ const codexBin = await resolveExecutableOnPath("codex");
1833
+ if (!codexBin) {
1834
+ return {
1835
+ registered: false,
1836
+ hint: `Codex CLI was not found on PATH. Register manually with: codex mcp add embed-labs -- node ${bridgePath}`
1837
+ };
1838
+ }
1839
+ const embedCliBin = process.env.EMBED_CLI_BIN?.trim() || await resolveExecutableOnPath("embedlabs") || await resolveExecutableOnPath("embed") || "";
1840
+ const authFile = resolve(process.env.EMBED_AUTH_FILE?.trim() || DEFAULT_AUTH_FILE);
1841
+ const cloudUrl = pluginMcpCloudApiUrl(parsed);
1842
+ const existing = await runLocalProcess(codexBin, ["mcp", "get", "embed-labs", "--json"]);
1843
+ if (existing.code === 0 && codexMcpAlreadyRegistered(existing.stdout, bridgePath, cloudUrl, authFile, embedCliBin)) {
1844
+ const warning = await upsertCodexMcpRuntimeConfig(bridgePath);
1845
+ if (warning) {
1846
+ return { registered: true, warning };
1847
+ }
1848
+ return { registered: true };
1849
+ }
1850
+ await runLocalProcess(codexBin, ["mcp", "remove", "embed-labs"]);
1851
+ const args = [
1852
+ "mcp",
1853
+ "add",
1854
+ "embed-labs",
1855
+ "--env",
1856
+ `EMBED_CLOUD_API_URL=${cloudUrl}`,
1857
+ "--env",
1858
+ `EMBED_AUTH_FILE=${authFile}`
1859
+ ];
1860
+ if (embedCliBin) {
1861
+ args.push("--env", `EMBED_CLI_BIN=${embedCliBin}`);
1862
+ }
1863
+ args.push("--", process.execPath, bridgePath);
1864
+ const addResult = await runLocalProcess(codexBin, args);
1865
+ if (addResult.code !== 0) {
1866
+ return {
1867
+ registered: false,
1868
+ hint: `Codex plugin installed. Register manually with: codex mcp add embed-labs -- ${process.execPath} ${bridgePath}`,
1869
+ warning: `codex mcp add failed: ${addResult.stderr.trim() || addResult.stdout.trim() || `exit ${addResult.code}`}`
1870
+ };
1871
+ }
1872
+ const warning = await upsertCodexMcpRuntimeConfig(bridgePath);
1873
+ return warning ? { registered: true, warning } : { registered: true };
1874
+ }
1875
+ function codexMcpAlreadyRegistered(stdout, bridgePath, cloudUrl, authFile, embedCliBin) {
1876
+ try {
1877
+ const parsed = JSON.parse(stdout);
1878
+ const transport = parsed.transport;
1879
+ if (transport?.type !== "stdio" || transport.command !== process.execPath)
1880
+ return false;
1881
+ if (!transport.args?.includes(bridgePath))
1882
+ return false;
1883
+ const env = transport.env || {};
1884
+ if (env.EMBED_CLOUD_API_URL !== cloudUrl)
1885
+ return false;
1886
+ if (env.EMBED_AUTH_FILE !== authFile)
1887
+ return false;
1888
+ if (embedCliBin && env.EMBED_CLI_BIN !== embedCliBin)
1889
+ return false;
1890
+ return true;
1891
+ }
1892
+ catch {
1893
+ return false;
1894
+ }
1895
+ }
1896
+ async function upsertCodexMcpRuntimeConfig(bridgePath) {
1897
+ const configPath = join(process.env.CODEX_HOME?.trim() || join(homedir(), ".codex"), "config.toml");
1898
+ try {
1899
+ await mkdir(dirname(configPath), { recursive: true });
1900
+ let text = "";
1901
+ try {
1902
+ text = await readFile(configPath, "utf8");
1903
+ }
1904
+ catch {
1905
+ text = "";
1906
+ }
1907
+ const updated = upsertTomlTableKeys(text, "mcp_servers.embed-labs", {
1908
+ command: tomlString(process.execPath),
1909
+ args: `[${tomlString(bridgePath)}]`,
1910
+ startup_timeout_sec: "120"
1911
+ });
1912
+ if (updated !== text) {
1913
+ await writeFile(configPath, updated, "utf8");
1914
+ }
1915
+ return undefined;
1916
+ }
1917
+ catch (error) {
1918
+ return `Codex MCP was registered, but ${configPath} could not be updated with startup_timeout_sec: ${error instanceof Error ? error.message : String(error)}`;
1919
+ }
1920
+ }
1921
+ function upsertTomlTableKeys(text, tableName, values) {
1922
+ const normalized = text.endsWith("\n") || text.length === 0 ? text : `${text}\n`;
1923
+ const header = `[${tableName}]`;
1924
+ const tablePattern = new RegExp(`(^|\\n)(\\[${escapeRegExp(tableName)}\\]\\n)([\\s\\S]*?)(?=\\n\\[|$)`);
1925
+ const match = tablePattern.exec(normalized);
1926
+ const bodyWithValues = (body) => {
1927
+ let updated = body;
1928
+ for (const [key, value] of Object.entries(values)) {
1929
+ const keyPattern = new RegExp(`^${escapeRegExp(key)}\\s*=.*$`, "m");
1930
+ if (keyPattern.test(updated)) {
1931
+ updated = updated.replace(keyPattern, `${key} = ${value}`);
1932
+ }
1933
+ else {
1934
+ updated = `${updated.replace(/\s*$/, "\n")}${key} = ${value}\n`;
1935
+ }
1936
+ }
1937
+ return updated;
1938
+ };
1939
+ if (!match) {
1940
+ const prefix = normalized.length > 0 && !normalized.endsWith("\n\n") ? `${normalized}\n` : normalized;
1941
+ return `${prefix}${header}\n${bodyWithValues("")}`;
1942
+ }
1943
+ return `${normalized.slice(0, match.index)}${match[1]}${match[2]}${bodyWithValues(match[3])}${normalized.slice(match.index + match[0].length)}`;
1944
+ }
1945
+ function tomlString(value) {
1946
+ return JSON.stringify(value);
1947
+ }
1948
+ function escapeRegExp(value) {
1949
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1950
+ }
1951
+ function pluginMcpCloudApiUrl(parsed) {
1952
+ const explicit = process.env.EMBED_CLOUD_API_URL?.trim();
1953
+ if (explicit)
1954
+ return explicit.replace(/\/+$/, "");
1955
+ const releaseUrl = stringFlag(parsed, "release-url") || DEFAULT_PLUGIN_RELEASE_URL;
1956
+ try {
1957
+ return new URL(releaseUrl).origin;
1958
+ }
1959
+ catch {
1960
+ return DEFAULT_CLOUD_API_URL.replace(/\/+$/, "");
1961
+ }
1962
+ }
1963
+ async function resolveExecutableOnPath(name) {
1964
+ if (name.includes("/")) {
1965
+ try {
1966
+ await access(name, constants.X_OK);
1967
+ return name;
1968
+ }
1969
+ catch {
1970
+ return undefined;
1971
+ }
1972
+ }
1973
+ const pathEntries = (process.env.PATH || "").split(delimiter).filter(Boolean);
1974
+ for (const entry of pathEntries) {
1975
+ const candidate = join(entry, name);
1976
+ try {
1977
+ await access(candidate, constants.X_OK);
1978
+ return candidate;
1979
+ }
1980
+ catch {
1981
+ // Keep searching PATH.
1982
+ }
1983
+ }
1984
+ return undefined;
1985
+ }
1648
1986
  function defaultOpenCodeRoot() {
1649
1987
  return join(process.cwd(), ".opencode");
1650
1988
  }
1989
+ async function openCodeDuplicatePluginWarning(targetRoot) {
1990
+ const globalRoot = join(homedir(), ".config", "opencode");
1991
+ if (resolve(targetRoot) === resolve(globalRoot))
1992
+ return undefined;
1993
+ const configPath = join(globalRoot, "opencode.json");
1994
+ try {
1995
+ const parsed = JSON.parse(await readFile(configPath, "utf8"));
1996
+ const configuredPlugins = Array.isArray(parsed.plugin)
1997
+ ? parsed.plugin
1998
+ : Array.isArray(parsed.plugins)
1999
+ ? parsed.plugins
2000
+ : [];
2001
+ if (configuredPlugins.some((item) => item === "embed-labs")) {
2002
+ return "Global OpenCode config already enables embed-labs; OpenCode may load both the global plugin and this project .opencode plugin. Disable one entry if duplicate DBT tools appear.";
2003
+ }
2004
+ }
2005
+ catch {
2006
+ return undefined;
2007
+ }
2008
+ return undefined;
2009
+ }
1651
2010
  async function localPluginVersion(kind) {
1652
2011
  const filePath = kind === "codex"
1653
- ? resolve(process.cwd(), "platform_plugins", "codex_plugin", "plugins", "dbt-agent", ".codex-plugin", "plugin.json")
1654
- : resolve(process.cwd(), "platform_plugins", "opencode_plugin", "package.json");
2012
+ ? sourceCheckoutPath("platform_plugins", "codex_plugin", "plugins", "embed-labs", ".codex-plugin", "plugin.json")
2013
+ : sourceCheckoutPath("platform_plugins", "opencode_plugin", "package.json");
1655
2014
  try {
1656
2015
  const parsed = JSON.parse(await readFile(filePath, "utf8"));
1657
2016
  return parsed.version;
@@ -1661,8 +2020,11 @@ async function localPluginVersion(kind) {
1661
2020
  }
1662
2021
  }
1663
2022
  async function localPluginSourcesAvailable() {
1664
- return await pathExists(resolve(process.cwd(), "platform_plugins", "codex_plugin", "plugins", "dbt-agent", ".codex-plugin", "plugin.json"))
1665
- && await pathExists(resolve(process.cwd(), "platform_plugins", "opencode_plugin", "package.json"));
2023
+ return await pathExists(sourceCheckoutPath("platform_plugins", "codex_plugin", "plugins", "embed-labs", ".codex-plugin", "plugin.json"))
2024
+ && await pathExists(sourceCheckoutPath("platform_plugins", "opencode_plugin", "package.json"));
2025
+ }
2026
+ function sourceCheckoutPath(...segments) {
2027
+ return resolve(SOURCE_CHECKOUT_ROOT, ...segments);
1666
2028
  }
1667
2029
  async function pathExists(pathValue) {
1668
2030
  try {
@@ -2098,6 +2460,36 @@ function agentRunToolInputs(parsed) {
2098
2460
  ports: ports.values.length > 0 ? ports.values : [22, 15301],
2099
2461
  timeout_ms: timeout.value !== undefined ? timeout.value * 1000 : undefined
2100
2462
  });
2463
+ inputs["wifi.scan"] = compactBody({
2464
+ host: host.value,
2465
+ user: user.value,
2466
+ timeout_seconds: timeout.value
2467
+ });
2468
+ inputs["bluetooth.scan"] = compactBody({
2469
+ host: host.value,
2470
+ user: user.value,
2471
+ timeout_seconds: timeout.value
2472
+ });
2473
+ inputs["chip.cpu.frequency"] = compactBody({
2474
+ host: host.value,
2475
+ user: user.value,
2476
+ timeout_seconds: timeout.value
2477
+ });
2478
+ inputs["chip.temperature"] = compactBody({
2479
+ host: host.value,
2480
+ user: user.value,
2481
+ timeout_seconds: timeout.value
2482
+ });
2483
+ inputs["qml.runtime.status"] = compactBody({
2484
+ host: host.value,
2485
+ user: user.value,
2486
+ timeout_seconds: timeout.value
2487
+ });
2488
+ inputs["qml.runtime.start"] = compactBody({
2489
+ host: host.value,
2490
+ user: user.value,
2491
+ timeout_seconds: timeout.value
2492
+ });
2101
2493
  }
2102
2494
  if (artifact.value || remotePath.value || user.value || booleanFlag(parsed, "run")) {
2103
2495
  inputs["taishanpi.deploy"] = compactBody({
@@ -2778,6 +3170,231 @@ function buildImageGenerateBody(parsed) {
2778
3170
  worker_pool: workerPool.value
2779
3171
  });
2780
3172
  }
3173
+ async function buildImageBootLogoPackageRequest(parsed) {
3174
+ const unknownFlag = firstUnknownFlag(parsed, [
3175
+ "json",
3176
+ "account",
3177
+ "project",
3178
+ "project-id",
3179
+ "board",
3180
+ "board-id",
3181
+ "variant",
3182
+ "variant-id",
3183
+ "logo",
3184
+ "logo-path",
3185
+ "kernel-logo",
3186
+ "kernel-logo-path",
3187
+ "rotate",
3188
+ "scale",
3189
+ "output"
3190
+ ]);
3191
+ if (unknownFlag) {
3192
+ return `Unknown flag --${unknownFlag}. ${BUILD_IMAGE_BOOT_LOGO_USAGE}`;
3193
+ }
3194
+ const extra = parsed.command.slice(3);
3195
+ if (extra.length > 0) {
3196
+ return `Unexpected argument: ${extra[0]}. ${BUILD_IMAGE_BOOT_LOGO_USAGE}`;
3197
+ }
3198
+ const logo = optionalTrimmedStringAliasFlag(parsed, ["logo", "logo-path"], "logo or logo-path");
3199
+ if (logo.error) {
3200
+ return logo.error;
3201
+ }
3202
+ if (!logo.value) {
3203
+ return BUILD_IMAGE_BOOT_LOGO_USAGE;
3204
+ }
3205
+ const logoPath = resolve(logo.value);
3206
+ const logoRead = await readLocalFileForUpload(logoPath, "logo");
3207
+ if (typeof logoRead === "string") {
3208
+ return logoRead;
3209
+ }
3210
+ const kernelLogo = optionalTrimmedStringAliasFlag(parsed, ["kernel-logo", "kernel-logo-path"], "kernel-logo or kernel-logo-path");
3211
+ if (kernelLogo.error) {
3212
+ return kernelLogo.error;
3213
+ }
3214
+ let kernelLogoRead;
3215
+ if (kernelLogo.value) {
3216
+ const kernelLogoPath = resolve(kernelLogo.value);
3217
+ const read = await readLocalFileForUpload(kernelLogoPath, "kernel-logo");
3218
+ if (typeof read === "string") {
3219
+ return read;
3220
+ }
3221
+ kernelLogoRead = read;
3222
+ }
3223
+ const account = optionalTrimmedStringFlag(parsed, "account");
3224
+ if (account.error) {
3225
+ return account.error;
3226
+ }
3227
+ const project = optionalTrimmedStringAliasFlag(parsed, ["project", "project-id"], "project or project-id");
3228
+ if (project.error) {
3229
+ return project.error;
3230
+ }
3231
+ const board = optionalTrimmedStringAliasFlag(parsed, ["board", "board-id"], "board or board-id");
3232
+ if (board.error) {
3233
+ return board.error;
3234
+ }
3235
+ const variant = optionalTrimmedStringAliasFlag(parsed, ["variant", "variant-id"], "variant or variant-id");
3236
+ if (variant.error) {
3237
+ return variant.error;
3238
+ }
3239
+ const rotate = optionalTrimmedStringFlag(parsed, "rotate");
3240
+ if (rotate.error) {
3241
+ return rotate.error;
3242
+ }
3243
+ const scale = optionalTrimmedStringFlag(parsed, "scale");
3244
+ if (scale.error) {
3245
+ return scale.error;
3246
+ }
3247
+ const outputPath = optionalTrimmedStringFlag(parsed, "output");
3248
+ if (outputPath.error) {
3249
+ return outputPath.error;
3250
+ }
3251
+ const body = {
3252
+ account_id: account.value,
3253
+ project_id: project.value,
3254
+ board_id: board.value ?? "taishanpi",
3255
+ variant_id: variant.value,
3256
+ logo_name: basename(logoRead.path),
3257
+ logo_content_base64: logoRead.bytes.toString("base64"),
3258
+ logo_sha256: logoRead.sha256,
3259
+ kernel_logo_name: kernelLogoRead ? basename(kernelLogoRead.path) : undefined,
3260
+ kernel_logo_content_base64: kernelLogoRead?.bytes.toString("base64"),
3261
+ kernel_logo_sha256: kernelLogoRead?.sha256,
3262
+ rotate: rotate.value,
3263
+ scale: scale.value
3264
+ };
3265
+ return {
3266
+ body,
3267
+ output_path: outputPath.value ? resolve(outputPath.value) : undefined
3268
+ };
3269
+ }
3270
+ async function buildImageDtbPackageRequest(parsed) {
3271
+ const unknownFlag = firstUnknownFlag(parsed, [
3272
+ "json",
3273
+ "account",
3274
+ "project",
3275
+ "project-id",
3276
+ "board",
3277
+ "board-id",
3278
+ "variant",
3279
+ "variant-id",
3280
+ "dtb",
3281
+ "dtb-path",
3282
+ "dts",
3283
+ "dts-path",
3284
+ "input-format",
3285
+ "output"
3286
+ ]);
3287
+ if (unknownFlag) {
3288
+ return `Unknown flag --${unknownFlag}. ${BUILD_IMAGE_DTB_USAGE}`;
3289
+ }
3290
+ const extra = parsed.command.slice(3);
3291
+ if (extra.length > 0) {
3292
+ return `Unexpected argument: ${extra[0]}. ${BUILD_IMAGE_DTB_USAGE}`;
3293
+ }
3294
+ const dtb = optionalTrimmedStringAliasFlag(parsed, ["dtb", "dtb-path", "dts", "dts-path"], "dtb or dts path");
3295
+ if (dtb.error) {
3296
+ return dtb.error;
3297
+ }
3298
+ if (!dtb.value) {
3299
+ return BUILD_IMAGE_DTB_USAGE;
3300
+ }
3301
+ const dtbPath = resolve(dtb.value);
3302
+ const dtbRead = await readLocalFileForUpload(dtbPath, "dtb");
3303
+ if (typeof dtbRead === "string") {
3304
+ return dtbRead;
3305
+ }
3306
+ const account = optionalTrimmedStringFlag(parsed, "account");
3307
+ if (account.error) {
3308
+ return account.error;
3309
+ }
3310
+ const project = optionalTrimmedStringAliasFlag(parsed, ["project", "project-id"], "project or project-id");
3311
+ if (project.error) {
3312
+ return project.error;
3313
+ }
3314
+ const board = optionalTrimmedStringAliasFlag(parsed, ["board", "board-id"], "board or board-id");
3315
+ if (board.error) {
3316
+ return board.error;
3317
+ }
3318
+ const variant = optionalTrimmedStringAliasFlag(parsed, ["variant", "variant-id"], "variant or variant-id");
3319
+ if (variant.error) {
3320
+ return variant.error;
3321
+ }
3322
+ const inputFormat = optionalTrimmedStringFlag(parsed, "input-format");
3323
+ if (inputFormat.error) {
3324
+ return inputFormat.error;
3325
+ }
3326
+ const outputPath = optionalTrimmedStringFlag(parsed, "output");
3327
+ if (outputPath.error) {
3328
+ return outputPath.error;
3329
+ }
3330
+ const body = {
3331
+ account_id: account.value,
3332
+ project_id: project.value,
3333
+ board_id: board.value ?? "taishanpi",
3334
+ variant_id: variant.value,
3335
+ dtb_name: basename(dtbRead.path),
3336
+ dtb_content_base64: dtbRead.bytes.toString("base64"),
3337
+ dtb_sha256: dtbRead.sha256,
3338
+ input_format: inputFormat.value
3339
+ };
3340
+ return {
3341
+ body,
3342
+ output_path: outputPath.value ? resolve(outputPath.value) : undefined
3343
+ };
3344
+ }
3345
+ function imageBootLogoComposeRequest(parsed, usage) {
3346
+ const unknownFlag = firstUnknownFlag(parsed, [
3347
+ "json",
3348
+ "package",
3349
+ "package-path",
3350
+ "base-image",
3351
+ "base-image-path",
3352
+ "output",
3353
+ "output-image",
3354
+ "manifest",
3355
+ "manifest-path",
3356
+ "force"
3357
+ ]);
3358
+ if (unknownFlag) {
3359
+ return `Unknown flag --${unknownFlag}. ${usage}`;
3360
+ }
3361
+ const extra = parsed.command.slice(3);
3362
+ if (extra.length > 0) {
3363
+ return `Unexpected argument: ${extra[0]}. ${usage}`;
3364
+ }
3365
+ const packagePath = optionalTrimmedStringAliasFlag(parsed, ["package", "package-path"], "package or package-path");
3366
+ if (packagePath.error) {
3367
+ return packagePath.error;
3368
+ }
3369
+ if (!packagePath.value) {
3370
+ return usage;
3371
+ }
3372
+ const baseImage = optionalTrimmedStringAliasFlag(parsed, ["base-image", "base-image-path"], "base-image or base-image-path");
3373
+ if (baseImage.error) {
3374
+ return baseImage.error;
3375
+ }
3376
+ if (!baseImage.value) {
3377
+ return usage;
3378
+ }
3379
+ const outputImage = optionalTrimmedStringAliasFlag(parsed, ["output", "output-image"], "output or output-image");
3380
+ if (outputImage.error) {
3381
+ return outputImage.error;
3382
+ }
3383
+ if (!outputImage.value) {
3384
+ return usage;
3385
+ }
3386
+ const manifest = optionalTrimmedStringAliasFlag(parsed, ["manifest", "manifest-path"], "manifest or manifest-path");
3387
+ if (manifest.error) {
3388
+ return manifest.error;
3389
+ }
3390
+ return {
3391
+ packagePath: resolve(packagePath.value),
3392
+ baseImagePath: resolve(baseImage.value),
3393
+ outputImagePath: resolve(outputImage.value),
3394
+ manifestPath: manifest.value ? resolve(manifest.value) : undefined,
3395
+ force: booleanFlag(parsed, "force")
3396
+ };
3397
+ }
2781
3398
  function boardKnowledgeFileRequest(parsed) {
2782
3399
  const unknownFlag = firstUnknownFlag(parsed, ["json", "source", "path", "output"]);
2783
3400
  if (unknownFlag) {
@@ -3110,6 +3727,73 @@ function usageRecordBody(parsed) {
3110
3727
  created_at: createdAtResult.value
3111
3728
  });
3112
3729
  }
3730
+ function mcpToolEventBody(parsed) {
3731
+ const unknownFlag = firstUnknownFlag(parsed, [
3732
+ "json",
3733
+ "account",
3734
+ "account-id",
3735
+ "tool",
3736
+ "client",
3737
+ "mode",
3738
+ "server-model-used",
3739
+ "success",
3740
+ "request-id",
3741
+ "duration-ms",
3742
+ "input-summary",
3743
+ "output-summary"
3744
+ ]);
3745
+ if (unknownFlag) {
3746
+ return `Unknown flag --${unknownFlag}. ${MCP_TOOL_EVENT_USAGE}`;
3747
+ }
3748
+ const extra = parsed.command.slice(2);
3749
+ if (extra.length > 0) {
3750
+ return `Unexpected argument: ${extra[0]}. ${MCP_TOOL_EVENT_USAGE}`;
3751
+ }
3752
+ const toolResult = optionalTrimmedStringFlag(parsed, "tool");
3753
+ if (toolResult.error)
3754
+ return toolResult.error;
3755
+ if (!toolResult.value)
3756
+ return MCP_TOOL_EVENT_USAGE;
3757
+ const accountResult = optionalTrimmedStringAliasFlag(parsed, ["account", "account-id"], "account or account-id");
3758
+ if (accountResult.error)
3759
+ return accountResult.error;
3760
+ const clientResult = optionalTrimmedStringFlag(parsed, "client");
3761
+ if (clientResult.error)
3762
+ return clientResult.error;
3763
+ const modeResult = optionalTrimmedStringFlag(parsed, "mode");
3764
+ if (modeResult.error)
3765
+ return modeResult.error;
3766
+ const requestIdResult = optionalTrimmedStringFlag(parsed, "request-id");
3767
+ if (requestIdResult.error)
3768
+ return requestIdResult.error;
3769
+ const inputSummaryResult = optionalTrimmedStringFlag(parsed, "input-summary");
3770
+ if (inputSummaryResult.error)
3771
+ return inputSummaryResult.error;
3772
+ const outputSummaryResult = optionalTrimmedStringFlag(parsed, "output-summary");
3773
+ if (outputSummaryResult.error)
3774
+ return outputSummaryResult.error;
3775
+ const durationResult = optionalNonNegativeIntegerFlag(parsed, "duration-ms");
3776
+ if (durationResult.error)
3777
+ return durationResult.error;
3778
+ const serverModelUsed = optionalBooleanFlag(parsed, "server-model-used");
3779
+ if (typeof serverModelUsed === "string")
3780
+ return serverModelUsed;
3781
+ const success = optionalBooleanFlag(parsed, "success");
3782
+ if (typeof success === "string")
3783
+ return success;
3784
+ return compactBody({
3785
+ account_id: accountResult.value,
3786
+ tool_name: toolResult.value,
3787
+ client: clientResult.value,
3788
+ mode: modeResult.value,
3789
+ server_model_used: serverModelUsed,
3790
+ success,
3791
+ request_id: requestIdResult.value,
3792
+ duration_ms: durationResult.value,
3793
+ input_summary: inputSummaryResult.value,
3794
+ output_summary: outputSummaryResult.value
3795
+ });
3796
+ }
3113
3797
  function usageSummaryRequest(parsed) {
3114
3798
  const unknownFlag = firstUnknownFlag(parsed, ["json", "account", "account-id", "api-key-id", "from", "to"]);
3115
3799
  if (unknownFlag) {
@@ -3445,6 +4129,181 @@ function billingSnapshotListRequest(parsed) {
3445
4129
  }
3446
4130
  return { path: `/v1/accounts/${encodeURIComponent(accountResult.value)}/billing/snapshots` };
3447
4131
  }
4132
+ function localToolchainLatestRequest(parsed) {
4133
+ const unknownFlag = firstUnknownFlag(parsed, ["json", "board", "board-id", "channel", "metadata-root"]);
4134
+ if (unknownFlag) {
4135
+ return `Unknown flag --${unknownFlag}. ${LOCAL_TOOLCHAIN_LATEST_USAGE}`;
4136
+ }
4137
+ const extra = parsed.command.slice(3);
4138
+ if (extra.length > 0) {
4139
+ return `Unexpected argument: ${extra[0]}. ${LOCAL_TOOLCHAIN_LATEST_USAGE}`;
4140
+ }
4141
+ const board = optionalTrimmedStringAliasFlag(parsed, ["board", "board-id"], "board or board-id");
4142
+ if (board.error)
4143
+ return board.error;
4144
+ const channel = optionalTrimmedStringFlag(parsed, "channel");
4145
+ if (channel.error)
4146
+ return channel.error;
4147
+ const metadataRoot = optionalTrimmedStringFlag(parsed, "metadata-root");
4148
+ if (metadataRoot.error)
4149
+ return metadataRoot.error;
4150
+ return { boardId: board.value, channel: channel.value, metadataRoot: metadataRoot.value };
4151
+ }
4152
+ function localToolchainCurrentRequest(parsed) {
4153
+ const unknownFlag = firstUnknownFlag(parsed, ["json", "install-root"]);
4154
+ if (unknownFlag) {
4155
+ return `Unknown flag --${unknownFlag}. ${LOCAL_TOOLCHAIN_CURRENT_USAGE}`;
4156
+ }
4157
+ const extra = parsed.command.slice(3);
4158
+ if (extra.length > 0) {
4159
+ return `Unexpected argument: ${extra[0]}. ${LOCAL_TOOLCHAIN_CURRENT_USAGE}`;
4160
+ }
4161
+ const installRoot = optionalTrimmedStringFlag(parsed, "install-root");
4162
+ if (installRoot.error)
4163
+ return installRoot.error;
4164
+ return { installRoot: installRoot.value };
4165
+ }
4166
+ function localToolchainInstallRequest(parsed) {
4167
+ const unknownFlag = firstUnknownFlag(parsed, ["json", "board", "board-id", "channel", "metadata-root", "source-url", "source-release-root", "install-root", "force"]);
4168
+ if (unknownFlag) {
4169
+ return `Unknown flag --${unknownFlag}. ${LOCAL_TOOLCHAIN_INSTALL_USAGE}`;
4170
+ }
4171
+ const extra = parsed.command.slice(3);
4172
+ if (extra.length > 0) {
4173
+ return `Unexpected argument: ${extra[0]}. ${LOCAL_TOOLCHAIN_INSTALL_USAGE}`;
4174
+ }
4175
+ const board = optionalTrimmedStringAliasFlag(parsed, ["board", "board-id"], "board or board-id");
4176
+ if (board.error)
4177
+ return board.error;
4178
+ const channel = optionalTrimmedStringFlag(parsed, "channel");
4179
+ if (channel.error)
4180
+ return channel.error;
4181
+ const metadataRoot = optionalTrimmedStringFlag(parsed, "metadata-root");
4182
+ if (metadataRoot.error)
4183
+ return metadataRoot.error;
4184
+ const sourceUrl = optionalTrimmedStringFlag(parsed, "source-url");
4185
+ if (sourceUrl.error)
4186
+ return sourceUrl.error;
4187
+ const sourceReleaseRoot = optionalTrimmedStringFlag(parsed, "source-release-root");
4188
+ if (sourceReleaseRoot.error)
4189
+ return sourceReleaseRoot.error;
4190
+ if (sourceUrl.value && sourceReleaseRoot.value) {
4191
+ return "Use only one of --source-url or --source-release-root.";
4192
+ }
4193
+ const installRoot = optionalTrimmedStringFlag(parsed, "install-root");
4194
+ if (installRoot.error)
4195
+ return installRoot.error;
4196
+ return {
4197
+ boardId: board.value,
4198
+ channel: channel.value,
4199
+ metadataRoot: metadataRoot.value,
4200
+ sourceUrl: sourceUrl.value,
4201
+ sourceReleaseRoot: sourceReleaseRoot.value,
4202
+ installRoot: installRoot.value,
4203
+ force: booleanFlag(parsed, "force")
4204
+ };
4205
+ }
4206
+ function localToolchainValidateRequest(parsed) {
4207
+ const unknownFlag = firstUnknownFlag(parsed, ["json", "release-root"]);
4208
+ if (unknownFlag) {
4209
+ return `Unknown flag --${unknownFlag}. ${LOCAL_TOOLCHAIN_VALIDATE_USAGE}`;
4210
+ }
4211
+ const extra = parsed.command.slice(3);
4212
+ if (extra.length > 0) {
4213
+ return `Unexpected argument: ${extra[0]}. ${LOCAL_TOOLCHAIN_VALIDATE_USAGE}`;
4214
+ }
4215
+ const releaseRoot = optionalTrimmedStringFlag(parsed, "release-root");
4216
+ if (releaseRoot.error) {
4217
+ return releaseRoot.error;
4218
+ }
4219
+ return { releaseRoot: releaseRoot.value };
4220
+ }
4221
+ function localCompileTaishanPiRequest(parsed, auth) {
4222
+ const unknownFlag = firstUnknownFlag(parsed, ["json", "source", "output", "release-root", "account", "account-id"]);
4223
+ if (unknownFlag) {
4224
+ return `Unknown flag --${unknownFlag}. ${LOCAL_COMPILE_TAISHANPI_USAGE}`;
4225
+ }
4226
+ const extra = parsed.command.slice(3);
4227
+ if (extra.length > 0) {
4228
+ return `Unexpected argument: ${extra[0]}. ${LOCAL_COMPILE_TAISHANPI_USAGE}`;
4229
+ }
4230
+ const source = optionalTrimmedStringFlag(parsed, "source");
4231
+ if (source.error) {
4232
+ return source.error;
4233
+ }
4234
+ const outputPath = optionalTrimmedStringFlag(parsed, "output");
4235
+ if (outputPath.error) {
4236
+ return outputPath.error;
4237
+ }
4238
+ if (!source.value || !outputPath.value) {
4239
+ return LOCAL_COMPILE_TAISHANPI_USAGE;
4240
+ }
4241
+ const releaseRoot = optionalTrimmedStringFlag(parsed, "release-root");
4242
+ if (releaseRoot.error) {
4243
+ return releaseRoot.error;
4244
+ }
4245
+ const account = optionalTrimmedStringAliasFlag(parsed, ["account", "account-id"], "account or account-id");
4246
+ if (account.error) {
4247
+ return account.error;
4248
+ }
4249
+ return {
4250
+ boardId: "taishanpi-1m-rk3566",
4251
+ sourcePath: source.value,
4252
+ outputPath: outputPath.value,
4253
+ releaseRoot: releaseRoot.value,
4254
+ accountId: account.value,
4255
+ auth: localToolchainAuthContext(auth, account.value)
4256
+ };
4257
+ }
4258
+ function localBuildQtSmokeRequest(parsed, auth) {
4259
+ const unknownFlag = firstUnknownFlag(parsed, ["json", "source", "build-dir", "release-root", "target-name", "account", "account-id"]);
4260
+ if (unknownFlag) {
4261
+ return `Unknown flag --${unknownFlag}. ${LOCAL_BUILD_QT_SMOKE_USAGE}`;
4262
+ }
4263
+ const extra = parsed.command.slice(3);
4264
+ if (extra.length > 0) {
4265
+ return `Unexpected argument: ${extra[0]}. ${LOCAL_BUILD_QT_SMOKE_USAGE}`;
4266
+ }
4267
+ const buildDir = optionalTrimmedStringFlag(parsed, "build-dir");
4268
+ if (buildDir.error) {
4269
+ return buildDir.error;
4270
+ }
4271
+ if (!buildDir.value) {
4272
+ return LOCAL_BUILD_QT_SMOKE_USAGE;
4273
+ }
4274
+ const source = optionalTrimmedStringFlag(parsed, "source");
4275
+ if (source.error) {
4276
+ return source.error;
4277
+ }
4278
+ const releaseRoot = optionalTrimmedStringFlag(parsed, "release-root");
4279
+ if (releaseRoot.error) {
4280
+ return releaseRoot.error;
4281
+ }
4282
+ const targetName = optionalTrimmedStringFlag(parsed, "target-name");
4283
+ if (targetName.error) {
4284
+ return targetName.error;
4285
+ }
4286
+ const account = optionalTrimmedStringAliasFlag(parsed, ["account", "account-id"], "account or account-id");
4287
+ if (account.error) {
4288
+ return account.error;
4289
+ }
4290
+ return {
4291
+ sourceDir: source.value,
4292
+ buildDir: buildDir.value,
4293
+ releaseRoot: releaseRoot.value,
4294
+ targetName: targetName.value,
4295
+ accountId: account.value,
4296
+ auth: localToolchainAuthContext(auth, account.value)
4297
+ };
4298
+ }
4299
+ function localToolchainAuthContext(auth, accountId) {
4300
+ return {
4301
+ authenticated: auth.authenticated,
4302
+ profile: auth.profile,
4303
+ source: auth.source,
4304
+ account_id: accountId
4305
+ };
4306
+ }
3448
4307
  function usageEventsRequest(parsed) {
3449
4308
  const unknownFlag = firstUnknownFlag(parsed, ["json", "account", "account-id", "api-key-id", "from", "to", "limit"]);
3450
4309
  if (unknownFlag) {
@@ -3502,6 +4361,26 @@ function compactBody(input) {
3502
4361
  }
3503
4362
  return body;
3504
4363
  }
4364
+ async function readLocalFileForUpload(filePath, label) {
4365
+ try {
4366
+ const info = await stat(filePath);
4367
+ if (!info.isFile()) {
4368
+ return `${label} must point to a file: ${filePath}`;
4369
+ }
4370
+ const bytes = await readFile(filePath);
4371
+ if (bytes.length === 0) {
4372
+ return `${label} must not be empty: ${filePath}`;
4373
+ }
4374
+ return {
4375
+ path: filePath,
4376
+ bytes,
4377
+ sha256: createHash("sha256").update(bytes).digest("hex")
4378
+ };
4379
+ }
4380
+ catch (error) {
4381
+ return `Could not read ${label} file ${filePath}: ${error instanceof Error ? error.message : String(error)}`;
4382
+ }
4383
+ }
3505
4384
  function projectCreateBody(parsed) {
3506
4385
  const extra = parsed.command.slice(3);
3507
4386
  if (extra.length > 0) {
@@ -3795,8 +4674,15 @@ function renderServiceModeCatalog(catalog) {
3795
4674
  ` model_gateway=${catalog.boundaries.model_gateway}`,
3796
4675
  ` server_resource_orchestration=${catalog.boundaries.server_resource_orchestration}`,
3797
4676
  ` local_bridge=${catalog.boundaries.local_bridge}`,
4677
+ catalog.boundaries.free_registered_operations ? ` free_registered_operations=${catalog.boundaries.free_registered_operations}` : "",
4678
+ catalog.boundaries.paid_server_operations ? ` paid_server_operations=${catalog.boundaries.paid_server_operations}` : "",
4679
+ catalog.allocation_rule ? `allocation_rule=${catalog.allocation_rule}` : "",
4680
+ "free_registered_capabilities:",
4681
+ ...(catalog.free_registered_capabilities ?? []).map((item) => ` - ${item.policy_id} workspace=${item.requires_server_workspace} lease=${item.allocates_server_resource_lease} meters=${item.meters.join(",") || "none"}`),
4682
+ "paid_capabilities:",
4683
+ ...(catalog.paid_capabilities ?? []).map((item) => ` - ${item.policy_id} workspace=${item.requires_server_workspace} lease=${item.allocates_server_resource_lease} meters=${item.meters.join(",") || "none"}`),
3798
4684
  "modes:"
3799
- ];
4685
+ ].filter(Boolean);
3800
4686
  for (const mode of catalog.modes) {
3801
4687
  lines.push([
3802
4688
  ` ${mode.default ? "*" : "-"}`,
@@ -3837,6 +4723,15 @@ function renderPluginInstall(result) {
3837
4723
  if (item.command_hint) {
3838
4724
  lines.push(` ${item.command_hint}`);
3839
4725
  }
4726
+ if (item.warning) {
4727
+ lines.push(` warning=${item.warning}`);
4728
+ }
4729
+ if (item.mcp_registered !== undefined) {
4730
+ lines.push(` codex_mcp_registered=${item.mcp_registered}`);
4731
+ }
4732
+ if (item.mcp_warning) {
4733
+ lines.push(` warning=${item.mcp_warning}`);
4734
+ }
3840
4735
  }
3841
4736
  return lines.join("\n");
3842
4737
  }
@@ -3920,6 +4815,20 @@ function renderUsageRecord(record) {
3920
4815
  `created_at=${record.created_at}`
3921
4816
  ].filter(Boolean).join(" ");
3922
4817
  }
4818
+ function renderMcpToolEvent(event) {
4819
+ return [
4820
+ `${event.event_id} tool=${event.tool_name}`,
4821
+ event.account_id ? `account=${event.account_id}` : "",
4822
+ event.api_key_id ? `api_key=${event.api_key_id}` : "",
4823
+ `client=${event.client}`,
4824
+ `mode=${event.mode}`,
4825
+ `server_model_used=${event.server_model_used}`,
4826
+ `success=${event.success}`,
4827
+ event.request_id ? `request=${event.request_id}` : "",
4828
+ event.duration_ms !== undefined ? `duration_ms=${event.duration_ms}` : "",
4829
+ `created_at=${event.created_at}`
4830
+ ].filter(Boolean).join(" ");
4831
+ }
3923
4832
  function renderUsageSummary(summary) {
3924
4833
  const lines = [
3925
4834
  summary.account_id ? `account=${summary.account_id}` : "",
@@ -4208,10 +5117,13 @@ function renderBoardMethods(data) {
4208
5117
  method.operation,
4209
5118
  `status=${method.status}`,
4210
5119
  `runtime=${method.runtime}`,
5120
+ method.access_tier ? `access=${method.access_tier}` : "",
4211
5121
  `endpoint=${method.http_method} ${method.path}`,
4212
5122
  `workspace=${method.requires_workspace}`,
5123
+ method.allocates_server_workspace !== undefined ? `alloc_workspace=${method.allocates_server_workspace}` : "",
5124
+ method.allocates_server_resource_lease !== undefined ? `alloc_lease=${method.allocates_server_resource_lease}` : "",
4213
5125
  `approval=${method.requires_approval}`
4214
- ].join(" ")).join("\n");
5126
+ ].filter(Boolean).join(" ")).join("\n");
4215
5127
  }
4216
5128
  function renderBoardKnowledge(data) {
4217
5129
  const files = isJsonObject(data) && Array.isArray(data.knowledge_files)
@@ -4440,6 +5352,123 @@ function renderBuildWorkspaceSourcePatch(result) {
4440
5352
  }
4441
5353
  return lines.join("\n");
4442
5354
  }
5355
+ function renderLocalToolchainLatest(result) {
5356
+ const lines = [
5357
+ `board=${result.board_id}`,
5358
+ `channel=${result.channel}`,
5359
+ `version=${result.version}`,
5360
+ `host=${result.host}`,
5361
+ result.metadata_root ? `metadata_root=${result.metadata_root}` : "metadata=built-in",
5362
+ result.download ? `download=${result.download.mirror_kind}:${result.download.source_url}` : "",
5363
+ result.download ? `archive_sha256=${result.download.archive.sha256}` : "",
5364
+ result.download ? `archive_size_bytes=${result.download.archive.size_bytes}` : "",
5365
+ result.download_error ? `download_error=${result.download_error}` : ""
5366
+ ].filter(Boolean);
5367
+ if (result.packages.length > 0) {
5368
+ lines.push("packages:");
5369
+ for (const pkg of result.packages) {
5370
+ lines.push(` ${pkg.id}@${pkg.version}`);
5371
+ }
5372
+ }
5373
+ return lines.join("\n");
5374
+ }
5375
+ function renderLocalToolchainCurrent(result) {
5376
+ if (!result.installed) {
5377
+ return [
5378
+ "No local toolchain installed.",
5379
+ `board=${result.board_id}`,
5380
+ `install_root=${result.install_root}`,
5381
+ `registry=${result.registry_path}`
5382
+ ].join("\n");
5383
+ }
5384
+ return [
5385
+ "Local toolchain installed.",
5386
+ `board=${result.board_id}`,
5387
+ result.version ? `version=${result.version}` : "",
5388
+ result.channel ? `channel=${result.channel}` : "",
5389
+ result.release_root ? `release_root=${result.release_root}` : "",
5390
+ `install_root=${result.install_root}`,
5391
+ `registry=${result.registry_path}`
5392
+ ].filter(Boolean).join("\n");
5393
+ }
5394
+ function renderLocalToolchainInstall(result) {
5395
+ const lines = [
5396
+ "Local toolchain installed.",
5397
+ `board=${result.board_id}`,
5398
+ `version=${result.version}`,
5399
+ `channel=${result.channel}`,
5400
+ `host=${result.host}`,
5401
+ `install_root=${result.install_root}`,
5402
+ `release_root=${result.release_root}`,
5403
+ `registry=${result.registry_path}`,
5404
+ `source=${result.source.kind}:${result.source.value}`,
5405
+ result.source.downloaded_path ? `downloaded=${result.source.downloaded_path}` : "",
5406
+ `validation=${result.validation.ok ? "ok" : "failed"}`
5407
+ ].filter(Boolean);
5408
+ if (result.installed_paths.length > 0) {
5409
+ lines.push("installed_paths:");
5410
+ for (const installedPath of result.installed_paths) {
5411
+ lines.push(` ${installedPath}`);
5412
+ }
5413
+ }
5414
+ if (result.packages.length > 0) {
5415
+ lines.push("packages:");
5416
+ for (const pkg of result.packages) {
5417
+ lines.push(` ${pkg.id}@${pkg.version}`);
5418
+ }
5419
+ }
5420
+ return lines.join("\n");
5421
+ }
5422
+ function renderLocalToolchainValidation(result) {
5423
+ const lines = [
5424
+ result.ok ? "Local toolchain ready." : "Local toolchain not ready.",
5425
+ `board=${result.board_id}`,
5426
+ `host=${result.host.platform}/${result.host.arch}`,
5427
+ `release_root=${result.release_root}`
5428
+ ];
5429
+ for (const check of result.checked_paths) {
5430
+ lines.push(`${check.exists ? "ok" : "missing"} ${check.label}: ${check.path}`);
5431
+ }
5432
+ if (result.notes.length > 0) {
5433
+ lines.push("notes:");
5434
+ for (const note of result.notes) {
5435
+ lines.push(` ${note}`);
5436
+ }
5437
+ }
5438
+ return lines.join("\n");
5439
+ }
5440
+ function renderLocalCompileResult(result) {
5441
+ const lines = [
5442
+ `${result.operation} succeeded.`,
5443
+ `board=${result.board_id}`,
5444
+ result.account_id ? `account=${result.account_id}` : "",
5445
+ `auth=${result.auth.authenticated ? "authenticated" : "anonymous"} profile=${result.auth.profile}${result.auth.source ? ` source=${result.auth.source}` : ""}`,
5446
+ `source=${result.source_path}`,
5447
+ result.build_dir ? `build_dir=${result.build_dir}` : "",
5448
+ `artifact=${result.artifact_path}`,
5449
+ `size=${result.artifact_size_bytes}`,
5450
+ `sha256=${result.artifact_sha256}`,
5451
+ result.file_info ? `file=${result.file_info}` : "",
5452
+ `manifest=${result.manifest_path}`
5453
+ ].filter(Boolean);
5454
+ for (const command of result.commands) {
5455
+ lines.push(`command_exit=${command.exit_code} cwd=${command.cwd}`);
5456
+ lines.push(` ${command.command.join(" ")}`);
5457
+ if (command.stdout_tail.length > 0) {
5458
+ lines.push(" stdout_tail:");
5459
+ for (const line of command.stdout_tail.slice(-12)) {
5460
+ lines.push(` ${line}`);
5461
+ }
5462
+ }
5463
+ if (command.stderr_tail.length > 0) {
5464
+ lines.push(" stderr_tail:");
5465
+ for (const line of command.stderr_tail.slice(-12)) {
5466
+ lines.push(` ${line}`);
5467
+ }
5468
+ }
5469
+ }
5470
+ return lines.join("\n");
5471
+ }
4443
5472
  function renderDoctor(result) {
4444
5473
  const lines = [
4445
5474
  `${result.status === "ready" ? "Ready" : "Not ready"}: ${result.summary_for_user}`,
@@ -4610,6 +5639,46 @@ function renderJob(job) {
4610
5639
  }
4611
5640
  return lines.join("\n");
4612
5641
  }
5642
+ function renderBootLogoPackageResult(result) {
5643
+ const lines = [renderJob(result.task)];
5644
+ if (result.downloaded_artifact) {
5645
+ lines.push("downloaded_package:");
5646
+ lines.push(` artifact=${result.downloaded_artifact.artifact_id}`);
5647
+ lines.push(` output=${result.downloaded_artifact.output_path}`);
5648
+ lines.push(` size=${result.downloaded_artifact.size_bytes}`);
5649
+ }
5650
+ lines.push("next=merge the downloaded package with the local base image, then flash only after explicit approval.");
5651
+ return lines.join("\n");
5652
+ }
5653
+ function renderDtbPackageResult(result) {
5654
+ const lines = [renderJob(result.task)];
5655
+ if (result.downloaded_artifact) {
5656
+ lines.push("downloaded_package:");
5657
+ lines.push(` artifact=${result.downloaded_artifact.artifact_id}`);
5658
+ lines.push(` output=${result.downloaded_artifact.output_path}`);
5659
+ lines.push(` size=${result.downloaded_artifact.size_bytes}`);
5660
+ }
5661
+ lines.push("next=merge the downloaded DTB package with the local base image, then flash only after explicit approval.");
5662
+ return lines.join("\n");
5663
+ }
5664
+ function renderBootLogoComposeResult(result) {
5665
+ const label = result.operation_kind === "image.dtb.compose" ? "DTB" : "Boot-logo";
5666
+ return [
5667
+ result.ready_for_flash ? `${label} compose ready for flash review.` : `${label} compose created a guarded local result.`,
5668
+ `board=${result.board_id}`,
5669
+ result.variant_id ? `variant=${result.variant_id}` : "",
5670
+ `package=${result.package_path}`,
5671
+ `base=${result.base_image_path}`,
5672
+ `output=${result.output_image_path}`,
5673
+ `manifest=${result.manifest_path}`,
5674
+ `strategy=${result.local_merge_strategy}`,
5675
+ `patches=${result.patch_operations_applied}/${result.patch_operations_total}`,
5676
+ `replacement_applied=${result.replacement_applied}`,
5677
+ `ready_for_flash=${result.ready_for_flash}`,
5678
+ `cross_platform=${result.cross_platform} platform=${result.platform}/${result.arch}`,
5679
+ result.summary_for_user
5680
+ ].filter(Boolean).join("\n");
5681
+ }
4613
5682
  function safeArtifactOutputFileName(name) {
4614
5683
  return name.replace(/[^A-Za-z0-9._-]+/g, "_") || "artifact.out";
4615
5684
  }
@@ -4808,6 +5877,22 @@ function stringFlag(parsed, name) {
4808
5877
  function booleanFlag(parsed, name) {
4809
5878
  return parsed.flags[name] === true;
4810
5879
  }
5880
+ function optionalBooleanFlag(parsed, name) {
5881
+ const values = flagValues(parsed, name);
5882
+ if (values.length === 0)
5883
+ return undefined;
5884
+ const value = values[values.length - 1];
5885
+ if (value === true)
5886
+ return true;
5887
+ if (typeof value !== "string")
5888
+ return `--${name} must be true or false.`;
5889
+ const normalized = value.trim().toLowerCase();
5890
+ if (["1", "true", "yes", "y", "on"].includes(normalized))
5891
+ return true;
5892
+ if (["0", "false", "no", "n", "off"].includes(normalized))
5893
+ return false;
5894
+ return `--${name} must be true or false.`;
5895
+ }
4811
5896
  function switchFlag(parsed, name) {
4812
5897
  const values = flagValues(parsed, name);
4813
5898
  for (const value of values) {
@@ -5089,7 +6174,7 @@ async function waitForever() {
5089
6174
  return 0;
5090
6175
  }
5091
6176
  function printHelp() {
5092
- console.log(`embed CLI
6177
+ printCliHelp(`embed CLI
5093
6178
 
5094
6179
  Usage:
5095
6180
  embed <command> [options]
@@ -5111,13 +6196,19 @@ Main workflow:
5111
6196
  embed model default
5112
6197
  4. Run a natural-language local tool loop:
5113
6198
  embed agent run --prompt "验证开发板状态"
5114
- 5. Pick a cloud build template:
6199
+ 5. Validate or use the local TaishanPi toolchain:
6200
+ embed local toolchain latest
6201
+ embed local toolchain install
6202
+ embed local toolchain validate
6203
+ embed local compile taishanpi --source ./main.c --output ./.embed-labs/build/main
6204
+ embed local build qt-smoke --build-dir ./.embed-labs/build/qt-smoke
6205
+ 6. Pick a cloud build template:
5115
6206
  embed board registry list
5116
6207
  embed board methods taishanpi-1m-rk3566
5117
6208
  embed board knowledge taishanpi-1m-rk3566
5118
6209
  embed build template list
5119
6210
  embed build template show <template_id>
5120
- 6. Provision and populate a build workspace:
6211
+ 7. Provision and populate a build workspace:
5121
6212
  embed build workspace provision --account <account_id> --project <project_id> --template <template_id>
5122
6213
  embed build resource lease create --workspace <workspace_id> --execution-mode cloud_worker
5123
6214
  embed build workspace source put <workspace_id> --file ./main.c:src/main.c
@@ -5126,13 +6217,15 @@ Main workflow:
5126
6217
  embed build workspace source search <workspace_id> --query init --glob "**/*.c"
5127
6218
  embed build workspace source patch <workspace_id> --patch ./fix.patch
5128
6219
  embed build workspace release <workspace_id> --dry-run
5129
- 7. Generate application source on the server and follow artifacts:
6220
+ 8. Generate application source on the server and follow artifacts:
5130
6221
  embed build application generate --workspace <workspace_id> --prompt "Create a minimal Linux app" --provider bai --model gpt-5.2
5131
6222
  embed build application compile --workspace <workspace_id> --source app/generated.c --execution-mode docker_worker
5132
6223
  embed build image generate --workspace <workspace_id> --prompt "Generate a minimal TaishanPi image"
6224
+ embed build image boot-logo --logo ./logo.png --board taishanpi --variant 1M-RK3566 --output ./boot-logo-package.json
6225
+ embed image boot-logo compose --package ./boot-logo-package.json --base-image ./boot.img --output ./boot-logo.img
5133
6226
  embed cloud task artifacts <task_id>
5134
6227
  embed artifact download <artifact_id> --output ./artifact.bin
5135
- 8. Check credits or create a recharge QR:
6228
+ 9. Check credits or create a recharge QR:
5136
6229
  embed billing balance --account <account_id>
5137
6230
  embed billing tokens --account <account_id>
5138
6231
  embed billing ledger --account <account_id>
@@ -5147,7 +6240,17 @@ Local hardware:
5147
6240
  embed agent run --prompt "部署生成的泰山派应用" --host 198.19.77.2 --artifact-task <task_id> --remote-path /userdata/embed-labs/apps/app --approve --run
5148
6241
  embed tool list
5149
6242
  embed tool call device.probe --input-json '{"host":"198.19.77.2","ports":[22,15301]}'
6243
+ embed tool call wifi.scan --input-json '{"host":"198.19.77.2","user":"root"}'
6244
+ embed tool call chip.temperature --input-json '{"host":"198.19.77.2","user":"root"}'
6245
+ embed tool call qml.runtime.status --input-json '{"host":"198.19.77.2","user":"root","port":18130}'
5150
6246
  embed device list
6247
+ embed local toolchain latest
6248
+ embed local toolchain current
6249
+ embed local toolchain install
6250
+ embed local toolchain validate
6251
+ embed local compile taishanpi --source ./main.c --output ./.embed-labs/build/main
6252
+ embed local build qt-smoke --build-dir ./.embed-labs/build/qt-smoke
6253
+ embed image boot-logo compose --package ./boot-logo-package.json --base-image ./boot.img --output ./boot-logo.img
5151
6254
  embed deploy taishanpi --host 198.19.77.2 --artifact ./artifact.bin --approve --run
5152
6255
  embed flash plan --board <rp2350|taishanpi>
5153
6256
 
@@ -5182,7 +6285,7 @@ function printHelpTopic(topic) {
5182
6285
  printCommandReference();
5183
6286
  return;
5184
6287
  }
5185
- console.log(`Unknown help topic: ${topic}
6288
+ printCliHelp(`Unknown help topic: ${topic}
5186
6289
 
5187
6290
  Available topics:
5188
6291
  embed help getting-started
@@ -5190,7 +6293,7 @@ Available topics:
5190
6293
  `);
5191
6294
  }
5192
6295
  function printGettingStartedHelp() {
5193
- console.log(`embed getting started
6296
+ printCliHelp(`embed getting started
5194
6297
 
5195
6298
  After npm install, use the installed embed binary directly:
5196
6299
 
@@ -5219,6 +6322,9 @@ Cloud build path:
5219
6322
  embed model list
5220
6323
  embed model default
5221
6324
  embed agent run --prompt "验证开发板状态"
6325
+ embed local toolchain validate
6326
+ embed local compile taishanpi --source ./main.c --output ./.embed-labs/build/main
6327
+ embed local build qt-smoke --build-dir ./.embed-labs/build/qt-smoke
5222
6328
  embed board registry list
5223
6329
  embed board registry show taishanpi-1m-rk3566
5224
6330
  embed board methods taishanpi-1m-rk3566
@@ -5236,6 +6342,8 @@ Cloud build path:
5236
6342
  embed build application generate --workspace <workspace_id> --prompt "Create a minimal Linux app" --provider bai --model gpt-5.2
5237
6343
  embed build application compile --workspace <workspace_id> --source app/generated.c --execution-mode docker_worker
5238
6344
  embed build image generate --workspace <workspace_id> --prompt "Generate a minimal TaishanPi image"
6345
+ embed build image boot-logo --logo ./logo.png --board taishanpi --variant 1M-RK3566 --output ./boot-logo-package.json
6346
+ embed image boot-logo compose --package ./boot-logo-package.json --base-image ./boot.img --output ./boot-logo.img
5239
6347
  embed cloud task status <task_id>
5240
6348
  embed cloud task artifacts <task_id>
5241
6349
  embed artifact download <artifact_id> --output ./artifact.bin
@@ -5273,7 +6381,7 @@ Use --json on commands when scripting or running in CI.
5273
6381
  `);
5274
6382
  }
5275
6383
  function printCommandReference() {
5276
- console.log(`embed CLI
6384
+ printCliHelp(`embed CLI
5277
6385
 
5278
6386
  Usage:
5279
6387
  embed doctor [--json]
@@ -5293,7 +6401,7 @@ Usage:
5293
6401
  embed board methods <template_id> [--json]
5294
6402
  embed board knowledge <template_id> [--json]
5295
6403
  embed board knowledge file <template_id> --source board_pack|build_template|registry --path <relative_path> [--output <local_path>] [--json]
5296
- embed agent run --prompt <request> [--account <account_id>] [--workspace <workspace_id>] [--provider stub|openai|bai|cc|claude-code] [--model <model>] [--max-tool-calls 4] [--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]
6404
+ 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]
5297
6405
  embed run <natural language request> [--provider stub|openai|bai|cc|claude-code] [--approve] [--json]
5298
6406
  embed tool list [--json]
5299
6407
  embed tool call <capability_id> [--input-json '<json>'] [--approve] [--json]
@@ -5338,7 +6446,26 @@ Usage:
5338
6446
  embed build application generate --workspace <workspace_id> --prompt <request> [--account <account_id>] [--target <source_path>] [--provider stub|openai|bai|cc|claude-code] [--model <model>] [--json]
5339
6447
  embed build application compile --workspace <workspace_id> [--account <account_id>] [--source <source_path>] [--execution-mode docker_worker|server_direct|dry_run] [--compiler auto|cc|gcc|clang|aarch64-linux-gnu-gcc] [--json]
5340
6448
  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]
6449
+ 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]
6450
+ embed image boot-logo compose --package <boot-logo-package.json> --base-image <boot.img|image.img> --output <image> [--manifest <manifest.json>] [--force] [--json]
6451
+ embed local toolchain latest [--board taishanpi-1m-rk3566] [--channel stable] [--metadata-root <path>] [--json]
6452
+ embed local toolchain current [--install-root <path>] [--json]
6453
+ 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]
6454
+ Defaults to the production download channel at download.embedboard.com.
6455
+ embed local toolchain validate [--release-root <path>] [--json]
6456
+ embed local compile taishanpi --source <main.c|main.cpp> --output <artifact> [--release-root <path>] [--account <account_id>] [--json]
6457
+ embed local build qt-smoke --build-dir <dir> [--source <qt-smoke-dir>] [--release-root <path>] [--account <account_id>] [--json]
5341
6458
  embed debug tools [--json]
6459
+ embed tool list [--json]
6460
+ embed tool call wifi.scan --input-json '{"host":"198.19.77.2","user":"root"}' [--json]
6461
+ embed tool call bluetooth.scan --input-json '{"host":"198.19.77.2","user":"root","duration_seconds":8}' [--json]
6462
+ embed tool call chip.cpu.frequency --input-json '{"host":"198.19.77.2","user":"root"}' [--json]
6463
+ embed tool call chip.temperature --input-json '{"host":"198.19.77.2","user":"root"}' [--json]
6464
+ embed tool call qml.runtime.status --input-json '{"host":"198.19.77.2","user":"root","port":18130}' [--json]
6465
+ embed tool call qml.runtime.start --input-json '{"host":"198.19.77.2","user":"root","port":18130}' [--json]
6466
+ embed tool call rp2350.monitor.status [--json]
6467
+ embed tool call rp2350.monitor.logic.capture --input-json '{"pin_base":16,"pin_count":4,"sample_rate":1000000,"samples":4096}' [--json]
6468
+ embed tool call rp2350.monitor.logic.decode --input-json '{"input_path":".embed-labs/rp2350-monitor/captures/logic.jsonl","decoder":"summary"}' [--json]
5342
6469
  embed deploy taishanpi --host <ip> --artifact <local_file> --approve [--remote-path /userdata/embed-labs/apps/app] [--run] [--json]
5343
6470
  embed board deploy taishanpi --host <ip> --artifact <local_file> --approve [--remote-path /userdata/embed-labs/apps/app] [--run] [--json]
5344
6471
  embed device list [--json]
@@ -5377,6 +6504,23 @@ Environment:
5377
6504
  CODEX_HOME=~/.codex
5378
6505
  `);
5379
6506
  }
6507
+ function printCliHelp(text) {
6508
+ console.log(formatCliHelp(text));
6509
+ }
6510
+ function formatCliHelp(text) {
6511
+ if (currentCommandName() !== "embedlabs") {
6512
+ return text;
6513
+ }
6514
+ return text
6515
+ .replace(/^embed CLI$/m, "embedlabs CLI")
6516
+ .replace(/^embed getting started$/m, "embedlabs getting started")
6517
+ .replace("After npm install, use the installed embed binary directly:", "After npm install, use the installed embedlabs binary directly:")
6518
+ .replace(/(^|\n)(\s*)embed(\s+)/g, "$1$2embedlabs$3");
6519
+ }
6520
+ function currentCommandName() {
6521
+ const invoked = basename(process.argv[1] ?? "");
6522
+ return invoked.startsWith("embedlabs") ? "embedlabs" : "embed";
6523
+ }
5380
6524
  main(process.argv.slice(2))
5381
6525
  .then((code) => {
5382
6526
  process.exitCode = code;