@kvell007/embed-labs-cli 0.1.0-alpha.13 → 0.1.0-alpha.15
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 +120 -17
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
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
5
|
import { access, cp, mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
|
|
@@ -1352,27 +1352,45 @@ function isApiResponse(value) {
|
|
|
1352
1352
|
}
|
|
1353
1353
|
async function bridgeGet(path) {
|
|
1354
1354
|
const response = await fetch(`${DEFAULT_BRIDGE_URL}${path}`, {
|
|
1355
|
-
headers: bridgeHeaders()
|
|
1355
|
+
headers: bridgeHeaders("GET", path, "")
|
|
1356
1356
|
});
|
|
1357
1357
|
return await response.json();
|
|
1358
1358
|
}
|
|
1359
1359
|
async function bridgePost(path, body) {
|
|
1360
|
+
const bodyText = JSON.stringify(body);
|
|
1360
1361
|
const response = await fetch(`${DEFAULT_BRIDGE_URL}${path}`, {
|
|
1361
1362
|
method: "POST",
|
|
1362
|
-
headers: bridgeHeaders({ "content-type": "application/json" }),
|
|
1363
|
-
body:
|
|
1363
|
+
headers: bridgeHeaders("POST", path, bodyText, { "content-type": "application/json" }),
|
|
1364
|
+
body: bodyText
|
|
1364
1365
|
});
|
|
1365
1366
|
return await response.json();
|
|
1366
1367
|
}
|
|
1367
|
-
function bridgeHeaders(base = {}) {
|
|
1368
|
+
function bridgeHeaders(method, path, bodyText, base = {}) {
|
|
1368
1369
|
const token = process.env.EMBED_BRIDGE_TOKEN?.trim();
|
|
1369
1370
|
if (!token) {
|
|
1370
1371
|
return base;
|
|
1371
1372
|
}
|
|
1372
|
-
|
|
1373
|
+
const headers = {
|
|
1373
1374
|
...base,
|
|
1374
1375
|
authorization: `Bearer ${token}`
|
|
1375
1376
|
};
|
|
1377
|
+
addBridgeRequestSignature(headers, method, path, bodyText, token);
|
|
1378
|
+
return headers;
|
|
1379
|
+
}
|
|
1380
|
+
function addBridgeRequestSignature(headers, method, pathWithQuery, bodyText, token) {
|
|
1381
|
+
if (process.env.EMBED_BRIDGE_SIGNING === "0") {
|
|
1382
|
+
return;
|
|
1383
|
+
}
|
|
1384
|
+
const timestamp = String(Math.floor(Date.now() / 1000));
|
|
1385
|
+
const nonce = randomBytes(16).toString("hex");
|
|
1386
|
+
const bodySha256 = createHash("sha256").update(bodyText).digest("hex");
|
|
1387
|
+
const keyId = createHash("sha256").update(token).digest("hex").slice(0, 16);
|
|
1388
|
+
const canonical = cloudRequestCanonicalString(method, pathWithQuery, timestamp, nonce, bodySha256);
|
|
1389
|
+
headers["x-embed-key-id"] = keyId;
|
|
1390
|
+
headers["x-embed-timestamp"] = timestamp;
|
|
1391
|
+
headers["x-embed-nonce"] = nonce;
|
|
1392
|
+
headers["x-embed-body-sha256"] = bodySha256;
|
|
1393
|
+
headers["x-embed-signature"] = createHmac("sha256", token).update(canonical).digest("hex");
|
|
1376
1394
|
}
|
|
1377
1395
|
async function cloudGet(path) {
|
|
1378
1396
|
return await cloudRequest("GET", path);
|
|
@@ -1386,6 +1404,7 @@ async function cloudDownloadArtifact(artifactId, outputPath) {
|
|
|
1386
1404
|
const token = await cloudAuthToken();
|
|
1387
1405
|
if (token) {
|
|
1388
1406
|
headers.authorization = `Bearer ${token}`;
|
|
1407
|
+
addCloudRequestSignature(headers, "GET", `/v1/artifacts/${encodeURIComponent(artifactId)}/download`, "", token);
|
|
1389
1408
|
}
|
|
1390
1409
|
const response = await fetch(`${serviceBaseUrl(DEFAULT_CLOUD_API_URL)}/v1/artifacts/${encodeURIComponent(artifactId)}/download`, {
|
|
1391
1410
|
headers: Object.keys(headers).length > 0 ? headers : undefined
|
|
@@ -1421,17 +1440,19 @@ async function cloudDownloadArtifact(artifactId, outputPath) {
|
|
|
1421
1440
|
async function cloudRequest(method, path, body) {
|
|
1422
1441
|
try {
|
|
1423
1442
|
const headers = {};
|
|
1424
|
-
|
|
1443
|
+
const bodyText = body === undefined ? "" : JSON.stringify(body);
|
|
1444
|
+
if (bodyText) {
|
|
1425
1445
|
headers["content-type"] = "application/json";
|
|
1426
1446
|
}
|
|
1427
1447
|
const token = await cloudAuthToken();
|
|
1428
1448
|
if (token) {
|
|
1429
1449
|
headers.authorization = `Bearer ${token}`;
|
|
1450
|
+
addCloudRequestSignature(headers, method, path, bodyText, token);
|
|
1430
1451
|
}
|
|
1431
1452
|
const response = await fetch(`${serviceBaseUrl(DEFAULT_CLOUD_API_URL)}${path}`, {
|
|
1432
1453
|
method,
|
|
1433
1454
|
headers: Object.keys(headers).length > 0 ? headers : undefined,
|
|
1434
|
-
body: body === undefined ? undefined :
|
|
1455
|
+
body: body === undefined ? undefined : bodyText
|
|
1435
1456
|
});
|
|
1436
1457
|
return await response.json();
|
|
1437
1458
|
}
|
|
@@ -1441,6 +1462,39 @@ async function cloudRequest(method, path, body) {
|
|
|
1441
1462
|
});
|
|
1442
1463
|
}
|
|
1443
1464
|
}
|
|
1465
|
+
function addCloudRequestSignature(headers, method, pathWithQuery, bodyText, token) {
|
|
1466
|
+
if (process.env.EMBED_CLOUD_API_SIGNING === "0") {
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
const timestamp = String(Math.floor(Date.now() / 1000));
|
|
1470
|
+
const nonce = randomBytes(16).toString("hex");
|
|
1471
|
+
const bodySha256 = createHash("sha256").update(bodyText).digest("hex");
|
|
1472
|
+
const keyId = createHash("sha256").update(token).digest("hex").slice(0, 16);
|
|
1473
|
+
const canonical = cloudRequestCanonicalString(method, pathWithQuery, timestamp, nonce, bodySha256);
|
|
1474
|
+
headers["x-embed-key-id"] = keyId;
|
|
1475
|
+
headers["x-embed-timestamp"] = timestamp;
|
|
1476
|
+
headers["x-embed-nonce"] = nonce;
|
|
1477
|
+
headers["x-embed-body-sha256"] = bodySha256;
|
|
1478
|
+
headers["x-embed-signature"] = createHmac("sha256", token).update(canonical).digest("hex");
|
|
1479
|
+
}
|
|
1480
|
+
function cloudRequestCanonicalString(method, pathWithQuery, timestamp, nonce, bodySha256) {
|
|
1481
|
+
return [
|
|
1482
|
+
method.toUpperCase(),
|
|
1483
|
+
normalizeCloudPathForSignature(pathWithQuery),
|
|
1484
|
+
timestamp,
|
|
1485
|
+
nonce,
|
|
1486
|
+
bodySha256
|
|
1487
|
+
].join("\n");
|
|
1488
|
+
}
|
|
1489
|
+
function normalizeCloudPathForSignature(pathWithQuery) {
|
|
1490
|
+
try {
|
|
1491
|
+
const parsed = new URL(pathWithQuery, "http://embed.local");
|
|
1492
|
+
return `${parsed.pathname}${parsed.search}`;
|
|
1493
|
+
}
|
|
1494
|
+
catch {
|
|
1495
|
+
return pathWithQuery.startsWith("/") ? pathWithQuery : `/${pathWithQuery}`;
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1444
1498
|
async function pluginList(parsed) {
|
|
1445
1499
|
const releaseDir = stringFlag(parsed, "release-dir");
|
|
1446
1500
|
const manifest = releaseDir ? await readPluginReleaseManifest(releaseDir) : undefined;
|
|
@@ -1850,6 +1904,7 @@ async function cleanupLegacyCodexPluginRemnants(targetRoot) {
|
|
|
1850
1904
|
const removedPaths = [];
|
|
1851
1905
|
const removedConfigTables = [];
|
|
1852
1906
|
const warnings = [];
|
|
1907
|
+
const stoppedProcesses = await stopLegacyCodexPluginProcesses(warnings);
|
|
1853
1908
|
const legacyPaths = [
|
|
1854
1909
|
join(targetRoot, "dbt-agent"),
|
|
1855
1910
|
join(targetRoot, "development-board-toolchain"),
|
|
@@ -1859,7 +1914,7 @@ async function cleanupLegacyCodexPluginRemnants(targetRoot) {
|
|
|
1859
1914
|
];
|
|
1860
1915
|
legacyPaths.push(...await discoverLegacyCodexCachePaths(targetRoot));
|
|
1861
1916
|
if (resolve(targetRoot) === resolve(defaultCodexPluginRoot())) {
|
|
1862
|
-
legacyPaths.push(join(defaultCodexHome(), ".tmp", "plugins"), join(defaultCodexHome(), ".tmp", "plugins.sha"));
|
|
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"));
|
|
1863
1918
|
}
|
|
1864
1919
|
for (const candidate of legacyPaths) {
|
|
1865
1920
|
try {
|
|
@@ -1888,14 +1943,51 @@ async function cleanupLegacyCodexPluginRemnants(targetRoot) {
|
|
|
1888
1943
|
warnings.push(`Could not update ${configPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1889
1944
|
}
|
|
1890
1945
|
}
|
|
1891
|
-
const removedHistoryEntries = await
|
|
1946
|
+
const removedHistoryEntries = await cleanupLegacyCodexTextState(warnings);
|
|
1892
1947
|
return {
|
|
1893
1948
|
legacy_removed_paths: Array.from(new Set(removedPaths)),
|
|
1894
1949
|
legacy_removed_config_tables: removedConfigTables,
|
|
1895
1950
|
legacy_removed_history_entries: removedHistoryEntries,
|
|
1951
|
+
legacy_stopped_processes: stoppedProcesses,
|
|
1896
1952
|
...(warnings.length > 0 ? { warnings } : {})
|
|
1897
1953
|
};
|
|
1898
1954
|
}
|
|
1955
|
+
async function stopLegacyCodexPluginProcesses(warnings) {
|
|
1956
|
+
if (process.platform === "win32")
|
|
1957
|
+
return 0;
|
|
1958
|
+
try {
|
|
1959
|
+
const ps = await runLocalProcess("ps", ["-axo", "pid=,command="]);
|
|
1960
|
+
if (ps.code !== 0)
|
|
1961
|
+
return 0;
|
|
1962
|
+
let stopped = 0;
|
|
1963
|
+
for (const line of ps.stdout.split("\n")) {
|
|
1964
|
+
const match = /^\s*(\d+)\s+(.+)$/.exec(line);
|
|
1965
|
+
if (!match)
|
|
1966
|
+
continue;
|
|
1967
|
+
const pid = Number(match[1]);
|
|
1968
|
+
const command = match[2] || "";
|
|
1969
|
+
if (!isLegacyCodexPluginProcess(command))
|
|
1970
|
+
continue;
|
|
1971
|
+
try {
|
|
1972
|
+
process.kill(pid, "SIGTERM");
|
|
1973
|
+
stopped += 1;
|
|
1974
|
+
}
|
|
1975
|
+
catch (error) {
|
|
1976
|
+
warnings.push(`Could not stop legacy Codex plugin process ${pid}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
return stopped;
|
|
1980
|
+
}
|
|
1981
|
+
catch (error) {
|
|
1982
|
+
warnings.push(`Could not scan legacy Codex plugin processes: ${error instanceof Error ? error.message : String(error)}`);
|
|
1983
|
+
return 0;
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
function isLegacyCodexPluginProcess(command) {
|
|
1987
|
+
const trimmed = command.trim();
|
|
1988
|
+
return /^\/.*\/dbt-agent-mcp-bridge(?:\s|$)/.test(trimmed)
|
|
1989
|
+
|| /^dbt-agent-mcp-bridge(?:\s|$)/.test(trimmed);
|
|
1990
|
+
}
|
|
1899
1991
|
async function discoverLegacyCodexCachePaths(targetRoot) {
|
|
1900
1992
|
const paths = [];
|
|
1901
1993
|
const cacheRoot = join(targetRoot, "cache");
|
|
@@ -1913,12 +2005,18 @@ async function discoverLegacyCodexCachePaths(targetRoot) {
|
|
|
1913
2005
|
}
|
|
1914
2006
|
return paths;
|
|
1915
2007
|
}
|
|
1916
|
-
async function
|
|
1917
|
-
|
|
2008
|
+
async function cleanupLegacyCodexTextState(warnings) {
|
|
2009
|
+
let removed = 0;
|
|
2010
|
+
removed += await cleanupLegacyCodexTextFile(join(defaultCodexHome(), "history.jsonl"), warnings);
|
|
2011
|
+
removed += await cleanupLegacyCodexTextFile(join(defaultCodexHome(), "session_index.jsonl"), warnings);
|
|
2012
|
+
removed += await cleanupLegacyCodexTextFile(join(defaultCodexHome(), "rules", "default.rules"), warnings);
|
|
2013
|
+
return removed;
|
|
2014
|
+
}
|
|
2015
|
+
async function cleanupLegacyCodexTextFile(filePath, warnings) {
|
|
1918
2016
|
try {
|
|
1919
|
-
if (!await pathExists(
|
|
2017
|
+
if (!await pathExists(filePath))
|
|
1920
2018
|
return 0;
|
|
1921
|
-
const current = await readFile(
|
|
2019
|
+
const current = await readFile(filePath, "utf8");
|
|
1922
2020
|
const lines = current.split("\n");
|
|
1923
2021
|
let removed = 0;
|
|
1924
2022
|
const kept = lines.filter((line) => {
|
|
@@ -1931,18 +2029,20 @@ async function cleanupLegacyCodexHistoryMentions(warnings) {
|
|
|
1931
2029
|
return true;
|
|
1932
2030
|
});
|
|
1933
2031
|
if (removed > 0) {
|
|
1934
|
-
await writeFile(
|
|
2032
|
+
await writeFile(filePath, kept.join("\n"), "utf8");
|
|
1935
2033
|
}
|
|
1936
2034
|
return removed;
|
|
1937
2035
|
}
|
|
1938
2036
|
catch (error) {
|
|
1939
|
-
warnings.push(`Could not clean Codex legacy
|
|
2037
|
+
warnings.push(`Could not clean Codex legacy text state ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1940
2038
|
return 0;
|
|
1941
2039
|
}
|
|
1942
2040
|
}
|
|
1943
2041
|
function isLegacyCodexHistoryMention(line) {
|
|
1944
2042
|
return line.includes("plugin://dbt-agent@plugins")
|
|
1945
2043
|
|| line.includes("plugin://dbt-agent@embed-labs")
|
|
2044
|
+
|| line.includes("development-board-toolchain-dev")
|
|
2045
|
+
|| line.includes("development-board-toolchain")
|
|
1946
2046
|
|| /dbt-agent/i.test(line);
|
|
1947
2047
|
}
|
|
1948
2048
|
function removeLegacyCodexConfigTables(text) {
|
|
@@ -1985,7 +2085,10 @@ function legacyCodexCleanupWarning(cleanup) {
|
|
|
1985
2085
|
parts.push(`removed ${cleanup.legacy_removed_config_tables.length} legacy Codex config table(s)`);
|
|
1986
2086
|
}
|
|
1987
2087
|
if (cleanup.legacy_removed_history_entries) {
|
|
1988
|
-
parts.push(`removed ${cleanup.legacy_removed_history_entries} legacy Codex
|
|
2088
|
+
parts.push(`removed ${cleanup.legacy_removed_history_entries} legacy Codex text-state mention(s)`);
|
|
2089
|
+
}
|
|
2090
|
+
if (cleanup.legacy_stopped_processes) {
|
|
2091
|
+
parts.push(`stopped ${cleanup.legacy_stopped_processes} legacy Codex plugin process(es)`);
|
|
1989
2092
|
}
|
|
1990
2093
|
if (cleanup.warnings?.length) {
|
|
1991
2094
|
parts.push(`cleanup warning(s): ${cleanup.warnings.join("; ")}`);
|