@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/README.md +47 -13
- package/dist/image-compose.d.ts +38 -0
- package/dist/image-compose.js +334 -0
- package/dist/image-compose.js.map +1 -0
- package/dist/index.js +1871 -98
- package/dist/index.js.map +1 -1
- package/dist/local-toolchain.d.ts +208 -0
- package/dist/local-toolchain.js +1019 -0
- package/dist/local-toolchain.js.map +1 -0
- package/package.json +5 -4
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
|
|
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:
|
|
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:
|
|
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
|
|
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
|
-
|
|
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 :
|
|
1460
|
+
body: body === undefined ? undefined : bodyText
|
|
1290
1461
|
});
|
|
1291
|
-
|
|
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-
|
|
1311
|
-
const opencodePackage = manifest?.data.packages?.find((item) => item.id === "opencode-
|
|
1312
|
-
const effectiveCodexPackage = effectiveManifest?.packages?.find((item) => item.id === "codex-
|
|
1313
|
-
const effectiveOpenCodePackage = effectiveManifest?.packages?.find((item) => item.id === "opencode-
|
|
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
|
|
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
|
|
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,
|
|
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:
|
|
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
|
|
1428
|
-
|
|
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
|
-
|
|
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:
|
|
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-
|
|
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-
|
|
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", "
|
|
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/
|
|
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", "
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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", "
|
|
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", "
|
|
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
|
-
"
|
|
2721
|
-
"
|
|
2722
|
-
"
|
|
2723
|
-
"
|
|
2724
|
-
"
|
|
2725
|
-
"
|
|
2726
|
-
"
|
|
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}. ${
|
|
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]}. ${
|
|
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
|
-
|
|
2740
|
-
|
|
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
|
-
|
|
2743
|
-
|
|
2744
|
-
return prompt.error;
|
|
3898
|
+
if (!dtb.value) {
|
|
3899
|
+
return BUILD_IMAGE_DTB_USAGE;
|
|
2745
3900
|
}
|
|
2746
|
-
|
|
2747
|
-
|
|
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
|
|
2754
|
-
if (
|
|
2755
|
-
return
|
|
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
|
|
2762
|
-
if (
|
|
2763
|
-
return
|
|
3914
|
+
const board = optionalTrimmedStringAliasFlag(parsed, ["board", "board-id"], "board or board-id");
|
|
3915
|
+
if (board.error) {
|
|
3916
|
+
return board.error;
|
|
2764
3917
|
}
|
|
2765
|
-
const
|
|
2766
|
-
if (
|
|
2767
|
-
return
|
|
3918
|
+
const variant = optionalTrimmedStringAliasFlag(parsed, ["variant", "variant-id"], "variant or variant-id");
|
|
3919
|
+
if (variant.error) {
|
|
3920
|
+
return variant.error;
|
|
2768
3921
|
}
|
|
2769
|
-
|
|
2770
|
-
|
|
3922
|
+
const inputFormat = optionalTrimmedStringFlag(parsed, "input-format");
|
|
3923
|
+
if (inputFormat.error) {
|
|
3924
|
+
return inputFormat.error;
|
|
2771
3925
|
}
|
|
2772
|
-
const
|
|
2773
|
-
if (
|
|
2774
|
-
return
|
|
3926
|
+
const outputPath = optionalTrimmedStringFlag(parsed, "output");
|
|
3927
|
+
if (outputPath.error) {
|
|
3928
|
+
return outputPath.error;
|
|
2775
3929
|
}
|
|
2776
|
-
|
|
2777
|
-
workspace_id: workspace.value,
|
|
3930
|
+
const body = {
|
|
2778
3931
|
account_id: account.value,
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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;
|