@kvell007/embed-labs-cli 0.1.0-alpha.2 → 0.1.0-alpha.20

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,12 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import { createHash } from "node:crypto";
2
+ import { createHash, createHmac, randomBytes } 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, readdir, 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";
8
9
  import { fileURLToPath } from "node:url";
9
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";
10
13
  import { fail, ok } from "@embed-labs/protocol";
11
14
  const require = createRequire(import.meta.url);
12
15
  const CLI_MODULE_DIR = dirname(fileURLToPath(import.meta.url));
@@ -15,6 +18,7 @@ const qrcodeTerminal = require("qrcode-terminal");
15
18
  const DEFAULT_BRIDGE_URL = process.env.EMBED_BRIDGE_URL ?? "http://127.0.0.1:18083";
16
19
  const DEFAULT_CLOUD_API_URL = process.env.EMBED_CLOUD_API_URL ?? "http://127.0.0.1:18100";
17
20
  const DEFAULT_AUTH_FILE = process.env.EMBED_AUTH_FILE ?? ".embed-labs/auth.json";
21
+ const DEFAULT_DASHBOARD_URL = process.env.EMBED_DASHBOARD_URL ?? "https://api.embedboard.com/dashboard";
18
22
  const DEFAULT_AGENT_ARTIFACT_DIR = process.env.EMBED_AGENT_ARTIFACT_DIR ?? ".embed-labs/artifacts";
19
23
  const DOCTOR_USAGE = "Usage: embed doctor [--json]";
20
24
  const DOCTOR_HTTP_TIMEOUT_MS = 8000;
@@ -24,6 +28,9 @@ const DEVICE_PROBE_USAGE = "Usage: embed device probe --host <host> --ports 22,1
24
28
  const QUERY_USAGE = "Usage: embed query <natural language request> [--account <account_id>] [--qr] [--json]";
25
29
  const DEFAULT_PLUGIN_RELEASE_URL = process.env.EMBED_PLUGIN_RELEASE_URL?.trim() || "https://api.embedboard.com/plugin-releases/agent-plugins/latest";
26
30
  const PLUGIN_LIST_USAGE = "Usage: embed plugin list [--release-dir <dir>] [--release-url <url>] [--json]";
31
+ const CODEX_PLUGIN_NAME = "embed-labs";
32
+ const CODEX_MARKETPLACE_NAME = "embed-labs";
33
+ const LEGACY_CODEX_MARKETPLACE_NAMES = new Set(["embed-labs-plugins"]);
27
34
  const PLUGIN_INSTALL_USAGE = "Usage: embed plugin install <codex|opencode|all> [--release-dir <dir>] [--release-url <url>] [--target <dir>] [--codex-target <dir>] [--opencode-target <dir>] [--force] [--json]";
28
35
  const CLOUD_TASK_ARTIFACTS_USAGE = "Usage: embed cloud task artifacts <task_id> [--json]";
29
36
  const CLOUD_TASK_EVIDENCE_USAGE = "Usage: embed cloud task evidence <task_id> [--json]";
@@ -74,6 +81,16 @@ const BUILD_APPLICATION_STUB_USAGE = "Usage: embed build application stub --work
74
81
  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]";
75
82
  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]";
76
83
  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]";
84
+ 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]";
85
+ 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]";
86
+ 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]";
87
+ 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]";
88
+ const LOCAL_TOOLCHAIN_LATEST_USAGE = "Usage: embed local toolchain latest [--board taishanpi-1m-rk3566] [--channel stable] [--metadata-root <path>] [--json]";
89
+ const LOCAL_TOOLCHAIN_CURRENT_USAGE = "Usage: embed local toolchain current [--install-root <path>] [--json]";
90
+ 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>] [--mode minimal|compile|qt|full|images] [--force] [--json]\nDefault source: the production download channel at download.embedboard.com.";
91
+ const LOCAL_TOOLCHAIN_VALIDATE_USAGE = "Usage: embed local toolchain validate [--release-root <path>] [--mode minimal|compile|qt|full|images] [--json]";
92
+ const LOCAL_COMPILE_TAISHANPI_USAGE = "Usage: embed local compile taishanpi --source <main.c|main.cpp> --output <artifact> [--release-root <path>] [--account <account_id>] [--json]";
93
+ 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]";
77
94
  const BOARD_REGISTRY_LIST_USAGE = "Usage: embed board registry list [--json]";
78
95
  const BOARD_REGISTRY_SHOW_USAGE = "Usage: embed board registry show <template_id> [--json]";
79
96
  const BOARD_METHODS_USAGE = "Usage: embed board methods <template_id> [--json]";
@@ -82,9 +99,10 @@ const BOARD_KNOWLEDGE_FILE_USAGE = "Usage: embed board knowledge file <template_
82
99
  const MODEL_LIST_USAGE = "Usage: embed model list [--json]";
83
100
  const MODEL_DEFAULT_USAGE = "Usage: embed model default [--json]";
84
101
  const SERVICE_MODES_USAGE = "Usage: embed service modes [--json]";
85
- 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]";
102
+ 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]";
86
103
  const TOOL_LIST_USAGE = "Usage: embed tool list [--json]";
87
104
  const TOOL_CALL_USAGE = "Usage: embed tool call <capability_id> [--input-json '<json>'] [--approve] [--json]";
105
+ 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]";
88
106
  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]";
89
107
  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]";
90
108
  const TASK_STATES = new Set([
@@ -354,6 +372,16 @@ async function main(argv) {
354
372
  USAGE_EVENTS_USAGE
355
373
  ].join("\n")), undefined, 2);
356
374
  }
375
+ if (area === "mcp") {
376
+ if (action === "log" || action === "tool-event") {
377
+ const body = mcpToolEventBody(parsed);
378
+ if (typeof body === "string") {
379
+ return output(parsed, fail("invalid_args", body), undefined, 2);
380
+ }
381
+ return output(parsed, await cloudPost("/v1/mcp/tool-events", body), renderMcpToolEvent);
382
+ }
383
+ return output(parsed, fail("invalid_args", MCP_TOOL_EVENT_USAGE), undefined, 2);
384
+ }
357
385
  if (area === "billing") {
358
386
  if (action === "statement") {
359
387
  const request = billingStatementRequest(parsed);
@@ -475,6 +503,75 @@ async function main(argv) {
475
503
  BILLING_SNAPSHOT_SHOW_USAGE
476
504
  ].join("\n")), undefined, 2);
477
505
  }
506
+ if (area === "image") {
507
+ if ((action === "boot-logo" && parsed.command[2] === "compose") || (action === "compose" && parsed.command[2] === "boot-logo")) {
508
+ const request = imageBootLogoComposeRequest(parsed, IMAGE_BOOT_LOGO_COMPOSE_USAGE);
509
+ if (typeof request === "string") {
510
+ return output(parsed, fail("invalid_args", request), undefined, 2);
511
+ }
512
+ return output(parsed, ok(await composeBootLogoPackage(request)), renderBootLogoComposeResult);
513
+ }
514
+ if ((action === "dtb" && parsed.command[2] === "compose") || (action === "compose" && parsed.command[2] === "dtb")) {
515
+ const request = imageBootLogoComposeRequest(parsed, IMAGE_DTB_COMPOSE_USAGE);
516
+ if (typeof request === "string") {
517
+ return output(parsed, fail("invalid_args", request), undefined, 2);
518
+ }
519
+ return output(parsed, ok(await composeBootLogoPackage(request)), renderBootLogoComposeResult);
520
+ }
521
+ return output(parsed, fail("invalid_args", [IMAGE_BOOT_LOGO_COMPOSE_USAGE, IMAGE_DTB_COMPOSE_USAGE].join("\n")), undefined, 2);
522
+ }
523
+ if (area === "local") {
524
+ if (action === "toolchain" && parsed.command[2] === "latest") {
525
+ const request = localToolchainLatestRequest(parsed);
526
+ if (typeof request === "string") {
527
+ return output(parsed, fail("invalid_args", request), undefined, 2);
528
+ }
529
+ return output(parsed, ok(await latestLocalToolchain(request)), renderLocalToolchainLatest);
530
+ }
531
+ if (action === "toolchain" && parsed.command[2] === "current") {
532
+ const request = localToolchainCurrentRequest(parsed);
533
+ if (typeof request === "string") {
534
+ return output(parsed, fail("invalid_args", request), undefined, 2);
535
+ }
536
+ return output(parsed, ok(await currentLocalToolchain(request.installRoot)), renderLocalToolchainCurrent);
537
+ }
538
+ if (action === "toolchain" && parsed.command[2] === "install") {
539
+ const request = localToolchainInstallRequest(parsed);
540
+ if (typeof request === "string") {
541
+ return output(parsed, fail("invalid_args", request), undefined, 2);
542
+ }
543
+ return output(parsed, ok(await installLocalToolchain(request)), renderLocalToolchainInstall);
544
+ }
545
+ if (action === "toolchain" && parsed.command[2] === "validate") {
546
+ const request = localToolchainValidateRequest(parsed);
547
+ if (typeof request === "string") {
548
+ return output(parsed, fail("invalid_args", request), undefined, 2);
549
+ }
550
+ return output(parsed, ok(await validateLocalToolchain(request)), renderLocalToolchainValidation);
551
+ }
552
+ if (action === "compile" && parsed.command[2] === "taishanpi") {
553
+ const request = localCompileTaishanPiRequest(parsed, await authStatus());
554
+ if (typeof request === "string") {
555
+ return output(parsed, fail("invalid_args", request), undefined, 2);
556
+ }
557
+ return output(parsed, ok(await compileTaishanPiSingleFile(request)), renderLocalCompileResult);
558
+ }
559
+ if (action === "build" && parsed.command[2] === "qt-smoke") {
560
+ const request = localBuildQtSmokeRequest(parsed, await authStatus());
561
+ if (typeof request === "string") {
562
+ return output(parsed, fail("invalid_args", request), undefined, 2);
563
+ }
564
+ return output(parsed, ok(await buildTaishanPiQtSmoke(request)), renderLocalCompileResult);
565
+ }
566
+ return output(parsed, fail("invalid_args", [
567
+ LOCAL_TOOLCHAIN_LATEST_USAGE,
568
+ LOCAL_TOOLCHAIN_CURRENT_USAGE,
569
+ LOCAL_TOOLCHAIN_INSTALL_USAGE,
570
+ LOCAL_TOOLCHAIN_VALIDATE_USAGE,
571
+ LOCAL_COMPILE_TAISHANPI_USAGE,
572
+ LOCAL_BUILD_QT_SMOKE_USAGE
573
+ ].join("\n")), undefined, 2);
574
+ }
478
575
  if (area === "build") {
479
576
  if (action === "template") {
480
577
  const templateAction = parsed.command[2];
@@ -620,6 +717,44 @@ async function main(argv) {
620
717
  }
621
718
  return output(parsed, await cloudPost("/v1/build/image-generate", body), renderJob);
622
719
  }
720
+ if (action === "image" && (parsed.command[2] === "boot-logo" || parsed.command[2] === "boot-logo-package")) {
721
+ const request = await buildImageBootLogoPackageRequest(parsed);
722
+ if (typeof request === "string") {
723
+ return output(parsed, fail("invalid_args", request), undefined, 2);
724
+ }
725
+ const created = await cloudPost("/v1/build/image/boot-logo-package", request.body);
726
+ if (!created.ok || !request.output_path) {
727
+ return output(parsed, created, renderJob, created.ok ? 0 : 2);
728
+ }
729
+ const packageArtifact = created.data.artifacts?.find((artifact) => artifact.kind === "patch" && artifact.name === "boot-logo-package.json");
730
+ if (!packageArtifact) {
731
+ return output(parsed, fail("artifact_not_found", "Boot-logo package task did not include boot-logo-package.json."), undefined, 2);
732
+ }
733
+ const downloaded = await cloudDownloadArtifact(packageArtifact.artifact_id, request.output_path);
734
+ if (!downloaded.ok) {
735
+ return output(parsed, downloaded, renderArtifactDownload, 2);
736
+ }
737
+ return output(parsed, ok({ task: created.data, downloaded_artifact: downloaded.data }), renderBootLogoPackageResult);
738
+ }
739
+ if (action === "image" && (parsed.command[2] === "dtb" || parsed.command[2] === "dtb-package")) {
740
+ const request = await buildImageDtbPackageRequest(parsed);
741
+ if (typeof request === "string") {
742
+ return output(parsed, fail("invalid_args", request), undefined, 2);
743
+ }
744
+ const created = await cloudPost("/v1/build/image/dtb-package", request.body);
745
+ if (!created.ok || !request.output_path) {
746
+ return output(parsed, created, renderJob, created.ok ? 0 : 2);
747
+ }
748
+ const packageArtifact = created.data.artifacts?.find((artifact) => artifact.kind === "patch" && artifact.name === "dtb-package.json");
749
+ if (!packageArtifact) {
750
+ return output(parsed, fail("artifact_not_found", "DTB package task did not include dtb-package.json."), undefined, 2);
751
+ }
752
+ const downloaded = await cloudDownloadArtifact(packageArtifact.artifact_id, request.output_path);
753
+ if (!downloaded.ok) {
754
+ return output(parsed, downloaded, renderArtifactDownload, 2);
755
+ }
756
+ return output(parsed, ok({ task: created.data, downloaded_artifact: downloaded.data }), renderDtbPackageResult);
757
+ }
623
758
  return output(parsed, fail("invalid_args", [
624
759
  BUILD_TEMPLATE_LIST_USAGE,
625
760
  BUILD_TEMPLATE_SHOW_USAGE,
@@ -636,7 +771,9 @@ async function main(argv) {
636
771
  BUILD_APPLICATION_STUB_USAGE,
637
772
  BUILD_APPLICATION_GENERATE_USAGE,
638
773
  BUILD_APPLICATION_COMPILE_USAGE,
639
- BUILD_IMAGE_GENERATE_USAGE
774
+ BUILD_IMAGE_GENERATE_USAGE,
775
+ BUILD_IMAGE_BOOT_LOGO_USAGE,
776
+ BUILD_IMAGE_DTB_USAGE
640
777
  ].join("\n")), undefined, 2);
641
778
  }
642
779
  if (area === "project" && action === "create") {
@@ -1067,7 +1204,8 @@ function authDoctorCheck(status) {
1067
1204
  : {
1068
1205
  code: "auth_not_ready",
1069
1206
  message: "No CLI auth token is configured.",
1070
- remediation: "Run: embed auth login --token <token>"
1207
+ remediation: cloudAuthSetupRemediation(),
1208
+ details: cloudAuthSetupDetails()
1071
1209
  }
1072
1210
  };
1073
1211
  }
@@ -1218,17 +1356,47 @@ function isApiResponse(value) {
1218
1356
  return isJsonObject(error) && typeof error.code === "string" && typeof error.message === "string";
1219
1357
  }
1220
1358
  async function bridgeGet(path) {
1221
- const response = await fetch(`${DEFAULT_BRIDGE_URL}${path}`);
1359
+ const response = await fetch(`${DEFAULT_BRIDGE_URL}${path}`, {
1360
+ headers: bridgeHeaders("GET", path, "")
1361
+ });
1222
1362
  return await response.json();
1223
1363
  }
1224
1364
  async function bridgePost(path, body) {
1365
+ const bodyText = JSON.stringify(body);
1225
1366
  const response = await fetch(`${DEFAULT_BRIDGE_URL}${path}`, {
1226
1367
  method: "POST",
1227
- headers: { "content-type": "application/json" },
1228
- body: JSON.stringify(body)
1368
+ headers: bridgeHeaders("POST", path, bodyText, { "content-type": "application/json" }),
1369
+ body: bodyText
1229
1370
  });
1230
1371
  return await response.json();
1231
1372
  }
1373
+ function bridgeHeaders(method, path, bodyText, base = {}) {
1374
+ const token = process.env.EMBED_BRIDGE_TOKEN?.trim();
1375
+ if (!token) {
1376
+ return base;
1377
+ }
1378
+ const headers = {
1379
+ ...base,
1380
+ authorization: `Bearer ${token}`
1381
+ };
1382
+ addBridgeRequestSignature(headers, method, path, bodyText, token);
1383
+ return headers;
1384
+ }
1385
+ function addBridgeRequestSignature(headers, method, pathWithQuery, bodyText, token) {
1386
+ if (process.env.EMBED_BRIDGE_SIGNING === "0") {
1387
+ return;
1388
+ }
1389
+ const timestamp = String(Math.floor(Date.now() / 1000));
1390
+ const nonce = randomBytes(16).toString("hex");
1391
+ const bodySha256 = createHash("sha256").update(bodyText).digest("hex");
1392
+ const keyId = createHash("sha256").update(token).digest("hex").slice(0, 16);
1393
+ const canonical = cloudRequestCanonicalString(method, pathWithQuery, timestamp, nonce, bodySha256);
1394
+ headers["x-embed-key-id"] = keyId;
1395
+ headers["x-embed-timestamp"] = timestamp;
1396
+ headers["x-embed-nonce"] = nonce;
1397
+ headers["x-embed-body-sha256"] = bodySha256;
1398
+ headers["x-embed-signature"] = createHmac("sha256", token).update(canonical).digest("hex");
1399
+ }
1232
1400
  async function cloudGet(path) {
1233
1401
  return await cloudRequest("GET", path);
1234
1402
  }
@@ -1241,13 +1409,14 @@ async function cloudDownloadArtifact(artifactId, outputPath) {
1241
1409
  const token = await cloudAuthToken();
1242
1410
  if (token) {
1243
1411
  headers.authorization = `Bearer ${token}`;
1412
+ addCloudRequestSignature(headers, "GET", `/v1/artifacts/${encodeURIComponent(artifactId)}/download`, "", token);
1244
1413
  }
1245
1414
  const response = await fetch(`${serviceBaseUrl(DEFAULT_CLOUD_API_URL)}/v1/artifacts/${encodeURIComponent(artifactId)}/download`, {
1246
1415
  headers: Object.keys(headers).length > 0 ? headers : undefined
1247
1416
  });
1248
1417
  if (!response.ok) {
1249
1418
  const parsed = await parseErrorResponse(response);
1250
- return parsed ?? fail("artifact_download_failed", `Artifact download failed with HTTP ${response.status}.`);
1419
+ return parsed ? enrichCloudAuthFailure(parsed, Boolean(token)) : fail("artifact_download_failed", `Artifact download failed with HTTP ${response.status}.`);
1251
1420
  }
1252
1421
  const bytes = Buffer.from(await response.arrayBuffer());
1253
1422
  const expectedSha256 = response.headers.get("x-embed-artifact-sha256")?.trim();
@@ -1276,19 +1445,22 @@ async function cloudDownloadArtifact(artifactId, outputPath) {
1276
1445
  async function cloudRequest(method, path, body) {
1277
1446
  try {
1278
1447
  const headers = {};
1279
- if (body !== undefined) {
1448
+ const bodyText = body === undefined ? "" : JSON.stringify(body);
1449
+ if (bodyText) {
1280
1450
  headers["content-type"] = "application/json";
1281
1451
  }
1282
1452
  const token = await cloudAuthToken();
1283
1453
  if (token) {
1284
1454
  headers.authorization = `Bearer ${token}`;
1455
+ addCloudRequestSignature(headers, method, path, bodyText, token);
1285
1456
  }
1286
1457
  const response = await fetch(`${serviceBaseUrl(DEFAULT_CLOUD_API_URL)}${path}`, {
1287
1458
  method,
1288
1459
  headers: Object.keys(headers).length > 0 ? headers : undefined,
1289
- body: body === undefined ? undefined : JSON.stringify(body)
1460
+ body: body === undefined ? undefined : bodyText
1290
1461
  });
1291
- return await response.json();
1462
+ const parsed = await response.json();
1463
+ return enrichCloudAuthFailure(parsed, Boolean(token));
1292
1464
  }
1293
1465
  catch (error) {
1294
1466
  return fail("cloud_api_unreachable", error instanceof Error ? error.message : String(error), {
@@ -1296,6 +1468,72 @@ async function cloudRequest(method, path, body) {
1296
1468
  });
1297
1469
  }
1298
1470
  }
1471
+ function enrichCloudAuthFailure(response, hadToken) {
1472
+ if (response.ok || response.error.code !== "unauthorized") {
1473
+ return response;
1474
+ }
1475
+ if (!hadToken) {
1476
+ return fail("auth_token_missing", "Embed Labs API Token is not configured. Register or sign in, create an API Token, then configure it locally before using cloud and plugin services.", {
1477
+ remediation: cloudAuthSetupRemediation(),
1478
+ details: cloudAuthSetupDetails()
1479
+ });
1480
+ }
1481
+ return fail("auth_token_rejected", "The configured Embed Labs API Token was rejected by the server. Recreate or copy a fresh token from the dashboard and sign in again.", {
1482
+ remediation: cloudAuthSetupRemediation(),
1483
+ details: cloudAuthSetupDetails()
1484
+ });
1485
+ }
1486
+ function cloudAuthSetupRemediation() {
1487
+ return [
1488
+ `1. Open ${DEFAULT_DASHBOARD_URL} and register or sign in.`,
1489
+ "2. Create or copy your Embed Labs API Token from the user dashboard.",
1490
+ "3. Run: embedlabs auth login --token <your_token>",
1491
+ "4. For automation, set: EMBED_API_TOKEN=<your_token>",
1492
+ "5. Verify with: embedlabs auth status"
1493
+ ].join("\n");
1494
+ }
1495
+ function cloudAuthSetupDetails() {
1496
+ return {
1497
+ dashboard_url: DEFAULT_DASHBOARD_URL,
1498
+ login_command: "embedlabs auth login --token <your_token>",
1499
+ env_var: "EMBED_API_TOKEN",
1500
+ auth_status_command: "embedlabs auth status",
1501
+ auth_file: DEFAULT_AUTH_FILE
1502
+ };
1503
+ }
1504
+ function addCloudRequestSignature(headers, method, pathWithQuery, bodyText, token) {
1505
+ if (process.env.EMBED_CLOUD_API_SIGNING === "0") {
1506
+ return;
1507
+ }
1508
+ const timestamp = String(Math.floor(Date.now() / 1000));
1509
+ const nonce = randomBytes(16).toString("hex");
1510
+ const bodySha256 = createHash("sha256").update(bodyText).digest("hex");
1511
+ const keyId = createHash("sha256").update(token).digest("hex").slice(0, 16);
1512
+ const canonical = cloudRequestCanonicalString(method, pathWithQuery, timestamp, nonce, bodySha256);
1513
+ headers["x-embed-key-id"] = keyId;
1514
+ headers["x-embed-timestamp"] = timestamp;
1515
+ headers["x-embed-nonce"] = nonce;
1516
+ headers["x-embed-body-sha256"] = bodySha256;
1517
+ headers["x-embed-signature"] = createHmac("sha256", token).update(canonical).digest("hex");
1518
+ }
1519
+ function cloudRequestCanonicalString(method, pathWithQuery, timestamp, nonce, bodySha256) {
1520
+ return [
1521
+ method.toUpperCase(),
1522
+ normalizeCloudPathForSignature(pathWithQuery),
1523
+ timestamp,
1524
+ nonce,
1525
+ bodySha256
1526
+ ].join("\n");
1527
+ }
1528
+ function normalizeCloudPathForSignature(pathWithQuery) {
1529
+ try {
1530
+ const parsed = new URL(pathWithQuery, "http://embed.local");
1531
+ return `${parsed.pathname}${parsed.search}`;
1532
+ }
1533
+ catch {
1534
+ return pathWithQuery.startsWith("/") ? pathWithQuery : `/${pathWithQuery}`;
1535
+ }
1536
+ }
1299
1537
  async function pluginList(parsed) {
1300
1538
  const releaseDir = stringFlag(parsed, "release-dir");
1301
1539
  const manifest = releaseDir ? await readPluginReleaseManifest(releaseDir) : undefined;
@@ -1307,16 +1545,16 @@ async function pluginList(parsed) {
1307
1545
  return remoteManifest;
1308
1546
  }
1309
1547
  const effectiveManifest = manifest?.data ?? remoteManifest?.data;
1310
- const codexPackage = manifest?.data.packages?.find((item) => item.id === "codex-dbt-agent");
1311
- const opencodePackage = manifest?.data.packages?.find((item) => item.id === "opencode-dbt-agent");
1312
- const effectiveCodexPackage = effectiveManifest?.packages?.find((item) => item.id === "codex-dbt-agent") ?? codexPackage;
1313
- const effectiveOpenCodePackage = effectiveManifest?.packages?.find((item) => item.id === "opencode-dbt-agent") ?? opencodePackage;
1548
+ const codexPackage = manifest?.data.packages?.find((item) => item.id === "codex-embed-labs");
1549
+ const opencodePackage = manifest?.data.packages?.find((item) => item.id === "opencode-embed-labs");
1550
+ const effectiveCodexPackage = effectiveManifest?.packages?.find((item) => item.id === "codex-embed-labs") ?? codexPackage;
1551
+ const effectiveOpenCodePackage = effectiveManifest?.packages?.find((item) => item.id === "opencode-embed-labs") ?? opencodePackage;
1314
1552
  const source = releaseDir || remoteManifest ? "release_dir" : "source_checkout";
1315
1553
  return ok({
1316
1554
  plugins: [
1317
1555
  {
1318
1556
  id: "codex",
1319
- display_name: "Embed Labs Cloud Codex plugin",
1557
+ display_name: "Embed Labs Codex plugin",
1320
1558
  source,
1321
1559
  version: effectiveCodexPackage?.version ?? effectiveManifest?.version ?? await localPluginVersion("codex"),
1322
1560
  release_file: effectiveCodexPackage?.file,
@@ -1325,7 +1563,7 @@ async function pluginList(parsed) {
1325
1563
  },
1326
1564
  {
1327
1565
  id: "opencode",
1328
- display_name: "Embed Labs Cloud OpenCode plugin",
1566
+ display_name: "Embed Labs OpenCode plugin",
1329
1567
  source,
1330
1568
  version: effectiveOpenCodePackage?.version ?? effectiveManifest?.version ?? await localPluginVersion("opencode"),
1331
1569
  release_file: effectiveOpenCodePackage?.file,
@@ -1401,7 +1639,8 @@ async function installCodexPlugin(parsed, context) {
1401
1639
  return source;
1402
1640
  }
1403
1641
  const targetRoot = codexPluginTargetRoot(parsed, context.installingAll);
1404
- const targetPath = join(targetRoot, "dbt-agent");
1642
+ const targetPath = join(targetRoot, CODEX_PLUGIN_NAME);
1643
+ const legacyCleanup = await cleanupLegacyCodexPluginRemnants(targetRoot);
1405
1644
  if (await pathExists(targetPath) && !booleanFlag(parsed, "force")) {
1406
1645
  return fail("plugin_already_installed", `Codex plugin already exists at ${targetPath}.`, {
1407
1646
  remediation: "Pass --force to replace it, or pass --codex-target/--target to install into a different directory."
@@ -1410,12 +1649,25 @@ async function installCodexPlugin(parsed, context) {
1410
1649
  await rm(targetPath, { recursive: true, force: true });
1411
1650
  await mkdir(targetRoot, { recursive: true });
1412
1651
  await cp(source.data.sourcePath, targetPath, { recursive: true });
1652
+ const mcpRegistration = await maybeRegisterCodexMcp(parsed, targetRoot, targetPath);
1653
+ const marketplaceRegistration = await maybeRegisterCodexMarketplace(parsed, targetRoot, targetPath);
1413
1654
  return ok({
1414
1655
  id: "codex",
1415
1656
  target_path: targetPath,
1416
1657
  source: source.data.sourceLabel,
1417
1658
  version: source.data.version,
1418
- command_hint: "Restart Codex or reload plugins after installing."
1659
+ command_hint: mcpRegistration.registered
1660
+ ? (marketplaceRegistration.registered
1661
+ ? "Codex MCP and plugin marketplace entry were registered. Fully restart Codex to reload @Embed Labs."
1662
+ : "Codex MCP was registered. Start a new Codex session to reload tools.")
1663
+ : mcpRegistration.hint,
1664
+ warning: legacyCodexCleanupWarning(legacyCleanup),
1665
+ mcp_registered: mcpRegistration.registered,
1666
+ mcp_warning: mcpRegistration.warning,
1667
+ marketplace_registered: marketplaceRegistration.registered,
1668
+ marketplace_path: marketplaceRegistration.marketplacePath,
1669
+ marketplace_warning: marketplaceRegistration.warning,
1670
+ cleanup: legacyCleanup
1419
1671
  });
1420
1672
  }
1421
1673
  async function installOpenCodePlugin(parsed, context) {
@@ -1424,8 +1676,10 @@ async function installOpenCodePlugin(parsed, context) {
1424
1676
  return source;
1425
1677
  }
1426
1678
  const targetRoot = openCodePluginTargetRoot(parsed, context.installingAll);
1427
- const wrapperPath = join(targetRoot, "plugins", "development-board-toolchain.js");
1428
- if (await pathExists(wrapperPath) && !booleanFlag(parsed, "force")) {
1679
+ const globalInstall = isGlobalOpenCodeRoot(targetRoot);
1680
+ const wrapperPath = join(targetRoot, "plugins", "embed-labs.js");
1681
+ const legacyCleanup = await cleanupLegacyOpenCodePluginRemnants(targetRoot, globalInstall);
1682
+ if (!globalInstall && await pathExists(wrapperPath) && !booleanFlag(parsed, "force")) {
1429
1683
  return fail("plugin_already_installed", `OpenCode plugin wrapper already exists at ${wrapperPath}.`, {
1430
1684
  remediation: "Pass --force to replace it, or pass --opencode-target/--target to install into a different directory."
1431
1685
  });
@@ -1451,20 +1705,32 @@ async function installOpenCodePlugin(parsed, context) {
1451
1705
  });
1452
1706
  }
1453
1707
  await ensureOpenCodeInstallPackageJson(targetRoot);
1454
- await writeFile(wrapperPath, `export { default, DevelopmentBoardToolchainPlugin } from "dbt-agent";\n`, "utf8");
1708
+ if (globalInstall) {
1709
+ await rm(wrapperPath, { force: true });
1710
+ legacyCleanup.legacy_removed_config_entries?.push(...await ensureOpenCodeGlobalPluginConfig());
1711
+ }
1712
+ else {
1713
+ await writeFile(wrapperPath, `export { default, DevelopmentBoardToolchainPlugin } from "embed-labs";\n`, "utf8");
1714
+ }
1715
+ const duplicateWarning = await openCodeDuplicatePluginWarning(targetRoot);
1716
+ const cleanupWarning = legacyOpenCodeCleanupWarning(legacyCleanup);
1455
1717
  return ok({
1456
1718
  id: "opencode",
1457
1719
  target_path: targetRoot,
1458
1720
  source: source.data.sourceLabel,
1459
1721
  version: source.data.version,
1460
- command_hint: "Start OpenCode from the project containing this .opencode directory."
1722
+ command_hint: globalInstall
1723
+ ? "Restart OpenCode so the global embed-labs package plugin is reloaded."
1724
+ : "Start OpenCode from the project containing this .opencode directory.",
1725
+ warning: combineWarnings(cleanupWarning, duplicateWarning),
1726
+ cleanup: legacyCleanup
1461
1727
  });
1462
1728
  }
1463
1729
  async function resolveCodexPluginSource(context) {
1464
1730
  if (context.releaseDir) {
1465
- const item = context.manifest?.packages?.find((entry) => entry.id === "codex-dbt-agent");
1731
+ const item = context.manifest?.packages?.find((entry) => entry.id === "codex-embed-labs");
1466
1732
  if (!item?.file) {
1467
- return fail("plugin_release_not_found", `Release manifest in ${context.releaseDir} does not include codex-dbt-agent.`);
1733
+ return fail("plugin_release_not_found", `Release manifest in ${context.releaseDir} does not include codex-embed-labs.`);
1468
1734
  }
1469
1735
  const tarball = resolve(context.releaseDir, item.file);
1470
1736
  const verified = await verifyReleasePackage(tarball, item);
@@ -1479,13 +1745,13 @@ async function resolveCodexPluginSource(context) {
1479
1745
  details: { exit_code: tarResult.code, stderr_tail: tarResult.stderr.split("\n").slice(-20) }
1480
1746
  });
1481
1747
  }
1482
- const sourcePath = join(extractDir, "codex_plugin", "plugins", "dbt-agent");
1748
+ const sourcePath = join(extractDir, "codex_plugin", "plugins", "embed-labs");
1483
1749
  if (!await pathExists(join(sourcePath, ".codex-plugin", "plugin.json"))) {
1484
- return fail("plugin_release_invalid", `Codex plugin release ${tarball} did not contain plugins/dbt-agent/.codex-plugin/plugin.json.`);
1750
+ return fail("plugin_release_invalid", `Codex plugin release ${tarball} did not contain plugins/embed-labs/.codex-plugin/plugin.json.`);
1485
1751
  }
1486
1752
  return ok({ sourcePath, sourceLabel: tarball, version: item.version ?? context.manifest?.version });
1487
1753
  }
1488
- const sourcePath = sourceCheckoutPath("platform_plugins", "codex_plugin", "plugins", "dbt-agent");
1754
+ const sourcePath = sourceCheckoutPath("platform_plugins", "codex_plugin", "plugins", "embed-labs");
1489
1755
  if (!await pathExists(join(sourcePath, ".codex-plugin", "plugin.json"))) {
1490
1756
  return fail("plugin_source_not_found", "Could not find Codex plugin source in this checkout.", {
1491
1757
  remediation: "Run from the Embed-Labs-Cloud repo root or pass --release-dir pointing to a plugin release directory."
@@ -1495,9 +1761,9 @@ async function resolveCodexPluginSource(context) {
1495
1761
  }
1496
1762
  async function resolveOpenCodePluginSource(context) {
1497
1763
  if (context.releaseDir) {
1498
- const item = context.manifest?.packages?.find((entry) => entry.id === "opencode-dbt-agent");
1764
+ const item = context.manifest?.packages?.find((entry) => entry.id === "opencode-embed-labs");
1499
1765
  if (!item?.file) {
1500
- return fail("plugin_release_not_found", `Release manifest in ${context.releaseDir} does not include opencode-dbt-agent.`);
1766
+ return fail("plugin_release_not_found", `Release manifest in ${context.releaseDir} does not include opencode-embed-labs.`);
1501
1767
  }
1502
1768
  const tarball = resolve(context.releaseDir, item.file);
1503
1769
  const verified = await verifyReleasePackage(tarball, item);
@@ -1512,7 +1778,32 @@ async function resolveOpenCodePluginSource(context) {
1512
1778
  remediation: "Run from the Embed-Labs-Cloud repo root or pass --release-dir pointing to a plugin release directory."
1513
1779
  });
1514
1780
  }
1515
- return ok({ packagePath, sourceLabel: packagePath, version: await localPluginVersion("opencode") });
1781
+ const packed = await runLocalProcess("npm", ["pack", packagePath, "--pack-destination", context.tempDir, "--json"]);
1782
+ if (packed.code !== 0) {
1783
+ return fail("opencode_plugin_pack_failed", "npm pack failed while preparing the OpenCode plugin source package.", {
1784
+ details: {
1785
+ exit_code: packed.code,
1786
+ stdout_tail: packed.stdout.split("\n").slice(-20),
1787
+ stderr_tail: packed.stderr.split("\n").slice(-20)
1788
+ }
1789
+ });
1790
+ }
1791
+ let tarballName = "";
1792
+ try {
1793
+ const parsed = JSON.parse(packed.stdout);
1794
+ tarballName = basename(parsed[0]?.filename || "");
1795
+ }
1796
+ catch {
1797
+ tarballName = "";
1798
+ }
1799
+ if (!tarballName) {
1800
+ return fail("opencode_plugin_pack_failed", "npm pack did not report an OpenCode plugin tarball filename.");
1801
+ }
1802
+ const tarballPath = join(context.tempDir, tarballName);
1803
+ if (!await pathExists(tarballPath)) {
1804
+ return fail("opencode_plugin_pack_failed", `npm pack reported ${tarballName}, but the tarball was not found.`);
1805
+ }
1806
+ return ok({ packagePath: tarballPath, sourceLabel: `${packagePath} -> ${tarballPath}`, version: await localPluginVersion("opencode") });
1516
1807
  }
1517
1808
  async function fetchRemotePluginManifest(parsed) {
1518
1809
  const manifestUrl = `${pluginReleaseBaseUrl(parsed)}/manifest.json`;
@@ -1646,14 +1937,679 @@ function openCodePluginTargetRoot(parsed, installingAll) {
1646
1937
  return resolve(target ?? defaultOpenCodeRoot());
1647
1938
  }
1648
1939
  function defaultCodexPluginRoot() {
1649
- return join(process.env.CODEX_HOME?.trim() || join(homedir(), ".codex"), "plugins");
1940
+ return join(defaultCodexHome(), "plugins");
1941
+ }
1942
+ function defaultCodexHome() {
1943
+ return resolve(process.env.CODEX_HOME?.trim() || join(homedir(), ".codex"));
1944
+ }
1945
+ function codexConfigPath() {
1946
+ return join(defaultCodexHome(), "config.toml");
1947
+ }
1948
+ async function cleanupLegacyCodexPluginRemnants(targetRoot) {
1949
+ const removedPaths = [];
1950
+ const removedConfigTables = [];
1951
+ const warnings = [];
1952
+ const stoppedProcesses = await stopLegacyCodexPluginProcesses(warnings);
1953
+ const legacyPaths = [
1954
+ join(targetRoot, "dbt-agent"),
1955
+ join(targetRoot, "development-board-toolchain"),
1956
+ join(targetRoot, "cache", "embed-labs", "dbt-agent"),
1957
+ join(targetRoot, "cache", "dbt-agent"),
1958
+ join(targetRoot, "cache", "plugins", "dbt-agent")
1959
+ ];
1960
+ legacyPaths.push(...await discoverLegacyCodexCachePaths(targetRoot));
1961
+ if (resolve(targetRoot) === resolve(defaultCodexPluginRoot())) {
1962
+ legacyPaths.push(join(defaultCodexHome(), ".tmp", "plugins"), join(defaultCodexHome(), ".tmp", "plugins.sha"), join(defaultCodexHome(), "ambient-suggestions"), join(defaultCodexHome(), "skills", "dbt-agent"), join(defaultCodexHome(), "skills", "development-board-toolchain-dev"), join(defaultCodexHome(), "memories", "skills", "dbt-agent-live-board-ops"), join(defaultCodexHome(), "memories", "skills", "dbt-agent-platform-plugin-maintenance"));
1963
+ legacyPaths.push(...await discoverLegacyHomeAgentsMarketplacePaths(warnings), ...legacyDevelopmentBoardRuntimePluginPaths());
1964
+ }
1965
+ for (const candidate of legacyPaths) {
1966
+ try {
1967
+ if (await pathExists(candidate)) {
1968
+ await rm(candidate, { recursive: true, force: true });
1969
+ removedPaths.push(candidate);
1970
+ }
1971
+ }
1972
+ catch (error) {
1973
+ warnings.push(`Could not remove ${candidate}: ${error instanceof Error ? error.message : String(error)}`);
1974
+ }
1975
+ }
1976
+ if (resolve(targetRoot) === resolve(defaultCodexPluginRoot())) {
1977
+ const configPath = codexConfigPath();
1978
+ try {
1979
+ if (await pathExists(configPath)) {
1980
+ const current = await readFile(configPath, "utf8");
1981
+ const updated = removeLegacyCodexConfigTables(current);
1982
+ if (updated.text !== current) {
1983
+ await writeFile(configPath, updated.text, "utf8");
1984
+ }
1985
+ removedConfigTables.push(...updated.removedTables);
1986
+ }
1987
+ }
1988
+ catch (error) {
1989
+ warnings.push(`Could not update ${configPath}: ${error instanceof Error ? error.message : String(error)}`);
1990
+ }
1991
+ }
1992
+ const removedHistoryEntries = await cleanupLegacyCodexTextState(warnings);
1993
+ return {
1994
+ legacy_removed_paths: Array.from(new Set(removedPaths)),
1995
+ legacy_removed_config_tables: removedConfigTables,
1996
+ legacy_removed_history_entries: removedHistoryEntries,
1997
+ legacy_stopped_processes: stoppedProcesses,
1998
+ ...(warnings.length > 0 ? { warnings } : {})
1999
+ };
2000
+ }
2001
+ async function stopLegacyCodexPluginProcesses(warnings) {
2002
+ if (process.platform === "win32")
2003
+ return 0;
2004
+ try {
2005
+ const ps = await runLocalProcess("ps", ["-axo", "pid=,command="]);
2006
+ if (ps.code !== 0)
2007
+ return 0;
2008
+ let stopped = 0;
2009
+ for (const line of ps.stdout.split("\n")) {
2010
+ const match = /^\s*(\d+)\s+(.+)$/.exec(line);
2011
+ if (!match)
2012
+ continue;
2013
+ const pid = Number(match[1]);
2014
+ const command = match[2] || "";
2015
+ if (!isLegacyCodexPluginProcess(command))
2016
+ continue;
2017
+ try {
2018
+ process.kill(pid, "SIGTERM");
2019
+ stopped += 1;
2020
+ }
2021
+ catch (error) {
2022
+ warnings.push(`Could not stop legacy Codex plugin process ${pid}: ${error instanceof Error ? error.message : String(error)}`);
2023
+ }
2024
+ }
2025
+ return stopped;
2026
+ }
2027
+ catch (error) {
2028
+ warnings.push(`Could not scan legacy Codex plugin processes: ${error instanceof Error ? error.message : String(error)}`);
2029
+ return 0;
2030
+ }
2031
+ }
2032
+ function isLegacyCodexPluginProcess(command) {
2033
+ const trimmed = command.trim();
2034
+ return /^\/.*\/dbt-agent-mcp-bridge(?:\s|$)/.test(trimmed)
2035
+ || /^dbt-agent-mcp-bridge(?:\s|$)/.test(trimmed)
2036
+ || legacyDevelopmentBoardRuntimeProcessPatterns().some((pattern) => pattern.test(trimmed));
2037
+ }
2038
+ function legacyDevelopmentBoardRuntimeProcessPatterns() {
2039
+ const home = escapeRegExp(homedir());
2040
+ return [
2041
+ new RegExp(`^${home}/Library/development-board-toolchain/agent/bin/dbt-agentd(?:\\s|$)`),
2042
+ new RegExp(`^${home}/Library/Application Support/development-board-toolchain/agent/bin/dbt-agentd(?:\\s|$)`),
2043
+ new RegExp(`^${home}/Library/development-board-toolchain/runtime/dbtctl\\s+status(?:\\s|$)`),
2044
+ new RegExp(`^${home}/Library/Application Support/development-board-toolchain/runtime/dbtctl\\s+status(?:\\s|$)`),
2045
+ new RegExp(`^${home}/.*?/DBT-Agent\\.app/Contents/MacOS/DBT-Agent(?:\\s|$)`)
2046
+ ];
2047
+ }
2048
+ function legacyDevelopmentBoardRuntimePluginPaths() {
2049
+ return [
2050
+ join(homedir(), "Library", "development-board-toolchain", "runtime", "editor_plugins"),
2051
+ join(homedir(), "Library", "development-board-toolchain", "runtime", "opencode_plugin"),
2052
+ join(homedir(), "Library", "Application Support", "development-board-toolchain", "runtime", "editor_plugins"),
2053
+ join(homedir(), "Library", "Application Support", "development-board-toolchain", "runtime", "opencode_plugin")
2054
+ ];
2055
+ }
2056
+ async function discoverLegacyHomeAgentsMarketplacePaths(warnings) {
2057
+ const paths = [];
2058
+ const pluginRoot = join(homedir(), ".agents", "plugins");
2059
+ try {
2060
+ const entries = await readdir(pluginRoot, { withFileTypes: true });
2061
+ for (const entry of entries) {
2062
+ if (!entry.isFile() || !entry.name.startsWith("marketplace.json"))
2063
+ continue;
2064
+ const filePath = join(pluginRoot, entry.name);
2065
+ try {
2066
+ const current = await readFile(filePath, "utf8");
2067
+ if (isLegacyHomeAgentsMarketplace(current)) {
2068
+ paths.push(filePath);
2069
+ }
2070
+ }
2071
+ catch (error) {
2072
+ warnings.push(`Could not inspect legacy Codex home marketplace ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
2073
+ }
2074
+ }
2075
+ }
2076
+ catch {
2077
+ return paths;
2078
+ }
2079
+ return paths;
2080
+ }
2081
+ function isLegacyHomeAgentsMarketplace(text) {
2082
+ return text.includes('"name" : "plugins"') || text.includes('"name": "plugins"')
2083
+ ? text.includes('"dbt-agent"') || text.includes('"./.codex/plugins/dbt-agent"') || text.includes('"./plugins/dbt-agent"')
2084
+ : false;
2085
+ }
2086
+ async function discoverLegacyCodexCachePaths(targetRoot) {
2087
+ const paths = [];
2088
+ const cacheRoot = join(targetRoot, "cache");
2089
+ try {
2090
+ const marketplaces = await readdir(cacheRoot, { withFileTypes: true });
2091
+ for (const entry of marketplaces) {
2092
+ if (!entry.isDirectory())
2093
+ continue;
2094
+ paths.push(join(cacheRoot, entry.name, "dbt-agent"));
2095
+ paths.push(join(cacheRoot, entry.name, "development-board-toolchain"));
2096
+ }
2097
+ }
2098
+ catch {
2099
+ return paths;
2100
+ }
2101
+ return paths;
2102
+ }
2103
+ async function cleanupLegacyCodexTextState(warnings) {
2104
+ let removed = 0;
2105
+ removed += await cleanupLegacyCodexTextFile(join(defaultCodexHome(), "history.jsonl"), warnings);
2106
+ removed += await cleanupLegacyCodexTextFile(join(defaultCodexHome(), "session_index.jsonl"), warnings);
2107
+ removed += await cleanupLegacyCodexTextFile(join(defaultCodexHome(), "rules", "default.rules"), warnings);
2108
+ return removed;
2109
+ }
2110
+ async function cleanupLegacyCodexTextFile(filePath, warnings) {
2111
+ try {
2112
+ if (!await pathExists(filePath))
2113
+ return 0;
2114
+ const current = await readFile(filePath, "utf8");
2115
+ const lines = current.split("\n");
2116
+ let removed = 0;
2117
+ const kept = lines.filter((line) => {
2118
+ if (!line)
2119
+ return true;
2120
+ if (isLegacyCodexHistoryMention(line)) {
2121
+ removed += 1;
2122
+ return false;
2123
+ }
2124
+ return true;
2125
+ });
2126
+ if (removed > 0) {
2127
+ await writeFile(filePath, kept.join("\n"), "utf8");
2128
+ }
2129
+ return removed;
2130
+ }
2131
+ catch (error) {
2132
+ warnings.push(`Could not clean Codex legacy text state ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
2133
+ return 0;
2134
+ }
2135
+ }
2136
+ function isLegacyCodexHistoryMention(line) {
2137
+ return line.includes("plugin://dbt-agent@plugins")
2138
+ || line.includes("plugin://dbt-agent@embed-labs")
2139
+ || line.includes("development-board-toolchain-dev")
2140
+ || line.includes("development-board-toolchain")
2141
+ || /dbt-agent/i.test(line);
2142
+ }
2143
+ function removeLegacyCodexConfigTables(text) {
2144
+ const lines = text.match(/[^\n]*\n|[^\n]+$/g) ?? [];
2145
+ const output = [];
2146
+ const removedTables = [];
2147
+ let skipping = false;
2148
+ for (const line of lines) {
2149
+ const table = parseTomlTableHeader(line);
2150
+ if (table) {
2151
+ skipping = isLegacyCodexConfigTable(table);
2152
+ if (skipping) {
2153
+ removedTables.push(table);
2154
+ continue;
2155
+ }
2156
+ }
2157
+ if (!skipping) {
2158
+ output.push(line);
2159
+ }
2160
+ }
2161
+ return { text: output.join("").replace(/\n{3,}/g, "\n\n"), removedTables };
2162
+ }
2163
+ function parseTomlTableHeader(line) {
2164
+ const match = /^\s*\[([^\]]+)\]\s*(?:#.*)?$/.exec(line);
2165
+ return match?.[1]?.trim();
2166
+ }
2167
+ function isLegacyCodexConfigTable(table) {
2168
+ return /^plugins\."dbt-agent@[^"]+"$/.test(table)
2169
+ || isLegacyEmbedLabsCodexMarketplaceConfigTable(table)
2170
+ || table === "mcp_servers.dbt-agent"
2171
+ || table.startsWith("mcp_servers.dbt-agent.")
2172
+ || table === 'mcp_servers."dbt-agent"'
2173
+ || table.startsWith('mcp_servers."dbt-agent".')
2174
+ || /^projects\."[^"]*\/DBT-Agent-Project(?:\/[^"]*)?"$/.test(table);
2175
+ }
2176
+ function isLegacyEmbedLabsCodexMarketplaceConfigTable(table) {
2177
+ for (const marketplaceName of LEGACY_CODEX_MARKETPLACE_NAMES) {
2178
+ if (table === `marketplaces.${marketplaceName}`) {
2179
+ return true;
2180
+ }
2181
+ if (table === `plugins."${CODEX_PLUGIN_NAME}@${marketplaceName}"`) {
2182
+ return true;
2183
+ }
2184
+ }
2185
+ return false;
2186
+ }
2187
+ function legacyCodexCleanupWarning(cleanup) {
2188
+ const parts = [];
2189
+ if (cleanup.legacy_removed_paths.length > 0) {
2190
+ parts.push(`removed ${cleanup.legacy_removed_paths.length} legacy Codex plugin path(s)`);
2191
+ }
2192
+ if (cleanup.legacy_removed_config_tables?.length) {
2193
+ parts.push(`removed ${cleanup.legacy_removed_config_tables.length} legacy Codex config table(s)`);
2194
+ }
2195
+ if (cleanup.legacy_removed_history_entries) {
2196
+ parts.push(`removed ${cleanup.legacy_removed_history_entries} legacy Codex text-state mention(s)`);
2197
+ }
2198
+ if (cleanup.legacy_stopped_processes) {
2199
+ parts.push(`stopped ${cleanup.legacy_stopped_processes} legacy Codex plugin process(es)`);
2200
+ }
2201
+ if (cleanup.warnings?.length) {
2202
+ parts.push(`cleanup warning(s): ${cleanup.warnings.join("; ")}`);
2203
+ }
2204
+ return parts.length > 0 ? `Legacy dbt-agent cleanup: ${parts.join(", ")}.` : undefined;
2205
+ }
2206
+ async function cleanupLegacyOpenCodePluginRemnants(targetRoot, globalInstall) {
2207
+ const removedPaths = [];
2208
+ const warnings = [];
2209
+ const legacyPaths = [
2210
+ join(targetRoot, "plugins", "development-board-toolchain.js"),
2211
+ join(targetRoot, "plugins", "dbt-agent.js"),
2212
+ join(targetRoot, "node_modules", "dbt-agent")
2213
+ ];
2214
+ if (globalInstall) {
2215
+ legacyPaths.push(join(targetRoot, "plugins", "embed-labs.js"));
2216
+ legacyPaths.push(...await discoverLegacyOpenCodeBackupPaths(targetRoot, warnings));
2217
+ }
2218
+ for (const candidate of legacyPaths) {
2219
+ try {
2220
+ if (await pathExists(candidate)) {
2221
+ await rm(candidate, { recursive: true, force: true });
2222
+ removedPaths.push(candidate);
2223
+ }
2224
+ }
2225
+ catch (error) {
2226
+ warnings.push(`Could not remove ${candidate}: ${error instanceof Error ? error.message : String(error)}`);
2227
+ }
2228
+ }
2229
+ return {
2230
+ legacy_removed_paths: removedPaths,
2231
+ legacy_removed_config_entries: [],
2232
+ ...(warnings.length > 0 ? { warnings } : {})
2233
+ };
2234
+ }
2235
+ async function discoverLegacyOpenCodeBackupPaths(targetRoot, warnings) {
2236
+ const paths = [];
2237
+ try {
2238
+ const entries = await readdir(targetRoot, { withFileTypes: true });
2239
+ for (const entry of entries) {
2240
+ if (!entry.isFile() || !entry.name.includes(".bak"))
2241
+ continue;
2242
+ const filePath = join(targetRoot, entry.name);
2243
+ try {
2244
+ const current = await readFile(filePath, "utf8");
2245
+ if (current.includes("dbt-agent") || current.includes("development-board-toolchain")) {
2246
+ paths.push(filePath);
2247
+ }
2248
+ }
2249
+ catch (error) {
2250
+ warnings.push(`Could not inspect legacy OpenCode backup ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
2251
+ }
2252
+ }
2253
+ }
2254
+ catch {
2255
+ return paths;
2256
+ }
2257
+ return paths;
2258
+ }
2259
+ function legacyOpenCodeCleanupWarning(cleanup) {
2260
+ const parts = [];
2261
+ if (cleanup.legacy_removed_paths.length > 0) {
2262
+ parts.push(`removed ${cleanup.legacy_removed_paths.length} legacy OpenCode plugin path(s)`);
2263
+ }
2264
+ if (cleanup.legacy_removed_config_entries?.length) {
2265
+ parts.push(`removed ${cleanup.legacy_removed_config_entries.length} legacy OpenCode config entry(s)`);
2266
+ }
2267
+ if (cleanup.warnings?.length) {
2268
+ parts.push(`cleanup warning(s): ${cleanup.warnings.join("; ")}`);
2269
+ }
2270
+ return parts.length > 0 ? `Legacy OpenCode cleanup: ${parts.join(", ")}.` : undefined;
2271
+ }
2272
+ function combineWarnings(...warnings) {
2273
+ const actual = warnings.filter((warning) => Boolean(warning));
2274
+ return actual.length > 0 ? actual.join(" ") : undefined;
2275
+ }
2276
+ async function maybeRegisterCodexMcp(parsed, targetRoot, targetPath) {
2277
+ const explicitTarget = Boolean(stringFlag(parsed, "target") || stringFlag(parsed, "codex-target"));
2278
+ if (explicitTarget && process.env.EMBED_CODEX_MCP_REGISTER !== "1") {
2279
+ return {
2280
+ registered: false,
2281
+ hint: `Installed into a custom target. Register manually with: codex mcp add embed-labs -- node ${join(targetPath, "scripts", "embed-labs-mcp-bridge.mjs")}`
2282
+ };
2283
+ }
2284
+ const bridgePath = join(targetPath, "scripts", "embed-labs-mcp-bridge.mjs");
2285
+ if (!await pathExists(bridgePath)) {
2286
+ return {
2287
+ registered: false,
2288
+ hint: "Restart Codex or reload plugins after installing.",
2289
+ warning: `Codex MCP bridge was not found at ${bridgePath}.`
2290
+ };
2291
+ }
2292
+ const codexBin = await resolveExecutableOnPath("codex");
2293
+ if (!codexBin) {
2294
+ return {
2295
+ registered: false,
2296
+ hint: `Codex CLI was not found on PATH. Register manually with: codex mcp add embed-labs -- node ${bridgePath}`
2297
+ };
2298
+ }
2299
+ const embedCliBin = process.env.EMBED_CLI_BIN?.trim() || await resolveExecutableOnPath("embedlabs") || await resolveExecutableOnPath("embed") || "";
2300
+ const authFile = resolve(process.env.EMBED_AUTH_FILE?.trim() || DEFAULT_AUTH_FILE);
2301
+ const cloudUrl = pluginMcpCloudApiUrl(parsed);
2302
+ const existing = await runLocalProcess(codexBin, ["mcp", "get", "embed-labs", "--json"]);
2303
+ if (existing.code === 0 && codexMcpAlreadyRegistered(existing.stdout, bridgePath, cloudUrl, authFile, embedCliBin)) {
2304
+ const warning = await upsertCodexMcpRuntimeConfig(bridgePath);
2305
+ if (warning) {
2306
+ return { registered: true, warning };
2307
+ }
2308
+ return { registered: true };
2309
+ }
2310
+ await runLocalProcess(codexBin, ["mcp", "remove", "embed-labs"]);
2311
+ const args = [
2312
+ "mcp",
2313
+ "add",
2314
+ "embed-labs",
2315
+ "--env",
2316
+ `EMBED_CLOUD_API_URL=${cloudUrl}`,
2317
+ "--env",
2318
+ `EMBED_AUTH_FILE=${authFile}`
2319
+ ];
2320
+ if (embedCliBin) {
2321
+ args.push("--env", `EMBED_CLI_BIN=${embedCliBin}`);
2322
+ }
2323
+ args.push("--", process.execPath, bridgePath);
2324
+ const addResult = await runLocalProcess(codexBin, args);
2325
+ if (addResult.code !== 0) {
2326
+ return {
2327
+ registered: false,
2328
+ hint: `Codex plugin installed. Register manually with: codex mcp add embed-labs -- ${process.execPath} ${bridgePath}`,
2329
+ warning: `codex mcp add failed: ${addResult.stderr.trim() || addResult.stdout.trim() || `exit ${addResult.code}`}`
2330
+ };
2331
+ }
2332
+ const warning = await upsertCodexMcpRuntimeConfig(bridgePath);
2333
+ return warning ? { registered: true, warning } : { registered: true };
2334
+ }
2335
+ async function maybeRegisterCodexMarketplace(parsed, targetRoot, targetPath) {
2336
+ const explicitTarget = Boolean(stringFlag(parsed, "target") || stringFlag(parsed, "codex-target"));
2337
+ if (explicitTarget && process.env.EMBED_CODEX_MARKETPLACE_REGISTER !== "1") {
2338
+ return {
2339
+ registered: false,
2340
+ warning: "Codex plugin marketplace entry was not registered because a custom target was used. Set EMBED_CODEX_MARKETPLACE_REGISTER=1 to register it anyway."
2341
+ };
2342
+ }
2343
+ if (resolve(targetRoot) !== resolve(defaultCodexPluginRoot()) && process.env.EMBED_CODEX_MARKETPLACE_REGISTER !== "1") {
2344
+ return {
2345
+ registered: false,
2346
+ warning: "Codex plugin marketplace entry was not registered because the install target is not the default Codex plugin root."
2347
+ };
2348
+ }
2349
+ const marketplacePath = defaultCodexLocalMarketplaceRoot();
2350
+ const marketplacePluginPath = join(marketplacePath, "plugins", CODEX_PLUGIN_NAME);
2351
+ try {
2352
+ if (!await pathExists(join(targetPath, ".codex-plugin", "plugin.json"))) {
2353
+ return {
2354
+ registered: false,
2355
+ warning: `Codex plugin manifest was not found at ${join(targetPath, ".codex-plugin", "plugin.json")}.`
2356
+ };
2357
+ }
2358
+ await rm(marketplacePluginPath, { recursive: true, force: true });
2359
+ await mkdir(dirname(marketplacePluginPath), { recursive: true });
2360
+ await cp(targetPath, marketplacePluginPath, { recursive: true });
2361
+ await writeCodexLocalMarketplaceManifest(marketplacePath);
2362
+ const warning = await upsertCodexPluginMarketplaceConfig(marketplacePath);
2363
+ return warning ? { registered: true, marketplacePath, warning } : { registered: true, marketplacePath };
2364
+ }
2365
+ catch (error) {
2366
+ return {
2367
+ registered: false,
2368
+ marketplacePath,
2369
+ warning: `Could not register Codex plugin marketplace entry: ${error instanceof Error ? error.message : String(error)}`
2370
+ };
2371
+ }
2372
+ }
2373
+ function defaultCodexLocalMarketplaceRoot() {
2374
+ return join(defaultCodexHome(), "local-marketplaces", CODEX_PLUGIN_NAME);
2375
+ }
2376
+ async function writeCodexLocalMarketplaceManifest(marketplacePath) {
2377
+ const manifestPath = join(marketplacePath, ".agents", "plugins", "marketplace.json");
2378
+ const manifest = {
2379
+ name: CODEX_MARKETPLACE_NAME,
2380
+ interface: {
2381
+ displayName: "Embed Labs"
2382
+ },
2383
+ plugins: [
2384
+ {
2385
+ name: CODEX_PLUGIN_NAME,
2386
+ source: {
2387
+ source: "local",
2388
+ path: `./plugins/${CODEX_PLUGIN_NAME}`
2389
+ },
2390
+ policy: {
2391
+ installation: "AVAILABLE",
2392
+ authentication: "ON_USE"
2393
+ },
2394
+ category: "Developer Tools"
2395
+ }
2396
+ ]
2397
+ };
2398
+ await mkdir(dirname(manifestPath), { recursive: true });
2399
+ await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
2400
+ }
2401
+ async function upsertCodexPluginMarketplaceConfig(marketplacePath) {
2402
+ const configPath = codexConfigPath();
2403
+ try {
2404
+ await mkdir(dirname(configPath), { recursive: true });
2405
+ let text = "";
2406
+ try {
2407
+ text = await readFile(configPath, "utf8");
2408
+ }
2409
+ catch {
2410
+ text = "";
2411
+ }
2412
+ const original = text;
2413
+ const cleaned = removeLegacyCodexConfigTables(text).text;
2414
+ let updated = upsertTomlTableKeys(cleaned, `marketplaces.${CODEX_MARKETPLACE_NAME}`, {
2415
+ source_type: tomlString("local"),
2416
+ source: tomlString(marketplacePath),
2417
+ last_updated: tomlString(new Date().toISOString().replace(/\.\d{3}Z$/, "Z"))
2418
+ });
2419
+ updated = upsertTomlTableKeys(updated, `plugins."${CODEX_PLUGIN_NAME}@${CODEX_MARKETPLACE_NAME}"`, {
2420
+ enabled: "true"
2421
+ });
2422
+ if (updated !== original) {
2423
+ await writeFile(configPath, updated, "utf8");
2424
+ }
2425
+ return undefined;
2426
+ }
2427
+ catch (error) {
2428
+ return `${configPath} could not be updated with the Embed Labs plugin marketplace entry: ${error instanceof Error ? error.message : String(error)}`;
2429
+ }
2430
+ }
2431
+ function codexMcpAlreadyRegistered(stdout, bridgePath, cloudUrl, authFile, embedCliBin) {
2432
+ try {
2433
+ const parsed = JSON.parse(stdout);
2434
+ const transport = parsed.transport;
2435
+ if (transport?.type !== "stdio" || transport.command !== process.execPath)
2436
+ return false;
2437
+ if (!transport.args?.includes(bridgePath))
2438
+ return false;
2439
+ const env = transport.env || {};
2440
+ if (env.EMBED_CLOUD_API_URL !== cloudUrl)
2441
+ return false;
2442
+ if (env.EMBED_AUTH_FILE !== authFile)
2443
+ return false;
2444
+ if (embedCliBin && env.EMBED_CLI_BIN !== embedCliBin)
2445
+ return false;
2446
+ return true;
2447
+ }
2448
+ catch {
2449
+ return false;
2450
+ }
2451
+ }
2452
+ async function upsertCodexMcpRuntimeConfig(bridgePath) {
2453
+ const configPath = join(process.env.CODEX_HOME?.trim() || join(homedir(), ".codex"), "config.toml");
2454
+ try {
2455
+ await mkdir(dirname(configPath), { recursive: true });
2456
+ let text = "";
2457
+ try {
2458
+ text = await readFile(configPath, "utf8");
2459
+ }
2460
+ catch {
2461
+ text = "";
2462
+ }
2463
+ const updated = upsertTomlTableKeys(text, "mcp_servers.embed-labs", {
2464
+ command: tomlString(process.execPath),
2465
+ args: `[${tomlString(bridgePath)}]`,
2466
+ startup_timeout_sec: "120"
2467
+ });
2468
+ if (updated !== text) {
2469
+ await writeFile(configPath, updated, "utf8");
2470
+ }
2471
+ return undefined;
2472
+ }
2473
+ catch (error) {
2474
+ return `Codex MCP was registered, but ${configPath} could not be updated with startup_timeout_sec: ${error instanceof Error ? error.message : String(error)}`;
2475
+ }
2476
+ }
2477
+ function upsertTomlTableKeys(text, tableName, values) {
2478
+ const normalized = text.endsWith("\n") || text.length === 0 ? text : `${text}\n`;
2479
+ const header = `[${tableName}]`;
2480
+ const tablePattern = new RegExp(`(^|\\n)(\\[${escapeRegExp(tableName)}\\]\\n)([\\s\\S]*?)(?=\\n\\[|$)`);
2481
+ const match = tablePattern.exec(normalized);
2482
+ const bodyWithValues = (body) => {
2483
+ let updated = body;
2484
+ for (const [key, value] of Object.entries(values)) {
2485
+ const keyPattern = new RegExp(`^${escapeRegExp(key)}\\s*=.*$`, "m");
2486
+ if (keyPattern.test(updated)) {
2487
+ updated = updated.replace(keyPattern, `${key} = ${value}`);
2488
+ }
2489
+ else {
2490
+ updated = `${updated.replace(/\s*$/, "\n")}${key} = ${value}\n`;
2491
+ }
2492
+ }
2493
+ return updated;
2494
+ };
2495
+ if (!match) {
2496
+ const prefix = normalized.length > 0 && !normalized.endsWith("\n\n") ? `${normalized}\n` : normalized;
2497
+ return `${prefix}${header}\n${bodyWithValues("")}`;
2498
+ }
2499
+ return `${normalized.slice(0, match.index)}${match[1]}${match[2]}${bodyWithValues(match[3])}${normalized.slice(match.index + match[0].length)}`;
2500
+ }
2501
+ function tomlString(value) {
2502
+ return JSON.stringify(value);
2503
+ }
2504
+ function escapeRegExp(value) {
2505
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2506
+ }
2507
+ function pluginMcpCloudApiUrl(parsed) {
2508
+ const explicit = process.env.EMBED_CLOUD_API_URL?.trim();
2509
+ if (explicit)
2510
+ return explicit.replace(/\/+$/, "");
2511
+ const releaseUrl = stringFlag(parsed, "release-url") || DEFAULT_PLUGIN_RELEASE_URL;
2512
+ try {
2513
+ return new URL(releaseUrl).origin;
2514
+ }
2515
+ catch {
2516
+ return DEFAULT_CLOUD_API_URL.replace(/\/+$/, "");
2517
+ }
2518
+ }
2519
+ async function resolveExecutableOnPath(name) {
2520
+ if (name.includes("/")) {
2521
+ try {
2522
+ await access(name, constants.X_OK);
2523
+ return name;
2524
+ }
2525
+ catch {
2526
+ return undefined;
2527
+ }
2528
+ }
2529
+ const pathEntries = (process.env.PATH || "").split(delimiter).filter(Boolean);
2530
+ for (const entry of pathEntries) {
2531
+ const candidate = join(entry, name);
2532
+ try {
2533
+ await access(candidate, constants.X_OK);
2534
+ return candidate;
2535
+ }
2536
+ catch {
2537
+ // Keep searching PATH.
2538
+ }
2539
+ }
2540
+ return undefined;
1650
2541
  }
1651
2542
  function defaultOpenCodeRoot() {
1652
- return join(process.cwd(), ".opencode");
2543
+ return globalOpenCodeRoot();
2544
+ }
2545
+ function globalOpenCodeRoot() {
2546
+ return join(homedir(), ".config", "opencode");
2547
+ }
2548
+ function isGlobalOpenCodeRoot(targetRoot) {
2549
+ return resolve(targetRoot) === resolve(globalOpenCodeRoot());
2550
+ }
2551
+ async function ensureOpenCodeGlobalPluginConfig() {
2552
+ const configPath = join(globalOpenCodeRoot(), "opencode.json");
2553
+ let existing = {};
2554
+ try {
2555
+ const parsed = JSON.parse(await readFile(configPath, "utf8"));
2556
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
2557
+ existing = parsed;
2558
+ }
2559
+ }
2560
+ catch {
2561
+ existing = {};
2562
+ }
2563
+ const configured = Array.isArray(existing.plugin)
2564
+ ? existing.plugin.filter((item) => typeof item === "string")
2565
+ : Array.isArray(existing.plugins)
2566
+ ? existing.plugins.filter((item) => typeof item === "string")
2567
+ : [];
2568
+ const removed = configured.filter(isLegacyOpenCodePluginConfigEntry);
2569
+ const cleaned = configured.filter((item) => !isLegacyOpenCodePluginConfigEntry(item));
2570
+ if (!cleaned.includes("embed-labs")) {
2571
+ cleaned.push("embed-labs");
2572
+ }
2573
+ await writeFile(configPath, `${JSON.stringify({
2574
+ ...existing,
2575
+ plugin: cleaned,
2576
+ plugins: undefined
2577
+ }, null, 2)}\n`, "utf8");
2578
+ return removed;
2579
+ }
2580
+ function isLegacyOpenCodePluginConfigEntry(item) {
2581
+ const normalized = item.replace(/\\/g, "/");
2582
+ return item === "dbt-agent"
2583
+ || item === "development-board-toolchain"
2584
+ || normalized === "./plugins/development-board-toolchain"
2585
+ || normalized === "./plugins/development-board-toolchain.js"
2586
+ || normalized.endsWith("/plugins/development-board-toolchain.js")
2587
+ || normalized.includes("development-board-toolchain");
2588
+ }
2589
+ async function openCodeDuplicatePluginWarning(targetRoot) {
2590
+ const globalRoot = globalOpenCodeRoot();
2591
+ if (resolve(targetRoot) === resolve(globalRoot))
2592
+ return undefined;
2593
+ const configPath = join(globalRoot, "opencode.json");
2594
+ try {
2595
+ const parsed = JSON.parse(await readFile(configPath, "utf8"));
2596
+ const configuredPlugins = Array.isArray(parsed.plugin)
2597
+ ? parsed.plugin
2598
+ : Array.isArray(parsed.plugins)
2599
+ ? parsed.plugins
2600
+ : [];
2601
+ if (configuredPlugins.some((item) => item === "embed-labs")) {
2602
+ 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.";
2603
+ }
2604
+ }
2605
+ catch {
2606
+ return undefined;
2607
+ }
2608
+ return undefined;
1653
2609
  }
1654
2610
  async function localPluginVersion(kind) {
1655
2611
  const filePath = kind === "codex"
1656
- ? sourceCheckoutPath("platform_plugins", "codex_plugin", "plugins", "dbt-agent", ".codex-plugin", "plugin.json")
2612
+ ? sourceCheckoutPath("platform_plugins", "codex_plugin", "plugins", "embed-labs", ".codex-plugin", "plugin.json")
1657
2613
  : sourceCheckoutPath("platform_plugins", "opencode_plugin", "package.json");
1658
2614
  try {
1659
2615
  const parsed = JSON.parse(await readFile(filePath, "utf8"));
@@ -1664,7 +2620,7 @@ async function localPluginVersion(kind) {
1664
2620
  }
1665
2621
  }
1666
2622
  async function localPluginSourcesAvailable() {
1667
- return await pathExists(sourceCheckoutPath("platform_plugins", "codex_plugin", "plugins", "dbt-agent", ".codex-plugin", "plugin.json"))
2623
+ return await pathExists(sourceCheckoutPath("platform_plugins", "codex_plugin", "plugins", "embed-labs", ".codex-plugin", "plugin.json"))
1668
2624
  && await pathExists(sourceCheckoutPath("platform_plugins", "opencode_plugin", "package.json"));
1669
2625
  }
1670
2626
  function sourceCheckoutPath(...segments) {
@@ -2104,6 +3060,36 @@ function agentRunToolInputs(parsed) {
2104
3060
  ports: ports.values.length > 0 ? ports.values : [22, 15301],
2105
3061
  timeout_ms: timeout.value !== undefined ? timeout.value * 1000 : undefined
2106
3062
  });
3063
+ inputs["wifi.scan"] = compactBody({
3064
+ host: host.value,
3065
+ user: user.value,
3066
+ timeout_seconds: timeout.value
3067
+ });
3068
+ inputs["bluetooth.scan"] = compactBody({
3069
+ host: host.value,
3070
+ user: user.value,
3071
+ timeout_seconds: timeout.value
3072
+ });
3073
+ inputs["chip.cpu.frequency"] = compactBody({
3074
+ host: host.value,
3075
+ user: user.value,
3076
+ timeout_seconds: timeout.value
3077
+ });
3078
+ inputs["chip.temperature"] = compactBody({
3079
+ host: host.value,
3080
+ user: user.value,
3081
+ timeout_seconds: timeout.value
3082
+ });
3083
+ inputs["qml.runtime.status"] = compactBody({
3084
+ host: host.value,
3085
+ user: user.value,
3086
+ timeout_seconds: timeout.value
3087
+ });
3088
+ inputs["qml.runtime.start"] = compactBody({
3089
+ host: host.value,
3090
+ user: user.value,
3091
+ timeout_seconds: timeout.value
3092
+ });
2107
3093
  }
2108
3094
  if (artifact.value || remotePath.value || user.value || booleanFlag(parsed, "run")) {
2109
3095
  inputs["taishanpi.deploy"] = compactBody({
@@ -2711,78 +3697,303 @@ function buildApplicationCompileBody(parsed) {
2711
3697
  compiler: compiler.value
2712
3698
  });
2713
3699
  }
2714
- function buildImageGenerateBody(parsed) {
3700
+ function buildImageGenerateBody(parsed) {
3701
+ const unknownFlag = firstUnknownFlag(parsed, [
3702
+ "json",
3703
+ "workspace",
3704
+ "workspace-id",
3705
+ "account",
3706
+ "prompt",
3707
+ "image-profile",
3708
+ "image-profile-id",
3709
+ "provider",
3710
+ "model",
3711
+ "execution-mode",
3712
+ "worker-pool"
3713
+ ]);
3714
+ if (unknownFlag) {
3715
+ return `Unknown flag --${unknownFlag}. ${BUILD_IMAGE_GENERATE_USAGE}`;
3716
+ }
3717
+ const extra = parsed.command.slice(3);
3718
+ if (extra.length > 0) {
3719
+ return `Unexpected argument: ${extra[0]}. ${BUILD_IMAGE_GENERATE_USAGE}`;
3720
+ }
3721
+ const workspace = optionalTrimmedStringAliasFlag(parsed, ["workspace", "workspace-id"], "workspace or workspace-id");
3722
+ if (workspace.error) {
3723
+ return workspace.error;
3724
+ }
3725
+ if (!workspace.value) {
3726
+ return BUILD_IMAGE_GENERATE_USAGE;
3727
+ }
3728
+ const prompt = optionalTrimmedStringFlag(parsed, "prompt");
3729
+ if (prompt.error) {
3730
+ return prompt.error;
3731
+ }
3732
+ if (!prompt.value) {
3733
+ return BUILD_IMAGE_GENERATE_USAGE;
3734
+ }
3735
+ const account = optionalTrimmedStringFlag(parsed, "account");
3736
+ if (account.error) {
3737
+ return account.error;
3738
+ }
3739
+ const imageProfile = optionalTrimmedStringAliasFlag(parsed, ["image-profile", "image-profile-id"], "image-profile or image-profile-id");
3740
+ if (imageProfile.error) {
3741
+ return imageProfile.error;
3742
+ }
3743
+ const provider = optionalTrimmedStringFlag(parsed, "provider");
3744
+ if (provider.error) {
3745
+ return provider.error;
3746
+ }
3747
+ const model = optionalTrimmedStringFlag(parsed, "model");
3748
+ if (model.error) {
3749
+ return model.error;
3750
+ }
3751
+ const executionMode = optionalTrimmedStringFlag(parsed, "execution-mode");
3752
+ if (executionMode.error) {
3753
+ return executionMode.error;
3754
+ }
3755
+ if (executionMode.value && !["cloud_worker", "dry_run"].includes(executionMode.value)) {
3756
+ return "--execution-mode must be cloud_worker or dry_run.";
3757
+ }
3758
+ const workerPool = optionalTrimmedStringFlag(parsed, "worker-pool");
3759
+ if (workerPool.error) {
3760
+ return workerPool.error;
3761
+ }
3762
+ return compactBody({
3763
+ workspace_id: workspace.value,
3764
+ account_id: account.value,
3765
+ prompt: prompt.value,
3766
+ image_profile_id: imageProfile.value,
3767
+ provider: provider.value,
3768
+ model: model.value,
3769
+ execution_mode: executionMode.value,
3770
+ worker_pool: workerPool.value
3771
+ });
3772
+ }
3773
+ async function buildImageBootLogoPackageRequest(parsed) {
3774
+ const unknownFlag = firstUnknownFlag(parsed, [
3775
+ "json",
3776
+ "account",
3777
+ "project",
3778
+ "project-id",
3779
+ "board",
3780
+ "board-id",
3781
+ "variant",
3782
+ "variant-id",
3783
+ "logo",
3784
+ "logo-path",
3785
+ "kernel-logo",
3786
+ "kernel-logo-path",
3787
+ "rotate",
3788
+ "scale",
3789
+ "output"
3790
+ ]);
3791
+ if (unknownFlag) {
3792
+ return `Unknown flag --${unknownFlag}. ${BUILD_IMAGE_BOOT_LOGO_USAGE}`;
3793
+ }
3794
+ const extra = parsed.command.slice(3);
3795
+ if (extra.length > 0) {
3796
+ return `Unexpected argument: ${extra[0]}. ${BUILD_IMAGE_BOOT_LOGO_USAGE}`;
3797
+ }
3798
+ const logo = optionalTrimmedStringAliasFlag(parsed, ["logo", "logo-path"], "logo or logo-path");
3799
+ if (logo.error) {
3800
+ return logo.error;
3801
+ }
3802
+ if (!logo.value) {
3803
+ return BUILD_IMAGE_BOOT_LOGO_USAGE;
3804
+ }
3805
+ const logoPath = resolve(logo.value);
3806
+ const logoRead = await readLocalFileForUpload(logoPath, "logo");
3807
+ if (typeof logoRead === "string") {
3808
+ return logoRead;
3809
+ }
3810
+ const kernelLogo = optionalTrimmedStringAliasFlag(parsed, ["kernel-logo", "kernel-logo-path"], "kernel-logo or kernel-logo-path");
3811
+ if (kernelLogo.error) {
3812
+ return kernelLogo.error;
3813
+ }
3814
+ let kernelLogoRead;
3815
+ if (kernelLogo.value) {
3816
+ const kernelLogoPath = resolve(kernelLogo.value);
3817
+ const read = await readLocalFileForUpload(kernelLogoPath, "kernel-logo");
3818
+ if (typeof read === "string") {
3819
+ return read;
3820
+ }
3821
+ kernelLogoRead = read;
3822
+ }
3823
+ const account = optionalTrimmedStringFlag(parsed, "account");
3824
+ if (account.error) {
3825
+ return account.error;
3826
+ }
3827
+ const project = optionalTrimmedStringAliasFlag(parsed, ["project", "project-id"], "project or project-id");
3828
+ if (project.error) {
3829
+ return project.error;
3830
+ }
3831
+ const board = optionalTrimmedStringAliasFlag(parsed, ["board", "board-id"], "board or board-id");
3832
+ if (board.error) {
3833
+ return board.error;
3834
+ }
3835
+ const variant = optionalTrimmedStringAliasFlag(parsed, ["variant", "variant-id"], "variant or variant-id");
3836
+ if (variant.error) {
3837
+ return variant.error;
3838
+ }
3839
+ const rotate = optionalTrimmedStringFlag(parsed, "rotate");
3840
+ if (rotate.error) {
3841
+ return rotate.error;
3842
+ }
3843
+ const scale = optionalTrimmedStringFlag(parsed, "scale");
3844
+ if (scale.error) {
3845
+ return scale.error;
3846
+ }
3847
+ const outputPath = optionalTrimmedStringFlag(parsed, "output");
3848
+ if (outputPath.error) {
3849
+ return outputPath.error;
3850
+ }
3851
+ const body = {
3852
+ account_id: account.value,
3853
+ project_id: project.value,
3854
+ board_id: board.value ?? "taishanpi",
3855
+ variant_id: variant.value,
3856
+ logo_name: basename(logoRead.path),
3857
+ logo_content_base64: logoRead.bytes.toString("base64"),
3858
+ logo_sha256: logoRead.sha256,
3859
+ kernel_logo_name: kernelLogoRead ? basename(kernelLogoRead.path) : undefined,
3860
+ kernel_logo_content_base64: kernelLogoRead?.bytes.toString("base64"),
3861
+ kernel_logo_sha256: kernelLogoRead?.sha256,
3862
+ rotate: rotate.value,
3863
+ scale: scale.value
3864
+ };
3865
+ return {
3866
+ body,
3867
+ output_path: outputPath.value ? resolve(outputPath.value) : undefined
3868
+ };
3869
+ }
3870
+ async function buildImageDtbPackageRequest(parsed) {
2715
3871
  const unknownFlag = firstUnknownFlag(parsed, [
2716
3872
  "json",
2717
- "workspace",
2718
- "workspace-id",
2719
3873
  "account",
2720
- "prompt",
2721
- "image-profile",
2722
- "image-profile-id",
2723
- "provider",
2724
- "model",
2725
- "execution-mode",
2726
- "worker-pool"
3874
+ "project",
3875
+ "project-id",
3876
+ "board",
3877
+ "board-id",
3878
+ "variant",
3879
+ "variant-id",
3880
+ "dtb",
3881
+ "dtb-path",
3882
+ "dts",
3883
+ "dts-path",
3884
+ "input-format",
3885
+ "output"
2727
3886
  ]);
2728
3887
  if (unknownFlag) {
2729
- return `Unknown flag --${unknownFlag}. ${BUILD_IMAGE_GENERATE_USAGE}`;
3888
+ return `Unknown flag --${unknownFlag}. ${BUILD_IMAGE_DTB_USAGE}`;
2730
3889
  }
2731
3890
  const extra = parsed.command.slice(3);
2732
3891
  if (extra.length > 0) {
2733
- return `Unexpected argument: ${extra[0]}. ${BUILD_IMAGE_GENERATE_USAGE}`;
2734
- }
2735
- const workspace = optionalTrimmedStringAliasFlag(parsed, ["workspace", "workspace-id"], "workspace or workspace-id");
2736
- if (workspace.error) {
2737
- return workspace.error;
3892
+ return `Unexpected argument: ${extra[0]}. ${BUILD_IMAGE_DTB_USAGE}`;
2738
3893
  }
2739
- if (!workspace.value) {
2740
- return BUILD_IMAGE_GENERATE_USAGE;
3894
+ const dtb = optionalTrimmedStringAliasFlag(parsed, ["dtb", "dtb-path", "dts", "dts-path"], "dtb or dts path");
3895
+ if (dtb.error) {
3896
+ return dtb.error;
2741
3897
  }
2742
- const prompt = optionalTrimmedStringFlag(parsed, "prompt");
2743
- if (prompt.error) {
2744
- return prompt.error;
3898
+ if (!dtb.value) {
3899
+ return BUILD_IMAGE_DTB_USAGE;
2745
3900
  }
2746
- if (!prompt.value) {
2747
- return BUILD_IMAGE_GENERATE_USAGE;
3901
+ const dtbPath = resolve(dtb.value);
3902
+ const dtbRead = await readLocalFileForUpload(dtbPath, "dtb");
3903
+ if (typeof dtbRead === "string") {
3904
+ return dtbRead;
2748
3905
  }
2749
3906
  const account = optionalTrimmedStringFlag(parsed, "account");
2750
3907
  if (account.error) {
2751
3908
  return account.error;
2752
3909
  }
2753
- const imageProfile = optionalTrimmedStringAliasFlag(parsed, ["image-profile", "image-profile-id"], "image-profile or image-profile-id");
2754
- if (imageProfile.error) {
2755
- return imageProfile.error;
2756
- }
2757
- const provider = optionalTrimmedStringFlag(parsed, "provider");
2758
- if (provider.error) {
2759
- return provider.error;
3910
+ const project = optionalTrimmedStringAliasFlag(parsed, ["project", "project-id"], "project or project-id");
3911
+ if (project.error) {
3912
+ return project.error;
2760
3913
  }
2761
- const model = optionalTrimmedStringFlag(parsed, "model");
2762
- if (model.error) {
2763
- return model.error;
3914
+ const board = optionalTrimmedStringAliasFlag(parsed, ["board", "board-id"], "board or board-id");
3915
+ if (board.error) {
3916
+ return board.error;
2764
3917
  }
2765
- const executionMode = optionalTrimmedStringFlag(parsed, "execution-mode");
2766
- if (executionMode.error) {
2767
- return executionMode.error;
3918
+ const variant = optionalTrimmedStringAliasFlag(parsed, ["variant", "variant-id"], "variant or variant-id");
3919
+ if (variant.error) {
3920
+ return variant.error;
2768
3921
  }
2769
- if (executionMode.value && !["cloud_worker", "dry_run"].includes(executionMode.value)) {
2770
- return "--execution-mode must be cloud_worker or dry_run.";
3922
+ const inputFormat = optionalTrimmedStringFlag(parsed, "input-format");
3923
+ if (inputFormat.error) {
3924
+ return inputFormat.error;
2771
3925
  }
2772
- const workerPool = optionalTrimmedStringFlag(parsed, "worker-pool");
2773
- if (workerPool.error) {
2774
- return workerPool.error;
3926
+ const outputPath = optionalTrimmedStringFlag(parsed, "output");
3927
+ if (outputPath.error) {
3928
+ return outputPath.error;
2775
3929
  }
2776
- return compactBody({
2777
- workspace_id: workspace.value,
3930
+ const body = {
2778
3931
  account_id: account.value,
2779
- prompt: prompt.value,
2780
- image_profile_id: imageProfile.value,
2781
- provider: provider.value,
2782
- model: model.value,
2783
- execution_mode: executionMode.value,
2784
- worker_pool: workerPool.value
2785
- });
3932
+ project_id: project.value,
3933
+ board_id: board.value ?? "taishanpi",
3934
+ variant_id: variant.value,
3935
+ dtb_name: basename(dtbRead.path),
3936
+ dtb_content_base64: dtbRead.bytes.toString("base64"),
3937
+ dtb_sha256: dtbRead.sha256,
3938
+ input_format: inputFormat.value
3939
+ };
3940
+ return {
3941
+ body,
3942
+ output_path: outputPath.value ? resolve(outputPath.value) : undefined
3943
+ };
3944
+ }
3945
+ function imageBootLogoComposeRequest(parsed, usage) {
3946
+ const unknownFlag = firstUnknownFlag(parsed, [
3947
+ "json",
3948
+ "package",
3949
+ "package-path",
3950
+ "base-image",
3951
+ "base-image-path",
3952
+ "output",
3953
+ "output-image",
3954
+ "manifest",
3955
+ "manifest-path",
3956
+ "force"
3957
+ ]);
3958
+ if (unknownFlag) {
3959
+ return `Unknown flag --${unknownFlag}. ${usage}`;
3960
+ }
3961
+ const extra = parsed.command.slice(3);
3962
+ if (extra.length > 0) {
3963
+ return `Unexpected argument: ${extra[0]}. ${usage}`;
3964
+ }
3965
+ const packagePath = optionalTrimmedStringAliasFlag(parsed, ["package", "package-path"], "package or package-path");
3966
+ if (packagePath.error) {
3967
+ return packagePath.error;
3968
+ }
3969
+ if (!packagePath.value) {
3970
+ return usage;
3971
+ }
3972
+ const baseImage = optionalTrimmedStringAliasFlag(parsed, ["base-image", "base-image-path"], "base-image or base-image-path");
3973
+ if (baseImage.error) {
3974
+ return baseImage.error;
3975
+ }
3976
+ if (!baseImage.value) {
3977
+ return usage;
3978
+ }
3979
+ const outputImage = optionalTrimmedStringAliasFlag(parsed, ["output", "output-image"], "output or output-image");
3980
+ if (outputImage.error) {
3981
+ return outputImage.error;
3982
+ }
3983
+ if (!outputImage.value) {
3984
+ return usage;
3985
+ }
3986
+ const manifest = optionalTrimmedStringAliasFlag(parsed, ["manifest", "manifest-path"], "manifest or manifest-path");
3987
+ if (manifest.error) {
3988
+ return manifest.error;
3989
+ }
3990
+ return {
3991
+ packagePath: resolve(packagePath.value),
3992
+ baseImagePath: resolve(baseImage.value),
3993
+ outputImagePath: resolve(outputImage.value),
3994
+ manifestPath: manifest.value ? resolve(manifest.value) : undefined,
3995
+ force: booleanFlag(parsed, "force")
3996
+ };
2786
3997
  }
2787
3998
  function boardKnowledgeFileRequest(parsed) {
2788
3999
  const unknownFlag = firstUnknownFlag(parsed, ["json", "source", "path", "output"]);
@@ -3116,6 +4327,73 @@ function usageRecordBody(parsed) {
3116
4327
  created_at: createdAtResult.value
3117
4328
  });
3118
4329
  }
4330
+ function mcpToolEventBody(parsed) {
4331
+ const unknownFlag = firstUnknownFlag(parsed, [
4332
+ "json",
4333
+ "account",
4334
+ "account-id",
4335
+ "tool",
4336
+ "client",
4337
+ "mode",
4338
+ "server-model-used",
4339
+ "success",
4340
+ "request-id",
4341
+ "duration-ms",
4342
+ "input-summary",
4343
+ "output-summary"
4344
+ ]);
4345
+ if (unknownFlag) {
4346
+ return `Unknown flag --${unknownFlag}. ${MCP_TOOL_EVENT_USAGE}`;
4347
+ }
4348
+ const extra = parsed.command.slice(2);
4349
+ if (extra.length > 0) {
4350
+ return `Unexpected argument: ${extra[0]}. ${MCP_TOOL_EVENT_USAGE}`;
4351
+ }
4352
+ const toolResult = optionalTrimmedStringFlag(parsed, "tool");
4353
+ if (toolResult.error)
4354
+ return toolResult.error;
4355
+ if (!toolResult.value)
4356
+ return MCP_TOOL_EVENT_USAGE;
4357
+ const accountResult = optionalTrimmedStringAliasFlag(parsed, ["account", "account-id"], "account or account-id");
4358
+ if (accountResult.error)
4359
+ return accountResult.error;
4360
+ const clientResult = optionalTrimmedStringFlag(parsed, "client");
4361
+ if (clientResult.error)
4362
+ return clientResult.error;
4363
+ const modeResult = optionalTrimmedStringFlag(parsed, "mode");
4364
+ if (modeResult.error)
4365
+ return modeResult.error;
4366
+ const requestIdResult = optionalTrimmedStringFlag(parsed, "request-id");
4367
+ if (requestIdResult.error)
4368
+ return requestIdResult.error;
4369
+ const inputSummaryResult = optionalTrimmedStringFlag(parsed, "input-summary");
4370
+ if (inputSummaryResult.error)
4371
+ return inputSummaryResult.error;
4372
+ const outputSummaryResult = optionalTrimmedStringFlag(parsed, "output-summary");
4373
+ if (outputSummaryResult.error)
4374
+ return outputSummaryResult.error;
4375
+ const durationResult = optionalNonNegativeIntegerFlag(parsed, "duration-ms");
4376
+ if (durationResult.error)
4377
+ return durationResult.error;
4378
+ const serverModelUsed = optionalBooleanFlag(parsed, "server-model-used");
4379
+ if (typeof serverModelUsed === "string")
4380
+ return serverModelUsed;
4381
+ const success = optionalBooleanFlag(parsed, "success");
4382
+ if (typeof success === "string")
4383
+ return success;
4384
+ return compactBody({
4385
+ account_id: accountResult.value,
4386
+ tool_name: toolResult.value,
4387
+ client: clientResult.value,
4388
+ mode: modeResult.value,
4389
+ server_model_used: serverModelUsed,
4390
+ success,
4391
+ request_id: requestIdResult.value,
4392
+ duration_ms: durationResult.value,
4393
+ input_summary: inputSummaryResult.value,
4394
+ output_summary: outputSummaryResult.value
4395
+ });
4396
+ }
3119
4397
  function usageSummaryRequest(parsed) {
3120
4398
  const unknownFlag = firstUnknownFlag(parsed, ["json", "account", "account-id", "api-key-id", "from", "to"]);
3121
4399
  if (unknownFlag) {
@@ -3451,6 +4729,189 @@ function billingSnapshotListRequest(parsed) {
3451
4729
  }
3452
4730
  return { path: `/v1/accounts/${encodeURIComponent(accountResult.value)}/billing/snapshots` };
3453
4731
  }
4732
+ function localToolchainLatestRequest(parsed) {
4733
+ const unknownFlag = firstUnknownFlag(parsed, ["json", "board", "board-id", "channel", "metadata-root"]);
4734
+ if (unknownFlag) {
4735
+ return `Unknown flag --${unknownFlag}. ${LOCAL_TOOLCHAIN_LATEST_USAGE}`;
4736
+ }
4737
+ const extra = parsed.command.slice(3);
4738
+ if (extra.length > 0) {
4739
+ return `Unexpected argument: ${extra[0]}. ${LOCAL_TOOLCHAIN_LATEST_USAGE}`;
4740
+ }
4741
+ const board = optionalTrimmedStringAliasFlag(parsed, ["board", "board-id"], "board or board-id");
4742
+ if (board.error)
4743
+ return board.error;
4744
+ const channel = optionalTrimmedStringFlag(parsed, "channel");
4745
+ if (channel.error)
4746
+ return channel.error;
4747
+ const metadataRoot = optionalTrimmedStringFlag(parsed, "metadata-root");
4748
+ if (metadataRoot.error)
4749
+ return metadataRoot.error;
4750
+ return { boardId: board.value, channel: channel.value, metadataRoot: metadataRoot.value };
4751
+ }
4752
+ function localToolchainCurrentRequest(parsed) {
4753
+ const unknownFlag = firstUnknownFlag(parsed, ["json", "install-root"]);
4754
+ if (unknownFlag) {
4755
+ return `Unknown flag --${unknownFlag}. ${LOCAL_TOOLCHAIN_CURRENT_USAGE}`;
4756
+ }
4757
+ const extra = parsed.command.slice(3);
4758
+ if (extra.length > 0) {
4759
+ return `Unexpected argument: ${extra[0]}. ${LOCAL_TOOLCHAIN_CURRENT_USAGE}`;
4760
+ }
4761
+ const installRoot = optionalTrimmedStringFlag(parsed, "install-root");
4762
+ if (installRoot.error)
4763
+ return installRoot.error;
4764
+ return { installRoot: installRoot.value };
4765
+ }
4766
+ function localToolchainInstallRequest(parsed) {
4767
+ const unknownFlag = firstUnknownFlag(parsed, ["json", "board", "board-id", "channel", "metadata-root", "source-url", "source-release-root", "install-root", "mode", "force"]);
4768
+ if (unknownFlag) {
4769
+ return `Unknown flag --${unknownFlag}. ${LOCAL_TOOLCHAIN_INSTALL_USAGE}`;
4770
+ }
4771
+ const extra = parsed.command.slice(3);
4772
+ if (extra.length > 0) {
4773
+ return `Unexpected argument: ${extra[0]}. ${LOCAL_TOOLCHAIN_INSTALL_USAGE}`;
4774
+ }
4775
+ const board = optionalTrimmedStringAliasFlag(parsed, ["board", "board-id"], "board or board-id");
4776
+ if (board.error)
4777
+ return board.error;
4778
+ const channel = optionalTrimmedStringFlag(parsed, "channel");
4779
+ if (channel.error)
4780
+ return channel.error;
4781
+ const metadataRoot = optionalTrimmedStringFlag(parsed, "metadata-root");
4782
+ if (metadataRoot.error)
4783
+ return metadataRoot.error;
4784
+ const sourceUrl = optionalTrimmedStringFlag(parsed, "source-url");
4785
+ if (sourceUrl.error)
4786
+ return sourceUrl.error;
4787
+ const sourceReleaseRoot = optionalTrimmedStringFlag(parsed, "source-release-root");
4788
+ if (sourceReleaseRoot.error)
4789
+ return sourceReleaseRoot.error;
4790
+ if (sourceUrl.value && sourceReleaseRoot.value) {
4791
+ return "Use only one of --source-url or --source-release-root.";
4792
+ }
4793
+ const installRoot = optionalTrimmedStringFlag(parsed, "install-root");
4794
+ if (installRoot.error)
4795
+ return installRoot.error;
4796
+ const mode = optionalTrimmedStringFlag(parsed, "mode");
4797
+ if (mode.error)
4798
+ return mode.error;
4799
+ return {
4800
+ boardId: board.value,
4801
+ channel: channel.value,
4802
+ metadataRoot: metadataRoot.value,
4803
+ sourceUrl: sourceUrl.value,
4804
+ sourceReleaseRoot: sourceReleaseRoot.value,
4805
+ installRoot: installRoot.value,
4806
+ mode: mode.value,
4807
+ force: booleanFlag(parsed, "force")
4808
+ };
4809
+ }
4810
+ function localToolchainValidateRequest(parsed) {
4811
+ const unknownFlag = firstUnknownFlag(parsed, ["json", "release-root", "mode"]);
4812
+ if (unknownFlag) {
4813
+ return `Unknown flag --${unknownFlag}. ${LOCAL_TOOLCHAIN_VALIDATE_USAGE}`;
4814
+ }
4815
+ const extra = parsed.command.slice(3);
4816
+ if (extra.length > 0) {
4817
+ return `Unexpected argument: ${extra[0]}. ${LOCAL_TOOLCHAIN_VALIDATE_USAGE}`;
4818
+ }
4819
+ const releaseRoot = optionalTrimmedStringFlag(parsed, "release-root");
4820
+ if (releaseRoot.error) {
4821
+ return releaseRoot.error;
4822
+ }
4823
+ const mode = optionalTrimmedStringFlag(parsed, "mode");
4824
+ if (mode.error) {
4825
+ return mode.error;
4826
+ }
4827
+ return { releaseRoot: releaseRoot.value, mode: mode.value };
4828
+ }
4829
+ function localCompileTaishanPiRequest(parsed, auth) {
4830
+ const unknownFlag = firstUnknownFlag(parsed, ["json", "source", "output", "release-root", "account", "account-id"]);
4831
+ if (unknownFlag) {
4832
+ return `Unknown flag --${unknownFlag}. ${LOCAL_COMPILE_TAISHANPI_USAGE}`;
4833
+ }
4834
+ const extra = parsed.command.slice(3);
4835
+ if (extra.length > 0) {
4836
+ return `Unexpected argument: ${extra[0]}. ${LOCAL_COMPILE_TAISHANPI_USAGE}`;
4837
+ }
4838
+ const source = optionalTrimmedStringFlag(parsed, "source");
4839
+ if (source.error) {
4840
+ return source.error;
4841
+ }
4842
+ const outputPath = optionalTrimmedStringFlag(parsed, "output");
4843
+ if (outputPath.error) {
4844
+ return outputPath.error;
4845
+ }
4846
+ if (!source.value || !outputPath.value) {
4847
+ return LOCAL_COMPILE_TAISHANPI_USAGE;
4848
+ }
4849
+ const releaseRoot = optionalTrimmedStringFlag(parsed, "release-root");
4850
+ if (releaseRoot.error) {
4851
+ return releaseRoot.error;
4852
+ }
4853
+ const account = optionalTrimmedStringAliasFlag(parsed, ["account", "account-id"], "account or account-id");
4854
+ if (account.error) {
4855
+ return account.error;
4856
+ }
4857
+ return {
4858
+ boardId: "taishanpi-1m-rk3566",
4859
+ sourcePath: source.value,
4860
+ outputPath: outputPath.value,
4861
+ releaseRoot: releaseRoot.value,
4862
+ accountId: account.value,
4863
+ auth: localToolchainAuthContext(auth, account.value)
4864
+ };
4865
+ }
4866
+ function localBuildQtSmokeRequest(parsed, auth) {
4867
+ const unknownFlag = firstUnknownFlag(parsed, ["json", "source", "build-dir", "release-root", "target-name", "account", "account-id"]);
4868
+ if (unknownFlag) {
4869
+ return `Unknown flag --${unknownFlag}. ${LOCAL_BUILD_QT_SMOKE_USAGE}`;
4870
+ }
4871
+ const extra = parsed.command.slice(3);
4872
+ if (extra.length > 0) {
4873
+ return `Unexpected argument: ${extra[0]}. ${LOCAL_BUILD_QT_SMOKE_USAGE}`;
4874
+ }
4875
+ const buildDir = optionalTrimmedStringFlag(parsed, "build-dir");
4876
+ if (buildDir.error) {
4877
+ return buildDir.error;
4878
+ }
4879
+ if (!buildDir.value) {
4880
+ return LOCAL_BUILD_QT_SMOKE_USAGE;
4881
+ }
4882
+ const source = optionalTrimmedStringFlag(parsed, "source");
4883
+ if (source.error) {
4884
+ return source.error;
4885
+ }
4886
+ const releaseRoot = optionalTrimmedStringFlag(parsed, "release-root");
4887
+ if (releaseRoot.error) {
4888
+ return releaseRoot.error;
4889
+ }
4890
+ const targetName = optionalTrimmedStringFlag(parsed, "target-name");
4891
+ if (targetName.error) {
4892
+ return targetName.error;
4893
+ }
4894
+ const account = optionalTrimmedStringAliasFlag(parsed, ["account", "account-id"], "account or account-id");
4895
+ if (account.error) {
4896
+ return account.error;
4897
+ }
4898
+ return {
4899
+ sourceDir: source.value,
4900
+ buildDir: buildDir.value,
4901
+ releaseRoot: releaseRoot.value,
4902
+ targetName: targetName.value,
4903
+ accountId: account.value,
4904
+ auth: localToolchainAuthContext(auth, account.value)
4905
+ };
4906
+ }
4907
+ function localToolchainAuthContext(auth, accountId) {
4908
+ return {
4909
+ authenticated: auth.authenticated,
4910
+ profile: auth.profile,
4911
+ source: auth.source,
4912
+ account_id: accountId
4913
+ };
4914
+ }
3454
4915
  function usageEventsRequest(parsed) {
3455
4916
  const unknownFlag = firstUnknownFlag(parsed, ["json", "account", "account-id", "api-key-id", "from", "to", "limit"]);
3456
4917
  if (unknownFlag) {
@@ -3508,6 +4969,26 @@ function compactBody(input) {
3508
4969
  }
3509
4970
  return body;
3510
4971
  }
4972
+ async function readLocalFileForUpload(filePath, label) {
4973
+ try {
4974
+ const info = await stat(filePath);
4975
+ if (!info.isFile()) {
4976
+ return `${label} must point to a file: ${filePath}`;
4977
+ }
4978
+ const bytes = await readFile(filePath);
4979
+ if (bytes.length === 0) {
4980
+ return `${label} must not be empty: ${filePath}`;
4981
+ }
4982
+ return {
4983
+ path: filePath,
4984
+ bytes,
4985
+ sha256: createHash("sha256").update(bytes).digest("hex")
4986
+ };
4987
+ }
4988
+ catch (error) {
4989
+ return `Could not read ${label} file ${filePath}: ${error instanceof Error ? error.message : String(error)}`;
4990
+ }
4991
+ }
3511
4992
  function projectCreateBody(parsed) {
3512
4993
  const extra = parsed.command.slice(3);
3513
4994
  if (extra.length > 0) {
@@ -3801,8 +5282,15 @@ function renderServiceModeCatalog(catalog) {
3801
5282
  ` model_gateway=${catalog.boundaries.model_gateway}`,
3802
5283
  ` server_resource_orchestration=${catalog.boundaries.server_resource_orchestration}`,
3803
5284
  ` local_bridge=${catalog.boundaries.local_bridge}`,
5285
+ catalog.boundaries.free_registered_operations ? ` free_registered_operations=${catalog.boundaries.free_registered_operations}` : "",
5286
+ catalog.boundaries.paid_server_operations ? ` paid_server_operations=${catalog.boundaries.paid_server_operations}` : "",
5287
+ catalog.allocation_rule ? `allocation_rule=${catalog.allocation_rule}` : "",
5288
+ "free_registered_capabilities:",
5289
+ ...(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"}`),
5290
+ "paid_capabilities:",
5291
+ ...(catalog.paid_capabilities ?? []).map((item) => ` - ${item.policy_id} workspace=${item.requires_server_workspace} lease=${item.allocates_server_resource_lease} meters=${item.meters.join(",") || "none"}`),
3804
5292
  "modes:"
3805
- ];
5293
+ ].filter(Boolean);
3806
5294
  for (const mode of catalog.modes) {
3807
5295
  lines.push([
3808
5296
  ` ${mode.default ? "*" : "-"}`,
@@ -3843,6 +5331,24 @@ function renderPluginInstall(result) {
3843
5331
  if (item.command_hint) {
3844
5332
  lines.push(` ${item.command_hint}`);
3845
5333
  }
5334
+ if (item.warning) {
5335
+ lines.push(` warning=${item.warning}`);
5336
+ }
5337
+ if (item.mcp_registered !== undefined) {
5338
+ lines.push(` codex_mcp_registered=${item.mcp_registered}`);
5339
+ }
5340
+ if (item.mcp_warning) {
5341
+ lines.push(` warning=${item.mcp_warning}`);
5342
+ }
5343
+ if (item.marketplace_registered !== undefined) {
5344
+ lines.push(` codex_marketplace_registered=${item.marketplace_registered}`);
5345
+ }
5346
+ if (item.marketplace_path) {
5347
+ lines.push(` codex_marketplace=${item.marketplace_path}`);
5348
+ }
5349
+ if (item.marketplace_warning) {
5350
+ lines.push(` warning=${item.marketplace_warning}`);
5351
+ }
3846
5352
  }
3847
5353
  return lines.join("\n");
3848
5354
  }
@@ -3926,6 +5432,20 @@ function renderUsageRecord(record) {
3926
5432
  `created_at=${record.created_at}`
3927
5433
  ].filter(Boolean).join(" ");
3928
5434
  }
5435
+ function renderMcpToolEvent(event) {
5436
+ return [
5437
+ `${event.event_id} tool=${event.tool_name}`,
5438
+ event.account_id ? `account=${event.account_id}` : "",
5439
+ event.api_key_id ? `api_key=${event.api_key_id}` : "",
5440
+ `client=${event.client}`,
5441
+ `mode=${event.mode}`,
5442
+ `server_model_used=${event.server_model_used}`,
5443
+ `success=${event.success}`,
5444
+ event.request_id ? `request=${event.request_id}` : "",
5445
+ event.duration_ms !== undefined ? `duration_ms=${event.duration_ms}` : "",
5446
+ `created_at=${event.created_at}`
5447
+ ].filter(Boolean).join(" ");
5448
+ }
3929
5449
  function renderUsageSummary(summary) {
3930
5450
  const lines = [
3931
5451
  summary.account_id ? `account=${summary.account_id}` : "",
@@ -4214,10 +5734,13 @@ function renderBoardMethods(data) {
4214
5734
  method.operation,
4215
5735
  `status=${method.status}`,
4216
5736
  `runtime=${method.runtime}`,
5737
+ method.access_tier ? `access=${method.access_tier}` : "",
4217
5738
  `endpoint=${method.http_method} ${method.path}`,
4218
5739
  `workspace=${method.requires_workspace}`,
5740
+ method.allocates_server_workspace !== undefined ? `alloc_workspace=${method.allocates_server_workspace}` : "",
5741
+ method.allocates_server_resource_lease !== undefined ? `alloc_lease=${method.allocates_server_resource_lease}` : "",
4219
5742
  `approval=${method.requires_approval}`
4220
- ].join(" ")).join("\n");
5743
+ ].filter(Boolean).join(" ")).join("\n");
4221
5744
  }
4222
5745
  function renderBoardKnowledge(data) {
4223
5746
  const files = isJsonObject(data) && Array.isArray(data.knowledge_files)
@@ -4446,6 +5969,141 @@ function renderBuildWorkspaceSourcePatch(result) {
4446
5969
  }
4447
5970
  return lines.join("\n");
4448
5971
  }
5972
+ function renderLocalToolchainLatest(result) {
5973
+ const lines = [
5974
+ `board=${result.board_id}`,
5975
+ `channel=${result.channel}`,
5976
+ `version=${result.version}`,
5977
+ `host=${result.host}`,
5978
+ result.metadata_root ? `metadata_root=${result.metadata_root}` : "metadata=built-in",
5979
+ result.download?.source_url ? `download=${result.download.mirror_kind}:${result.download.source_url}` : "",
5980
+ result.download?.archive ? `archive_sha256=${result.download.archive.sha256}` : "",
5981
+ result.download?.archive ? `archive_size_bytes=${result.download.archive.size_bytes}` : "",
5982
+ result.download?.components?.length ? `components=${result.download.components.length}` : "",
5983
+ result.download?.default_mode ? `default_mode=${result.download.default_mode}` : "",
5984
+ result.download_error ? `download_error=${result.download_error}` : ""
5985
+ ].filter(Boolean);
5986
+ if (result.download?.components?.length) {
5987
+ lines.push("download_components:");
5988
+ for (const component of result.download.components) {
5989
+ lines.push(` ${component.id}@${component.version} modes=${component.install_modes?.join(",") || "all"} bytes=${component.archive.size_bytes}`);
5990
+ }
5991
+ }
5992
+ if (result.packages.length > 0) {
5993
+ lines.push("packages:");
5994
+ for (const pkg of result.packages) {
5995
+ lines.push(` ${pkg.id}@${pkg.version}`);
5996
+ }
5997
+ }
5998
+ return lines.join("\n");
5999
+ }
6000
+ function renderLocalToolchainCurrent(result) {
6001
+ if (!result.installed) {
6002
+ return [
6003
+ "No local toolchain installed.",
6004
+ `board=${result.board_id}`,
6005
+ `install_root=${result.install_root}`,
6006
+ `registry=${result.registry_path}`
6007
+ ].join("\n");
6008
+ }
6009
+ return [
6010
+ "Local toolchain installed.",
6011
+ `board=${result.board_id}`,
6012
+ result.version ? `version=${result.version}` : "",
6013
+ result.channel ? `channel=${result.channel}` : "",
6014
+ result.mode ? `mode=${result.mode}` : "",
6015
+ result.release_root ? `release_root=${result.release_root}` : "",
6016
+ `install_root=${result.install_root}`,
6017
+ `registry=${result.registry_path}`
6018
+ ].filter(Boolean).join("\n");
6019
+ }
6020
+ function renderLocalToolchainInstall(result) {
6021
+ const lines = [
6022
+ "Local toolchain installed.",
6023
+ `board=${result.board_id}`,
6024
+ `version=${result.version}`,
6025
+ `channel=${result.channel}`,
6026
+ `host=${result.host}`,
6027
+ `mode=${result.mode}`,
6028
+ `install_root=${result.install_root}`,
6029
+ `release_root=${result.release_root}`,
6030
+ `registry=${result.registry_path}`,
6031
+ `source=${result.source.kind}:${result.source.value}`,
6032
+ result.source.downloaded_path ? `downloaded=${result.source.downloaded_path}` : "",
6033
+ result.source.components?.length ? `components=${result.source.components.length}` : "",
6034
+ `validation=${result.validation.ok ? "ok" : "failed"}`
6035
+ ].filter(Boolean);
6036
+ if (result.source.components?.length) {
6037
+ lines.push("installed_components:");
6038
+ for (const component of result.source.components) {
6039
+ lines.push(` ${component.id}@${component.version} ${component.mirror_kind || ""} bytes=${component.size_bytes}`);
6040
+ }
6041
+ }
6042
+ if (result.installed_paths.length > 0) {
6043
+ lines.push("installed_paths:");
6044
+ for (const installedPath of result.installed_paths) {
6045
+ lines.push(` ${installedPath}`);
6046
+ }
6047
+ }
6048
+ if (result.packages.length > 0) {
6049
+ lines.push("packages:");
6050
+ for (const pkg of result.packages) {
6051
+ lines.push(` ${pkg.id}@${pkg.version}`);
6052
+ }
6053
+ }
6054
+ return lines.join("\n");
6055
+ }
6056
+ function renderLocalToolchainValidation(result) {
6057
+ const lines = [
6058
+ result.ok ? "Local toolchain ready." : "Local toolchain not ready.",
6059
+ `board=${result.board_id}`,
6060
+ `mode=${result.mode}`,
6061
+ `host=${result.host.platform}/${result.host.arch}`,
6062
+ `release_root=${result.release_root}`
6063
+ ];
6064
+ for (const check of result.checked_paths) {
6065
+ lines.push(`${check.exists ? "ok" : "missing"} ${check.label}: ${check.path}`);
6066
+ }
6067
+ if (result.notes.length > 0) {
6068
+ lines.push("notes:");
6069
+ for (const note of result.notes) {
6070
+ lines.push(` ${note}`);
6071
+ }
6072
+ }
6073
+ return lines.join("\n");
6074
+ }
6075
+ function renderLocalCompileResult(result) {
6076
+ const lines = [
6077
+ `${result.operation} succeeded.`,
6078
+ `board=${result.board_id}`,
6079
+ result.account_id ? `account=${result.account_id}` : "",
6080
+ `auth=${result.auth.authenticated ? "authenticated" : "anonymous"} profile=${result.auth.profile}${result.auth.source ? ` source=${result.auth.source}` : ""}`,
6081
+ `source=${result.source_path}`,
6082
+ result.build_dir ? `build_dir=${result.build_dir}` : "",
6083
+ `artifact=${result.artifact_path}`,
6084
+ `size=${result.artifact_size_bytes}`,
6085
+ `sha256=${result.artifact_sha256}`,
6086
+ result.file_info ? `file=${result.file_info}` : "",
6087
+ `manifest=${result.manifest_path}`
6088
+ ].filter(Boolean);
6089
+ for (const command of result.commands) {
6090
+ lines.push(`command_exit=${command.exit_code} cwd=${command.cwd}`);
6091
+ lines.push(` ${command.command.join(" ")}`);
6092
+ if (command.stdout_tail.length > 0) {
6093
+ lines.push(" stdout_tail:");
6094
+ for (const line of command.stdout_tail.slice(-12)) {
6095
+ lines.push(` ${line}`);
6096
+ }
6097
+ }
6098
+ if (command.stderr_tail.length > 0) {
6099
+ lines.push(" stderr_tail:");
6100
+ for (const line of command.stderr_tail.slice(-12)) {
6101
+ lines.push(` ${line}`);
6102
+ }
6103
+ }
6104
+ }
6105
+ return lines.join("\n");
6106
+ }
4449
6107
  function renderDoctor(result) {
4450
6108
  const lines = [
4451
6109
  `${result.status === "ready" ? "Ready" : "Not ready"}: ${result.summary_for_user}`,
@@ -4616,6 +6274,46 @@ function renderJob(job) {
4616
6274
  }
4617
6275
  return lines.join("\n");
4618
6276
  }
6277
+ function renderBootLogoPackageResult(result) {
6278
+ const lines = [renderJob(result.task)];
6279
+ if (result.downloaded_artifact) {
6280
+ lines.push("downloaded_package:");
6281
+ lines.push(` artifact=${result.downloaded_artifact.artifact_id}`);
6282
+ lines.push(` output=${result.downloaded_artifact.output_path}`);
6283
+ lines.push(` size=${result.downloaded_artifact.size_bytes}`);
6284
+ }
6285
+ lines.push("next=merge the downloaded package with the local base image, then flash only after explicit approval.");
6286
+ return lines.join("\n");
6287
+ }
6288
+ function renderDtbPackageResult(result) {
6289
+ const lines = [renderJob(result.task)];
6290
+ if (result.downloaded_artifact) {
6291
+ lines.push("downloaded_package:");
6292
+ lines.push(` artifact=${result.downloaded_artifact.artifact_id}`);
6293
+ lines.push(` output=${result.downloaded_artifact.output_path}`);
6294
+ lines.push(` size=${result.downloaded_artifact.size_bytes}`);
6295
+ }
6296
+ lines.push("next=merge the downloaded DTB package with the local base image, then flash only after explicit approval.");
6297
+ return lines.join("\n");
6298
+ }
6299
+ function renderBootLogoComposeResult(result) {
6300
+ const label = result.operation_kind === "image.dtb.compose" ? "DTB" : "Boot-logo";
6301
+ return [
6302
+ result.ready_for_flash ? `${label} compose ready for flash review.` : `${label} compose created a guarded local result.`,
6303
+ `board=${result.board_id}`,
6304
+ result.variant_id ? `variant=${result.variant_id}` : "",
6305
+ `package=${result.package_path}`,
6306
+ `base=${result.base_image_path}`,
6307
+ `output=${result.output_image_path}`,
6308
+ `manifest=${result.manifest_path}`,
6309
+ `strategy=${result.local_merge_strategy}`,
6310
+ `patches=${result.patch_operations_applied}/${result.patch_operations_total}`,
6311
+ `replacement_applied=${result.replacement_applied}`,
6312
+ `ready_for_flash=${result.ready_for_flash}`,
6313
+ `cross_platform=${result.cross_platform} platform=${result.platform}/${result.arch}`,
6314
+ result.summary_for_user
6315
+ ].filter(Boolean).join("\n");
6316
+ }
4619
6317
  function safeArtifactOutputFileName(name) {
4620
6318
  return name.replace(/[^A-Za-z0-9._-]+/g, "_") || "artifact.out";
4621
6319
  }
@@ -4814,6 +6512,22 @@ function stringFlag(parsed, name) {
4814
6512
  function booleanFlag(parsed, name) {
4815
6513
  return parsed.flags[name] === true;
4816
6514
  }
6515
+ function optionalBooleanFlag(parsed, name) {
6516
+ const values = flagValues(parsed, name);
6517
+ if (values.length === 0)
6518
+ return undefined;
6519
+ const value = values[values.length - 1];
6520
+ if (value === true)
6521
+ return true;
6522
+ if (typeof value !== "string")
6523
+ return `--${name} must be true or false.`;
6524
+ const normalized = value.trim().toLowerCase();
6525
+ if (["1", "true", "yes", "y", "on"].includes(normalized))
6526
+ return true;
6527
+ if (["0", "false", "no", "n", "off"].includes(normalized))
6528
+ return false;
6529
+ return `--${name} must be true or false.`;
6530
+ }
4817
6531
  function switchFlag(parsed, name) {
4818
6532
  const values = flagValues(parsed, name);
4819
6533
  for (const value of values) {
@@ -5095,7 +6809,7 @@ async function waitForever() {
5095
6809
  return 0;
5096
6810
  }
5097
6811
  function printHelp() {
5098
- console.log(`embed CLI
6812
+ printCliHelp(`embed CLI
5099
6813
 
5100
6814
  Usage:
5101
6815
  embed <command> [options]
@@ -5117,13 +6831,19 @@ Main workflow:
5117
6831
  embed model default
5118
6832
  4. Run a natural-language local tool loop:
5119
6833
  embed agent run --prompt "验证开发板状态"
5120
- 5. Pick a cloud build template:
6834
+ 5. Validate or use the local TaishanPi toolchain:
6835
+ embed local toolchain latest
6836
+ embed local toolchain install
6837
+ embed local toolchain validate
6838
+ embed local compile taishanpi --source ./main.c --output ./.embed-labs/build/main
6839
+ embed local build qt-smoke --build-dir ./.embed-labs/build/qt-smoke
6840
+ 6. Pick a cloud build template:
5121
6841
  embed board registry list
5122
6842
  embed board methods taishanpi-1m-rk3566
5123
6843
  embed board knowledge taishanpi-1m-rk3566
5124
6844
  embed build template list
5125
6845
  embed build template show <template_id>
5126
- 6. Provision and populate a build workspace:
6846
+ 7. Provision and populate a build workspace:
5127
6847
  embed build workspace provision --account <account_id> --project <project_id> --template <template_id>
5128
6848
  embed build resource lease create --workspace <workspace_id> --execution-mode cloud_worker
5129
6849
  embed build workspace source put <workspace_id> --file ./main.c:src/main.c
@@ -5132,13 +6852,15 @@ Main workflow:
5132
6852
  embed build workspace source search <workspace_id> --query init --glob "**/*.c"
5133
6853
  embed build workspace source patch <workspace_id> --patch ./fix.patch
5134
6854
  embed build workspace release <workspace_id> --dry-run
5135
- 7. Generate application source on the server and follow artifacts:
6855
+ 8. Generate application source on the server and follow artifacts:
5136
6856
  embed build application generate --workspace <workspace_id> --prompt "Create a minimal Linux app" --provider bai --model gpt-5.2
5137
6857
  embed build application compile --workspace <workspace_id> --source app/generated.c --execution-mode docker_worker
5138
6858
  embed build image generate --workspace <workspace_id> --prompt "Generate a minimal TaishanPi image"
6859
+ embed build image boot-logo --logo ./logo.png --board taishanpi --variant 1M-RK3566 --output ./boot-logo-package.json
6860
+ embed image boot-logo compose --package ./boot-logo-package.json --base-image ./boot.img --output ./boot-logo.img
5139
6861
  embed cloud task artifacts <task_id>
5140
6862
  embed artifact download <artifact_id> --output ./artifact.bin
5141
- 8. Check credits or create a recharge QR:
6863
+ 9. Check credits or create a recharge QR:
5142
6864
  embed billing balance --account <account_id>
5143
6865
  embed billing tokens --account <account_id>
5144
6866
  embed billing ledger --account <account_id>
@@ -5153,7 +6875,17 @@ Local hardware:
5153
6875
  embed agent run --prompt "部署生成的泰山派应用" --host 198.19.77.2 --artifact-task <task_id> --remote-path /userdata/embed-labs/apps/app --approve --run
5154
6876
  embed tool list
5155
6877
  embed tool call device.probe --input-json '{"host":"198.19.77.2","ports":[22,15301]}'
6878
+ embed tool call wifi.scan --input-json '{"host":"198.19.77.2","user":"root"}'
6879
+ embed tool call chip.temperature --input-json '{"host":"198.19.77.2","user":"root"}'
6880
+ embed tool call qml.runtime.status --input-json '{"host":"198.19.77.2","user":"root","port":18130}'
5156
6881
  embed device list
6882
+ embed local toolchain latest
6883
+ embed local toolchain current
6884
+ embed local toolchain install
6885
+ embed local toolchain validate
6886
+ embed local compile taishanpi --source ./main.c --output ./.embed-labs/build/main
6887
+ embed local build qt-smoke --build-dir ./.embed-labs/build/qt-smoke
6888
+ embed image boot-logo compose --package ./boot-logo-package.json --base-image ./boot.img --output ./boot-logo.img
5157
6889
  embed deploy taishanpi --host 198.19.77.2 --artifact ./artifact.bin --approve --run
5158
6890
  embed flash plan --board <rp2350|taishanpi>
5159
6891
 
@@ -5188,7 +6920,7 @@ function printHelpTopic(topic) {
5188
6920
  printCommandReference();
5189
6921
  return;
5190
6922
  }
5191
- console.log(`Unknown help topic: ${topic}
6923
+ printCliHelp(`Unknown help topic: ${topic}
5192
6924
 
5193
6925
  Available topics:
5194
6926
  embed help getting-started
@@ -5196,7 +6928,7 @@ Available topics:
5196
6928
  `);
5197
6929
  }
5198
6930
  function printGettingStartedHelp() {
5199
- console.log(`embed getting started
6931
+ printCliHelp(`embed getting started
5200
6932
 
5201
6933
  After npm install, use the installed embed binary directly:
5202
6934
 
@@ -5225,6 +6957,9 @@ Cloud build path:
5225
6957
  embed model list
5226
6958
  embed model default
5227
6959
  embed agent run --prompt "验证开发板状态"
6960
+ embed local toolchain validate
6961
+ embed local compile taishanpi --source ./main.c --output ./.embed-labs/build/main
6962
+ embed local build qt-smoke --build-dir ./.embed-labs/build/qt-smoke
5228
6963
  embed board registry list
5229
6964
  embed board registry show taishanpi-1m-rk3566
5230
6965
  embed board methods taishanpi-1m-rk3566
@@ -5242,6 +6977,8 @@ Cloud build path:
5242
6977
  embed build application generate --workspace <workspace_id> --prompt "Create a minimal Linux app" --provider bai --model gpt-5.2
5243
6978
  embed build application compile --workspace <workspace_id> --source app/generated.c --execution-mode docker_worker
5244
6979
  embed build image generate --workspace <workspace_id> --prompt "Generate a minimal TaishanPi image"
6980
+ embed build image boot-logo --logo ./logo.png --board taishanpi --variant 1M-RK3566 --output ./boot-logo-package.json
6981
+ embed image boot-logo compose --package ./boot-logo-package.json --base-image ./boot.img --output ./boot-logo.img
5245
6982
  embed cloud task status <task_id>
5246
6983
  embed cloud task artifacts <task_id>
5247
6984
  embed artifact download <artifact_id> --output ./artifact.bin
@@ -5279,7 +7016,7 @@ Use --json on commands when scripting or running in CI.
5279
7016
  `);
5280
7017
  }
5281
7018
  function printCommandReference() {
5282
- console.log(`embed CLI
7019
+ printCliHelp(`embed CLI
5283
7020
 
5284
7021
  Usage:
5285
7022
  embed doctor [--json]
@@ -5299,7 +7036,7 @@ Usage:
5299
7036
  embed board methods <template_id> [--json]
5300
7037
  embed board knowledge <template_id> [--json]
5301
7038
  embed board knowledge file <template_id> --source board_pack|build_template|registry --path <relative_path> [--output <local_path>] [--json]
5302
- 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]
7039
+ 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]
5303
7040
  embed run <natural language request> [--provider stub|openai|bai|cc|claude-code] [--approve] [--json]
5304
7041
  embed tool list [--json]
5305
7042
  embed tool call <capability_id> [--input-json '<json>'] [--approve] [--json]
@@ -5344,7 +7081,26 @@ Usage:
5344
7081
  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]
5345
7082
  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]
5346
7083
  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]
7084
+ 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]
7085
+ embed image boot-logo compose --package <boot-logo-package.json> --base-image <boot.img|image.img> --output <image> [--manifest <manifest.json>] [--force] [--json]
7086
+ embed local toolchain latest [--board taishanpi-1m-rk3566] [--channel stable] [--metadata-root <path>] [--json]
7087
+ embed local toolchain current [--install-root <path>] [--json]
7088
+ 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>] [--mode minimal|compile|qt|full|images] [--force] [--json]
7089
+ Defaults to the production download channel at download.embedboard.com.
7090
+ embed local toolchain validate [--release-root <path>] [--mode minimal|compile|qt|full|images] [--json]
7091
+ embed local compile taishanpi --source <main.c|main.cpp> --output <artifact> [--release-root <path>] [--account <account_id>] [--json]
7092
+ embed local build qt-smoke --build-dir <dir> [--source <qt-smoke-dir>] [--release-root <path>] [--account <account_id>] [--json]
5347
7093
  embed debug tools [--json]
7094
+ embed tool list [--json]
7095
+ embed tool call wifi.scan --input-json '{"host":"198.19.77.2","user":"root"}' [--json]
7096
+ embed tool call bluetooth.scan --input-json '{"host":"198.19.77.2","user":"root","duration_seconds":8}' [--json]
7097
+ embed tool call chip.cpu.frequency --input-json '{"host":"198.19.77.2","user":"root"}' [--json]
7098
+ embed tool call chip.temperature --input-json '{"host":"198.19.77.2","user":"root"}' [--json]
7099
+ embed tool call qml.runtime.status --input-json '{"host":"198.19.77.2","user":"root","port":18130}' [--json]
7100
+ embed tool call qml.runtime.start --input-json '{"host":"198.19.77.2","user":"root","port":18130}' [--json]
7101
+ embed tool call rp2350.monitor.status [--json]
7102
+ embed tool call rp2350.monitor.logic.capture --input-json '{"pin_base":16,"pin_count":4,"sample_rate":1000000,"samples":4096}' [--json]
7103
+ embed tool call rp2350.monitor.logic.decode --input-json '{"input_path":".embed-labs/rp2350-monitor/captures/logic.jsonl","decoder":"summary"}' [--json]
5348
7104
  embed deploy taishanpi --host <ip> --artifact <local_file> --approve [--remote-path /userdata/embed-labs/apps/app] [--run] [--json]
5349
7105
  embed board deploy taishanpi --host <ip> --artifact <local_file> --approve [--remote-path /userdata/embed-labs/apps/app] [--run] [--json]
5350
7106
  embed device list [--json]
@@ -5383,6 +7139,23 @@ Environment:
5383
7139
  CODEX_HOME=~/.codex
5384
7140
  `);
5385
7141
  }
7142
+ function printCliHelp(text) {
7143
+ console.log(formatCliHelp(text));
7144
+ }
7145
+ function formatCliHelp(text) {
7146
+ if (currentCommandName() !== "embedlabs") {
7147
+ return text;
7148
+ }
7149
+ return text
7150
+ .replace(/^embed CLI$/m, "embedlabs CLI")
7151
+ .replace(/^embed getting started$/m, "embedlabs getting started")
7152
+ .replace("After npm install, use the installed embed binary directly:", "After npm install, use the installed embedlabs binary directly:")
7153
+ .replace(/(^|\n)(\s*)embed(\s+)/g, "$1$2embedlabs$3");
7154
+ }
7155
+ function currentCommandName() {
7156
+ const invoked = basename(process.argv[1] ?? "");
7157
+ return invoked.startsWith("embedlabs") ? "embedlabs" : "embed";
7158
+ }
5386
7159
  main(process.argv.slice(2))
5387
7160
  .then((code) => {
5388
7161
  process.exitCode = code;