@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 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: JSON.stringify(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
- return {
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
- if (body !== undefined) {
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 : JSON.stringify(body)
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 cleanupLegacyCodexHistoryMentions(warnings);
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 cleanupLegacyCodexHistoryMentions(warnings) {
1917
- const historyPath = join(defaultCodexHome(), "history.jsonl");
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(historyPath))
2017
+ if (!await pathExists(filePath))
1920
2018
  return 0;
1921
- const current = await readFile(historyPath, "utf8");
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(historyPath, kept.join("\n"), "utf8");
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 history mentions: ${error instanceof Error ? error.message : String(error)}`);
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 history mention(s)`);
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("; ")}`);