@kvell007/embed-labs-cli 0.1.0-alpha.12 → 0.1.0-alpha.14
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 +366 -15
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { createHash } from "node:crypto";
|
|
2
|
+
import { createHash, createHmac, randomBytes } 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
|
}
|
|
@@ -1374,6 +1386,7 @@ async function cloudDownloadArtifact(artifactId, outputPath) {
|
|
|
1374
1386
|
const token = await cloudAuthToken();
|
|
1375
1387
|
if (token) {
|
|
1376
1388
|
headers.authorization = `Bearer ${token}`;
|
|
1389
|
+
addCloudRequestSignature(headers, "GET", `/v1/artifacts/${encodeURIComponent(artifactId)}/download`, "", token);
|
|
1377
1390
|
}
|
|
1378
1391
|
const response = await fetch(`${serviceBaseUrl(DEFAULT_CLOUD_API_URL)}/v1/artifacts/${encodeURIComponent(artifactId)}/download`, {
|
|
1379
1392
|
headers: Object.keys(headers).length > 0 ? headers : undefined
|
|
@@ -1409,17 +1422,19 @@ async function cloudDownloadArtifact(artifactId, outputPath) {
|
|
|
1409
1422
|
async function cloudRequest(method, path, body) {
|
|
1410
1423
|
try {
|
|
1411
1424
|
const headers = {};
|
|
1412
|
-
|
|
1425
|
+
const bodyText = body === undefined ? "" : JSON.stringify(body);
|
|
1426
|
+
if (bodyText) {
|
|
1413
1427
|
headers["content-type"] = "application/json";
|
|
1414
1428
|
}
|
|
1415
1429
|
const token = await cloudAuthToken();
|
|
1416
1430
|
if (token) {
|
|
1417
1431
|
headers.authorization = `Bearer ${token}`;
|
|
1432
|
+
addCloudRequestSignature(headers, method, path, bodyText, token);
|
|
1418
1433
|
}
|
|
1419
1434
|
const response = await fetch(`${serviceBaseUrl(DEFAULT_CLOUD_API_URL)}${path}`, {
|
|
1420
1435
|
method,
|
|
1421
1436
|
headers: Object.keys(headers).length > 0 ? headers : undefined,
|
|
1422
|
-
body: body === undefined ? undefined :
|
|
1437
|
+
body: body === undefined ? undefined : bodyText
|
|
1423
1438
|
});
|
|
1424
1439
|
return await response.json();
|
|
1425
1440
|
}
|
|
@@ -1429,6 +1444,39 @@ async function cloudRequest(method, path, body) {
|
|
|
1429
1444
|
});
|
|
1430
1445
|
}
|
|
1431
1446
|
}
|
|
1447
|
+
function addCloudRequestSignature(headers, method, pathWithQuery, bodyText, token) {
|
|
1448
|
+
if (process.env.EMBED_CLOUD_API_SIGNING === "0") {
|
|
1449
|
+
return;
|
|
1450
|
+
}
|
|
1451
|
+
const timestamp = String(Math.floor(Date.now() / 1000));
|
|
1452
|
+
const nonce = randomBytes(16).toString("hex");
|
|
1453
|
+
const bodySha256 = createHash("sha256").update(bodyText).digest("hex");
|
|
1454
|
+
const keyId = createHash("sha256").update(token).digest("hex").slice(0, 16);
|
|
1455
|
+
const canonical = cloudRequestCanonicalString(method, pathWithQuery, timestamp, nonce, bodySha256);
|
|
1456
|
+
headers["x-embed-key-id"] = keyId;
|
|
1457
|
+
headers["x-embed-timestamp"] = timestamp;
|
|
1458
|
+
headers["x-embed-nonce"] = nonce;
|
|
1459
|
+
headers["x-embed-body-sha256"] = bodySha256;
|
|
1460
|
+
headers["x-embed-signature"] = createHmac("sha256", token).update(canonical).digest("hex");
|
|
1461
|
+
}
|
|
1462
|
+
function cloudRequestCanonicalString(method, pathWithQuery, timestamp, nonce, bodySha256) {
|
|
1463
|
+
return [
|
|
1464
|
+
method.toUpperCase(),
|
|
1465
|
+
normalizeCloudPathForSignature(pathWithQuery),
|
|
1466
|
+
timestamp,
|
|
1467
|
+
nonce,
|
|
1468
|
+
bodySha256
|
|
1469
|
+
].join("\n");
|
|
1470
|
+
}
|
|
1471
|
+
function normalizeCloudPathForSignature(pathWithQuery) {
|
|
1472
|
+
try {
|
|
1473
|
+
const parsed = new URL(pathWithQuery, "http://embed.local");
|
|
1474
|
+
return `${parsed.pathname}${parsed.search}`;
|
|
1475
|
+
}
|
|
1476
|
+
catch {
|
|
1477
|
+
return pathWithQuery.startsWith("/") ? pathWithQuery : `/${pathWithQuery}`;
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1432
1480
|
async function pluginList(parsed) {
|
|
1433
1481
|
const releaseDir = stringFlag(parsed, "release-dir");
|
|
1434
1482
|
const manifest = releaseDir ? await readPluginReleaseManifest(releaseDir) : undefined;
|
|
@@ -1535,6 +1583,7 @@ async function installCodexPlugin(parsed, context) {
|
|
|
1535
1583
|
}
|
|
1536
1584
|
const targetRoot = codexPluginTargetRoot(parsed, context.installingAll);
|
|
1537
1585
|
const targetPath = join(targetRoot, "embed-labs");
|
|
1586
|
+
const legacyCleanup = await cleanupLegacyCodexPluginRemnants(targetRoot);
|
|
1538
1587
|
if (await pathExists(targetPath) && !booleanFlag(parsed, "force")) {
|
|
1539
1588
|
return fail("plugin_already_installed", `Codex plugin already exists at ${targetPath}.`, {
|
|
1540
1589
|
remediation: "Pass --force to replace it, or pass --codex-target/--target to install into a different directory."
|
|
@@ -1552,8 +1601,10 @@ async function installCodexPlugin(parsed, context) {
|
|
|
1552
1601
|
command_hint: mcpRegistration.registered
|
|
1553
1602
|
? "Codex MCP was registered. Start a new Codex session to reload tools."
|
|
1554
1603
|
: mcpRegistration.hint,
|
|
1604
|
+
warning: legacyCodexCleanupWarning(legacyCleanup),
|
|
1555
1605
|
mcp_registered: mcpRegistration.registered,
|
|
1556
|
-
mcp_warning: mcpRegistration.warning
|
|
1606
|
+
mcp_warning: mcpRegistration.warning,
|
|
1607
|
+
cleanup: legacyCleanup
|
|
1557
1608
|
});
|
|
1558
1609
|
}
|
|
1559
1610
|
async function installOpenCodePlugin(parsed, context) {
|
|
@@ -1562,8 +1613,10 @@ async function installOpenCodePlugin(parsed, context) {
|
|
|
1562
1613
|
return source;
|
|
1563
1614
|
}
|
|
1564
1615
|
const targetRoot = openCodePluginTargetRoot(parsed, context.installingAll);
|
|
1565
|
-
const
|
|
1566
|
-
|
|
1616
|
+
const globalInstall = isGlobalOpenCodeRoot(targetRoot);
|
|
1617
|
+
const wrapperPath = join(targetRoot, "plugins", "embed-labs.js");
|
|
1618
|
+
const legacyCleanup = await cleanupLegacyOpenCodePluginRemnants(targetRoot, globalInstall);
|
|
1619
|
+
if (!globalInstall && await pathExists(wrapperPath) && !booleanFlag(parsed, "force")) {
|
|
1567
1620
|
return fail("plugin_already_installed", `OpenCode plugin wrapper already exists at ${wrapperPath}.`, {
|
|
1568
1621
|
remediation: "Pass --force to replace it, or pass --opencode-target/--target to install into a different directory."
|
|
1569
1622
|
});
|
|
@@ -1589,15 +1642,25 @@ async function installOpenCodePlugin(parsed, context) {
|
|
|
1589
1642
|
});
|
|
1590
1643
|
}
|
|
1591
1644
|
await ensureOpenCodeInstallPackageJson(targetRoot);
|
|
1592
|
-
|
|
1645
|
+
if (globalInstall) {
|
|
1646
|
+
await rm(wrapperPath, { force: true });
|
|
1647
|
+
legacyCleanup.legacy_removed_config_entries?.push(...await ensureOpenCodeGlobalPluginConfig());
|
|
1648
|
+
}
|
|
1649
|
+
else {
|
|
1650
|
+
await writeFile(wrapperPath, `export { default, DevelopmentBoardToolchainPlugin } from "embed-labs";\n`, "utf8");
|
|
1651
|
+
}
|
|
1593
1652
|
const duplicateWarning = await openCodeDuplicatePluginWarning(targetRoot);
|
|
1653
|
+
const cleanupWarning = legacyOpenCodeCleanupWarning(legacyCleanup);
|
|
1594
1654
|
return ok({
|
|
1595
1655
|
id: "opencode",
|
|
1596
1656
|
target_path: targetRoot,
|
|
1597
1657
|
source: source.data.sourceLabel,
|
|
1598
1658
|
version: source.data.version,
|
|
1599
|
-
command_hint:
|
|
1600
|
-
|
|
1659
|
+
command_hint: globalInstall
|
|
1660
|
+
? "Restart OpenCode so the global embed-labs package plugin is reloaded."
|
|
1661
|
+
: "Start OpenCode from the project containing this .opencode directory.",
|
|
1662
|
+
warning: combineWarnings(cleanupWarning, duplicateWarning),
|
|
1663
|
+
cleanup: legacyCleanup
|
|
1601
1664
|
});
|
|
1602
1665
|
}
|
|
1603
1666
|
async function resolveCodexPluginSource(context) {
|
|
@@ -1811,7 +1874,251 @@ function openCodePluginTargetRoot(parsed, installingAll) {
|
|
|
1811
1874
|
return resolve(target ?? defaultOpenCodeRoot());
|
|
1812
1875
|
}
|
|
1813
1876
|
function defaultCodexPluginRoot() {
|
|
1814
|
-
return join(
|
|
1877
|
+
return join(defaultCodexHome(), "plugins");
|
|
1878
|
+
}
|
|
1879
|
+
function defaultCodexHome() {
|
|
1880
|
+
return resolve(process.env.CODEX_HOME?.trim() || join(homedir(), ".codex"));
|
|
1881
|
+
}
|
|
1882
|
+
function codexConfigPath() {
|
|
1883
|
+
return join(defaultCodexHome(), "config.toml");
|
|
1884
|
+
}
|
|
1885
|
+
async function cleanupLegacyCodexPluginRemnants(targetRoot) {
|
|
1886
|
+
const removedPaths = [];
|
|
1887
|
+
const removedConfigTables = [];
|
|
1888
|
+
const warnings = [];
|
|
1889
|
+
const stoppedProcesses = await stopLegacyCodexPluginProcesses(warnings);
|
|
1890
|
+
const legacyPaths = [
|
|
1891
|
+
join(targetRoot, "dbt-agent"),
|
|
1892
|
+
join(targetRoot, "development-board-toolchain"),
|
|
1893
|
+
join(targetRoot, "cache", "embed-labs", "dbt-agent"),
|
|
1894
|
+
join(targetRoot, "cache", "dbt-agent"),
|
|
1895
|
+
join(targetRoot, "cache", "plugins", "dbt-agent")
|
|
1896
|
+
];
|
|
1897
|
+
legacyPaths.push(...await discoverLegacyCodexCachePaths(targetRoot));
|
|
1898
|
+
if (resolve(targetRoot) === resolve(defaultCodexPluginRoot())) {
|
|
1899
|
+
legacyPaths.push(join(defaultCodexHome(), ".tmp", "plugins"), join(defaultCodexHome(), ".tmp", "plugins.sha"), join(defaultCodexHome(), "memories", "skills", "dbt-agent-live-board-ops"), join(defaultCodexHome(), "memories", "skills", "dbt-agent-platform-plugin-maintenance"));
|
|
1900
|
+
}
|
|
1901
|
+
for (const candidate of legacyPaths) {
|
|
1902
|
+
try {
|
|
1903
|
+
if (await pathExists(candidate)) {
|
|
1904
|
+
await rm(candidate, { recursive: true, force: true });
|
|
1905
|
+
removedPaths.push(candidate);
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
catch (error) {
|
|
1909
|
+
warnings.push(`Could not remove ${candidate}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
if (resolve(targetRoot) === resolve(defaultCodexPluginRoot())) {
|
|
1913
|
+
const configPath = codexConfigPath();
|
|
1914
|
+
try {
|
|
1915
|
+
if (await pathExists(configPath)) {
|
|
1916
|
+
const current = await readFile(configPath, "utf8");
|
|
1917
|
+
const updated = removeLegacyCodexConfigTables(current);
|
|
1918
|
+
if (updated.text !== current) {
|
|
1919
|
+
await writeFile(configPath, updated.text, "utf8");
|
|
1920
|
+
}
|
|
1921
|
+
removedConfigTables.push(...updated.removedTables);
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
catch (error) {
|
|
1925
|
+
warnings.push(`Could not update ${configPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
const removedHistoryEntries = await cleanupLegacyCodexTextState(warnings);
|
|
1929
|
+
return {
|
|
1930
|
+
legacy_removed_paths: Array.from(new Set(removedPaths)),
|
|
1931
|
+
legacy_removed_config_tables: removedConfigTables,
|
|
1932
|
+
legacy_removed_history_entries: removedHistoryEntries,
|
|
1933
|
+
legacy_stopped_processes: stoppedProcesses,
|
|
1934
|
+
...(warnings.length > 0 ? { warnings } : {})
|
|
1935
|
+
};
|
|
1936
|
+
}
|
|
1937
|
+
async function stopLegacyCodexPluginProcesses(warnings) {
|
|
1938
|
+
if (process.platform === "win32")
|
|
1939
|
+
return 0;
|
|
1940
|
+
try {
|
|
1941
|
+
const ps = await runLocalProcess("ps", ["-axo", "pid=,command="]);
|
|
1942
|
+
if (ps.code !== 0)
|
|
1943
|
+
return 0;
|
|
1944
|
+
let stopped = 0;
|
|
1945
|
+
for (const line of ps.stdout.split("\n")) {
|
|
1946
|
+
const match = /^\s*(\d+)\s+(.+)$/.exec(line);
|
|
1947
|
+
if (!match)
|
|
1948
|
+
continue;
|
|
1949
|
+
const pid = Number(match[1]);
|
|
1950
|
+
const command = match[2] || "";
|
|
1951
|
+
if (!isLegacyCodexPluginProcess(command))
|
|
1952
|
+
continue;
|
|
1953
|
+
try {
|
|
1954
|
+
process.kill(pid, "SIGTERM");
|
|
1955
|
+
stopped += 1;
|
|
1956
|
+
}
|
|
1957
|
+
catch (error) {
|
|
1958
|
+
warnings.push(`Could not stop legacy Codex plugin process ${pid}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
return stopped;
|
|
1962
|
+
}
|
|
1963
|
+
catch (error) {
|
|
1964
|
+
warnings.push(`Could not scan legacy Codex plugin processes: ${error instanceof Error ? error.message : String(error)}`);
|
|
1965
|
+
return 0;
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
function isLegacyCodexPluginProcess(command) {
|
|
1969
|
+
const trimmed = command.trim();
|
|
1970
|
+
return /^\/.*\/dbt-agent-mcp-bridge(?:\s|$)/.test(trimmed)
|
|
1971
|
+
|| /^dbt-agent-mcp-bridge(?:\s|$)/.test(trimmed);
|
|
1972
|
+
}
|
|
1973
|
+
async function discoverLegacyCodexCachePaths(targetRoot) {
|
|
1974
|
+
const paths = [];
|
|
1975
|
+
const cacheRoot = join(targetRoot, "cache");
|
|
1976
|
+
try {
|
|
1977
|
+
const marketplaces = await readdir(cacheRoot, { withFileTypes: true });
|
|
1978
|
+
for (const entry of marketplaces) {
|
|
1979
|
+
if (!entry.isDirectory())
|
|
1980
|
+
continue;
|
|
1981
|
+
paths.push(join(cacheRoot, entry.name, "dbt-agent"));
|
|
1982
|
+
paths.push(join(cacheRoot, entry.name, "development-board-toolchain"));
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
catch {
|
|
1986
|
+
return paths;
|
|
1987
|
+
}
|
|
1988
|
+
return paths;
|
|
1989
|
+
}
|
|
1990
|
+
async function cleanupLegacyCodexTextState(warnings) {
|
|
1991
|
+
let removed = 0;
|
|
1992
|
+
removed += await cleanupLegacyCodexTextFile(join(defaultCodexHome(), "history.jsonl"), warnings);
|
|
1993
|
+
removed += await cleanupLegacyCodexTextFile(join(defaultCodexHome(), "session_index.jsonl"), warnings);
|
|
1994
|
+
removed += await cleanupLegacyCodexTextFile(join(defaultCodexHome(), "rules", "default.rules"), warnings);
|
|
1995
|
+
return removed;
|
|
1996
|
+
}
|
|
1997
|
+
async function cleanupLegacyCodexTextFile(filePath, warnings) {
|
|
1998
|
+
try {
|
|
1999
|
+
if (!await pathExists(filePath))
|
|
2000
|
+
return 0;
|
|
2001
|
+
const current = await readFile(filePath, "utf8");
|
|
2002
|
+
const lines = current.split("\n");
|
|
2003
|
+
let removed = 0;
|
|
2004
|
+
const kept = lines.filter((line) => {
|
|
2005
|
+
if (!line)
|
|
2006
|
+
return true;
|
|
2007
|
+
if (isLegacyCodexHistoryMention(line)) {
|
|
2008
|
+
removed += 1;
|
|
2009
|
+
return false;
|
|
2010
|
+
}
|
|
2011
|
+
return true;
|
|
2012
|
+
});
|
|
2013
|
+
if (removed > 0) {
|
|
2014
|
+
await writeFile(filePath, kept.join("\n"), "utf8");
|
|
2015
|
+
}
|
|
2016
|
+
return removed;
|
|
2017
|
+
}
|
|
2018
|
+
catch (error) {
|
|
2019
|
+
warnings.push(`Could not clean Codex legacy text state ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2020
|
+
return 0;
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
function isLegacyCodexHistoryMention(line) {
|
|
2024
|
+
return line.includes("plugin://dbt-agent@plugins")
|
|
2025
|
+
|| line.includes("plugin://dbt-agent@embed-labs")
|
|
2026
|
+
|| /dbt-agent/i.test(line);
|
|
2027
|
+
}
|
|
2028
|
+
function removeLegacyCodexConfigTables(text) {
|
|
2029
|
+
const lines = text.match(/[^\n]*\n|[^\n]+$/g) ?? [];
|
|
2030
|
+
const output = [];
|
|
2031
|
+
const removedTables = [];
|
|
2032
|
+
let skipping = false;
|
|
2033
|
+
for (const line of lines) {
|
|
2034
|
+
const table = parseTomlTableHeader(line);
|
|
2035
|
+
if (table) {
|
|
2036
|
+
skipping = isLegacyCodexConfigTable(table);
|
|
2037
|
+
if (skipping) {
|
|
2038
|
+
removedTables.push(table);
|
|
2039
|
+
continue;
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
if (!skipping) {
|
|
2043
|
+
output.push(line);
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
return { text: output.join("").replace(/\n{3,}/g, "\n\n"), removedTables };
|
|
2047
|
+
}
|
|
2048
|
+
function parseTomlTableHeader(line) {
|
|
2049
|
+
const match = /^\s*\[([^\]]+)\]\s*(?:#.*)?$/.exec(line);
|
|
2050
|
+
return match?.[1]?.trim();
|
|
2051
|
+
}
|
|
2052
|
+
function isLegacyCodexConfigTable(table) {
|
|
2053
|
+
return /^plugins\."dbt-agent@[^"]+"$/.test(table)
|
|
2054
|
+
|| table === "mcp_servers.dbt-agent"
|
|
2055
|
+
|| table.startsWith("mcp_servers.dbt-agent.")
|
|
2056
|
+
|| table === 'mcp_servers."dbt-agent"'
|
|
2057
|
+
|| table.startsWith('mcp_servers."dbt-agent".');
|
|
2058
|
+
}
|
|
2059
|
+
function legacyCodexCleanupWarning(cleanup) {
|
|
2060
|
+
const parts = [];
|
|
2061
|
+
if (cleanup.legacy_removed_paths.length > 0) {
|
|
2062
|
+
parts.push(`removed ${cleanup.legacy_removed_paths.length} legacy Codex plugin path(s)`);
|
|
2063
|
+
}
|
|
2064
|
+
if (cleanup.legacy_removed_config_tables?.length) {
|
|
2065
|
+
parts.push(`removed ${cleanup.legacy_removed_config_tables.length} legacy Codex config table(s)`);
|
|
2066
|
+
}
|
|
2067
|
+
if (cleanup.legacy_removed_history_entries) {
|
|
2068
|
+
parts.push(`removed ${cleanup.legacy_removed_history_entries} legacy Codex text-state mention(s)`);
|
|
2069
|
+
}
|
|
2070
|
+
if (cleanup.legacy_stopped_processes) {
|
|
2071
|
+
parts.push(`stopped ${cleanup.legacy_stopped_processes} legacy Codex plugin process(es)`);
|
|
2072
|
+
}
|
|
2073
|
+
if (cleanup.warnings?.length) {
|
|
2074
|
+
parts.push(`cleanup warning(s): ${cleanup.warnings.join("; ")}`);
|
|
2075
|
+
}
|
|
2076
|
+
return parts.length > 0 ? `Legacy dbt-agent cleanup: ${parts.join(", ")}.` : undefined;
|
|
2077
|
+
}
|
|
2078
|
+
async function cleanupLegacyOpenCodePluginRemnants(targetRoot, globalInstall) {
|
|
2079
|
+
const removedPaths = [];
|
|
2080
|
+
const warnings = [];
|
|
2081
|
+
const legacyPaths = [
|
|
2082
|
+
join(targetRoot, "plugins", "development-board-toolchain.js"),
|
|
2083
|
+
join(targetRoot, "plugins", "dbt-agent.js"),
|
|
2084
|
+
join(targetRoot, "node_modules", "dbt-agent")
|
|
2085
|
+
];
|
|
2086
|
+
if (globalInstall) {
|
|
2087
|
+
legacyPaths.push(join(targetRoot, "plugins", "embed-labs.js"));
|
|
2088
|
+
}
|
|
2089
|
+
for (const candidate of legacyPaths) {
|
|
2090
|
+
try {
|
|
2091
|
+
if (await pathExists(candidate)) {
|
|
2092
|
+
await rm(candidate, { recursive: true, force: true });
|
|
2093
|
+
removedPaths.push(candidate);
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
catch (error) {
|
|
2097
|
+
warnings.push(`Could not remove ${candidate}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
return {
|
|
2101
|
+
legacy_removed_paths: removedPaths,
|
|
2102
|
+
legacy_removed_config_entries: [],
|
|
2103
|
+
...(warnings.length > 0 ? { warnings } : {})
|
|
2104
|
+
};
|
|
2105
|
+
}
|
|
2106
|
+
function legacyOpenCodeCleanupWarning(cleanup) {
|
|
2107
|
+
const parts = [];
|
|
2108
|
+
if (cleanup.legacy_removed_paths.length > 0) {
|
|
2109
|
+
parts.push(`removed ${cleanup.legacy_removed_paths.length} legacy OpenCode plugin path(s)`);
|
|
2110
|
+
}
|
|
2111
|
+
if (cleanup.legacy_removed_config_entries?.length) {
|
|
2112
|
+
parts.push(`removed ${cleanup.legacy_removed_config_entries.length} legacy OpenCode config entry(s)`);
|
|
2113
|
+
}
|
|
2114
|
+
if (cleanup.warnings?.length) {
|
|
2115
|
+
parts.push(`cleanup warning(s): ${cleanup.warnings.join("; ")}`);
|
|
2116
|
+
}
|
|
2117
|
+
return parts.length > 0 ? `Legacy OpenCode cleanup: ${parts.join(", ")}.` : undefined;
|
|
2118
|
+
}
|
|
2119
|
+
function combineWarnings(...warnings) {
|
|
2120
|
+
const actual = warnings.filter((warning) => Boolean(warning));
|
|
2121
|
+
return actual.length > 0 ? actual.join(" ") : undefined;
|
|
1815
2122
|
}
|
|
1816
2123
|
async function maybeRegisterCodexMcp(parsed, targetRoot, targetPath) {
|
|
1817
2124
|
const explicitTarget = Boolean(stringFlag(parsed, "target") || stringFlag(parsed, "codex-target"));
|
|
@@ -1984,10 +2291,54 @@ async function resolveExecutableOnPath(name) {
|
|
|
1984
2291
|
return undefined;
|
|
1985
2292
|
}
|
|
1986
2293
|
function defaultOpenCodeRoot() {
|
|
1987
|
-
return
|
|
2294
|
+
return globalOpenCodeRoot();
|
|
2295
|
+
}
|
|
2296
|
+
function globalOpenCodeRoot() {
|
|
2297
|
+
return join(homedir(), ".config", "opencode");
|
|
2298
|
+
}
|
|
2299
|
+
function isGlobalOpenCodeRoot(targetRoot) {
|
|
2300
|
+
return resolve(targetRoot) === resolve(globalOpenCodeRoot());
|
|
2301
|
+
}
|
|
2302
|
+
async function ensureOpenCodeGlobalPluginConfig() {
|
|
2303
|
+
const configPath = join(globalOpenCodeRoot(), "opencode.json");
|
|
2304
|
+
let existing = {};
|
|
2305
|
+
try {
|
|
2306
|
+
const parsed = JSON.parse(await readFile(configPath, "utf8"));
|
|
2307
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
2308
|
+
existing = parsed;
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
catch {
|
|
2312
|
+
existing = {};
|
|
2313
|
+
}
|
|
2314
|
+
const configured = Array.isArray(existing.plugin)
|
|
2315
|
+
? existing.plugin.filter((item) => typeof item === "string")
|
|
2316
|
+
: Array.isArray(existing.plugins)
|
|
2317
|
+
? existing.plugins.filter((item) => typeof item === "string")
|
|
2318
|
+
: [];
|
|
2319
|
+
const removed = configured.filter(isLegacyOpenCodePluginConfigEntry);
|
|
2320
|
+
const cleaned = configured.filter((item) => !isLegacyOpenCodePluginConfigEntry(item));
|
|
2321
|
+
if (!cleaned.includes("embed-labs")) {
|
|
2322
|
+
cleaned.push("embed-labs");
|
|
2323
|
+
}
|
|
2324
|
+
await writeFile(configPath, `${JSON.stringify({
|
|
2325
|
+
...existing,
|
|
2326
|
+
plugin: cleaned,
|
|
2327
|
+
plugins: undefined
|
|
2328
|
+
}, null, 2)}\n`, "utf8");
|
|
2329
|
+
return removed;
|
|
2330
|
+
}
|
|
2331
|
+
function isLegacyOpenCodePluginConfigEntry(item) {
|
|
2332
|
+
const normalized = item.replace(/\\/g, "/");
|
|
2333
|
+
return item === "dbt-agent"
|
|
2334
|
+
|| item === "development-board-toolchain"
|
|
2335
|
+
|| normalized === "./plugins/development-board-toolchain"
|
|
2336
|
+
|| normalized === "./plugins/development-board-toolchain.js"
|
|
2337
|
+
|| normalized.endsWith("/plugins/development-board-toolchain.js")
|
|
2338
|
+
|| normalized.includes("development-board-toolchain");
|
|
1988
2339
|
}
|
|
1989
2340
|
async function openCodeDuplicatePluginWarning(targetRoot) {
|
|
1990
|
-
const globalRoot =
|
|
2341
|
+
const globalRoot = globalOpenCodeRoot();
|
|
1991
2342
|
if (resolve(targetRoot) === resolve(globalRoot))
|
|
1992
2343
|
return undefined;
|
|
1993
2344
|
const configPath = join(globalRoot, "opencode.json");
|