@kvell007/embed-labs-cli 0.1.0-alpha.60 → 0.1.0-alpha.61

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
@@ -33,6 +33,7 @@ const DEFAULT_PLUGIN_RELEASE_URL = process.env.EMBED_PLUGIN_RELEASE_URL?.trim()
33
33
  const PLUGIN_LIST_USAGE = "Usage: embed plugin list [--release-dir <dir>] [--release-url <url>] [--json]";
34
34
  const CODEX_PLUGIN_NAME = "embed-labs";
35
35
  const CODEX_MARKETPLACE_NAME = "embed-labs";
36
+ const MCP_START_USAGE = "Usage: embed mcp start [--bridge-path <path>]";
36
37
  const LEGACY_CODEX_PLUGIN_NAMES = [
37
38
  "dbt-agent",
38
39
  "Dbt Agent",
@@ -180,6 +181,12 @@ async function main(argv) {
180
181
  if (area === "bridge" && action === "status") {
181
182
  return output(parsed, await bridgeGet("/healthz"), renderBridgeStatus);
182
183
  }
184
+ if (area === "mcp" && action === "start") {
185
+ return await runMcpStart(parsed);
186
+ }
187
+ if (area === "mcp") {
188
+ return output(parsed, fail("invalid_args", MCP_START_USAGE), undefined, 2);
189
+ }
183
190
  if (area === "cloud" && action === "status") {
184
191
  return output(parsed, await cloudGet("/healthz"), renderCloudStatus);
185
192
  }
@@ -411,6 +418,9 @@ async function main(argv) {
411
418
  ACCOUNT_KEY_REVOKE_USAGE
412
419
  ].join("\n")), undefined, 2);
413
420
  }
421
+ if (area === "usage" || area === "billing") {
422
+ return output(parsed, quotaAndBillingDisabled(), undefined, 2);
423
+ }
414
424
  if (area === "usage") {
415
425
  if (action === "record") {
416
426
  const body = usageRecordBody(parsed);
@@ -2774,7 +2784,7 @@ async function maybeRegisterCodexMcp(parsed, targetRoot, targetPath) {
2774
2784
  if (explicitTarget && process.env.EMBED_CODEX_MCP_REGISTER !== "1") {
2775
2785
  return {
2776
2786
  registered: false,
2777
- hint: `Installed into a custom target. Register manually with: codex mcp add embed-labs -- node ${join(targetPath, "scripts", "embed-labs-mcp-bridge.mjs")}`
2787
+ hint: "Installed into a custom target. Register manually with: codex mcp add embed-labs -- embedlabs mcp start"
2778
2788
  };
2779
2789
  }
2780
2790
  const bridgePath = join(targetPath, "scripts", "embed-labs-mcp-bridge.mjs");
@@ -2793,11 +2803,12 @@ async function maybeRegisterCodexMcp(parsed, targetRoot, targetPath) {
2793
2803
  };
2794
2804
  }
2795
2805
  const embedCliBin = process.env.EMBED_CLI_BIN?.trim() || await resolveExecutableOnPath("embedlabs") || await resolveExecutableOnPath("embed") || "";
2806
+ const mcpLauncher = await resolveEmbedCliMcpLauncher(embedCliBin);
2796
2807
  const authFile = resolve(process.env.EMBED_AUTH_FILE?.trim() || DEFAULT_AUTH_FILE);
2797
2808
  const cloudUrl = pluginMcpCloudApiUrl(parsed);
2798
2809
  const existing = await runLocalProcess(codexBin, ["mcp", "get", "embed-labs", "--json"]);
2799
- if (existing.code === 0 && codexMcpAlreadyRegistered(existing.stdout, bridgePath, cloudUrl, authFile, embedCliBin)) {
2800
- const warning = await upsertCodexMcpRuntimeConfig(bridgePath);
2810
+ if (existing.code === 0 && codexMcpAlreadyRegistered(existing.stdout, bridgePath, cloudUrl, authFile, embedCliBin, mcpLauncher.command, mcpLauncher.args)) {
2811
+ const warning = await upsertCodexMcpRuntimeConfig(mcpLauncher.command, mcpLauncher.args);
2801
2812
  if (warning) {
2802
2813
  return { registered: true, warning };
2803
2814
  }
@@ -2811,21 +2822,23 @@ async function maybeRegisterCodexMcp(parsed, targetRoot, targetPath) {
2811
2822
  "--env",
2812
2823
  `EMBED_CLOUD_API_URL=${cloudUrl}`,
2813
2824
  "--env",
2814
- `EMBED_AUTH_FILE=${authFile}`
2825
+ `EMBED_AUTH_FILE=${authFile}`,
2826
+ "--env",
2827
+ `EMBED_MCP_BRIDGE_PATH=${bridgePath}`
2815
2828
  ];
2816
2829
  if (embedCliBin) {
2817
2830
  args.push("--env", `EMBED_CLI_BIN=${embedCliBin}`);
2818
2831
  }
2819
- args.push("--", process.execPath, bridgePath);
2832
+ args.push("--", mcpLauncher.command, ...mcpLauncher.args);
2820
2833
  const addResult = await runLocalProcess(codexBin, args);
2821
2834
  if (addResult.code !== 0) {
2822
2835
  return {
2823
2836
  registered: false,
2824
- hint: `Codex plugin installed. Register manually with: codex mcp add embed-labs -- ${process.execPath} ${bridgePath}`,
2837
+ hint: "Codex plugin installed. Register manually with: codex mcp add embed-labs -- embedlabs mcp start",
2825
2838
  warning: `codex mcp add failed: ${addResult.stderr.trim() || addResult.stdout.trim() || `exit ${addResult.code}`}`
2826
2839
  };
2827
2840
  }
2828
- const warning = await upsertCodexMcpRuntimeConfig(bridgePath);
2841
+ const warning = await upsertCodexMcpRuntimeConfig(mcpLauncher.command, mcpLauncher.args);
2829
2842
  return warning ? { registered: true, warning } : { registered: true };
2830
2843
  }
2831
2844
  async function maybeRegisterCodexMarketplace(parsed, targetRoot, targetPath) {
@@ -2937,14 +2950,24 @@ async function upsertCodexPluginMarketplaceConfig(marketplacePath) {
2937
2950
  return `${configPath} could not be updated with the Embed Labs plugin marketplace entry: ${error instanceof Error ? error.message : String(error)}`;
2938
2951
  }
2939
2952
  }
2940
- function codexMcpAlreadyRegistered(stdout, bridgePath, cloudUrl, authFile, embedCliBin) {
2953
+ async function resolveEmbedCliMcpLauncher(embedCliBin) {
2954
+ const explicit = embedCliBin?.trim();
2955
+ if (explicit) {
2956
+ return { command: explicit, args: ["mcp", "start"] };
2957
+ }
2958
+ const pathBin = await resolveExecutableOnPath("embedlabs") || await resolveExecutableOnPath("embed");
2959
+ if (pathBin) {
2960
+ return { command: pathBin, args: ["mcp", "start"] };
2961
+ }
2962
+ return { command: process.execPath, args: [join(CLI_MODULE_DIR, "index.js"), "mcp", "start"] };
2963
+ }
2964
+ function codexMcpAlreadyRegistered(stdout, bridgePath, cloudUrl, authFile, embedCliBin, mcpCommand, mcpArgs) {
2941
2965
  try {
2942
2966
  const parsed = JSON.parse(stdout);
2943
2967
  const transport = parsed.transport;
2944
- if (transport?.type !== "stdio" || transport.command !== process.execPath)
2945
- return false;
2946
- if (!transport.args?.includes(bridgePath))
2968
+ if (transport?.type !== "stdio")
2947
2969
  return false;
2970
+ const args = transport.args || [];
2948
2971
  const env = transport.env || {};
2949
2972
  if (env.EMBED_CLOUD_API_URL !== cloudUrl)
2950
2973
  return false;
@@ -2952,13 +2975,16 @@ function codexMcpAlreadyRegistered(stdout, bridgePath, cloudUrl, authFile, embed
2952
2975
  return false;
2953
2976
  if (embedCliBin && env.EMBED_CLI_BIN !== embedCliBin)
2954
2977
  return false;
2955
- return true;
2978
+ const directBridge = transport.command === process.execPath && args.includes(bridgePath);
2979
+ const cliMcp = transport.command === mcpCommand
2980
+ && mcpArgs.every((arg, index) => args[index] === arg);
2981
+ return directBridge || cliMcp;
2956
2982
  }
2957
2983
  catch {
2958
2984
  return false;
2959
2985
  }
2960
2986
  }
2961
- async function upsertCodexMcpRuntimeConfig(bridgePath) {
2987
+ async function upsertCodexMcpRuntimeConfig(command, args) {
2962
2988
  const configPath = join(process.env.CODEX_HOME?.trim() || join(homedir(), ".codex"), "config.toml");
2963
2989
  try {
2964
2990
  await mkdir(dirname(configPath), { recursive: true });
@@ -2970,8 +2996,8 @@ async function upsertCodexMcpRuntimeConfig(bridgePath) {
2970
2996
  text = "";
2971
2997
  }
2972
2998
  const updated = upsertTomlTableKeys(text, "mcp_servers.embed-labs", {
2973
- command: tomlString(process.execPath),
2974
- args: `[${tomlString(bridgePath)}]`,
2999
+ command: tomlString(command),
3000
+ args: `[${args.map(tomlString).join(", ")}]`,
2975
3001
  startup_timeout_sec: "120"
2976
3002
  });
2977
3003
  if (updated !== text) {
@@ -3094,6 +3120,75 @@ async function runBridgeStart(parsed) {
3094
3120
  });
3095
3121
  });
3096
3122
  }
3123
+ async function runMcpStart(parsed) {
3124
+ const unknownFlag = firstUnknownFlag(parsed, ["bridge-path"]);
3125
+ const unexpected = parsed.command.slice(2);
3126
+ if (unknownFlag || unexpected.length > 0) {
3127
+ console.error(unknownFlag ? `Unknown flag --${unknownFlag}. ${MCP_START_USAGE}` : MCP_START_USAGE);
3128
+ return 2;
3129
+ }
3130
+ const bridge = await resolveMcpBridgeLauncher(stringFlag(parsed, "bridge-path"));
3131
+ if (!bridge) {
3132
+ console.error([
3133
+ "Embed Labs MCP bridge was not found.",
3134
+ "Run npm run build in the source checkout, reinstall the embedlabs npm package, or set EMBED_MCP_BRIDGE_PATH to the bridge script.",
3135
+ "Expected command shape for MCP clients: embedlabs mcp start"
3136
+ ].join("\n"));
3137
+ return 1;
3138
+ }
3139
+ const child = spawn(bridge.command, bridge.args, {
3140
+ stdio: "inherit",
3141
+ env: {
3142
+ ...process.env,
3143
+ EMBED_CLIENT_KIND: process.env.EMBED_CLIENT_KIND || "mcp_client",
3144
+ EMBED_MCP_BRIDGE_PATH: bridge.bridgePath
3145
+ }
3146
+ });
3147
+ const forwardSignal = (signal) => {
3148
+ if (!child.killed) {
3149
+ child.kill(signal);
3150
+ }
3151
+ };
3152
+ process.once("SIGINT", forwardSignal);
3153
+ process.once("SIGTERM", forwardSignal);
3154
+ return await new Promise((resolveCode) => {
3155
+ child.on("error", (error) => {
3156
+ process.off("SIGINT", forwardSignal);
3157
+ process.off("SIGTERM", forwardSignal);
3158
+ console.error(error instanceof Error ? error.message : String(error));
3159
+ resolveCode(1);
3160
+ });
3161
+ child.on("close", (code, signal) => {
3162
+ process.off("SIGINT", forwardSignal);
3163
+ process.off("SIGTERM", forwardSignal);
3164
+ if (signal === "SIGINT" || signal === "SIGTERM") {
3165
+ resolveCode(0);
3166
+ }
3167
+ else {
3168
+ resolveCode(code ?? 0);
3169
+ }
3170
+ });
3171
+ });
3172
+ }
3173
+ async function resolveMcpBridgeLauncher(overridePath) {
3174
+ const explicitPath = overridePath?.trim() || process.env.EMBED_MCP_BRIDGE_PATH?.trim();
3175
+ const candidates = [
3176
+ explicitPath ? resolve(explicitPath) : "",
3177
+ join(CLI_MODULE_DIR, "embed-labs-mcp-bridge.mjs"),
3178
+ sourceCheckoutPath("platform_plugins", "codex_plugin", "plugins", "embed-labs", "scripts", "embed-labs-mcp-bridge.mjs"),
3179
+ join(defaultCodexPluginRoot(), CODEX_PLUGIN_NAME, "scripts", "embed-labs-mcp-bridge.mjs")
3180
+ ].filter(Boolean);
3181
+ for (const candidate of candidates) {
3182
+ try {
3183
+ await access(candidate, constants.R_OK);
3184
+ return { command: process.execPath, args: [candidate], bridgePath: candidate };
3185
+ }
3186
+ catch {
3187
+ // Keep looking.
3188
+ }
3189
+ }
3190
+ return undefined;
3191
+ }
3097
3192
  async function resolveBridgeLauncher() {
3098
3193
  const explicitBinary = process.env.EMBED_LOCAL_BRIDGE_BINARY?.trim();
3099
3194
  if (explicitBinary) {
@@ -3387,9 +3482,16 @@ async function naturalLanguageQuery(parsed) {
3387
3482
  }
3388
3483
  const text = parsed.command.slice(1).join(" ").trim();
3389
3484
  if (!text) {
3390
- return { response: fail("invalid_args", QUERY_USAGE, { remediation: "Try: embed query \"查一下我的额度\"." }) };
3485
+ return { response: fail("invalid_args", QUERY_USAGE, { remediation: "Try: embed query \"当前支持哪些开发板\"." }) };
3391
3486
  }
3392
3487
  const normalized = text.toLowerCase();
3488
+ if (isQuotaOrBillingIntent(normalized)) {
3489
+ return {
3490
+ response: fail("quota_not_supported", "当前 Embed Labs 服务器只提供 MCP 服务,不提供额度、充值、余额、账本或存储配额功能。", {
3491
+ remediation: "请使用 embedlabs auth status 查看登录和设备绑定状态,或在 MCP 客户端中直接调用开发板、工具链、知识库和本地硬件相关工具。"
3492
+ })
3493
+ };
3494
+ }
3393
3495
  const accountId = await queryAccountId(parsed);
3394
3496
  const needsAccount = queryNeedsAccount(normalized);
3395
3497
  if (needsAccount && !accountId.ok) {
@@ -3408,7 +3510,7 @@ async function naturalLanguageQuery(parsed) {
3408
3510
  if (!amountUsd) {
3409
3511
  return {
3410
3512
  response: fail("amount_required", "Recharge requests need an amount.", {
3411
- remediation: "Try: embed query \"用 USDC 链上充值 100 美元\"."
3513
+ remediation: "当前 MCP 服务不提供充值流程。"
3412
3514
  })
3413
3515
  };
3414
3516
  }
@@ -3501,13 +3603,8 @@ async function naturalLanguageQuery(parsed) {
3501
3603
  response: fail("query_intent_unknown", "I could not map that request to a stable CLI action yet.", {
3502
3604
  remediation: [
3503
3605
  "Supported examples:",
3504
- "embed query \"查一下我的额度\"",
3505
- "embed query \"用 USDC 链上充值 100 美元\"",
3506
- "embed query \"看一下 token 用量\"",
3507
- "embed query \"看一下磁盘空间\"",
3508
3606
  "embed query \"列出我的 API key\"",
3509
3607
  "embed query \"有哪些开发板模板\"",
3510
- "embed query \"当前可以用哪些模型\"",
3511
3608
  "embed query \"本地有哪些工具能力\""
3512
3609
  ].join("\n")
3513
3610
  })
@@ -3858,13 +3955,15 @@ async function queryAccountId(parsed) {
3858
3955
  return { ok: true, value: auth.data.account_id };
3859
3956
  }
3860
3957
  function queryNeedsAccount(normalized) {
3958
+ return isApiKeyIntent(normalized);
3959
+ }
3960
+ function isQuotaOrBillingIntent(normalized) {
3861
3961
  return isRechargeIntent(normalized)
3862
3962
  || isBalanceIntent(normalized)
3863
3963
  || isUsageIntent(normalized)
3864
3964
  || isBillingStatementIntent(normalized)
3865
3965
  || isStorageSettlementIntent(normalized)
3866
- || isStorageIntent(normalized)
3867
- || isApiKeyIntent(normalized);
3966
+ || isStorageIntent(normalized);
3868
3967
  }
3869
3968
  function isRechargeIntent(normalized) {
3870
3969
  return /(充值|充钱|续费|购买额度|买额度|top\s*up|recharge|add credits|buy credits)/i.test(normalized);
@@ -7157,7 +7256,7 @@ function renderLocalToolchainList(result) {
7157
7256
  }
7158
7257
  lines.push(` mode_downloads=${localToolchainModeSummaries(environment.install_modes, environment.components).join("; ")}`);
7159
7258
  lines.push(` package_groups=${localToolchainComponentGroups(environment.components).join(", ")}`);
7160
- lines.push(` detail_command=embedlabs local toolchain latest --board ${environment.board_id}`);
7259
+ lines.push(` detail_command=embedlabs local toolchain latest --board ${environment.board_id}${result.channel === "stable" ? "" : ` --channel ${result.channel}`}`);
7161
7260
  }
7162
7261
  if (environment.notes.length > 0) {
7163
7262
  for (const note of environment.notes) {
@@ -8196,12 +8295,17 @@ async function waitForever() {
8196
8295
  });
8197
8296
  return 0;
8198
8297
  }
8298
+ function quotaAndBillingDisabled() {
8299
+ return fail("quota_not_supported", "当前 Embed Labs 服务器只提供 MCP 服务,不提供额度、充值、余额、账本、存储配额或用量计费功能。", {
8300
+ remediation: "请使用 auth、plugin、board、local toolchain、mcp start、device 和 Local Bridge 相关命令。"
8301
+ });
8302
+ }
8199
8303
  function printHelp() {
8200
8304
  printCliHelp(`embed CLI
8201
8305
 
8202
8306
  Usage:
8203
8307
  embed <command> [options]
8204
- embed query "查一下我的额度"
8308
+ embed query "当前支持哪些开发板"
8205
8309
  embed help getting-started
8206
8310
  embed help commands
8207
8311
 
@@ -8211,13 +8315,10 @@ Main workflow:
8211
8315
  2. Sign in with a token:
8212
8316
  embed auth login --token <token>
8213
8317
  # or set EMBED_API_TOKEN / create an account API key for automation.
8214
- 3. Inspect server model routing:
8318
+ 3. Install or update local AI client plugins:
8215
8319
  embed plugin install codex
8216
8320
  embed plugin install opencode
8217
8321
  embed plugin update check
8218
- embed service modes
8219
- embed model list
8220
- embed model default
8221
8322
  4. Run a natural-language local tool loop:
8222
8323
  embed agent run --prompt "验证开发板状态"
8223
8324
  5. Validate or use the local TaishanPi toolchain:
@@ -8242,16 +8343,9 @@ Main workflow:
8242
8343
  embed image boot-logo compose --package ./boot-logo-package.json --base-image ./boot.img --output ./boot-logo.img
8243
8344
  embed cloud task artifacts <task_id>
8244
8345
  embed artifact download <artifact_id> --output ./artifact.bin
8245
- 8. Check credits or create a recharge QR:
8246
- embed billing balance --account <account_id>
8247
- embed billing tokens --account <account_id>
8248
- embed billing ledger --account <account_id>
8249
- embed billing storage --account <account_id>
8250
- embed billing storage settle --account <account_id> --dry-run
8251
- embed billing recharge create --account <account_id> --amount-usd 10 --provider onchain --qr
8252
-
8253
8346
  Local hardware:
8254
8347
  embed bridge start
8348
+ embed mcp start
8255
8349
  embed agent run --prompt "验证开发板状态"
8256
8350
  embed agent run --prompt "部署泰山派应用" --host 198.19.77.2 --artifact ./artifact.bin --remote-path /userdata/embed-labs/apps/app --approve --run
8257
8351
  embed agent run --prompt "部署生成的泰山派应用" --host 198.19.77.2 --artifact-task <task_id> --remote-path /userdata/embed-labs/apps/app --approve --run
@@ -8287,11 +8381,9 @@ Environment:
8287
8381
  CODEX_HOME=~/.codex
8288
8382
 
8289
8383
  Natural language:
8290
- embed query "查一下我的额度"
8291
- embed query "用 USDC 链上充值 100 美元"
8292
- embed query "看一下 token 用量"
8293
- embed query "看一下磁盘空间"
8294
- embed query "结算磁盘空间扣费"
8384
+ embed query "当前支持哪些开发板"
8385
+ embed query "验证开发板状态"
8386
+ embed query "安装泰山派本地开发环境"
8295
8387
  `);
8296
8388
  }
8297
8389
  function printHelpTopic(topic) {
@@ -8340,11 +8432,8 @@ Install local AI client plugins explicitly:
8340
8432
  embed plugin update check
8341
8433
  embed plugin update all
8342
8434
 
8343
- Cloud build path:
8435
+ Local MCP service path:
8344
8436
 
8345
- embed service modes
8346
- embed model list
8347
- embed model default
8348
8437
  embed agent run --prompt "验证开发板状态"
8349
8438
  embed local toolchain validate
8350
8439
  embed local compile taishanpi --source ./main.c --output ./.embed-labs/build/main
@@ -8373,25 +8462,14 @@ Cloud build path:
8373
8462
 
8374
8463
  Natural language shortcuts:
8375
8464
 
8376
- embed query "查一下我的额度"
8377
- embed query "用 USDC 链上充值 100 美元"
8378
- embed query "看一下 token 用量"
8379
- embed query "看一下磁盘空间"
8380
-
8381
- Billing path:
8382
-
8383
- embed billing balance --account <account_id>
8384
- embed billing tokens --account <account_id>
8385
- embed billing ledger --account <account_id>
8386
- embed billing storage --account <account_id>
8387
- embed billing storage settle --account <account_id> --dry-run
8388
- embed billing recharge create --account <account_id> --amount-usd 10 --provider onchain --qr
8389
- embed billing recharge submit-tx <recharge_session_id> --tx-hash <hash>
8390
- embed billing recharge list --account <account_id>
8465
+ embed query "当前支持哪些开发板"
8466
+ embed query "验证开发板状态"
8467
+ embed query "安装泰山派本地开发环境"
8391
8468
 
8392
8469
  Local hardware path:
8393
8470
 
8394
8471
  embed bridge start
8472
+ embed mcp start
8395
8473
  embed run "验证开发板状态"
8396
8474
  embed tool list
8397
8475
  embed tool call debug.tools.scan
@@ -8411,6 +8489,7 @@ Usage:
8411
8489
  embed query <natural language request> [--account <account_id>] [--qr] [--json]
8412
8490
  embed bridge start [--host 127.0.0.1] [--port 18083]
8413
8491
  embed bridge status [--json]
8492
+ embed mcp start [--bridge-path <path>]
8414
8493
  embed auth login --token <token> [--profile default] [--json]
8415
8494
  embed auth status [--json]
8416
8495
  embed auth logout [--json]
@@ -8436,23 +8515,6 @@ Usage:
8436
8515
  embed account keys create --account <account_id> [--name <name>] [--json]
8437
8516
  embed account keys list --account <account_id> [--json]
8438
8517
  embed account keys revoke <api_key_id> [--json]
8439
- embed usage record [--api-key <key>|--api-key-id <api_key_id>] --model <model> --input-tokens <n> --output-tokens <n> [--provider <name>] [--operation <name>] [--task <task_id>] [--request-id <id>] [--json]
8440
- embed usage summary --account <account_id>|--api-key-id <api_key_id> [--from <iso>] [--to <iso>] [--json]
8441
- embed usage events --account <account_id> [--api-key-id <api_key_id>] [--from <iso>] [--to <iso>] [--limit 100] [--json]
8442
- embed billing statement --account <account_id> [--from <iso>] [--to <iso>] [--json]
8443
- embed billing balance --account <account_id> [--json]
8444
- embed billing tokens --account <account_id> [--json]
8445
- embed billing ledger --account <account_id> [--json]
8446
- embed billing storage --account <account_id> [--json]
8447
- embed billing storage settle --account <account_id> [--dry-run] [--json]
8448
- embed billing recharge create --account <account_id> --amount-usd <amount> [--provider mock|stripe|onchain] [--chain <chain>] [--token <symbol>] [--success-url <url>] [--cancel-url <url>] [--qr] [--json]
8449
- embed billing recharge list --account <account_id> [--json]
8450
- embed billing recharge show <recharge_session_id> [--json]
8451
- embed billing recharge submit-tx <recharge_session_id> --tx-hash <hash> [--json]
8452
- embed billing recharge confirm <recharge_session_id> [--json]
8453
- embed billing snapshot create --account <account_id> [--from <iso>] [--to <iso>] [--json]
8454
- embed billing snapshot list --account <account_id> [--json]
8455
- embed billing snapshot show <billing_snapshot_id> [--json]
8456
8518
  embed service modes [--json]
8457
8519
  embed build template list [--json]
8458
8520
  embed build template show <template_id> [--json]