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

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, 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,13 @@ 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_VALIDATE_USAGE = "Usage: embed local toolchain validate [--release-root <path>] [--json]";
85
+ const LOCAL_COMPILE_TAISHANPI_USAGE = "Usage: embed local compile taishanpi --source <main.c|main.cpp> --output <artifact> [--release-root <path>] [--account <account_id>] [--json]";
86
+ 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
87
  const BOARD_REGISTRY_LIST_USAGE = "Usage: embed board registry list [--json]";
75
88
  const BOARD_REGISTRY_SHOW_USAGE = "Usage: embed board registry show <template_id> [--json]";
76
89
  const BOARD_METHODS_USAGE = "Usage: embed board methods <template_id> [--json]";
@@ -79,7 +92,7 @@ const BOARD_KNOWLEDGE_FILE_USAGE = "Usage: embed board knowledge file <template_
79
92
  const MODEL_LIST_USAGE = "Usage: embed model list [--json]";
80
93
  const MODEL_DEFAULT_USAGE = "Usage: embed model default [--json]";
81
94
  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]";
95
+ 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
96
  const TOOL_LIST_USAGE = "Usage: embed tool list [--json]";
84
97
  const TOOL_CALL_USAGE = "Usage: embed tool call <capability_id> [--input-json '<json>'] [--approve] [--json]";
85
98
  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]";
@@ -472,6 +485,51 @@ async function main(argv) {
472
485
  BILLING_SNAPSHOT_SHOW_USAGE
473
486
  ].join("\n")), undefined, 2);
474
487
  }
488
+ if (area === "image") {
489
+ if ((action === "boot-logo" && parsed.command[2] === "compose") || (action === "compose" && parsed.command[2] === "boot-logo")) {
490
+ const request = imageBootLogoComposeRequest(parsed, IMAGE_BOOT_LOGO_COMPOSE_USAGE);
491
+ if (typeof request === "string") {
492
+ return output(parsed, fail("invalid_args", request), undefined, 2);
493
+ }
494
+ return output(parsed, ok(await composeBootLogoPackage(request)), renderBootLogoComposeResult);
495
+ }
496
+ if ((action === "dtb" && parsed.command[2] === "compose") || (action === "compose" && parsed.command[2] === "dtb")) {
497
+ const request = imageBootLogoComposeRequest(parsed, IMAGE_DTB_COMPOSE_USAGE);
498
+ if (typeof request === "string") {
499
+ return output(parsed, fail("invalid_args", request), undefined, 2);
500
+ }
501
+ return output(parsed, ok(await composeBootLogoPackage(request)), renderBootLogoComposeResult);
502
+ }
503
+ return output(parsed, fail("invalid_args", [IMAGE_BOOT_LOGO_COMPOSE_USAGE, IMAGE_DTB_COMPOSE_USAGE].join("\n")), undefined, 2);
504
+ }
505
+ if (area === "local") {
506
+ if (action === "toolchain" && parsed.command[2] === "validate") {
507
+ const request = localToolchainValidateRequest(parsed);
508
+ if (typeof request === "string") {
509
+ return output(parsed, fail("invalid_args", request), undefined, 2);
510
+ }
511
+ return output(parsed, ok(await validateLocalToolchain(request.releaseRoot)), renderLocalToolchainValidation);
512
+ }
513
+ if (action === "compile" && parsed.command[2] === "taishanpi") {
514
+ const request = localCompileTaishanPiRequest(parsed, await authStatus());
515
+ if (typeof request === "string") {
516
+ return output(parsed, fail("invalid_args", request), undefined, 2);
517
+ }
518
+ return output(parsed, ok(await compileTaishanPiSingleFile(request)), renderLocalCompileResult);
519
+ }
520
+ if (action === "build" && parsed.command[2] === "qt-smoke") {
521
+ const request = localBuildQtSmokeRequest(parsed, await authStatus());
522
+ if (typeof request === "string") {
523
+ return output(parsed, fail("invalid_args", request), undefined, 2);
524
+ }
525
+ return output(parsed, ok(await buildTaishanPiQtSmoke(request)), renderLocalCompileResult);
526
+ }
527
+ return output(parsed, fail("invalid_args", [
528
+ LOCAL_TOOLCHAIN_VALIDATE_USAGE,
529
+ LOCAL_COMPILE_TAISHANPI_USAGE,
530
+ LOCAL_BUILD_QT_SMOKE_USAGE
531
+ ].join("\n")), undefined, 2);
532
+ }
475
533
  if (area === "build") {
476
534
  if (action === "template") {
477
535
  const templateAction = parsed.command[2];
@@ -617,6 +675,44 @@ async function main(argv) {
617
675
  }
618
676
  return output(parsed, await cloudPost("/v1/build/image-generate", body), renderJob);
619
677
  }
678
+ if (action === "image" && (parsed.command[2] === "boot-logo" || parsed.command[2] === "boot-logo-package")) {
679
+ const request = await buildImageBootLogoPackageRequest(parsed);
680
+ if (typeof request === "string") {
681
+ return output(parsed, fail("invalid_args", request), undefined, 2);
682
+ }
683
+ const created = await cloudPost("/v1/build/image/boot-logo-package", request.body);
684
+ if (!created.ok || !request.output_path) {
685
+ return output(parsed, created, renderJob, created.ok ? 0 : 2);
686
+ }
687
+ const packageArtifact = created.data.artifacts?.find((artifact) => artifact.kind === "patch" && artifact.name === "boot-logo-package.json");
688
+ if (!packageArtifact) {
689
+ return output(parsed, fail("artifact_not_found", "Boot-logo package task did not include boot-logo-package.json."), undefined, 2);
690
+ }
691
+ const downloaded = await cloudDownloadArtifact(packageArtifact.artifact_id, request.output_path);
692
+ if (!downloaded.ok) {
693
+ return output(parsed, downloaded, renderArtifactDownload, 2);
694
+ }
695
+ return output(parsed, ok({ task: created.data, downloaded_artifact: downloaded.data }), renderBootLogoPackageResult);
696
+ }
697
+ if (action === "image" && (parsed.command[2] === "dtb" || parsed.command[2] === "dtb-package")) {
698
+ const request = await buildImageDtbPackageRequest(parsed);
699
+ if (typeof request === "string") {
700
+ return output(parsed, fail("invalid_args", request), undefined, 2);
701
+ }
702
+ const created = await cloudPost("/v1/build/image/dtb-package", request.body);
703
+ if (!created.ok || !request.output_path) {
704
+ return output(parsed, created, renderJob, created.ok ? 0 : 2);
705
+ }
706
+ const packageArtifact = created.data.artifacts?.find((artifact) => artifact.kind === "patch" && artifact.name === "dtb-package.json");
707
+ if (!packageArtifact) {
708
+ return output(parsed, fail("artifact_not_found", "DTB package task did not include dtb-package.json."), undefined, 2);
709
+ }
710
+ const downloaded = await cloudDownloadArtifact(packageArtifact.artifact_id, request.output_path);
711
+ if (!downloaded.ok) {
712
+ return output(parsed, downloaded, renderArtifactDownload, 2);
713
+ }
714
+ return output(parsed, ok({ task: created.data, downloaded_artifact: downloaded.data }), renderDtbPackageResult);
715
+ }
620
716
  return output(parsed, fail("invalid_args", [
621
717
  BUILD_TEMPLATE_LIST_USAGE,
622
718
  BUILD_TEMPLATE_SHOW_USAGE,
@@ -633,7 +729,9 @@ async function main(argv) {
633
729
  BUILD_APPLICATION_STUB_USAGE,
634
730
  BUILD_APPLICATION_GENERATE_USAGE,
635
731
  BUILD_APPLICATION_COMPILE_USAGE,
636
- BUILD_IMAGE_GENERATE_USAGE
732
+ BUILD_IMAGE_GENERATE_USAGE,
733
+ BUILD_IMAGE_BOOT_LOGO_USAGE,
734
+ BUILD_IMAGE_DTB_USAGE
637
735
  ].join("\n")), undefined, 2);
638
736
  }
639
737
  if (area === "project" && action === "create") {
@@ -1304,16 +1402,16 @@ async function pluginList(parsed) {
1304
1402
  return remoteManifest;
1305
1403
  }
1306
1404
  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;
1405
+ const codexPackage = manifest?.data.packages?.find((item) => item.id === "codex-embed-labs");
1406
+ const opencodePackage = manifest?.data.packages?.find((item) => item.id === "opencode-embed-labs");
1407
+ const effectiveCodexPackage = effectiveManifest?.packages?.find((item) => item.id === "codex-embed-labs") ?? codexPackage;
1408
+ const effectiveOpenCodePackage = effectiveManifest?.packages?.find((item) => item.id === "opencode-embed-labs") ?? opencodePackage;
1311
1409
  const source = releaseDir || remoteManifest ? "release_dir" : "source_checkout";
1312
1410
  return ok({
1313
1411
  plugins: [
1314
1412
  {
1315
1413
  id: "codex",
1316
- display_name: "Embed Labs Cloud Codex plugin",
1414
+ display_name: "Embed Labs Codex plugin",
1317
1415
  source,
1318
1416
  version: effectiveCodexPackage?.version ?? effectiveManifest?.version ?? await localPluginVersion("codex"),
1319
1417
  release_file: effectiveCodexPackage?.file,
@@ -1322,7 +1420,7 @@ async function pluginList(parsed) {
1322
1420
  },
1323
1421
  {
1324
1422
  id: "opencode",
1325
- display_name: "Embed Labs Cloud OpenCode plugin",
1423
+ display_name: "Embed Labs OpenCode plugin",
1326
1424
  source,
1327
1425
  version: effectiveOpenCodePackage?.version ?? effectiveManifest?.version ?? await localPluginVersion("opencode"),
1328
1426
  release_file: effectiveOpenCodePackage?.file,
@@ -1398,7 +1496,7 @@ async function installCodexPlugin(parsed, context) {
1398
1496
  return source;
1399
1497
  }
1400
1498
  const targetRoot = codexPluginTargetRoot(parsed, context.installingAll);
1401
- const targetPath = join(targetRoot, "dbt-agent");
1499
+ const targetPath = join(targetRoot, "embed-labs");
1402
1500
  if (await pathExists(targetPath) && !booleanFlag(parsed, "force")) {
1403
1501
  return fail("plugin_already_installed", `Codex plugin already exists at ${targetPath}.`, {
1404
1502
  remediation: "Pass --force to replace it, or pass --codex-target/--target to install into a different directory."
@@ -1407,12 +1505,17 @@ async function installCodexPlugin(parsed, context) {
1407
1505
  await rm(targetPath, { recursive: true, force: true });
1408
1506
  await mkdir(targetRoot, { recursive: true });
1409
1507
  await cp(source.data.sourcePath, targetPath, { recursive: true });
1508
+ const mcpRegistration = await maybeRegisterCodexMcp(parsed, targetRoot, targetPath);
1410
1509
  return ok({
1411
1510
  id: "codex",
1412
1511
  target_path: targetPath,
1413
1512
  source: source.data.sourceLabel,
1414
1513
  version: source.data.version,
1415
- command_hint: "Restart Codex or reload plugins after installing."
1514
+ command_hint: mcpRegistration.registered
1515
+ ? "Codex MCP was registered. Start a new Codex session to reload tools."
1516
+ : mcpRegistration.hint,
1517
+ mcp_registered: mcpRegistration.registered,
1518
+ mcp_warning: mcpRegistration.warning
1416
1519
  });
1417
1520
  }
1418
1521
  async function installOpenCodePlugin(parsed, context) {
@@ -1448,20 +1551,22 @@ async function installOpenCodePlugin(parsed, context) {
1448
1551
  });
1449
1552
  }
1450
1553
  await ensureOpenCodeInstallPackageJson(targetRoot);
1451
- await writeFile(wrapperPath, `export { default, DevelopmentBoardToolchainPlugin } from "dbt-agent";\n`, "utf8");
1554
+ await writeFile(wrapperPath, `export { default, DevelopmentBoardToolchainPlugin } from "embed-labs";\n`, "utf8");
1555
+ const duplicateWarning = await openCodeDuplicatePluginWarning(targetRoot);
1452
1556
  return ok({
1453
1557
  id: "opencode",
1454
1558
  target_path: targetRoot,
1455
1559
  source: source.data.sourceLabel,
1456
1560
  version: source.data.version,
1457
- command_hint: "Start OpenCode from the project containing this .opencode directory."
1561
+ command_hint: "Start OpenCode from the project containing this .opencode directory.",
1562
+ warning: duplicateWarning
1458
1563
  });
1459
1564
  }
1460
1565
  async function resolveCodexPluginSource(context) {
1461
1566
  if (context.releaseDir) {
1462
- const item = context.manifest?.packages?.find((entry) => entry.id === "codex-dbt-agent");
1567
+ const item = context.manifest?.packages?.find((entry) => entry.id === "codex-embed-labs");
1463
1568
  if (!item?.file) {
1464
- return fail("plugin_release_not_found", `Release manifest in ${context.releaseDir} does not include codex-dbt-agent.`);
1569
+ return fail("plugin_release_not_found", `Release manifest in ${context.releaseDir} does not include codex-embed-labs.`);
1465
1570
  }
1466
1571
  const tarball = resolve(context.releaseDir, item.file);
1467
1572
  const verified = await verifyReleasePackage(tarball, item);
@@ -1476,13 +1581,13 @@ async function resolveCodexPluginSource(context) {
1476
1581
  details: { exit_code: tarResult.code, stderr_tail: tarResult.stderr.split("\n").slice(-20) }
1477
1582
  });
1478
1583
  }
1479
- const sourcePath = join(extractDir, "codex_plugin", "plugins", "dbt-agent");
1584
+ const sourcePath = join(extractDir, "codex_plugin", "plugins", "embed-labs");
1480
1585
  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.`);
1586
+ return fail("plugin_release_invalid", `Codex plugin release ${tarball} did not contain plugins/embed-labs/.codex-plugin/plugin.json.`);
1482
1587
  }
1483
1588
  return ok({ sourcePath, sourceLabel: tarball, version: item.version ?? context.manifest?.version });
1484
1589
  }
1485
- const sourcePath = resolve(process.cwd(), "platform_plugins", "codex_plugin", "plugins", "dbt-agent");
1590
+ const sourcePath = sourceCheckoutPath("platform_plugins", "codex_plugin", "plugins", "embed-labs");
1486
1591
  if (!await pathExists(join(sourcePath, ".codex-plugin", "plugin.json"))) {
1487
1592
  return fail("plugin_source_not_found", "Could not find Codex plugin source in this checkout.", {
1488
1593
  remediation: "Run from the Embed-Labs-Cloud repo root or pass --release-dir pointing to a plugin release directory."
@@ -1492,9 +1597,9 @@ async function resolveCodexPluginSource(context) {
1492
1597
  }
1493
1598
  async function resolveOpenCodePluginSource(context) {
1494
1599
  if (context.releaseDir) {
1495
- const item = context.manifest?.packages?.find((entry) => entry.id === "opencode-dbt-agent");
1600
+ const item = context.manifest?.packages?.find((entry) => entry.id === "opencode-embed-labs");
1496
1601
  if (!item?.file) {
1497
- return fail("plugin_release_not_found", `Release manifest in ${context.releaseDir} does not include opencode-dbt-agent.`);
1602
+ return fail("plugin_release_not_found", `Release manifest in ${context.releaseDir} does not include opencode-embed-labs.`);
1498
1603
  }
1499
1604
  const tarball = resolve(context.releaseDir, item.file);
1500
1605
  const verified = await verifyReleasePackage(tarball, item);
@@ -1503,13 +1608,38 @@ async function resolveOpenCodePluginSource(context) {
1503
1608
  }
1504
1609
  return ok({ packagePath: tarball, sourceLabel: tarball, version: item.version ?? context.manifest?.version });
1505
1610
  }
1506
- const packagePath = resolve(process.cwd(), "platform_plugins", "opencode_plugin");
1611
+ const packagePath = sourceCheckoutPath("platform_plugins", "opencode_plugin");
1507
1612
  if (!await pathExists(join(packagePath, "package.json"))) {
1508
1613
  return fail("plugin_source_not_found", "Could not find OpenCode plugin source in this checkout.", {
1509
1614
  remediation: "Run from the Embed-Labs-Cloud repo root or pass --release-dir pointing to a plugin release directory."
1510
1615
  });
1511
1616
  }
1512
- return ok({ packagePath, sourceLabel: packagePath, version: await localPluginVersion("opencode") });
1617
+ const packed = await runLocalProcess("npm", ["pack", packagePath, "--pack-destination", context.tempDir, "--json"]);
1618
+ if (packed.code !== 0) {
1619
+ return fail("opencode_plugin_pack_failed", "npm pack failed while preparing the OpenCode plugin source package.", {
1620
+ details: {
1621
+ exit_code: packed.code,
1622
+ stdout_tail: packed.stdout.split("\n").slice(-20),
1623
+ stderr_tail: packed.stderr.split("\n").slice(-20)
1624
+ }
1625
+ });
1626
+ }
1627
+ let tarballName = "";
1628
+ try {
1629
+ const parsed = JSON.parse(packed.stdout);
1630
+ tarballName = basename(parsed[0]?.filename || "");
1631
+ }
1632
+ catch {
1633
+ tarballName = "";
1634
+ }
1635
+ if (!tarballName) {
1636
+ return fail("opencode_plugin_pack_failed", "npm pack did not report an OpenCode plugin tarball filename.");
1637
+ }
1638
+ const tarballPath = join(context.tempDir, tarballName);
1639
+ if (!await pathExists(tarballPath)) {
1640
+ return fail("opencode_plugin_pack_failed", `npm pack reported ${tarballName}, but the tarball was not found.`);
1641
+ }
1642
+ return ok({ packagePath: tarballPath, sourceLabel: `${packagePath} -> ${tarballPath}`, version: await localPluginVersion("opencode") });
1513
1643
  }
1514
1644
  async function fetchRemotePluginManifest(parsed) {
1515
1645
  const manifestUrl = `${pluginReleaseBaseUrl(parsed)}/manifest.json`;
@@ -1645,13 +1775,204 @@ function openCodePluginTargetRoot(parsed, installingAll) {
1645
1775
  function defaultCodexPluginRoot() {
1646
1776
  return join(process.env.CODEX_HOME?.trim() || join(homedir(), ".codex"), "plugins");
1647
1777
  }
1778
+ async function maybeRegisterCodexMcp(parsed, targetRoot, targetPath) {
1779
+ const explicitTarget = Boolean(stringFlag(parsed, "target") || stringFlag(parsed, "codex-target"));
1780
+ if (explicitTarget && process.env.EMBED_CODEX_MCP_REGISTER !== "1") {
1781
+ return {
1782
+ registered: false,
1783
+ hint: `Installed into a custom target. Register manually with: codex mcp add embed-labs -- node ${join(targetPath, "scripts", "embed-labs-mcp-bridge.mjs")}`
1784
+ };
1785
+ }
1786
+ const bridgePath = join(targetPath, "scripts", "embed-labs-mcp-bridge.mjs");
1787
+ if (!await pathExists(bridgePath)) {
1788
+ return {
1789
+ registered: false,
1790
+ hint: "Restart Codex or reload plugins after installing.",
1791
+ warning: `Codex MCP bridge was not found at ${bridgePath}.`
1792
+ };
1793
+ }
1794
+ const codexBin = await resolveExecutableOnPath("codex");
1795
+ if (!codexBin) {
1796
+ return {
1797
+ registered: false,
1798
+ hint: `Codex CLI was not found on PATH. Register manually with: codex mcp add embed-labs -- node ${bridgePath}`
1799
+ };
1800
+ }
1801
+ const embedCliBin = process.env.EMBED_CLI_BIN?.trim() || await resolveExecutableOnPath("embedlabs") || await resolveExecutableOnPath("embed") || "";
1802
+ const authFile = resolve(process.env.EMBED_AUTH_FILE?.trim() || DEFAULT_AUTH_FILE);
1803
+ const cloudUrl = pluginMcpCloudApiUrl(parsed);
1804
+ const existing = await runLocalProcess(codexBin, ["mcp", "get", "embed-labs", "--json"]);
1805
+ if (existing.code === 0 && codexMcpAlreadyRegistered(existing.stdout, bridgePath, cloudUrl, authFile, embedCliBin)) {
1806
+ const warning = await upsertCodexMcpRuntimeConfig(bridgePath);
1807
+ if (warning) {
1808
+ return { registered: true, warning };
1809
+ }
1810
+ return { registered: true };
1811
+ }
1812
+ await runLocalProcess(codexBin, ["mcp", "remove", "embed-labs"]);
1813
+ const args = [
1814
+ "mcp",
1815
+ "add",
1816
+ "embed-labs",
1817
+ "--env",
1818
+ `EMBED_CLOUD_API_URL=${cloudUrl}`,
1819
+ "--env",
1820
+ `EMBED_AUTH_FILE=${authFile}`
1821
+ ];
1822
+ if (embedCliBin) {
1823
+ args.push("--env", `EMBED_CLI_BIN=${embedCliBin}`);
1824
+ }
1825
+ args.push("--", process.execPath, bridgePath);
1826
+ const addResult = await runLocalProcess(codexBin, args);
1827
+ if (addResult.code !== 0) {
1828
+ return {
1829
+ registered: false,
1830
+ hint: `Codex plugin installed. Register manually with: codex mcp add embed-labs -- ${process.execPath} ${bridgePath}`,
1831
+ warning: `codex mcp add failed: ${addResult.stderr.trim() || addResult.stdout.trim() || `exit ${addResult.code}`}`
1832
+ };
1833
+ }
1834
+ const warning = await upsertCodexMcpRuntimeConfig(bridgePath);
1835
+ return warning ? { registered: true, warning } : { registered: true };
1836
+ }
1837
+ function codexMcpAlreadyRegistered(stdout, bridgePath, cloudUrl, authFile, embedCliBin) {
1838
+ try {
1839
+ const parsed = JSON.parse(stdout);
1840
+ const transport = parsed.transport;
1841
+ if (transport?.type !== "stdio" || transport.command !== process.execPath)
1842
+ return false;
1843
+ if (!transport.args?.includes(bridgePath))
1844
+ return false;
1845
+ const env = transport.env || {};
1846
+ if (env.EMBED_CLOUD_API_URL !== cloudUrl)
1847
+ return false;
1848
+ if (env.EMBED_AUTH_FILE !== authFile)
1849
+ return false;
1850
+ if (embedCliBin && env.EMBED_CLI_BIN !== embedCliBin)
1851
+ return false;
1852
+ return true;
1853
+ }
1854
+ catch {
1855
+ return false;
1856
+ }
1857
+ }
1858
+ async function upsertCodexMcpRuntimeConfig(bridgePath) {
1859
+ const configPath = join(process.env.CODEX_HOME?.trim() || join(homedir(), ".codex"), "config.toml");
1860
+ try {
1861
+ await mkdir(dirname(configPath), { recursive: true });
1862
+ let text = "";
1863
+ try {
1864
+ text = await readFile(configPath, "utf8");
1865
+ }
1866
+ catch {
1867
+ text = "";
1868
+ }
1869
+ const updated = upsertTomlTableKeys(text, "mcp_servers.embed-labs", {
1870
+ command: tomlString(process.execPath),
1871
+ args: `[${tomlString(bridgePath)}]`,
1872
+ startup_timeout_sec: "120"
1873
+ });
1874
+ if (updated !== text) {
1875
+ await writeFile(configPath, updated, "utf8");
1876
+ }
1877
+ return undefined;
1878
+ }
1879
+ catch (error) {
1880
+ return `Codex MCP was registered, but ${configPath} could not be updated with startup_timeout_sec: ${error instanceof Error ? error.message : String(error)}`;
1881
+ }
1882
+ }
1883
+ function upsertTomlTableKeys(text, tableName, values) {
1884
+ const normalized = text.endsWith("\n") || text.length === 0 ? text : `${text}\n`;
1885
+ const header = `[${tableName}]`;
1886
+ const tablePattern = new RegExp(`(^|\\n)(\\[${escapeRegExp(tableName)}\\]\\n)([\\s\\S]*?)(?=\\n\\[|$)`);
1887
+ const match = tablePattern.exec(normalized);
1888
+ const bodyWithValues = (body) => {
1889
+ let updated = body;
1890
+ for (const [key, value] of Object.entries(values)) {
1891
+ const keyPattern = new RegExp(`^${escapeRegExp(key)}\\s*=.*$`, "m");
1892
+ if (keyPattern.test(updated)) {
1893
+ updated = updated.replace(keyPattern, `${key} = ${value}`);
1894
+ }
1895
+ else {
1896
+ updated = `${updated.replace(/\s*$/, "\n")}${key} = ${value}\n`;
1897
+ }
1898
+ }
1899
+ return updated;
1900
+ };
1901
+ if (!match) {
1902
+ const prefix = normalized.length > 0 && !normalized.endsWith("\n\n") ? `${normalized}\n` : normalized;
1903
+ return `${prefix}${header}\n${bodyWithValues("")}`;
1904
+ }
1905
+ return `${normalized.slice(0, match.index)}${match[1]}${match[2]}${bodyWithValues(match[3])}${normalized.slice(match.index + match[0].length)}`;
1906
+ }
1907
+ function tomlString(value) {
1908
+ return JSON.stringify(value);
1909
+ }
1910
+ function escapeRegExp(value) {
1911
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1912
+ }
1913
+ function pluginMcpCloudApiUrl(parsed) {
1914
+ const explicit = process.env.EMBED_CLOUD_API_URL?.trim();
1915
+ if (explicit)
1916
+ return explicit.replace(/\/+$/, "");
1917
+ const releaseUrl = stringFlag(parsed, "release-url") || DEFAULT_PLUGIN_RELEASE_URL;
1918
+ try {
1919
+ return new URL(releaseUrl).origin;
1920
+ }
1921
+ catch {
1922
+ return DEFAULT_CLOUD_API_URL.replace(/\/+$/, "");
1923
+ }
1924
+ }
1925
+ async function resolveExecutableOnPath(name) {
1926
+ if (name.includes("/")) {
1927
+ try {
1928
+ await access(name, constants.X_OK);
1929
+ return name;
1930
+ }
1931
+ catch {
1932
+ return undefined;
1933
+ }
1934
+ }
1935
+ const pathEntries = (process.env.PATH || "").split(delimiter).filter(Boolean);
1936
+ for (const entry of pathEntries) {
1937
+ const candidate = join(entry, name);
1938
+ try {
1939
+ await access(candidate, constants.X_OK);
1940
+ return candidate;
1941
+ }
1942
+ catch {
1943
+ // Keep searching PATH.
1944
+ }
1945
+ }
1946
+ return undefined;
1947
+ }
1648
1948
  function defaultOpenCodeRoot() {
1649
1949
  return join(process.cwd(), ".opencode");
1650
1950
  }
1951
+ async function openCodeDuplicatePluginWarning(targetRoot) {
1952
+ const globalRoot = join(homedir(), ".config", "opencode");
1953
+ if (resolve(targetRoot) === resolve(globalRoot))
1954
+ return undefined;
1955
+ const configPath = join(globalRoot, "opencode.json");
1956
+ try {
1957
+ const parsed = JSON.parse(await readFile(configPath, "utf8"));
1958
+ const configuredPlugins = Array.isArray(parsed.plugin)
1959
+ ? parsed.plugin
1960
+ : Array.isArray(parsed.plugins)
1961
+ ? parsed.plugins
1962
+ : [];
1963
+ if (configuredPlugins.some((item) => item === "embed-labs")) {
1964
+ 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.";
1965
+ }
1966
+ }
1967
+ catch {
1968
+ return undefined;
1969
+ }
1970
+ return undefined;
1971
+ }
1651
1972
  async function localPluginVersion(kind) {
1652
1973
  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");
1974
+ ? sourceCheckoutPath("platform_plugins", "codex_plugin", "plugins", "embed-labs", ".codex-plugin", "plugin.json")
1975
+ : sourceCheckoutPath("platform_plugins", "opencode_plugin", "package.json");
1655
1976
  try {
1656
1977
  const parsed = JSON.parse(await readFile(filePath, "utf8"));
1657
1978
  return parsed.version;
@@ -1661,8 +1982,11 @@ async function localPluginVersion(kind) {
1661
1982
  }
1662
1983
  }
1663
1984
  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"));
1985
+ return await pathExists(sourceCheckoutPath("platform_plugins", "codex_plugin", "plugins", "embed-labs", ".codex-plugin", "plugin.json"))
1986
+ && await pathExists(sourceCheckoutPath("platform_plugins", "opencode_plugin", "package.json"));
1987
+ }
1988
+ function sourceCheckoutPath(...segments) {
1989
+ return resolve(SOURCE_CHECKOUT_ROOT, ...segments);
1666
1990
  }
1667
1991
  async function pathExists(pathValue) {
1668
1992
  try {
@@ -2098,6 +2422,36 @@ function agentRunToolInputs(parsed) {
2098
2422
  ports: ports.values.length > 0 ? ports.values : [22, 15301],
2099
2423
  timeout_ms: timeout.value !== undefined ? timeout.value * 1000 : undefined
2100
2424
  });
2425
+ inputs["wifi.scan"] = compactBody({
2426
+ host: host.value,
2427
+ user: user.value,
2428
+ timeout_seconds: timeout.value
2429
+ });
2430
+ inputs["bluetooth.scan"] = compactBody({
2431
+ host: host.value,
2432
+ user: user.value,
2433
+ timeout_seconds: timeout.value
2434
+ });
2435
+ inputs["chip.cpu.frequency"] = compactBody({
2436
+ host: host.value,
2437
+ user: user.value,
2438
+ timeout_seconds: timeout.value
2439
+ });
2440
+ inputs["chip.temperature"] = compactBody({
2441
+ host: host.value,
2442
+ user: user.value,
2443
+ timeout_seconds: timeout.value
2444
+ });
2445
+ inputs["qml.runtime.status"] = compactBody({
2446
+ host: host.value,
2447
+ user: user.value,
2448
+ timeout_seconds: timeout.value
2449
+ });
2450
+ inputs["qml.runtime.start"] = compactBody({
2451
+ host: host.value,
2452
+ user: user.value,
2453
+ timeout_seconds: timeout.value
2454
+ });
2101
2455
  }
2102
2456
  if (artifact.value || remotePath.value || user.value || booleanFlag(parsed, "run")) {
2103
2457
  inputs["taishanpi.deploy"] = compactBody({
@@ -2778,6 +3132,231 @@ function buildImageGenerateBody(parsed) {
2778
3132
  worker_pool: workerPool.value
2779
3133
  });
2780
3134
  }
3135
+ async function buildImageBootLogoPackageRequest(parsed) {
3136
+ const unknownFlag = firstUnknownFlag(parsed, [
3137
+ "json",
3138
+ "account",
3139
+ "project",
3140
+ "project-id",
3141
+ "board",
3142
+ "board-id",
3143
+ "variant",
3144
+ "variant-id",
3145
+ "logo",
3146
+ "logo-path",
3147
+ "kernel-logo",
3148
+ "kernel-logo-path",
3149
+ "rotate",
3150
+ "scale",
3151
+ "output"
3152
+ ]);
3153
+ if (unknownFlag) {
3154
+ return `Unknown flag --${unknownFlag}. ${BUILD_IMAGE_BOOT_LOGO_USAGE}`;
3155
+ }
3156
+ const extra = parsed.command.slice(3);
3157
+ if (extra.length > 0) {
3158
+ return `Unexpected argument: ${extra[0]}. ${BUILD_IMAGE_BOOT_LOGO_USAGE}`;
3159
+ }
3160
+ const logo = optionalTrimmedStringAliasFlag(parsed, ["logo", "logo-path"], "logo or logo-path");
3161
+ if (logo.error) {
3162
+ return logo.error;
3163
+ }
3164
+ if (!logo.value) {
3165
+ return BUILD_IMAGE_BOOT_LOGO_USAGE;
3166
+ }
3167
+ const logoPath = resolve(logo.value);
3168
+ const logoRead = await readLocalFileForUpload(logoPath, "logo");
3169
+ if (typeof logoRead === "string") {
3170
+ return logoRead;
3171
+ }
3172
+ const kernelLogo = optionalTrimmedStringAliasFlag(parsed, ["kernel-logo", "kernel-logo-path"], "kernel-logo or kernel-logo-path");
3173
+ if (kernelLogo.error) {
3174
+ return kernelLogo.error;
3175
+ }
3176
+ let kernelLogoRead;
3177
+ if (kernelLogo.value) {
3178
+ const kernelLogoPath = resolve(kernelLogo.value);
3179
+ const read = await readLocalFileForUpload(kernelLogoPath, "kernel-logo");
3180
+ if (typeof read === "string") {
3181
+ return read;
3182
+ }
3183
+ kernelLogoRead = read;
3184
+ }
3185
+ const account = optionalTrimmedStringFlag(parsed, "account");
3186
+ if (account.error) {
3187
+ return account.error;
3188
+ }
3189
+ const project = optionalTrimmedStringAliasFlag(parsed, ["project", "project-id"], "project or project-id");
3190
+ if (project.error) {
3191
+ return project.error;
3192
+ }
3193
+ const board = optionalTrimmedStringAliasFlag(parsed, ["board", "board-id"], "board or board-id");
3194
+ if (board.error) {
3195
+ return board.error;
3196
+ }
3197
+ const variant = optionalTrimmedStringAliasFlag(parsed, ["variant", "variant-id"], "variant or variant-id");
3198
+ if (variant.error) {
3199
+ return variant.error;
3200
+ }
3201
+ const rotate = optionalTrimmedStringFlag(parsed, "rotate");
3202
+ if (rotate.error) {
3203
+ return rotate.error;
3204
+ }
3205
+ const scale = optionalTrimmedStringFlag(parsed, "scale");
3206
+ if (scale.error) {
3207
+ return scale.error;
3208
+ }
3209
+ const outputPath = optionalTrimmedStringFlag(parsed, "output");
3210
+ if (outputPath.error) {
3211
+ return outputPath.error;
3212
+ }
3213
+ const body = {
3214
+ account_id: account.value,
3215
+ project_id: project.value,
3216
+ board_id: board.value ?? "taishanpi",
3217
+ variant_id: variant.value,
3218
+ logo_name: basename(logoRead.path),
3219
+ logo_content_base64: logoRead.bytes.toString("base64"),
3220
+ logo_sha256: logoRead.sha256,
3221
+ kernel_logo_name: kernelLogoRead ? basename(kernelLogoRead.path) : undefined,
3222
+ kernel_logo_content_base64: kernelLogoRead?.bytes.toString("base64"),
3223
+ kernel_logo_sha256: kernelLogoRead?.sha256,
3224
+ rotate: rotate.value,
3225
+ scale: scale.value
3226
+ };
3227
+ return {
3228
+ body,
3229
+ output_path: outputPath.value ? resolve(outputPath.value) : undefined
3230
+ };
3231
+ }
3232
+ async function buildImageDtbPackageRequest(parsed) {
3233
+ const unknownFlag = firstUnknownFlag(parsed, [
3234
+ "json",
3235
+ "account",
3236
+ "project",
3237
+ "project-id",
3238
+ "board",
3239
+ "board-id",
3240
+ "variant",
3241
+ "variant-id",
3242
+ "dtb",
3243
+ "dtb-path",
3244
+ "dts",
3245
+ "dts-path",
3246
+ "input-format",
3247
+ "output"
3248
+ ]);
3249
+ if (unknownFlag) {
3250
+ return `Unknown flag --${unknownFlag}. ${BUILD_IMAGE_DTB_USAGE}`;
3251
+ }
3252
+ const extra = parsed.command.slice(3);
3253
+ if (extra.length > 0) {
3254
+ return `Unexpected argument: ${extra[0]}. ${BUILD_IMAGE_DTB_USAGE}`;
3255
+ }
3256
+ const dtb = optionalTrimmedStringAliasFlag(parsed, ["dtb", "dtb-path", "dts", "dts-path"], "dtb or dts path");
3257
+ if (dtb.error) {
3258
+ return dtb.error;
3259
+ }
3260
+ if (!dtb.value) {
3261
+ return BUILD_IMAGE_DTB_USAGE;
3262
+ }
3263
+ const dtbPath = resolve(dtb.value);
3264
+ const dtbRead = await readLocalFileForUpload(dtbPath, "dtb");
3265
+ if (typeof dtbRead === "string") {
3266
+ return dtbRead;
3267
+ }
3268
+ const account = optionalTrimmedStringFlag(parsed, "account");
3269
+ if (account.error) {
3270
+ return account.error;
3271
+ }
3272
+ const project = optionalTrimmedStringAliasFlag(parsed, ["project", "project-id"], "project or project-id");
3273
+ if (project.error) {
3274
+ return project.error;
3275
+ }
3276
+ const board = optionalTrimmedStringAliasFlag(parsed, ["board", "board-id"], "board or board-id");
3277
+ if (board.error) {
3278
+ return board.error;
3279
+ }
3280
+ const variant = optionalTrimmedStringAliasFlag(parsed, ["variant", "variant-id"], "variant or variant-id");
3281
+ if (variant.error) {
3282
+ return variant.error;
3283
+ }
3284
+ const inputFormat = optionalTrimmedStringFlag(parsed, "input-format");
3285
+ if (inputFormat.error) {
3286
+ return inputFormat.error;
3287
+ }
3288
+ const outputPath = optionalTrimmedStringFlag(parsed, "output");
3289
+ if (outputPath.error) {
3290
+ return outputPath.error;
3291
+ }
3292
+ const body = {
3293
+ account_id: account.value,
3294
+ project_id: project.value,
3295
+ board_id: board.value ?? "taishanpi",
3296
+ variant_id: variant.value,
3297
+ dtb_name: basename(dtbRead.path),
3298
+ dtb_content_base64: dtbRead.bytes.toString("base64"),
3299
+ dtb_sha256: dtbRead.sha256,
3300
+ input_format: inputFormat.value
3301
+ };
3302
+ return {
3303
+ body,
3304
+ output_path: outputPath.value ? resolve(outputPath.value) : undefined
3305
+ };
3306
+ }
3307
+ function imageBootLogoComposeRequest(parsed, usage) {
3308
+ const unknownFlag = firstUnknownFlag(parsed, [
3309
+ "json",
3310
+ "package",
3311
+ "package-path",
3312
+ "base-image",
3313
+ "base-image-path",
3314
+ "output",
3315
+ "output-image",
3316
+ "manifest",
3317
+ "manifest-path",
3318
+ "force"
3319
+ ]);
3320
+ if (unknownFlag) {
3321
+ return `Unknown flag --${unknownFlag}. ${usage}`;
3322
+ }
3323
+ const extra = parsed.command.slice(3);
3324
+ if (extra.length > 0) {
3325
+ return `Unexpected argument: ${extra[0]}. ${usage}`;
3326
+ }
3327
+ const packagePath = optionalTrimmedStringAliasFlag(parsed, ["package", "package-path"], "package or package-path");
3328
+ if (packagePath.error) {
3329
+ return packagePath.error;
3330
+ }
3331
+ if (!packagePath.value) {
3332
+ return usage;
3333
+ }
3334
+ const baseImage = optionalTrimmedStringAliasFlag(parsed, ["base-image", "base-image-path"], "base-image or base-image-path");
3335
+ if (baseImage.error) {
3336
+ return baseImage.error;
3337
+ }
3338
+ if (!baseImage.value) {
3339
+ return usage;
3340
+ }
3341
+ const outputImage = optionalTrimmedStringAliasFlag(parsed, ["output", "output-image"], "output or output-image");
3342
+ if (outputImage.error) {
3343
+ return outputImage.error;
3344
+ }
3345
+ if (!outputImage.value) {
3346
+ return usage;
3347
+ }
3348
+ const manifest = optionalTrimmedStringAliasFlag(parsed, ["manifest", "manifest-path"], "manifest or manifest-path");
3349
+ if (manifest.error) {
3350
+ return manifest.error;
3351
+ }
3352
+ return {
3353
+ packagePath: resolve(packagePath.value),
3354
+ baseImagePath: resolve(baseImage.value),
3355
+ outputImagePath: resolve(outputImage.value),
3356
+ manifestPath: manifest.value ? resolve(manifest.value) : undefined,
3357
+ force: booleanFlag(parsed, "force")
3358
+ };
3359
+ }
2781
3360
  function boardKnowledgeFileRequest(parsed) {
2782
3361
  const unknownFlag = firstUnknownFlag(parsed, ["json", "source", "path", "output"]);
2783
3362
  if (unknownFlag) {
@@ -3445,6 +4024,107 @@ function billingSnapshotListRequest(parsed) {
3445
4024
  }
3446
4025
  return { path: `/v1/accounts/${encodeURIComponent(accountResult.value)}/billing/snapshots` };
3447
4026
  }
4027
+ function localToolchainValidateRequest(parsed) {
4028
+ const unknownFlag = firstUnknownFlag(parsed, ["json", "release-root"]);
4029
+ if (unknownFlag) {
4030
+ return `Unknown flag --${unknownFlag}. ${LOCAL_TOOLCHAIN_VALIDATE_USAGE}`;
4031
+ }
4032
+ const extra = parsed.command.slice(3);
4033
+ if (extra.length > 0) {
4034
+ return `Unexpected argument: ${extra[0]}. ${LOCAL_TOOLCHAIN_VALIDATE_USAGE}`;
4035
+ }
4036
+ const releaseRoot = optionalTrimmedStringFlag(parsed, "release-root");
4037
+ if (releaseRoot.error) {
4038
+ return releaseRoot.error;
4039
+ }
4040
+ return { releaseRoot: releaseRoot.value };
4041
+ }
4042
+ function localCompileTaishanPiRequest(parsed, auth) {
4043
+ const unknownFlag = firstUnknownFlag(parsed, ["json", "source", "output", "release-root", "account", "account-id"]);
4044
+ if (unknownFlag) {
4045
+ return `Unknown flag --${unknownFlag}. ${LOCAL_COMPILE_TAISHANPI_USAGE}`;
4046
+ }
4047
+ const extra = parsed.command.slice(3);
4048
+ if (extra.length > 0) {
4049
+ return `Unexpected argument: ${extra[0]}. ${LOCAL_COMPILE_TAISHANPI_USAGE}`;
4050
+ }
4051
+ const source = optionalTrimmedStringFlag(parsed, "source");
4052
+ if (source.error) {
4053
+ return source.error;
4054
+ }
4055
+ const outputPath = optionalTrimmedStringFlag(parsed, "output");
4056
+ if (outputPath.error) {
4057
+ return outputPath.error;
4058
+ }
4059
+ if (!source.value || !outputPath.value) {
4060
+ return LOCAL_COMPILE_TAISHANPI_USAGE;
4061
+ }
4062
+ const releaseRoot = optionalTrimmedStringFlag(parsed, "release-root");
4063
+ if (releaseRoot.error) {
4064
+ return releaseRoot.error;
4065
+ }
4066
+ const account = optionalTrimmedStringAliasFlag(parsed, ["account", "account-id"], "account or account-id");
4067
+ if (account.error) {
4068
+ return account.error;
4069
+ }
4070
+ return {
4071
+ boardId: "taishanpi-1m-rk3566",
4072
+ sourcePath: source.value,
4073
+ outputPath: outputPath.value,
4074
+ releaseRoot: releaseRoot.value,
4075
+ accountId: account.value,
4076
+ auth: localToolchainAuthContext(auth, account.value)
4077
+ };
4078
+ }
4079
+ function localBuildQtSmokeRequest(parsed, auth) {
4080
+ const unknownFlag = firstUnknownFlag(parsed, ["json", "source", "build-dir", "release-root", "target-name", "account", "account-id"]);
4081
+ if (unknownFlag) {
4082
+ return `Unknown flag --${unknownFlag}. ${LOCAL_BUILD_QT_SMOKE_USAGE}`;
4083
+ }
4084
+ const extra = parsed.command.slice(3);
4085
+ if (extra.length > 0) {
4086
+ return `Unexpected argument: ${extra[0]}. ${LOCAL_BUILD_QT_SMOKE_USAGE}`;
4087
+ }
4088
+ const buildDir = optionalTrimmedStringFlag(parsed, "build-dir");
4089
+ if (buildDir.error) {
4090
+ return buildDir.error;
4091
+ }
4092
+ if (!buildDir.value) {
4093
+ return LOCAL_BUILD_QT_SMOKE_USAGE;
4094
+ }
4095
+ const source = optionalTrimmedStringFlag(parsed, "source");
4096
+ if (source.error) {
4097
+ return source.error;
4098
+ }
4099
+ const releaseRoot = optionalTrimmedStringFlag(parsed, "release-root");
4100
+ if (releaseRoot.error) {
4101
+ return releaseRoot.error;
4102
+ }
4103
+ const targetName = optionalTrimmedStringFlag(parsed, "target-name");
4104
+ if (targetName.error) {
4105
+ return targetName.error;
4106
+ }
4107
+ const account = optionalTrimmedStringAliasFlag(parsed, ["account", "account-id"], "account or account-id");
4108
+ if (account.error) {
4109
+ return account.error;
4110
+ }
4111
+ return {
4112
+ sourceDir: source.value,
4113
+ buildDir: buildDir.value,
4114
+ releaseRoot: releaseRoot.value,
4115
+ targetName: targetName.value,
4116
+ accountId: account.value,
4117
+ auth: localToolchainAuthContext(auth, account.value)
4118
+ };
4119
+ }
4120
+ function localToolchainAuthContext(auth, accountId) {
4121
+ return {
4122
+ authenticated: auth.authenticated,
4123
+ profile: auth.profile,
4124
+ source: auth.source,
4125
+ account_id: accountId
4126
+ };
4127
+ }
3448
4128
  function usageEventsRequest(parsed) {
3449
4129
  const unknownFlag = firstUnknownFlag(parsed, ["json", "account", "account-id", "api-key-id", "from", "to", "limit"]);
3450
4130
  if (unknownFlag) {
@@ -3502,6 +4182,26 @@ function compactBody(input) {
3502
4182
  }
3503
4183
  return body;
3504
4184
  }
4185
+ async function readLocalFileForUpload(filePath, label) {
4186
+ try {
4187
+ const info = await stat(filePath);
4188
+ if (!info.isFile()) {
4189
+ return `${label} must point to a file: ${filePath}`;
4190
+ }
4191
+ const bytes = await readFile(filePath);
4192
+ if (bytes.length === 0) {
4193
+ return `${label} must not be empty: ${filePath}`;
4194
+ }
4195
+ return {
4196
+ path: filePath,
4197
+ bytes,
4198
+ sha256: createHash("sha256").update(bytes).digest("hex")
4199
+ };
4200
+ }
4201
+ catch (error) {
4202
+ return `Could not read ${label} file ${filePath}: ${error instanceof Error ? error.message : String(error)}`;
4203
+ }
4204
+ }
3505
4205
  function projectCreateBody(parsed) {
3506
4206
  const extra = parsed.command.slice(3);
3507
4207
  if (extra.length > 0) {
@@ -3795,8 +4495,15 @@ function renderServiceModeCatalog(catalog) {
3795
4495
  ` model_gateway=${catalog.boundaries.model_gateway}`,
3796
4496
  ` server_resource_orchestration=${catalog.boundaries.server_resource_orchestration}`,
3797
4497
  ` local_bridge=${catalog.boundaries.local_bridge}`,
4498
+ catalog.boundaries.free_registered_operations ? ` free_registered_operations=${catalog.boundaries.free_registered_operations}` : "",
4499
+ catalog.boundaries.paid_server_operations ? ` paid_server_operations=${catalog.boundaries.paid_server_operations}` : "",
4500
+ catalog.allocation_rule ? `allocation_rule=${catalog.allocation_rule}` : "",
4501
+ "free_registered_capabilities:",
4502
+ ...(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"}`),
4503
+ "paid_capabilities:",
4504
+ ...(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
4505
  "modes:"
3799
- ];
4506
+ ].filter(Boolean);
3800
4507
  for (const mode of catalog.modes) {
3801
4508
  lines.push([
3802
4509
  ` ${mode.default ? "*" : "-"}`,
@@ -3837,6 +4544,15 @@ function renderPluginInstall(result) {
3837
4544
  if (item.command_hint) {
3838
4545
  lines.push(` ${item.command_hint}`);
3839
4546
  }
4547
+ if (item.warning) {
4548
+ lines.push(` warning=${item.warning}`);
4549
+ }
4550
+ if (item.mcp_registered !== undefined) {
4551
+ lines.push(` codex_mcp_registered=${item.mcp_registered}`);
4552
+ }
4553
+ if (item.mcp_warning) {
4554
+ lines.push(` warning=${item.mcp_warning}`);
4555
+ }
3840
4556
  }
3841
4557
  return lines.join("\n");
3842
4558
  }
@@ -4208,10 +4924,13 @@ function renderBoardMethods(data) {
4208
4924
  method.operation,
4209
4925
  `status=${method.status}`,
4210
4926
  `runtime=${method.runtime}`,
4927
+ method.access_tier ? `access=${method.access_tier}` : "",
4211
4928
  `endpoint=${method.http_method} ${method.path}`,
4212
4929
  `workspace=${method.requires_workspace}`,
4930
+ method.allocates_server_workspace !== undefined ? `alloc_workspace=${method.allocates_server_workspace}` : "",
4931
+ method.allocates_server_resource_lease !== undefined ? `alloc_lease=${method.allocates_server_resource_lease}` : "",
4213
4932
  `approval=${method.requires_approval}`
4214
- ].join(" ")).join("\n");
4933
+ ].filter(Boolean).join(" ")).join("\n");
4215
4934
  }
4216
4935
  function renderBoardKnowledge(data) {
4217
4936
  const files = isJsonObject(data) && Array.isArray(data.knowledge_files)
@@ -4440,6 +5159,56 @@ function renderBuildWorkspaceSourcePatch(result) {
4440
5159
  }
4441
5160
  return lines.join("\n");
4442
5161
  }
5162
+ function renderLocalToolchainValidation(result) {
5163
+ const lines = [
5164
+ result.ok ? "Local toolchain ready." : "Local toolchain not ready.",
5165
+ `board=${result.board_id}`,
5166
+ `host=${result.host.platform}/${result.host.arch}`,
5167
+ `release_root=${result.release_root}`
5168
+ ];
5169
+ for (const check of result.checked_paths) {
5170
+ lines.push(`${check.exists ? "ok" : "missing"} ${check.label}: ${check.path}`);
5171
+ }
5172
+ if (result.notes.length > 0) {
5173
+ lines.push("notes:");
5174
+ for (const note of result.notes) {
5175
+ lines.push(` ${note}`);
5176
+ }
5177
+ }
5178
+ return lines.join("\n");
5179
+ }
5180
+ function renderLocalCompileResult(result) {
5181
+ const lines = [
5182
+ `${result.operation} succeeded.`,
5183
+ `board=${result.board_id}`,
5184
+ result.account_id ? `account=${result.account_id}` : "",
5185
+ `auth=${result.auth.authenticated ? "authenticated" : "anonymous"} profile=${result.auth.profile}${result.auth.source ? ` source=${result.auth.source}` : ""}`,
5186
+ `source=${result.source_path}`,
5187
+ result.build_dir ? `build_dir=${result.build_dir}` : "",
5188
+ `artifact=${result.artifact_path}`,
5189
+ `size=${result.artifact_size_bytes}`,
5190
+ `sha256=${result.artifact_sha256}`,
5191
+ result.file_info ? `file=${result.file_info}` : "",
5192
+ `manifest=${result.manifest_path}`
5193
+ ].filter(Boolean);
5194
+ for (const command of result.commands) {
5195
+ lines.push(`command_exit=${command.exit_code} cwd=${command.cwd}`);
5196
+ lines.push(` ${command.command.join(" ")}`);
5197
+ if (command.stdout_tail.length > 0) {
5198
+ lines.push(" stdout_tail:");
5199
+ for (const line of command.stdout_tail.slice(-12)) {
5200
+ lines.push(` ${line}`);
5201
+ }
5202
+ }
5203
+ if (command.stderr_tail.length > 0) {
5204
+ lines.push(" stderr_tail:");
5205
+ for (const line of command.stderr_tail.slice(-12)) {
5206
+ lines.push(` ${line}`);
5207
+ }
5208
+ }
5209
+ }
5210
+ return lines.join("\n");
5211
+ }
4443
5212
  function renderDoctor(result) {
4444
5213
  const lines = [
4445
5214
  `${result.status === "ready" ? "Ready" : "Not ready"}: ${result.summary_for_user}`,
@@ -4610,6 +5379,46 @@ function renderJob(job) {
4610
5379
  }
4611
5380
  return lines.join("\n");
4612
5381
  }
5382
+ function renderBootLogoPackageResult(result) {
5383
+ const lines = [renderJob(result.task)];
5384
+ if (result.downloaded_artifact) {
5385
+ lines.push("downloaded_package:");
5386
+ lines.push(` artifact=${result.downloaded_artifact.artifact_id}`);
5387
+ lines.push(` output=${result.downloaded_artifact.output_path}`);
5388
+ lines.push(` size=${result.downloaded_artifact.size_bytes}`);
5389
+ }
5390
+ lines.push("next=merge the downloaded package with the local base image, then flash only after explicit approval.");
5391
+ return lines.join("\n");
5392
+ }
5393
+ function renderDtbPackageResult(result) {
5394
+ const lines = [renderJob(result.task)];
5395
+ if (result.downloaded_artifact) {
5396
+ lines.push("downloaded_package:");
5397
+ lines.push(` artifact=${result.downloaded_artifact.artifact_id}`);
5398
+ lines.push(` output=${result.downloaded_artifact.output_path}`);
5399
+ lines.push(` size=${result.downloaded_artifact.size_bytes}`);
5400
+ }
5401
+ lines.push("next=merge the downloaded DTB package with the local base image, then flash only after explicit approval.");
5402
+ return lines.join("\n");
5403
+ }
5404
+ function renderBootLogoComposeResult(result) {
5405
+ const label = result.operation_kind === "image.dtb.compose" ? "DTB" : "Boot-logo";
5406
+ return [
5407
+ result.ready_for_flash ? `${label} compose ready for flash review.` : `${label} compose created a guarded local result.`,
5408
+ `board=${result.board_id}`,
5409
+ result.variant_id ? `variant=${result.variant_id}` : "",
5410
+ `package=${result.package_path}`,
5411
+ `base=${result.base_image_path}`,
5412
+ `output=${result.output_image_path}`,
5413
+ `manifest=${result.manifest_path}`,
5414
+ `strategy=${result.local_merge_strategy}`,
5415
+ `patches=${result.patch_operations_applied}/${result.patch_operations_total}`,
5416
+ `replacement_applied=${result.replacement_applied}`,
5417
+ `ready_for_flash=${result.ready_for_flash}`,
5418
+ `cross_platform=${result.cross_platform} platform=${result.platform}/${result.arch}`,
5419
+ result.summary_for_user
5420
+ ].filter(Boolean).join("\n");
5421
+ }
4613
5422
  function safeArtifactOutputFileName(name) {
4614
5423
  return name.replace(/[^A-Za-z0-9._-]+/g, "_") || "artifact.out";
4615
5424
  }
@@ -5089,7 +5898,7 @@ async function waitForever() {
5089
5898
  return 0;
5090
5899
  }
5091
5900
  function printHelp() {
5092
- console.log(`embed CLI
5901
+ printCliHelp(`embed CLI
5093
5902
 
5094
5903
  Usage:
5095
5904
  embed <command> [options]
@@ -5111,13 +5920,17 @@ Main workflow:
5111
5920
  embed model default
5112
5921
  4. Run a natural-language local tool loop:
5113
5922
  embed agent run --prompt "验证开发板状态"
5114
- 5. Pick a cloud build template:
5923
+ 5. Validate or use the local TaishanPi toolchain:
5924
+ embed local toolchain validate
5925
+ embed local compile taishanpi --source ./main.c --output ./.embed-labs/build/main
5926
+ embed local build qt-smoke --build-dir ./.embed-labs/build/qt-smoke
5927
+ 6. Pick a cloud build template:
5115
5928
  embed board registry list
5116
5929
  embed board methods taishanpi-1m-rk3566
5117
5930
  embed board knowledge taishanpi-1m-rk3566
5118
5931
  embed build template list
5119
5932
  embed build template show <template_id>
5120
- 6. Provision and populate a build workspace:
5933
+ 7. Provision and populate a build workspace:
5121
5934
  embed build workspace provision --account <account_id> --project <project_id> --template <template_id>
5122
5935
  embed build resource lease create --workspace <workspace_id> --execution-mode cloud_worker
5123
5936
  embed build workspace source put <workspace_id> --file ./main.c:src/main.c
@@ -5126,13 +5939,15 @@ Main workflow:
5126
5939
  embed build workspace source search <workspace_id> --query init --glob "**/*.c"
5127
5940
  embed build workspace source patch <workspace_id> --patch ./fix.patch
5128
5941
  embed build workspace release <workspace_id> --dry-run
5129
- 7. Generate application source on the server and follow artifacts:
5942
+ 8. Generate application source on the server and follow artifacts:
5130
5943
  embed build application generate --workspace <workspace_id> --prompt "Create a minimal Linux app" --provider bai --model gpt-5.2
5131
5944
  embed build application compile --workspace <workspace_id> --source app/generated.c --execution-mode docker_worker
5132
5945
  embed build image generate --workspace <workspace_id> --prompt "Generate a minimal TaishanPi image"
5946
+ embed build image boot-logo --logo ./logo.png --board taishanpi --variant 1M-RK3566 --output ./boot-logo-package.json
5947
+ embed image boot-logo compose --package ./boot-logo-package.json --base-image ./boot.img --output ./boot-logo.img
5133
5948
  embed cloud task artifacts <task_id>
5134
5949
  embed artifact download <artifact_id> --output ./artifact.bin
5135
- 8. Check credits or create a recharge QR:
5950
+ 9. Check credits or create a recharge QR:
5136
5951
  embed billing balance --account <account_id>
5137
5952
  embed billing tokens --account <account_id>
5138
5953
  embed billing ledger --account <account_id>
@@ -5147,7 +5962,14 @@ Local hardware:
5147
5962
  embed agent run --prompt "部署生成的泰山派应用" --host 198.19.77.2 --artifact-task <task_id> --remote-path /userdata/embed-labs/apps/app --approve --run
5148
5963
  embed tool list
5149
5964
  embed tool call device.probe --input-json '{"host":"198.19.77.2","ports":[22,15301]}'
5965
+ embed tool call wifi.scan --input-json '{"host":"198.19.77.2","user":"root"}'
5966
+ embed tool call chip.temperature --input-json '{"host":"198.19.77.2","user":"root"}'
5967
+ embed tool call qml.runtime.status --input-json '{"host":"198.19.77.2","user":"root","port":18130}'
5150
5968
  embed device list
5969
+ embed local toolchain validate
5970
+ embed local compile taishanpi --source ./main.c --output ./.embed-labs/build/main
5971
+ embed local build qt-smoke --build-dir ./.embed-labs/build/qt-smoke
5972
+ embed image boot-logo compose --package ./boot-logo-package.json --base-image ./boot.img --output ./boot-logo.img
5151
5973
  embed deploy taishanpi --host 198.19.77.2 --artifact ./artifact.bin --approve --run
5152
5974
  embed flash plan --board <rp2350|taishanpi>
5153
5975
 
@@ -5182,7 +6004,7 @@ function printHelpTopic(topic) {
5182
6004
  printCommandReference();
5183
6005
  return;
5184
6006
  }
5185
- console.log(`Unknown help topic: ${topic}
6007
+ printCliHelp(`Unknown help topic: ${topic}
5186
6008
 
5187
6009
  Available topics:
5188
6010
  embed help getting-started
@@ -5190,7 +6012,7 @@ Available topics:
5190
6012
  `);
5191
6013
  }
5192
6014
  function printGettingStartedHelp() {
5193
- console.log(`embed getting started
6015
+ printCliHelp(`embed getting started
5194
6016
 
5195
6017
  After npm install, use the installed embed binary directly:
5196
6018
 
@@ -5219,6 +6041,9 @@ Cloud build path:
5219
6041
  embed model list
5220
6042
  embed model default
5221
6043
  embed agent run --prompt "验证开发板状态"
6044
+ embed local toolchain validate
6045
+ embed local compile taishanpi --source ./main.c --output ./.embed-labs/build/main
6046
+ embed local build qt-smoke --build-dir ./.embed-labs/build/qt-smoke
5222
6047
  embed board registry list
5223
6048
  embed board registry show taishanpi-1m-rk3566
5224
6049
  embed board methods taishanpi-1m-rk3566
@@ -5236,6 +6061,8 @@ Cloud build path:
5236
6061
  embed build application generate --workspace <workspace_id> --prompt "Create a minimal Linux app" --provider bai --model gpt-5.2
5237
6062
  embed build application compile --workspace <workspace_id> --source app/generated.c --execution-mode docker_worker
5238
6063
  embed build image generate --workspace <workspace_id> --prompt "Generate a minimal TaishanPi image"
6064
+ embed build image boot-logo --logo ./logo.png --board taishanpi --variant 1M-RK3566 --output ./boot-logo-package.json
6065
+ embed image boot-logo compose --package ./boot-logo-package.json --base-image ./boot.img --output ./boot-logo.img
5239
6066
  embed cloud task status <task_id>
5240
6067
  embed cloud task artifacts <task_id>
5241
6068
  embed artifact download <artifact_id> --output ./artifact.bin
@@ -5273,7 +6100,7 @@ Use --json on commands when scripting or running in CI.
5273
6100
  `);
5274
6101
  }
5275
6102
  function printCommandReference() {
5276
- console.log(`embed CLI
6103
+ printCliHelp(`embed CLI
5277
6104
 
5278
6105
  Usage:
5279
6106
  embed doctor [--json]
@@ -5293,7 +6120,7 @@ Usage:
5293
6120
  embed board methods <template_id> [--json]
5294
6121
  embed board knowledge <template_id> [--json]
5295
6122
  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]
6123
+ 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
6124
  embed run <natural language request> [--provider stub|openai|bai|cc|claude-code] [--approve] [--json]
5298
6125
  embed tool list [--json]
5299
6126
  embed tool call <capability_id> [--input-json '<json>'] [--approve] [--json]
@@ -5338,7 +6165,19 @@ Usage:
5338
6165
  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
6166
  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
6167
  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]
6168
+ 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]
6169
+ embed image boot-logo compose --package <boot-logo-package.json> --base-image <boot.img|image.img> --output <image> [--manifest <manifest.json>] [--force] [--json]
6170
+ embed local toolchain validate [--release-root <path>] [--json]
6171
+ embed local compile taishanpi --source <main.c|main.cpp> --output <artifact> [--release-root <path>] [--account <account_id>] [--json]
6172
+ embed local build qt-smoke --build-dir <dir> [--source <qt-smoke-dir>] [--release-root <path>] [--account <account_id>] [--json]
5341
6173
  embed debug tools [--json]
6174
+ embed tool list [--json]
6175
+ embed tool call wifi.scan --input-json '{"host":"198.19.77.2","user":"root"}' [--json]
6176
+ embed tool call bluetooth.scan --input-json '{"host":"198.19.77.2","user":"root","duration_seconds":8}' [--json]
6177
+ embed tool call chip.cpu.frequency --input-json '{"host":"198.19.77.2","user":"root"}' [--json]
6178
+ embed tool call chip.temperature --input-json '{"host":"198.19.77.2","user":"root"}' [--json]
6179
+ embed tool call qml.runtime.status --input-json '{"host":"198.19.77.2","user":"root","port":18130}' [--json]
6180
+ embed tool call qml.runtime.start --input-json '{"host":"198.19.77.2","user":"root","port":18130}' [--json]
5342
6181
  embed deploy taishanpi --host <ip> --artifact <local_file> --approve [--remote-path /userdata/embed-labs/apps/app] [--run] [--json]
5343
6182
  embed board deploy taishanpi --host <ip> --artifact <local_file> --approve [--remote-path /userdata/embed-labs/apps/app] [--run] [--json]
5344
6183
  embed device list [--json]
@@ -5377,6 +6216,23 @@ Environment:
5377
6216
  CODEX_HOME=~/.codex
5378
6217
  `);
5379
6218
  }
6219
+ function printCliHelp(text) {
6220
+ console.log(formatCliHelp(text));
6221
+ }
6222
+ function formatCliHelp(text) {
6223
+ if (currentCommandName() !== "embedlabs") {
6224
+ return text;
6225
+ }
6226
+ return text
6227
+ .replace(/^embed CLI$/m, "embedlabs CLI")
6228
+ .replace(/^embed getting started$/m, "embedlabs getting started")
6229
+ .replace("After npm install, use the installed embed binary directly:", "After npm install, use the installed embedlabs binary directly:")
6230
+ .replace(/(^|\n)(\s*)embed(\s+)/g, "$1$2embedlabs$3");
6231
+ }
6232
+ function currentCommandName() {
6233
+ const invoked = basename(process.argv[1] ?? "");
6234
+ return invoked.startsWith("embedlabs") ? "embedlabs" : "embed";
6235
+ }
5380
6236
  main(process.argv.slice(2))
5381
6237
  .then((code) => {
5382
6238
  process.exitCode = code;