@kvell007/embed-labs-cli 0.1.0-alpha.16 → 0.1.0-alpha.18

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 CHANGED
@@ -81,6 +81,18 @@ embedlabs doctor
81
81
  embedlabs auth login --token <token>
82
82
  ```
83
83
 
84
+ If a cloud or plugin command reaches a protected API before a token is
85
+ configured, the CLI returns `auth_token_missing` with the registration URL and
86
+ these setup options:
87
+
88
+ ```bash
89
+ open https://api.embedboard.com/dashboard
90
+ embedlabs auth login --token <your_token>
91
+ # or for automation:
92
+ export EMBED_API_TOKEN=<your_token>
93
+ embedlabs auth status
94
+ ```
95
+
84
96
  For the current public API plus local TaishanPi verification path, see
85
97
  `docs/runbooks/PUBLIC_CLI_USER_VERIFICATION.md`.
86
98
 
package/dist/index.js CHANGED
@@ -18,6 +18,7 @@ const qrcodeTerminal = require("qrcode-terminal");
18
18
  const DEFAULT_BRIDGE_URL = process.env.EMBED_BRIDGE_URL ?? "http://127.0.0.1:18083";
19
19
  const DEFAULT_CLOUD_API_URL = process.env.EMBED_CLOUD_API_URL ?? "http://127.0.0.1:18100";
20
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";
21
22
  const DEFAULT_AGENT_ARTIFACT_DIR = process.env.EMBED_AGENT_ARTIFACT_DIR ?? ".embed-labs/artifacts";
22
23
  const DOCTOR_USAGE = "Usage: embed doctor [--json]";
23
24
  const DOCTOR_HTTP_TIMEOUT_MS = 8000;
@@ -27,6 +28,8 @@ const DEVICE_PROBE_USAGE = "Usage: embed device probe --host <host> --ports 22,1
27
28
  const QUERY_USAGE = "Usage: embed query <natural language request> [--account <account_id>] [--qr] [--json]";
28
29
  const DEFAULT_PLUGIN_RELEASE_URL = process.env.EMBED_PLUGIN_RELEASE_URL?.trim() || "https://api.embedboard.com/plugin-releases/agent-plugins/latest";
29
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-plugins";
30
33
  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]";
31
34
  const CLOUD_TASK_ARTIFACTS_USAGE = "Usage: embed cloud task artifacts <task_id> [--json]";
32
35
  const CLOUD_TASK_EVIDENCE_USAGE = "Usage: embed cloud task evidence <task_id> [--json]";
@@ -1200,7 +1203,8 @@ function authDoctorCheck(status) {
1200
1203
  : {
1201
1204
  code: "auth_not_ready",
1202
1205
  message: "No CLI auth token is configured.",
1203
- remediation: "Run: embed auth login --token <token>"
1206
+ remediation: cloudAuthSetupRemediation(),
1207
+ details: cloudAuthSetupDetails()
1204
1208
  }
1205
1209
  };
1206
1210
  }
@@ -1411,7 +1415,7 @@ async function cloudDownloadArtifact(artifactId, outputPath) {
1411
1415
  });
1412
1416
  if (!response.ok) {
1413
1417
  const parsed = await parseErrorResponse(response);
1414
- return parsed ?? fail("artifact_download_failed", `Artifact download failed with HTTP ${response.status}.`);
1418
+ return parsed ? enrichCloudAuthFailure(parsed, Boolean(token)) : fail("artifact_download_failed", `Artifact download failed with HTTP ${response.status}.`);
1415
1419
  }
1416
1420
  const bytes = Buffer.from(await response.arrayBuffer());
1417
1421
  const expectedSha256 = response.headers.get("x-embed-artifact-sha256")?.trim();
@@ -1454,7 +1458,8 @@ async function cloudRequest(method, path, body) {
1454
1458
  headers: Object.keys(headers).length > 0 ? headers : undefined,
1455
1459
  body: body === undefined ? undefined : bodyText
1456
1460
  });
1457
- return await response.json();
1461
+ const parsed = await response.json();
1462
+ return enrichCloudAuthFailure(parsed, Boolean(token));
1458
1463
  }
1459
1464
  catch (error) {
1460
1465
  return fail("cloud_api_unreachable", error instanceof Error ? error.message : String(error), {
@@ -1462,6 +1467,39 @@ async function cloudRequest(method, path, body) {
1462
1467
  });
1463
1468
  }
1464
1469
  }
1470
+ function enrichCloudAuthFailure(response, hadToken) {
1471
+ if (response.ok || response.error.code !== "unauthorized") {
1472
+ return response;
1473
+ }
1474
+ if (!hadToken) {
1475
+ 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.", {
1476
+ remediation: cloudAuthSetupRemediation(),
1477
+ details: cloudAuthSetupDetails()
1478
+ });
1479
+ }
1480
+ 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.", {
1481
+ remediation: cloudAuthSetupRemediation(),
1482
+ details: cloudAuthSetupDetails()
1483
+ });
1484
+ }
1485
+ function cloudAuthSetupRemediation() {
1486
+ return [
1487
+ `1. Open ${DEFAULT_DASHBOARD_URL} and register or sign in.`,
1488
+ "2. Create or copy your Embed Labs API Token from the user dashboard.",
1489
+ "3. Run: embedlabs auth login --token <your_token>",
1490
+ "4. For automation, set: EMBED_API_TOKEN=<your_token>",
1491
+ "5. Verify with: embedlabs auth status"
1492
+ ].join("\n");
1493
+ }
1494
+ function cloudAuthSetupDetails() {
1495
+ return {
1496
+ dashboard_url: DEFAULT_DASHBOARD_URL,
1497
+ login_command: "embedlabs auth login --token <your_token>",
1498
+ env_var: "EMBED_API_TOKEN",
1499
+ auth_status_command: "embedlabs auth status",
1500
+ auth_file: DEFAULT_AUTH_FILE
1501
+ };
1502
+ }
1465
1503
  function addCloudRequestSignature(headers, method, pathWithQuery, bodyText, token) {
1466
1504
  if (process.env.EMBED_CLOUD_API_SIGNING === "0") {
1467
1505
  return;
@@ -1600,7 +1638,7 @@ async function installCodexPlugin(parsed, context) {
1600
1638
  return source;
1601
1639
  }
1602
1640
  const targetRoot = codexPluginTargetRoot(parsed, context.installingAll);
1603
- const targetPath = join(targetRoot, "embed-labs");
1641
+ const targetPath = join(targetRoot, CODEX_PLUGIN_NAME);
1604
1642
  const legacyCleanup = await cleanupLegacyCodexPluginRemnants(targetRoot);
1605
1643
  if (await pathExists(targetPath) && !booleanFlag(parsed, "force")) {
1606
1644
  return fail("plugin_already_installed", `Codex plugin already exists at ${targetPath}.`, {
@@ -1611,17 +1649,23 @@ async function installCodexPlugin(parsed, context) {
1611
1649
  await mkdir(targetRoot, { recursive: true });
1612
1650
  await cp(source.data.sourcePath, targetPath, { recursive: true });
1613
1651
  const mcpRegistration = await maybeRegisterCodexMcp(parsed, targetRoot, targetPath);
1652
+ const marketplaceRegistration = await maybeRegisterCodexMarketplace(parsed, targetRoot, targetPath);
1614
1653
  return ok({
1615
1654
  id: "codex",
1616
1655
  target_path: targetPath,
1617
1656
  source: source.data.sourceLabel,
1618
1657
  version: source.data.version,
1619
1658
  command_hint: mcpRegistration.registered
1620
- ? "Codex MCP was registered. Start a new Codex session to reload tools."
1659
+ ? (marketplaceRegistration.registered
1660
+ ? "Codex MCP and plugin marketplace entry were registered. Fully restart Codex to reload @Embed Labs."
1661
+ : "Codex MCP was registered. Start a new Codex session to reload tools.")
1621
1662
  : mcpRegistration.hint,
1622
1663
  warning: legacyCodexCleanupWarning(legacyCleanup),
1623
1664
  mcp_registered: mcpRegistration.registered,
1624
1665
  mcp_warning: mcpRegistration.warning,
1666
+ marketplace_registered: marketplaceRegistration.registered,
1667
+ marketplace_path: marketplaceRegistration.marketplacePath,
1668
+ marketplace_warning: marketplaceRegistration.warning,
1625
1669
  cleanup: legacyCleanup
1626
1670
  });
1627
1671
  }
@@ -1914,7 +1958,8 @@ async function cleanupLegacyCodexPluginRemnants(targetRoot) {
1914
1958
  ];
1915
1959
  legacyPaths.push(...await discoverLegacyCodexCachePaths(targetRoot));
1916
1960
  if (resolve(targetRoot) === resolve(defaultCodexPluginRoot())) {
1917
- legacyPaths.push(join(defaultCodexHome(), ".tmp", "plugins"), join(defaultCodexHome(), ".tmp", "plugins.sha"), 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"));
1961
+ 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"));
1962
+ legacyPaths.push(...await discoverLegacyHomeAgentsMarketplacePaths(warnings), ...legacyDevelopmentBoardRuntimePluginPaths());
1918
1963
  }
1919
1964
  for (const candidate of legacyPaths) {
1920
1965
  try {
@@ -1986,7 +2031,56 @@ async function stopLegacyCodexPluginProcesses(warnings) {
1986
2031
  function isLegacyCodexPluginProcess(command) {
1987
2032
  const trimmed = command.trim();
1988
2033
  return /^\/.*\/dbt-agent-mcp-bridge(?:\s|$)/.test(trimmed)
1989
- || /^dbt-agent-mcp-bridge(?:\s|$)/.test(trimmed);
2034
+ || /^dbt-agent-mcp-bridge(?:\s|$)/.test(trimmed)
2035
+ || legacyDevelopmentBoardRuntimeProcessPatterns().some((pattern) => pattern.test(trimmed));
2036
+ }
2037
+ function legacyDevelopmentBoardRuntimeProcessPatterns() {
2038
+ const home = escapeRegExp(homedir());
2039
+ return [
2040
+ new RegExp(`^${home}/Library/development-board-toolchain/agent/bin/dbt-agentd(?:\\s|$)`),
2041
+ new RegExp(`^${home}/Library/Application Support/development-board-toolchain/agent/bin/dbt-agentd(?:\\s|$)`),
2042
+ new RegExp(`^${home}/Library/development-board-toolchain/runtime/dbtctl\\s+status(?:\\s|$)`),
2043
+ new RegExp(`^${home}/Library/Application Support/development-board-toolchain/runtime/dbtctl\\s+status(?:\\s|$)`),
2044
+ new RegExp(`^${home}/.*?/DBT-Agent\\.app/Contents/MacOS/DBT-Agent(?:\\s|$)`)
2045
+ ];
2046
+ }
2047
+ function legacyDevelopmentBoardRuntimePluginPaths() {
2048
+ return [
2049
+ join(homedir(), "Library", "development-board-toolchain", "runtime", "editor_plugins"),
2050
+ join(homedir(), "Library", "development-board-toolchain", "runtime", "opencode_plugin"),
2051
+ join(homedir(), "Library", "Application Support", "development-board-toolchain", "runtime", "editor_plugins"),
2052
+ join(homedir(), "Library", "Application Support", "development-board-toolchain", "runtime", "opencode_plugin")
2053
+ ];
2054
+ }
2055
+ async function discoverLegacyHomeAgentsMarketplacePaths(warnings) {
2056
+ const paths = [];
2057
+ const pluginRoot = join(homedir(), ".agents", "plugins");
2058
+ try {
2059
+ const entries = await readdir(pluginRoot, { withFileTypes: true });
2060
+ for (const entry of entries) {
2061
+ if (!entry.isFile() || !entry.name.startsWith("marketplace.json"))
2062
+ continue;
2063
+ const filePath = join(pluginRoot, entry.name);
2064
+ try {
2065
+ const current = await readFile(filePath, "utf8");
2066
+ if (isLegacyHomeAgentsMarketplace(current)) {
2067
+ paths.push(filePath);
2068
+ }
2069
+ }
2070
+ catch (error) {
2071
+ warnings.push(`Could not inspect legacy Codex home marketplace ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
2072
+ }
2073
+ }
2074
+ }
2075
+ catch {
2076
+ return paths;
2077
+ }
2078
+ return paths;
2079
+ }
2080
+ function isLegacyHomeAgentsMarketplace(text) {
2081
+ return text.includes('"name" : "plugins"') || text.includes('"name": "plugins"')
2082
+ ? text.includes('"dbt-agent"') || text.includes('"./.codex/plugins/dbt-agent"') || text.includes('"./plugins/dbt-agent"')
2083
+ : false;
1990
2084
  }
1991
2085
  async function discoverLegacyCodexCachePaths(targetRoot) {
1992
2086
  const paths = [];
@@ -2074,7 +2168,8 @@ function isLegacyCodexConfigTable(table) {
2074
2168
  || table === "mcp_servers.dbt-agent"
2075
2169
  || table.startsWith("mcp_servers.dbt-agent.")
2076
2170
  || table === 'mcp_servers."dbt-agent"'
2077
- || table.startsWith('mcp_servers."dbt-agent".');
2171
+ || table.startsWith('mcp_servers."dbt-agent".')
2172
+ || /^projects\."[^"]*\/DBT-Agent-Project(?:\/[^"]*)?"$/.test(table);
2078
2173
  }
2079
2174
  function legacyCodexCleanupWarning(cleanup) {
2080
2175
  const parts = [];
@@ -2224,6 +2319,101 @@ async function maybeRegisterCodexMcp(parsed, targetRoot, targetPath) {
2224
2319
  const warning = await upsertCodexMcpRuntimeConfig(bridgePath);
2225
2320
  return warning ? { registered: true, warning } : { registered: true };
2226
2321
  }
2322
+ async function maybeRegisterCodexMarketplace(parsed, targetRoot, targetPath) {
2323
+ const explicitTarget = Boolean(stringFlag(parsed, "target") || stringFlag(parsed, "codex-target"));
2324
+ if (explicitTarget && process.env.EMBED_CODEX_MARKETPLACE_REGISTER !== "1") {
2325
+ return {
2326
+ registered: false,
2327
+ warning: "Codex plugin marketplace entry was not registered because a custom target was used. Set EMBED_CODEX_MARKETPLACE_REGISTER=1 to register it anyway."
2328
+ };
2329
+ }
2330
+ if (resolve(targetRoot) !== resolve(defaultCodexPluginRoot()) && process.env.EMBED_CODEX_MARKETPLACE_REGISTER !== "1") {
2331
+ return {
2332
+ registered: false,
2333
+ warning: "Codex plugin marketplace entry was not registered because the install target is not the default Codex plugin root."
2334
+ };
2335
+ }
2336
+ const marketplacePath = defaultCodexLocalMarketplaceRoot();
2337
+ const marketplacePluginPath = join(marketplacePath, "plugins", CODEX_PLUGIN_NAME);
2338
+ try {
2339
+ if (!await pathExists(join(targetPath, ".codex-plugin", "plugin.json"))) {
2340
+ return {
2341
+ registered: false,
2342
+ warning: `Codex plugin manifest was not found at ${join(targetPath, ".codex-plugin", "plugin.json")}.`
2343
+ };
2344
+ }
2345
+ await rm(marketplacePluginPath, { recursive: true, force: true });
2346
+ await mkdir(dirname(marketplacePluginPath), { recursive: true });
2347
+ await cp(targetPath, marketplacePluginPath, { recursive: true });
2348
+ await writeCodexLocalMarketplaceManifest(marketplacePath);
2349
+ const warning = await upsertCodexPluginMarketplaceConfig(marketplacePath);
2350
+ return warning ? { registered: true, marketplacePath, warning } : { registered: true, marketplacePath };
2351
+ }
2352
+ catch (error) {
2353
+ return {
2354
+ registered: false,
2355
+ marketplacePath,
2356
+ warning: `Could not register Codex plugin marketplace entry: ${error instanceof Error ? error.message : String(error)}`
2357
+ };
2358
+ }
2359
+ }
2360
+ function defaultCodexLocalMarketplaceRoot() {
2361
+ return join(defaultCodexHome(), "local-marketplaces", CODEX_PLUGIN_NAME);
2362
+ }
2363
+ async function writeCodexLocalMarketplaceManifest(marketplacePath) {
2364
+ const manifestPath = join(marketplacePath, ".agents", "plugins", "marketplace.json");
2365
+ const manifest = {
2366
+ name: CODEX_MARKETPLACE_NAME,
2367
+ interface: {
2368
+ displayName: "Embed Labs"
2369
+ },
2370
+ plugins: [
2371
+ {
2372
+ name: CODEX_PLUGIN_NAME,
2373
+ source: {
2374
+ source: "local",
2375
+ path: `./plugins/${CODEX_PLUGIN_NAME}`
2376
+ },
2377
+ policy: {
2378
+ installation: "AVAILABLE",
2379
+ authentication: "ON_USE"
2380
+ },
2381
+ category: "Developer Tools"
2382
+ }
2383
+ ]
2384
+ };
2385
+ await mkdir(dirname(manifestPath), { recursive: true });
2386
+ await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
2387
+ }
2388
+ async function upsertCodexPluginMarketplaceConfig(marketplacePath) {
2389
+ const configPath = codexConfigPath();
2390
+ try {
2391
+ await mkdir(dirname(configPath), { recursive: true });
2392
+ let text = "";
2393
+ try {
2394
+ text = await readFile(configPath, "utf8");
2395
+ }
2396
+ catch {
2397
+ text = "";
2398
+ }
2399
+ text = removeLegacyCodexConfigTables(text).text;
2400
+ let updated = upsertTomlTableKeys(text, `marketplaces.${CODEX_MARKETPLACE_NAME}`, {
2401
+ source_type: tomlString("local"),
2402
+ source: tomlString(marketplacePath),
2403
+ last_updated: tomlString(new Date().toISOString().replace(/\.\d{3}Z$/, "Z"))
2404
+ });
2405
+ updated = upsertTomlTableKeys(updated, `plugins."${CODEX_PLUGIN_NAME}@${CODEX_MARKETPLACE_NAME}"`, {
2406
+ enabled: "true"
2407
+ });
2408
+ if (updated !== text) {
2409
+ await writeFile(configPath, updated, "utf8");
2410
+ }
2411
+ return undefined;
2412
+ }
2413
+ catch (error) {
2414
+ return `${configPath} could not be updated with the Embed Labs plugin marketplace entry: ${error instanceof Error ? error.message : String(error)}`;
2415
+ }
2416
+ }
2227
2417
  function codexMcpAlreadyRegistered(stdout, bridgePath, cloudUrl, authFile, embedCliBin) {
2228
2418
  try {
2229
2419
  const parsed = JSON.parse(stdout);
@@ -5136,6 +5326,15 @@ function renderPluginInstall(result) {
5136
5326
  if (item.mcp_warning) {
5137
5327
  lines.push(` warning=${item.mcp_warning}`);
5138
5328
  }
5329
+ if (item.marketplace_registered !== undefined) {
5330
+ lines.push(` codex_marketplace_registered=${item.marketplace_registered}`);
5331
+ }
5332
+ if (item.marketplace_path) {
5333
+ lines.push(` codex_marketplace=${item.marketplace_path}`);
5334
+ }
5335
+ if (item.marketplace_warning) {
5336
+ lines.push(` warning=${item.marketplace_warning}`);
5337
+ }
5139
5338
  }
5140
5339
  return lines.join("\n");
5141
5340
  }