@kvell007/embed-labs-cli 0.1.0-alpha.28 → 0.1.0-alpha.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import { arch, hostname, homedir, platform, tmpdir } from "node:os";
8
8
  import { basename, delimiter, dirname, join, resolve } from "node:path";
9
9
  import { fileURLToPath } from "node:url";
10
10
  import { composeBootLogoPackage } from "./image-compose.js";
11
- import { buildTaishanPiQtSmoke, compileTaishanPiSingleFile, currentLocalToolchain, installLocalToolchain, latestLocalToolchain, validateLocalToolchain } from "./local-toolchain.js";
11
+ import { buildTaishanPiQtSmoke, compileTaishanPiSingleFile, currentLocalToolchain, installLocalToolchain, latestLocalToolchain, listLocalToolchainEnvironments, validateLocalToolchain } from "./local-toolchain.js";
12
12
  import { fail, ok } from "@embed-labs/protocol";
13
13
  const require = createRequire(import.meta.url);
14
14
  const CLI_MODULE_DIR = dirname(fileURLToPath(import.meta.url));
@@ -42,6 +42,8 @@ const LEGACY_CODEX_PLUGIN_NAMES = [
42
42
  ];
43
43
  const LEGACY_CODEX_MARKETPLACE_NAMES = new Set(["embed-labs-plugins", "plugins", "Plugins", "deve"]);
44
44
  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]";
45
+ const PLUGIN_UPDATE_CHECK_USAGE = "Usage: embed plugin update check [--release-url <url>] [--target <dir>] [--codex-target <dir>] [--opencode-target <dir>] [--json]";
46
+ const PLUGIN_UPDATE_USAGE = "Usage: embed plugin update <codex|opencode|all> [--release-url <url>] [--target <dir>] [--codex-target <dir>] [--opencode-target <dir>] [--json]";
45
47
  const CLOUD_TASK_ARTIFACTS_USAGE = "Usage: embed cloud task artifacts <task_id> [--json]";
46
48
  const CLOUD_TASK_EVIDENCE_USAGE = "Usage: embed cloud task evidence <task_id> [--json]";
47
49
  const ARTIFACT_STATUS_USAGE = "Usage: embed artifact status <artifact_id> [--json]";
@@ -95,6 +97,7 @@ const BUILD_IMAGE_BOOT_LOGO_USAGE = "Usage: embed build image boot-logo --logo <
95
97
  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]";
96
98
  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]";
97
99
  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]";
100
+ const LOCAL_TOOLCHAIN_LIST_USAGE = "Usage: embed local toolchain list [--board taishanpi-1m-rk3566] [--channel stable] [--metadata-root <path>] [--install-root <path>] [--json]";
98
101
  const LOCAL_TOOLCHAIN_LATEST_USAGE = "Usage: embed local toolchain latest [--board taishanpi-1m-rk3566] [--channel stable] [--metadata-root <path>] [--json]";
99
102
  const LOCAL_TOOLCHAIN_CURRENT_USAGE = "Usage: embed local toolchain current [--install-root <path>] [--json]";
100
103
  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.";
@@ -295,7 +298,18 @@ async function main(argv) {
295
298
  const result = await pluginInstall(parsed);
296
299
  return output(parsed, result, renderPluginInstall, result.ok ? 0 : 2);
297
300
  }
298
- return output(parsed, fail("invalid_args", [PLUGIN_LIST_USAGE, PLUGIN_INSTALL_USAGE].join("\n")), undefined, 2);
301
+ if (action === "update") {
302
+ if (parsed.command[2] === "check") {
303
+ const result = await pluginUpdateCheck(parsed);
304
+ return output(parsed, result, renderPluginUpdateCheck, result.ok ? 0 : 2);
305
+ }
306
+ if (["codex", "opencode", "all"].includes(parsed.command[2] ?? "")) {
307
+ const result = await pluginUpdate(parsed);
308
+ return output(parsed, result, renderPluginInstall, result.ok ? 0 : 2);
309
+ }
310
+ return output(parsed, fail("invalid_args", [PLUGIN_UPDATE_CHECK_USAGE, PLUGIN_UPDATE_USAGE].join("\n")), undefined, 2);
311
+ }
312
+ return output(parsed, fail("invalid_args", [PLUGIN_LIST_USAGE, PLUGIN_INSTALL_USAGE, PLUGIN_UPDATE_CHECK_USAGE, PLUGIN_UPDATE_USAGE].join("\n")), undefined, 2);
299
313
  }
300
314
  if (area === "auth" && action === "login") {
301
315
  const result = await authLogin(parsed);
@@ -557,6 +571,13 @@ async function main(argv) {
557
571
  return output(parsed, fail("invalid_args", [IMAGE_BOOT_LOGO_COMPOSE_USAGE, IMAGE_DTB_COMPOSE_USAGE].join("\n")), undefined, 2);
558
572
  }
559
573
  if (area === "local") {
574
+ if (action === "toolchain" && parsed.command[2] === "list") {
575
+ const request = localToolchainListRequest(parsed);
576
+ if (typeof request === "string") {
577
+ return output(parsed, fail("invalid_args", request), undefined, 2);
578
+ }
579
+ return output(parsed, ok(await listLocalToolchainEnvironments(request)), renderLocalToolchainList);
580
+ }
560
581
  if (action === "toolchain" && parsed.command[2] === "latest") {
561
582
  const request = localToolchainLatestRequest(parsed);
562
583
  if (typeof request === "string") {
@@ -600,6 +621,7 @@ async function main(argv) {
600
621
  return output(parsed, ok(await buildTaishanPiQtSmoke(request)), renderLocalCompileResult);
601
622
  }
602
623
  return output(parsed, fail("invalid_args", [
624
+ LOCAL_TOOLCHAIN_LIST_USAGE,
603
625
  LOCAL_TOOLCHAIN_LATEST_USAGE,
604
626
  LOCAL_TOOLCHAIN_CURRENT_USAGE,
605
627
  LOCAL_TOOLCHAIN_INSTALL_USAGE,
@@ -1834,6 +1856,96 @@ async function pluginInstall(parsed) {
1834
1856
  await rm(tempDir, { recursive: true, force: true });
1835
1857
  }
1836
1858
  }
1859
+ async function pluginUpdateCheck(parsed) {
1860
+ const unknownFlag = firstUnknownFlag(parsed, ["release-url", "target", "codex-target", "opencode-target", "json"]);
1861
+ if (unknownFlag) {
1862
+ return fail("invalid_args", `Unknown flag --${unknownFlag}. ${PLUGIN_UPDATE_CHECK_USAGE}`);
1863
+ }
1864
+ const unexpected = parsed.command.slice(3);
1865
+ if (unexpected.length > 0) {
1866
+ return fail("invalid_args", `Unexpected argument: ${unexpected[0]}. ${PLUGIN_UPDATE_CHECK_USAGE}`);
1867
+ }
1868
+ const remoteManifest = await fetchRemotePluginManifest(parsed);
1869
+ if (!remoteManifest.ok) {
1870
+ return remoteManifest;
1871
+ }
1872
+ const manifest = remoteManifest.data;
1873
+ const codexPackage = manifest.packages?.find((item) => item.id === "codex-embed-labs");
1874
+ const opencodePackage = manifest.packages?.find((item) => item.id === "opencode-embed-labs");
1875
+ const codexTarget = join(codexPluginTargetRoot(parsed, true), CODEX_PLUGIN_NAME);
1876
+ const openCodeTarget = openCodePluginTargetRoot(parsed, true);
1877
+ return ok({
1878
+ release_url: pluginReleaseBaseUrl(parsed),
1879
+ latest_version: manifest.version,
1880
+ plugins: [
1881
+ await pluginUpdateItem({
1882
+ id: "codex",
1883
+ displayName: "Embed Labs Codex plugin",
1884
+ targetPath: codexTarget,
1885
+ installedVersion: await installedCodexPluginVersion(codexTarget),
1886
+ latestVersion: codexPackage?.version ?? manifest.version,
1887
+ releaseFile: codexPackage?.file,
1888
+ updateCommand: "embedlabs plugin update codex"
1889
+ }),
1890
+ await pluginUpdateItem({
1891
+ id: "opencode",
1892
+ displayName: "Embed Labs OpenCode plugin",
1893
+ targetPath: openCodeTarget,
1894
+ installedVersion: await installedOpenCodePluginVersion(openCodeTarget),
1895
+ latestVersion: opencodePackage?.version ?? manifest.version,
1896
+ releaseFile: opencodePackage?.file,
1897
+ updateCommand: "embedlabs plugin update opencode"
1898
+ })
1899
+ ]
1900
+ });
1901
+ }
1902
+ async function pluginUpdate(parsed) {
1903
+ const unknownFlag = firstUnknownFlag(parsed, ["release-url", "target", "codex-target", "opencode-target", "json"]);
1904
+ if (unknownFlag) {
1905
+ return fail("invalid_args", `Unknown flag --${unknownFlag}. ${PLUGIN_UPDATE_USAGE}`);
1906
+ }
1907
+ const target = parsed.command[2];
1908
+ if (!target || !["codex", "opencode", "all"].includes(target)) {
1909
+ return fail("invalid_args", PLUGIN_UPDATE_USAGE);
1910
+ }
1911
+ const unexpected = parsed.command.slice(3);
1912
+ if (unexpected.length > 0) {
1913
+ return fail("invalid_args", `Unexpected argument: ${unexpected[0]}. ${PLUGIN_UPDATE_USAGE}`);
1914
+ }
1915
+ const installParsed = {
1916
+ ...parsed,
1917
+ command: ["plugin", "install", target],
1918
+ flags: { ...parsed.flags, force: true }
1919
+ };
1920
+ return await pluginInstall(installParsed);
1921
+ }
1922
+ async function pluginUpdateItem(input) {
1923
+ const installed = !!input.installedVersion;
1924
+ const updateAvailable = !!input.latestVersion && input.installedVersion !== input.latestVersion;
1925
+ const notes = [];
1926
+ if (!installed) {
1927
+ notes.push("Plugin is not installed in the selected target.");
1928
+ }
1929
+ else if (updateAvailable) {
1930
+ notes.push("A newer plugin release is available. Run the update command, then restart Codex/OpenCode.");
1931
+ }
1932
+ else {
1933
+ notes.push("Plugin is up to date for the selected release channel.");
1934
+ }
1935
+ notes.push("Release manifest does not yet carry detailed changelog entries; use the release version and package files as the update summary.");
1936
+ return {
1937
+ id: input.id,
1938
+ display_name: input.displayName,
1939
+ installed,
1940
+ installed_version: input.installedVersion,
1941
+ latest_version: input.latestVersion,
1942
+ update_available: updateAvailable,
1943
+ target_path: input.targetPath,
1944
+ release_file: input.releaseFile,
1945
+ update_command: input.updateCommand,
1946
+ notes
1947
+ };
1948
+ }
1837
1949
  async function installCodexPlugin(parsed, context) {
1838
1950
  const source = await resolveCodexPluginSource(context);
1839
1951
  if (!source.ok) {
@@ -3055,6 +3167,21 @@ async function localPluginVersion(kind) {
3055
3167
  return undefined;
3056
3168
  }
3057
3169
  }
3170
+ async function installedCodexPluginVersion(pluginPath) {
3171
+ return await readPackageVersion(join(pluginPath, ".codex-plugin", "plugin.json"));
3172
+ }
3173
+ async function installedOpenCodePluginVersion(targetRoot) {
3174
+ return await readPackageVersion(join(targetRoot, "node_modules", "embed-labs", "package.json"));
3175
+ }
3176
+ async function readPackageVersion(filePath) {
3177
+ try {
3178
+ const parsed = JSON.parse(await readFile(filePath, "utf8"));
3179
+ return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version.trim() : undefined;
3180
+ }
3181
+ catch {
3182
+ return undefined;
3183
+ }
3184
+ }
3058
3185
  async function localPluginSourcesAvailable() {
3059
3186
  return await pathExists(sourceCheckoutPath("platform_plugins", "codex_plugin", "plugins", "embed-labs", ".codex-plugin", "plugin.json"))
3060
3187
  && await pathExists(sourceCheckoutPath("platform_plugins", "opencode_plugin", "package.json"));
@@ -5460,6 +5587,29 @@ function billingSnapshotListRequest(parsed) {
5460
5587
  }
5461
5588
  return { path: `/v1/accounts/${encodeURIComponent(accountResult.value)}/billing/snapshots` };
5462
5589
  }
5590
+ function localToolchainListRequest(parsed) {
5591
+ const unknownFlag = firstUnknownFlag(parsed, ["json", "board", "board-id", "channel", "metadata-root", "install-root"]);
5592
+ if (unknownFlag) {
5593
+ return `Unknown flag --${unknownFlag}. ${LOCAL_TOOLCHAIN_LIST_USAGE}`;
5594
+ }
5595
+ const extra = parsed.command.slice(3);
5596
+ if (extra.length > 0) {
5597
+ return `Unexpected argument: ${extra[0]}. ${LOCAL_TOOLCHAIN_LIST_USAGE}`;
5598
+ }
5599
+ const board = optionalTrimmedStringAliasFlag(parsed, ["board", "board-id"], "board or board-id");
5600
+ if (board.error)
5601
+ return board.error;
5602
+ const channel = optionalTrimmedStringFlag(parsed, "channel");
5603
+ if (channel.error)
5604
+ return channel.error;
5605
+ const metadataRoot = optionalTrimmedStringFlag(parsed, "metadata-root");
5606
+ if (metadataRoot.error)
5607
+ return metadataRoot.error;
5608
+ const installRoot = optionalTrimmedStringFlag(parsed, "install-root");
5609
+ if (installRoot.error)
5610
+ return installRoot.error;
5611
+ return { boardId: board.value, channel: channel.value, metadataRoot: metadataRoot.value, installRoot: installRoot.value };
5612
+ }
5463
5613
  function localToolchainLatestRequest(parsed) {
5464
5614
  const unknownFlag = firstUnknownFlag(parsed, ["json", "board", "board-id", "channel", "metadata-root"]);
5465
5615
  if (unknownFlag) {
@@ -6052,6 +6202,29 @@ function renderPluginList(result) {
6052
6202
  `install="${plugin.install_command}"`
6053
6203
  ].filter(Boolean).join(" ")).join("\n");
6054
6204
  }
6205
+ function renderPluginUpdateCheck(result) {
6206
+ const lines = [
6207
+ `release_url=${result.release_url}`,
6208
+ result.latest_version ? `latest_version=${result.latest_version}` : ""
6209
+ ].filter(Boolean);
6210
+ for (const plugin of result.plugins) {
6211
+ lines.push("");
6212
+ lines.push(`${plugin.display_name} (${plugin.id})`);
6213
+ lines.push(` installed=${plugin.installed}`);
6214
+ lines.push(` installed_version=${plugin.installed_version ?? "none"}`);
6215
+ lines.push(` latest_version=${plugin.latest_version ?? "unknown"}`);
6216
+ lines.push(` update_available=${plugin.update_available}`);
6217
+ lines.push(` target=${plugin.target_path}`);
6218
+ lines.push(` update_command=${plugin.update_command}`);
6219
+ if (plugin.release_file) {
6220
+ lines.push(` release_file=${plugin.release_file}`);
6221
+ }
6222
+ for (const note of plugin.notes) {
6223
+ lines.push(` note=${note}`);
6224
+ }
6225
+ }
6226
+ return lines.join("\n");
6227
+ }
6055
6228
  function renderPluginInstall(result) {
6056
6229
  const lines = ["Installed plugins:"];
6057
6230
  for (const item of result.installed) {
@@ -6746,6 +6919,42 @@ function renderBuildWorkspaceSourcePatch(result) {
6746
6919
  }
6747
6920
  return lines.join("\n");
6748
6921
  }
6922
+ function renderLocalToolchainList(result) {
6923
+ const lines = [
6924
+ `Local development environments: ${result.environments.length}`,
6925
+ `host=${result.host}`,
6926
+ `channel=${result.channel}`,
6927
+ result.metadata_root ? `metadata_root=${result.metadata_root}` : "metadata=built-in",
6928
+ `install_root=${result.install_root}`,
6929
+ `registry=${result.registry_path}`
6930
+ ];
6931
+ for (const environment of result.environments) {
6932
+ lines.push("");
6933
+ lines.push(`${environment.display_name} (${environment.board_id})`);
6934
+ lines.push(` status=${environment.status}`);
6935
+ lines.push(` latest=${environment.latest.version}`);
6936
+ if (environment.installed) {
6937
+ lines.push(` installed=${environment.installed.version ?? "unknown"} mode=${environment.installed.mode ?? "unknown"}`);
6938
+ if (environment.installed.release_root) {
6939
+ lines.push(` release_root=${environment.installed.release_root}`);
6940
+ }
6941
+ }
6942
+ lines.push(` install_modes=${environment.install_modes.join(",")}`);
6943
+ lines.push(` install_command=${environment.install_command}`);
6944
+ if (environment.status === "update_available") {
6945
+ lines.push(` update_command=${environment.update_command}`);
6946
+ }
6947
+ if (environment.components?.length) {
6948
+ lines.push(` components=${environment.components.map((component) => `${component.id}@${component.version}`).join(", ")}`);
6949
+ }
6950
+ if (environment.notes.length > 0) {
6951
+ for (const note of environment.notes) {
6952
+ lines.push(` note=${note}`);
6953
+ }
6954
+ }
6955
+ }
6956
+ return lines.join("\n");
6957
+ }
6749
6958
  function renderLocalToolchainLatest(result) {
6750
6959
  const lines = [
6751
6960
  `board=${result.board_id}`,
@@ -7603,12 +7812,14 @@ Main workflow:
7603
7812
  3. Inspect server model routing:
7604
7813
  embed plugin install codex
7605
7814
  embed plugin install opencode
7815
+ embed plugin update check
7606
7816
  embed service modes
7607
7817
  embed model list
7608
7818
  embed model default
7609
7819
  4. Run a natural-language local tool loop:
7610
7820
  embed agent run --prompt "验证开发板状态"
7611
7821
  5. Validate or use the local TaishanPi toolchain:
7822
+ embed local toolchain list
7612
7823
  embed local toolchain latest
7613
7824
  embed local toolchain install
7614
7825
  embed local toolchain validate
@@ -7657,6 +7868,7 @@ Local hardware:
7657
7868
  embed tool call chip.temperature --input-json '{"host":"198.19.77.2","user":"root"}'
7658
7869
  embed tool call qml.runtime.status --input-json '{"host":"198.19.77.2","user":"root","port":18130}'
7659
7870
  embed device list
7871
+ embed local toolchain list
7660
7872
  embed local toolchain latest
7661
7873
  embed local toolchain current
7662
7874
  embed local toolchain install
@@ -7728,6 +7940,8 @@ Install local AI client plugins explicitly:
7728
7940
  embed plugin list
7729
7941
  embed plugin install codex
7730
7942
  embed plugin install opencode
7943
+ embed plugin update check
7944
+ embed plugin update all
7731
7945
 
7732
7946
  Cloud build path:
7733
7947
 
@@ -7806,6 +8020,8 @@ Usage:
7806
8020
  embed auth logout [--json]
7807
8021
  embed plugin list [--release-dir <dir>] [--release-url <url>] [--json]
7808
8022
  embed plugin install <codex|opencode|all> [--release-dir <dir>] [--release-url <url>] [--target <dir>] [--codex-target <dir>] [--opencode-target <dir>] [--force] [--json]
8023
+ embed plugin update check [--release-url <url>] [--target <dir>] [--codex-target <dir>] [--opencode-target <dir>] [--json]
8024
+ embed plugin update <codex|opencode|all> [--release-url <url>] [--target <dir>] [--codex-target <dir>] [--opencode-target <dir>] [--json]
7809
8025
  embed service modes [--json]
7810
8026
  embed model list [--json]
7811
8027
  embed model default [--json]
@@ -7861,6 +8077,7 @@ Usage:
7861
8077
  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]
7862
8078
  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]
7863
8079
  embed image boot-logo compose --package <boot-logo-package.json> --base-image <boot.img|image.img> --output <image> [--manifest <manifest.json>] [--force] [--json]
8080
+ embed local toolchain list [--board taishanpi-1m-rk3566] [--channel stable] [--metadata-root <path>] [--install-root <path>] [--json]
7864
8081
  embed local toolchain latest [--board taishanpi-1m-rk3566] [--channel stable] [--metadata-root <path>] [--json]
7865
8082
  embed local toolchain current [--install-root <path>] [--json]
7866
8083
  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]