@kvell007/embed-labs-cli 0.1.0-alpha.12 → 0.1.0-alpha.13

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
@@ -2,7 +2,7 @@
2
2
  import { createHash } from "node:crypto";
3
3
  import { constants } from "node:fs";
4
4
  import { spawn } from "node:child_process";
5
- import { access, 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";
6
6
  import { createRequire } from "node:module";
7
7
  import { homedir, tmpdir } from "node:os";
8
8
  import { basename, delimiter, dirname, join, resolve } from "node:path";
@@ -1351,17 +1351,29 @@ function isApiResponse(value) {
1351
1351
  return isJsonObject(error) && typeof error.code === "string" && typeof error.message === "string";
1352
1352
  }
1353
1353
  async function bridgeGet(path) {
1354
- const response = await fetch(`${DEFAULT_BRIDGE_URL}${path}`);
1354
+ const response = await fetch(`${DEFAULT_BRIDGE_URL}${path}`, {
1355
+ headers: bridgeHeaders()
1356
+ });
1355
1357
  return await response.json();
1356
1358
  }
1357
1359
  async function bridgePost(path, body) {
1358
1360
  const response = await fetch(`${DEFAULT_BRIDGE_URL}${path}`, {
1359
1361
  method: "POST",
1360
- headers: { "content-type": "application/json" },
1362
+ headers: bridgeHeaders({ "content-type": "application/json" }),
1361
1363
  body: JSON.stringify(body)
1362
1364
  });
1363
1365
  return await response.json();
1364
1366
  }
1367
+ function bridgeHeaders(base = {}) {
1368
+ const token = process.env.EMBED_BRIDGE_TOKEN?.trim();
1369
+ if (!token) {
1370
+ return base;
1371
+ }
1372
+ return {
1373
+ ...base,
1374
+ authorization: `Bearer ${token}`
1375
+ };
1376
+ }
1365
1377
  async function cloudGet(path) {
1366
1378
  return await cloudRequest("GET", path);
1367
1379
  }
@@ -1535,6 +1547,7 @@ async function installCodexPlugin(parsed, context) {
1535
1547
  }
1536
1548
  const targetRoot = codexPluginTargetRoot(parsed, context.installingAll);
1537
1549
  const targetPath = join(targetRoot, "embed-labs");
1550
+ const legacyCleanup = await cleanupLegacyCodexPluginRemnants(targetRoot);
1538
1551
  if (await pathExists(targetPath) && !booleanFlag(parsed, "force")) {
1539
1552
  return fail("plugin_already_installed", `Codex plugin already exists at ${targetPath}.`, {
1540
1553
  remediation: "Pass --force to replace it, or pass --codex-target/--target to install into a different directory."
@@ -1552,8 +1565,10 @@ async function installCodexPlugin(parsed, context) {
1552
1565
  command_hint: mcpRegistration.registered
1553
1566
  ? "Codex MCP was registered. Start a new Codex session to reload tools."
1554
1567
  : mcpRegistration.hint,
1568
+ warning: legacyCodexCleanupWarning(legacyCleanup),
1555
1569
  mcp_registered: mcpRegistration.registered,
1556
- mcp_warning: mcpRegistration.warning
1570
+ mcp_warning: mcpRegistration.warning,
1571
+ cleanup: legacyCleanup
1557
1572
  });
1558
1573
  }
1559
1574
  async function installOpenCodePlugin(parsed, context) {
@@ -1562,8 +1577,10 @@ async function installOpenCodePlugin(parsed, context) {
1562
1577
  return source;
1563
1578
  }
1564
1579
  const targetRoot = openCodePluginTargetRoot(parsed, context.installingAll);
1565
- const wrapperPath = join(targetRoot, "plugins", "development-board-toolchain.js");
1566
- if (await pathExists(wrapperPath) && !booleanFlag(parsed, "force")) {
1580
+ const globalInstall = isGlobalOpenCodeRoot(targetRoot);
1581
+ const wrapperPath = join(targetRoot, "plugins", "embed-labs.js");
1582
+ const legacyCleanup = await cleanupLegacyOpenCodePluginRemnants(targetRoot, globalInstall);
1583
+ if (!globalInstall && await pathExists(wrapperPath) && !booleanFlag(parsed, "force")) {
1567
1584
  return fail("plugin_already_installed", `OpenCode plugin wrapper already exists at ${wrapperPath}.`, {
1568
1585
  remediation: "Pass --force to replace it, or pass --opencode-target/--target to install into a different directory."
1569
1586
  });
@@ -1589,15 +1606,25 @@ async function installOpenCodePlugin(parsed, context) {
1589
1606
  });
1590
1607
  }
1591
1608
  await ensureOpenCodeInstallPackageJson(targetRoot);
1592
- await writeFile(wrapperPath, `export { default, DevelopmentBoardToolchainPlugin } from "embed-labs";\n`, "utf8");
1609
+ if (globalInstall) {
1610
+ await rm(wrapperPath, { force: true });
1611
+ legacyCleanup.legacy_removed_config_entries?.push(...await ensureOpenCodeGlobalPluginConfig());
1612
+ }
1613
+ else {
1614
+ await writeFile(wrapperPath, `export { default, DevelopmentBoardToolchainPlugin } from "embed-labs";\n`, "utf8");
1615
+ }
1593
1616
  const duplicateWarning = await openCodeDuplicatePluginWarning(targetRoot);
1617
+ const cleanupWarning = legacyOpenCodeCleanupWarning(legacyCleanup);
1594
1618
  return ok({
1595
1619
  id: "opencode",
1596
1620
  target_path: targetRoot,
1597
1621
  source: source.data.sourceLabel,
1598
1622
  version: source.data.version,
1599
- command_hint: "Start OpenCode from the project containing this .opencode directory.",
1600
- warning: duplicateWarning
1623
+ command_hint: globalInstall
1624
+ ? "Restart OpenCode so the global embed-labs package plugin is reloaded."
1625
+ : "Start OpenCode from the project containing this .opencode directory.",
1626
+ warning: combineWarnings(cleanupWarning, duplicateWarning),
1627
+ cleanup: legacyCleanup
1601
1628
  });
1602
1629
  }
1603
1630
  async function resolveCodexPluginSource(context) {
@@ -1811,7 +1838,204 @@ function openCodePluginTargetRoot(parsed, installingAll) {
1811
1838
  return resolve(target ?? defaultOpenCodeRoot());
1812
1839
  }
1813
1840
  function defaultCodexPluginRoot() {
1814
- return join(process.env.CODEX_HOME?.trim() || join(homedir(), ".codex"), "plugins");
1841
+ return join(defaultCodexHome(), "plugins");
1842
+ }
1843
+ function defaultCodexHome() {
1844
+ return resolve(process.env.CODEX_HOME?.trim() || join(homedir(), ".codex"));
1845
+ }
1846
+ function codexConfigPath() {
1847
+ return join(defaultCodexHome(), "config.toml");
1848
+ }
1849
+ async function cleanupLegacyCodexPluginRemnants(targetRoot) {
1850
+ const removedPaths = [];
1851
+ const removedConfigTables = [];
1852
+ const warnings = [];
1853
+ const legacyPaths = [
1854
+ join(targetRoot, "dbt-agent"),
1855
+ join(targetRoot, "development-board-toolchain"),
1856
+ join(targetRoot, "cache", "embed-labs", "dbt-agent"),
1857
+ join(targetRoot, "cache", "dbt-agent"),
1858
+ join(targetRoot, "cache", "plugins", "dbt-agent")
1859
+ ];
1860
+ legacyPaths.push(...await discoverLegacyCodexCachePaths(targetRoot));
1861
+ if (resolve(targetRoot) === resolve(defaultCodexPluginRoot())) {
1862
+ legacyPaths.push(join(defaultCodexHome(), ".tmp", "plugins"), join(defaultCodexHome(), ".tmp", "plugins.sha"));
1863
+ }
1864
+ for (const candidate of legacyPaths) {
1865
+ try {
1866
+ if (await pathExists(candidate)) {
1867
+ await rm(candidate, { recursive: true, force: true });
1868
+ removedPaths.push(candidate);
1869
+ }
1870
+ }
1871
+ catch (error) {
1872
+ warnings.push(`Could not remove ${candidate}: ${error instanceof Error ? error.message : String(error)}`);
1873
+ }
1874
+ }
1875
+ if (resolve(targetRoot) === resolve(defaultCodexPluginRoot())) {
1876
+ const configPath = codexConfigPath();
1877
+ try {
1878
+ if (await pathExists(configPath)) {
1879
+ const current = await readFile(configPath, "utf8");
1880
+ const updated = removeLegacyCodexConfigTables(current);
1881
+ if (updated.text !== current) {
1882
+ await writeFile(configPath, updated.text, "utf8");
1883
+ }
1884
+ removedConfigTables.push(...updated.removedTables);
1885
+ }
1886
+ }
1887
+ catch (error) {
1888
+ warnings.push(`Could not update ${configPath}: ${error instanceof Error ? error.message : String(error)}`);
1889
+ }
1890
+ }
1891
+ const removedHistoryEntries = await cleanupLegacyCodexHistoryMentions(warnings);
1892
+ return {
1893
+ legacy_removed_paths: Array.from(new Set(removedPaths)),
1894
+ legacy_removed_config_tables: removedConfigTables,
1895
+ legacy_removed_history_entries: removedHistoryEntries,
1896
+ ...(warnings.length > 0 ? { warnings } : {})
1897
+ };
1898
+ }
1899
+ async function discoverLegacyCodexCachePaths(targetRoot) {
1900
+ const paths = [];
1901
+ const cacheRoot = join(targetRoot, "cache");
1902
+ try {
1903
+ const marketplaces = await readdir(cacheRoot, { withFileTypes: true });
1904
+ for (const entry of marketplaces) {
1905
+ if (!entry.isDirectory())
1906
+ continue;
1907
+ paths.push(join(cacheRoot, entry.name, "dbt-agent"));
1908
+ paths.push(join(cacheRoot, entry.name, "development-board-toolchain"));
1909
+ }
1910
+ }
1911
+ catch {
1912
+ return paths;
1913
+ }
1914
+ return paths;
1915
+ }
1916
+ async function cleanupLegacyCodexHistoryMentions(warnings) {
1917
+ const historyPath = join(defaultCodexHome(), "history.jsonl");
1918
+ try {
1919
+ if (!await pathExists(historyPath))
1920
+ return 0;
1921
+ const current = await readFile(historyPath, "utf8");
1922
+ const lines = current.split("\n");
1923
+ let removed = 0;
1924
+ const kept = lines.filter((line) => {
1925
+ if (!line)
1926
+ return true;
1927
+ if (isLegacyCodexHistoryMention(line)) {
1928
+ removed += 1;
1929
+ return false;
1930
+ }
1931
+ return true;
1932
+ });
1933
+ if (removed > 0) {
1934
+ await writeFile(historyPath, kept.join("\n"), "utf8");
1935
+ }
1936
+ return removed;
1937
+ }
1938
+ catch (error) {
1939
+ warnings.push(`Could not clean Codex legacy history mentions: ${error instanceof Error ? error.message : String(error)}`);
1940
+ return 0;
1941
+ }
1942
+ }
1943
+ function isLegacyCodexHistoryMention(line) {
1944
+ return line.includes("plugin://dbt-agent@plugins")
1945
+ || line.includes("plugin://dbt-agent@embed-labs")
1946
+ || /dbt-agent/i.test(line);
1947
+ }
1948
+ function removeLegacyCodexConfigTables(text) {
1949
+ const lines = text.match(/[^\n]*\n|[^\n]+$/g) ?? [];
1950
+ const output = [];
1951
+ const removedTables = [];
1952
+ let skipping = false;
1953
+ for (const line of lines) {
1954
+ const table = parseTomlTableHeader(line);
1955
+ if (table) {
1956
+ skipping = isLegacyCodexConfigTable(table);
1957
+ if (skipping) {
1958
+ removedTables.push(table);
1959
+ continue;
1960
+ }
1961
+ }
1962
+ if (!skipping) {
1963
+ output.push(line);
1964
+ }
1965
+ }
1966
+ return { text: output.join("").replace(/\n{3,}/g, "\n\n"), removedTables };
1967
+ }
1968
+ function parseTomlTableHeader(line) {
1969
+ const match = /^\s*\[([^\]]+)\]\s*(?:#.*)?$/.exec(line);
1970
+ return match?.[1]?.trim();
1971
+ }
1972
+ function isLegacyCodexConfigTable(table) {
1973
+ return /^plugins\."dbt-agent@[^"]+"$/.test(table)
1974
+ || table === "mcp_servers.dbt-agent"
1975
+ || table.startsWith("mcp_servers.dbt-agent.")
1976
+ || table === 'mcp_servers."dbt-agent"'
1977
+ || table.startsWith('mcp_servers."dbt-agent".');
1978
+ }
1979
+ function legacyCodexCleanupWarning(cleanup) {
1980
+ const parts = [];
1981
+ if (cleanup.legacy_removed_paths.length > 0) {
1982
+ parts.push(`removed ${cleanup.legacy_removed_paths.length} legacy Codex plugin path(s)`);
1983
+ }
1984
+ if (cleanup.legacy_removed_config_tables?.length) {
1985
+ parts.push(`removed ${cleanup.legacy_removed_config_tables.length} legacy Codex config table(s)`);
1986
+ }
1987
+ if (cleanup.legacy_removed_history_entries) {
1988
+ parts.push(`removed ${cleanup.legacy_removed_history_entries} legacy Codex history mention(s)`);
1989
+ }
1990
+ if (cleanup.warnings?.length) {
1991
+ parts.push(`cleanup warning(s): ${cleanup.warnings.join("; ")}`);
1992
+ }
1993
+ return parts.length > 0 ? `Legacy dbt-agent cleanup: ${parts.join(", ")}.` : undefined;
1994
+ }
1995
+ async function cleanupLegacyOpenCodePluginRemnants(targetRoot, globalInstall) {
1996
+ const removedPaths = [];
1997
+ const warnings = [];
1998
+ const legacyPaths = [
1999
+ join(targetRoot, "plugins", "development-board-toolchain.js"),
2000
+ join(targetRoot, "plugins", "dbt-agent.js"),
2001
+ join(targetRoot, "node_modules", "dbt-agent")
2002
+ ];
2003
+ if (globalInstall) {
2004
+ legacyPaths.push(join(targetRoot, "plugins", "embed-labs.js"));
2005
+ }
2006
+ for (const candidate of legacyPaths) {
2007
+ try {
2008
+ if (await pathExists(candidate)) {
2009
+ await rm(candidate, { recursive: true, force: true });
2010
+ removedPaths.push(candidate);
2011
+ }
2012
+ }
2013
+ catch (error) {
2014
+ warnings.push(`Could not remove ${candidate}: ${error instanceof Error ? error.message : String(error)}`);
2015
+ }
2016
+ }
2017
+ return {
2018
+ legacy_removed_paths: removedPaths,
2019
+ legacy_removed_config_entries: [],
2020
+ ...(warnings.length > 0 ? { warnings } : {})
2021
+ };
2022
+ }
2023
+ function legacyOpenCodeCleanupWarning(cleanup) {
2024
+ const parts = [];
2025
+ if (cleanup.legacy_removed_paths.length > 0) {
2026
+ parts.push(`removed ${cleanup.legacy_removed_paths.length} legacy OpenCode plugin path(s)`);
2027
+ }
2028
+ if (cleanup.legacy_removed_config_entries?.length) {
2029
+ parts.push(`removed ${cleanup.legacy_removed_config_entries.length} legacy OpenCode config entry(s)`);
2030
+ }
2031
+ if (cleanup.warnings?.length) {
2032
+ parts.push(`cleanup warning(s): ${cleanup.warnings.join("; ")}`);
2033
+ }
2034
+ return parts.length > 0 ? `Legacy OpenCode cleanup: ${parts.join(", ")}.` : undefined;
2035
+ }
2036
+ function combineWarnings(...warnings) {
2037
+ const actual = warnings.filter((warning) => Boolean(warning));
2038
+ return actual.length > 0 ? actual.join(" ") : undefined;
1815
2039
  }
1816
2040
  async function maybeRegisterCodexMcp(parsed, targetRoot, targetPath) {
1817
2041
  const explicitTarget = Boolean(stringFlag(parsed, "target") || stringFlag(parsed, "codex-target"));
@@ -1984,10 +2208,54 @@ async function resolveExecutableOnPath(name) {
1984
2208
  return undefined;
1985
2209
  }
1986
2210
  function defaultOpenCodeRoot() {
1987
- return join(process.cwd(), ".opencode");
2211
+ return globalOpenCodeRoot();
2212
+ }
2213
+ function globalOpenCodeRoot() {
2214
+ return join(homedir(), ".config", "opencode");
2215
+ }
2216
+ function isGlobalOpenCodeRoot(targetRoot) {
2217
+ return resolve(targetRoot) === resolve(globalOpenCodeRoot());
2218
+ }
2219
+ async function ensureOpenCodeGlobalPluginConfig() {
2220
+ const configPath = join(globalOpenCodeRoot(), "opencode.json");
2221
+ let existing = {};
2222
+ try {
2223
+ const parsed = JSON.parse(await readFile(configPath, "utf8"));
2224
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
2225
+ existing = parsed;
2226
+ }
2227
+ }
2228
+ catch {
2229
+ existing = {};
2230
+ }
2231
+ const configured = Array.isArray(existing.plugin)
2232
+ ? existing.plugin.filter((item) => typeof item === "string")
2233
+ : Array.isArray(existing.plugins)
2234
+ ? existing.plugins.filter((item) => typeof item === "string")
2235
+ : [];
2236
+ const removed = configured.filter(isLegacyOpenCodePluginConfigEntry);
2237
+ const cleaned = configured.filter((item) => !isLegacyOpenCodePluginConfigEntry(item));
2238
+ if (!cleaned.includes("embed-labs")) {
2239
+ cleaned.push("embed-labs");
2240
+ }
2241
+ await writeFile(configPath, `${JSON.stringify({
2242
+ ...existing,
2243
+ plugin: cleaned,
2244
+ plugins: undefined
2245
+ }, null, 2)}\n`, "utf8");
2246
+ return removed;
2247
+ }
2248
+ function isLegacyOpenCodePluginConfigEntry(item) {
2249
+ const normalized = item.replace(/\\/g, "/");
2250
+ return item === "dbt-agent"
2251
+ || item === "development-board-toolchain"
2252
+ || normalized === "./plugins/development-board-toolchain"
2253
+ || normalized === "./plugins/development-board-toolchain.js"
2254
+ || normalized.endsWith("/plugins/development-board-toolchain.js")
2255
+ || normalized.includes("development-board-toolchain");
1988
2256
  }
1989
2257
  async function openCodeDuplicatePluginWarning(targetRoot) {
1990
- const globalRoot = join(homedir(), ".config", "opencode");
2258
+ const globalRoot = globalOpenCodeRoot();
1991
2259
  if (resolve(targetRoot) === resolve(globalRoot))
1992
2260
  return undefined;
1993
2261
  const configPath = join(globalRoot, "opencode.json");