@cubis/foundry 0.3.49 → 0.3.50

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/bin/cubis.js CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  readFile,
12
12
  rm,
13
13
  stat,
14
+ symlink,
14
15
  writeFile,
15
16
  } from "node:fs/promises";
16
17
  import { createRequire } from "node:module";
@@ -387,9 +388,7 @@ function normalizeMcpRuntime(value, fallback = DEFAULT_MCP_RUNTIME) {
387
388
  .trim()
388
389
  .toLowerCase();
389
390
  if (!MCP_RUNTIMES.has(normalized)) {
390
- throw new Error(
391
- `Unknown MCP runtime '${value}'. Use docker|local.`,
392
- );
391
+ throw new Error(`Unknown MCP runtime '${value}'. Use docker|local.`);
393
392
  }
394
393
  return normalized;
395
394
  }
@@ -399,9 +398,7 @@ function normalizeMcpFallback(value, fallback = DEFAULT_MCP_FALLBACK) {
399
398
  .trim()
400
399
  .toLowerCase();
401
400
  if (!MCP_FALLBACKS.has(normalized)) {
402
- throw new Error(
403
- `Unknown MCP fallback '${value}'. Use local|fail|skip.`,
404
- );
401
+ throw new Error(`Unknown MCP fallback '${value}'. Use local|fail|skip.`);
405
402
  }
406
403
  return normalized;
407
404
  }
@@ -411,9 +408,7 @@ function normalizeMcpUpdatePolicy(value, fallback = DEFAULT_MCP_UPDATE_POLICY) {
411
408
  .trim()
412
409
  .toLowerCase();
413
410
  if (!MCP_UPDATE_POLICIES.has(normalized)) {
414
- throw new Error(
415
- `Unknown MCP update policy '${value}'. Use pinned|latest.`,
416
- );
411
+ throw new Error(`Unknown MCP update policy '${value}'. Use pinned|latest.`);
417
412
  }
418
413
  return normalized;
419
414
  }
@@ -500,10 +495,7 @@ async function ensureMcpDockerImage({
500
495
  return pullMcpDockerImage({ image, cwd });
501
496
  }
502
497
 
503
- async function inspectDockerContainerByName({
504
- name,
505
- cwd = process.cwd(),
506
- }) {
498
+ async function inspectDockerContainerByName({ name, cwd = process.cwd() }) {
507
499
  try {
508
500
  const { stdout } = await execFile(
509
501
  "docker",
@@ -551,10 +543,7 @@ async function resolveDockerContainerHostPort({
551
543
  }
552
544
  }
553
545
 
554
- async function inspectDockerContainerMounts({
555
- name,
556
- cwd = process.cwd(),
557
- }) {
546
+ async function inspectDockerContainerMounts({ name, cwd = process.cwd() }) {
558
547
  try {
559
548
  const { stdout } = await execFile(
560
549
  "docker",
@@ -1498,6 +1487,152 @@ async function collectTechSnapshot(rootDir) {
1498
1487
  }
1499
1488
  }
1500
1489
 
1490
+ // --- Entry-point detection ---
1491
+ const entryPoints = [];
1492
+ try {
1493
+ const rootPkg = JSON.parse(await readFile(rootPackageJsonPath, "utf8"));
1494
+ if (rootPkg.bin) {
1495
+ const bins =
1496
+ typeof rootPkg.bin === "string"
1497
+ ? { [rootPkg.name || "bin"]: rootPkg.bin }
1498
+ : rootPkg.bin;
1499
+ for (const [name, target] of Object.entries(bins)) {
1500
+ entryPoints.push(`bin: ${name} → ${target}`);
1501
+ }
1502
+ }
1503
+ if (rootPkg.main) entryPoints.push(`main: ${rootPkg.main}`);
1504
+ if (rootPkg.module) entryPoints.push(`module: ${rootPkg.module}`);
1505
+ if (rootPkg.exports) entryPoints.push("package exports field present");
1506
+ } catch {
1507
+ // no root package.json or malformed
1508
+ }
1509
+ if (fileExists("Dockerfile") || fileExists("mcp/Dockerfile")) {
1510
+ try {
1511
+ const dockerfilePath = fileExists("Dockerfile")
1512
+ ? path.join(rootDir, "Dockerfile")
1513
+ : path.join(rootDir, "mcp/Dockerfile");
1514
+ const dockerContent = await readFile(dockerfilePath, "utf8");
1515
+ const entrypointMatch = dockerContent.match(
1516
+ /^(?:ENTRYPOINT|CMD)\s+(.+)$/m,
1517
+ );
1518
+ if (entrypointMatch) {
1519
+ entryPoints.push(`docker: ${entrypointMatch[0].slice(0, 80).trim()}`);
1520
+ }
1521
+ } catch {
1522
+ // ignore
1523
+ }
1524
+ }
1525
+
1526
+ // --- README excerpt (first 3 non-empty lines for project overview) ---
1527
+ let readmeExcerpt = "";
1528
+ for (const readmeName of ["README.md", "readme.md", "Readme.md"]) {
1529
+ if (fileExists(readmeName)) {
1530
+ try {
1531
+ const readmeContent = await readFile(
1532
+ path.join(rootDir, readmeName),
1533
+ "utf8",
1534
+ );
1535
+ const readmeLines = readmeContent.split("\n").filter((l) => {
1536
+ const trimmed = l.trim();
1537
+ return trimmed.length > 0 && !trimmed.startsWith("![");
1538
+ });
1539
+ readmeExcerpt = readmeLines.slice(0, 3).join("\n");
1540
+ } catch {
1541
+ // ignore
1542
+ }
1543
+ break;
1544
+ }
1545
+ }
1546
+
1547
+ // --- Monorepo detection ---
1548
+ const isMonorepo = packageJsonFiles.length > 1;
1549
+ let workspaceRoots = [];
1550
+ if (isMonorepo) {
1551
+ try {
1552
+ const rootPkg = JSON.parse(await readFile(rootPackageJsonPath, "utf8"));
1553
+ if (Array.isArray(rootPkg.workspaces)) {
1554
+ workspaceRoots = rootPkg.workspaces;
1555
+ } else if (rootPkg.workspaces?.packages) {
1556
+ workspaceRoots = rootPkg.workspaces.packages;
1557
+ }
1558
+ } catch {
1559
+ // ignore
1560
+ }
1561
+ }
1562
+
1563
+ // --- MCP server detection (is this project itself an MCP server?) ---
1564
+ const isMcpServer =
1565
+ javascriptPackages.has("@modelcontextprotocol/sdk") ||
1566
+ dartPackages.has("mcp_dart") ||
1567
+ pythonPackages.has("mcp");
1568
+
1569
+ // --- CI/CD detection ---
1570
+ const cicdSignals = [];
1571
+ const ciPaths = [
1572
+ [".github/workflows", "GitHub Actions"],
1573
+ ["Jenkinsfile", "Jenkins"],
1574
+ [".gitlab-ci.yml", "GitLab CI"],
1575
+ [".circleci", "CircleCI"],
1576
+ ["azure-pipelines.yml", "Azure Pipelines"],
1577
+ [".travis.yml", "Travis CI"],
1578
+ ["bitbucket-pipelines.yml", "Bitbucket Pipelines"],
1579
+ [".buildkite", "Buildkite"],
1580
+ ["vercel.json", "Vercel"],
1581
+ ["netlify.toml", "Netlify"],
1582
+ ["fly.toml", "Fly.io"],
1583
+ ["render.yaml", "Render"],
1584
+ ["railway.json", "Railway"],
1585
+ ];
1586
+ for (const [ciPath, ciName] of ciPaths) {
1587
+ if (fileExists(ciPath)) cicdSignals.push(ciName);
1588
+ }
1589
+
1590
+ // --- Directory purpose annotations ---
1591
+ const DIR_PURPOSES = new Map([
1592
+ ["src", "source"],
1593
+ ["lib", "library"],
1594
+ ["bin", "executables"],
1595
+ ["scripts", "automation"],
1596
+ ["test", "tests"],
1597
+ ["tests", "tests"],
1598
+ ["__tests__", "tests"],
1599
+ ["spec", "tests"],
1600
+ ["docs", "documentation"],
1601
+ ["doc", "documentation"],
1602
+ ["mcp", "MCP server"],
1603
+ ["api", "API layer"],
1604
+ ["app", "application"],
1605
+ ["pages", "routes (pages)"],
1606
+ ["routes", "routes"],
1607
+ ["components", "UI components"],
1608
+ ["public", "static assets"],
1609
+ ["static", "static assets"],
1610
+ ["assets", "assets"],
1611
+ ["config", "configuration"],
1612
+ ["generated", "generated code"],
1613
+ ["migrations", "DB migrations"],
1614
+ ["prisma", "Prisma schema"],
1615
+ ["workflows", "CI/CD or automation"],
1616
+ ["packages", "monorepo packages"],
1617
+ ["apps", "monorepo apps"],
1618
+ ["tools", "tooling"],
1619
+ ["utils", "utilities"],
1620
+ ["helpers", "helpers"],
1621
+ ["middleware", "middleware"],
1622
+ ["services", "services"],
1623
+ ["models", "data models"],
1624
+ ["controllers", "controllers"],
1625
+ ["views", "views"],
1626
+ ["templates", "templates"],
1627
+ ["fixtures", "test fixtures"],
1628
+ ["e2e", "end-to-end tests"],
1629
+ ["integration", "integration tests"],
1630
+ ]);
1631
+ const annotatedDirs = sortedTopDirs.map((dir) => ({
1632
+ name: dir,
1633
+ purpose: DIR_PURPOSES.get(dir.toLowerCase()) || null,
1634
+ }));
1635
+
1501
1636
  return {
1502
1637
  rootDir,
1503
1638
  scannedFiles: discoveredFiles.length,
@@ -1505,6 +1640,7 @@ async function collectTechSnapshot(rootDir) {
1505
1640
  frameworks: sortedFrameworks,
1506
1641
  lockfiles: sortedLockfiles,
1507
1642
  topDirs: sortedTopDirs,
1643
+ annotatedDirs,
1508
1644
  keyScripts,
1509
1645
  packageSignals: {
1510
1646
  javascript: toSortedArray(javascriptPackages),
@@ -1514,6 +1650,12 @@ async function collectTechSnapshot(rootDir) {
1514
1650
  rust: toSortedArray(rustCrates),
1515
1651
  },
1516
1652
  mcpSignals: toSortedArray(mcpSignals),
1653
+ entryPoints,
1654
+ readmeExcerpt,
1655
+ isMonorepo,
1656
+ workspaceRoots,
1657
+ isMcpServer,
1658
+ cicdSignals,
1517
1659
  };
1518
1660
  }
1519
1661
 
@@ -1538,6 +1680,7 @@ function appendTechPackageSection(lines, heading, packages) {
1538
1680
  function inferRecommendedSkills(snapshot) {
1539
1681
  const recommended = new Set();
1540
1682
  const hasFramework = (name) => snapshot.frameworks.includes(name);
1683
+ const hasJsPkg = (name) => snapshot.packageSignals.javascript.includes(name);
1541
1684
 
1542
1685
  if (hasFramework("Next.js")) {
1543
1686
  recommended.add("nextjs-developer");
@@ -1565,6 +1708,56 @@ function inferRecommendedSkills(snapshot) {
1565
1708
  recommended.add("flutter-expert");
1566
1709
  recommended.add("mobile-design");
1567
1710
  }
1711
+
1712
+ // Language-level signals when no framework match already added the skill
1713
+ const hasLanguage = (lang) => snapshot.languages.some(([l]) => l === lang);
1714
+ if (hasLanguage("TypeScript") && !recommended.has("typescript-pro")) {
1715
+ recommended.add("typescript-pro");
1716
+ }
1717
+ if (
1718
+ (hasLanguage("JavaScript") || hasLanguage("TypeScript")) &&
1719
+ !recommended.has("nodejs-best-practices") &&
1720
+ !recommended.has("nextjs-developer") &&
1721
+ !recommended.has("nestjs-expert")
1722
+ ) {
1723
+ recommended.add("nodejs-best-practices");
1724
+ }
1725
+ if (hasLanguage("Python") && !recommended.has("python-pro")) {
1726
+ recommended.add("python-pro");
1727
+ }
1728
+
1729
+ // MCP server detection
1730
+ if (snapshot.isMcpServer) {
1731
+ recommended.add("api-patterns");
1732
+ }
1733
+
1734
+ // Docker / DevOps signals
1735
+ if (
1736
+ snapshot.cicdSignals.length > 0 ||
1737
+ snapshot.entryPoints.some((ep) => ep.startsWith("docker:"))
1738
+ ) {
1739
+ recommended.add("devops-engineer");
1740
+ }
1741
+
1742
+ // Testing signals
1743
+ if (hasFramework("Playwright")) {
1744
+ recommended.add("playwright-expert");
1745
+ }
1746
+ if (hasFramework("Vitest") || hasFramework("Jest")) {
1747
+ recommended.add("test-master");
1748
+ }
1749
+
1750
+ // Database signals
1751
+ if (
1752
+ hasFramework("Prisma") ||
1753
+ hasFramework("Drizzle ORM") ||
1754
+ hasFramework("TypeORM") ||
1755
+ hasFramework("Mongoose") ||
1756
+ hasFramework("SQLAlchemy")
1757
+ ) {
1758
+ recommended.add("database-skills");
1759
+ }
1760
+
1568
1761
  if (recommended.size === 0) {
1569
1762
  recommended.add("clean-code");
1570
1763
  recommended.add("plan-writing");
@@ -1585,7 +1778,9 @@ function inferContextBudget(snapshot) {
1585
1778
  const webBackendDetected = snapshot.frameworks.some((framework) =>
1586
1779
  webBackendSignals.has(framework),
1587
1780
  );
1588
- const suggestedProfile = webBackendDetected ? "web-backend" : "core";
1781
+ const hasMcp = snapshot.isMcpServer || snapshot.mcpSignals.length > 0;
1782
+ const suggestedProfile =
1783
+ webBackendDetected || hasMcp ? "web-backend" : "core";
1589
1784
  return {
1590
1785
  suggestedProfile,
1591
1786
  };
@@ -1606,6 +1801,39 @@ function buildTechMd(snapshot, { compact = false } = {}) {
1606
1801
  lines.push(`Mode: ${compact ? "compact" : "full"}.`);
1607
1802
  lines.push("");
1608
1803
 
1804
+ // --- Project Overview ---
1805
+ lines.push("## Project Overview");
1806
+ if (snapshot.readmeExcerpt) {
1807
+ lines.push("");
1808
+ lines.push(snapshot.readmeExcerpt);
1809
+ }
1810
+ lines.push("");
1811
+ if (snapshot.isMonorepo) {
1812
+ lines.push("**Monorepo** — multiple package.json files detected.");
1813
+ if (snapshot.workspaceRoots.length > 0) {
1814
+ lines.push(
1815
+ `Workspace roots: ${snapshot.workspaceRoots.map((r) => `\`${r}\``).join(", ")}`,
1816
+ );
1817
+ }
1818
+ lines.push("");
1819
+ }
1820
+ if (snapshot.isMcpServer) {
1821
+ lines.push(
1822
+ "**MCP Server** — this project exposes tools via the Model Context Protocol.",
1823
+ );
1824
+ lines.push("");
1825
+ }
1826
+
1827
+ // --- Architecture: Entry Points ---
1828
+ if (snapshot.entryPoints.length > 0) {
1829
+ lines.push("## Entry Points");
1830
+ for (const ep of snapshot.entryPoints) {
1831
+ lines.push(`- ${ep}`);
1832
+ }
1833
+ lines.push("");
1834
+ }
1835
+
1836
+ // --- Stack Snapshot ---
1609
1837
  lines.push("## Stack Snapshot");
1610
1838
  if (snapshot.frameworks.length === 0) {
1611
1839
  lines.push("- No major framework signal detected.");
@@ -1632,6 +1860,15 @@ function buildTechMd(snapshot, { compact = false } = {}) {
1632
1860
  }
1633
1861
  lines.push("");
1634
1862
 
1863
+ // --- CI/CD & Deployment ---
1864
+ if (snapshot.cicdSignals.length > 0) {
1865
+ lines.push("## CI/CD & Deployment");
1866
+ for (const signal of snapshot.cicdSignals) {
1867
+ lines.push(`- ${signal}`);
1868
+ }
1869
+ lines.push("");
1870
+ }
1871
+
1635
1872
  lines.push("## MCP Footprint");
1636
1873
  if (!snapshot.mcpSignals || snapshot.mcpSignals.length === 0) {
1637
1874
  lines.push("- No MCP configuration signals detected in workspace.");
@@ -1687,7 +1924,7 @@ function buildTechMd(snapshot, { compact = false } = {}) {
1687
1924
  }
1688
1925
  lines.push("");
1689
1926
 
1690
- lines.push("## Key Scripts");
1927
+ lines.push("## Development Commands");
1691
1928
  if (snapshot.keyScripts.length === 0) {
1692
1929
  lines.push("- No common scripts detected.");
1693
1930
  } else {
@@ -1697,8 +1934,13 @@ function buildTechMd(snapshot, { compact = false } = {}) {
1697
1934
  }
1698
1935
  lines.push("");
1699
1936
 
1700
- lines.push("## Important Top-Level Paths");
1701
- if (snapshot.topDirs.length === 0) {
1937
+ lines.push("## Codebase Map");
1938
+ if (snapshot.annotatedDirs && snapshot.annotatedDirs.length > 0) {
1939
+ for (const { name, purpose } of snapshot.annotatedDirs) {
1940
+ const suffix = purpose ? ` — ${purpose}` : "";
1941
+ lines.push(`- \`${name}/\`${suffix}`);
1942
+ }
1943
+ } else if (snapshot.topDirs.length === 0) {
1702
1944
  lines.push("- No significant top-level directories detected.");
1703
1945
  } else {
1704
1946
  for (const dir of snapshot.topDirs) {
@@ -2776,13 +3018,10 @@ async function generateCodexWrapperSkills({
2776
3018
  const rewrittenBody = rewriteCodexAgentSkillReferences(metadata.body);
2777
3019
  const wrapperSkillId = `${CODEX_AGENT_SKILL_PREFIX}${metadata.id}`;
2778
3020
  const destinationDir = path.join(skillsDir, wrapperSkillId);
2779
- const content = buildCodexAgentWrapperSkillMarkdown(
2780
- wrapperSkillId,
2781
- {
2782
- ...metadata,
2783
- body: rewrittenBody,
2784
- },
2785
- );
3021
+ const content = buildCodexAgentWrapperSkillMarkdown(wrapperSkillId, {
3022
+ ...metadata,
3023
+ body: rewrittenBody,
3024
+ });
2786
3025
 
2787
3026
  const result = await writeGeneratedSkillArtifact({
2788
3027
  destinationDir,
@@ -2880,7 +3119,9 @@ function buildManagedWorkflowBlock(platformId, workflows) {
2880
3119
  lines.push("Selection policy:");
2881
3120
  lines.push("1. If user names a $workflow-*, use it directly.");
2882
3121
  lines.push("2. Else map intent to one primary workflow.");
2883
- lines.push("3. Use $agent-* wrappers only when role specialization is needed.");
3122
+ lines.push(
3123
+ "3. Use $agent-* wrappers only when role specialization is needed.",
3124
+ );
2884
3125
  lines.push("");
2885
3126
  lines.push("<!-- cbx:workflows:auto:end -->");
2886
3127
  return lines.join("\n");
@@ -3195,6 +3436,7 @@ async function copyArtifact({
3195
3436
  destination,
3196
3437
  overwrite,
3197
3438
  dryRun = false,
3439
+ useSymlinks = false,
3198
3440
  }) {
3199
3441
  const exists = await pathExists(destination);
3200
3442
  if (exists && !overwrite) {
@@ -3207,16 +3449,36 @@ async function copyArtifact({
3207
3449
 
3208
3450
  if (!dryRun) {
3209
3451
  await mkdir(path.dirname(destination), { recursive: true });
3210
- await cp(source, destination, { recursive: true, force: true });
3452
+ if (useSymlinks) {
3453
+ const absSource = path.resolve(source);
3454
+ await symlink(absSource, destination);
3455
+ } else {
3456
+ await cp(source, destination, { recursive: true, force: true });
3457
+ }
3211
3458
  }
3212
3459
 
3213
3460
  if (dryRun) {
3214
3461
  return {
3215
- action: exists ? "would-replace" : "would-install",
3462
+ action: exists
3463
+ ? useSymlinks
3464
+ ? "would-replace (link)"
3465
+ : "would-replace"
3466
+ : useSymlinks
3467
+ ? "would-link"
3468
+ : "would-install",
3216
3469
  path: destination,
3217
3470
  };
3218
3471
  }
3219
- return { action: exists ? "replaced" : "installed", path: destination };
3472
+ return {
3473
+ action: exists
3474
+ ? useSymlinks
3475
+ ? "linked (replaced)"
3476
+ : "replaced"
3477
+ : useSymlinks
3478
+ ? "linked"
3479
+ : "installed",
3480
+ path: destination,
3481
+ };
3220
3482
  }
3221
3483
 
3222
3484
  async function writeGeneratedArtifact({
@@ -4125,10 +4387,13 @@ async function resolvePostmanInstallSelection({
4125
4387
  }
4126
4388
 
4127
4389
  const stitchRequested = Boolean(options.stitch);
4128
- const enabled = Boolean(options.postman) || hasWorkspaceOption || stitchRequested;
4390
+ const enabled =
4391
+ Boolean(options.postman) || hasWorkspaceOption || stitchRequested;
4129
4392
  if (!enabled) return { enabled: false };
4130
4393
 
4131
- const envApiKey = normalizePostmanApiKey(process.env[POSTMAN_API_KEY_ENV_VAR]);
4394
+ const envApiKey = normalizePostmanApiKey(
4395
+ process.env[POSTMAN_API_KEY_ENV_VAR],
4396
+ );
4132
4397
  let defaultWorkspaceId = hasWorkspaceOption
4133
4398
  ? normalizePostmanWorkspaceId(options.postmanWorkspaceId)
4134
4399
  : null;
@@ -4308,7 +4573,11 @@ async function resolvePostmanInstallSelection({
4308
4573
  );
4309
4574
  }
4310
4575
 
4311
- const stitchApiKeySource = stitchEnabled ? (envStitchApiKey ? "env" : "unset") : null;
4576
+ const stitchApiKeySource = stitchEnabled
4577
+ ? envStitchApiKey
4578
+ ? "env"
4579
+ : "unset"
4580
+ : null;
4312
4581
  if (stitchEnabled && stitchApiKeySource === "unset") {
4313
4582
  warnings.push(
4314
4583
  `Google Stitch API key is not configured. Set ${profileEnvVarAlias("stitch", DEFAULT_CREDENTIAL_PROFILE_NAME)}.`,
@@ -4457,7 +4726,9 @@ async function configurePostmanInstallArtifacts({
4457
4726
  typeof existingCbxConfig.value === "object" &&
4458
4727
  !Array.isArray(existingCbxConfig.value)
4459
4728
  ) {
4460
- const migration = migrateInlineCredentialsInConfig(existingCbxConfig.value);
4729
+ const migration = migrateInlineCredentialsInConfig(
4730
+ existingCbxConfig.value,
4731
+ );
4461
4732
  if (migration.findings.length > 0) {
4462
4733
  warnings.push(
4463
4734
  `Detected ${migration.findings.length} inline key field(s) in existing ${CBX_CONFIG_FILENAME}; migrated to env-var aliases.`,
@@ -4625,8 +4896,12 @@ async function configurePostmanInstallArtifacts({
4625
4896
  let mcpCatalogSyncResults = [];
4626
4897
  if (postmanSelection.mcpToolSync && !dryRun) {
4627
4898
  try {
4628
- const currentConfig = await readJsonFileIfExists(postmanSelection.cbxConfigPath);
4629
- const services = shouldInstallStitch ? ["postman", "stitch"] : ["postman"];
4899
+ const currentConfig = await readJsonFileIfExists(
4900
+ postmanSelection.cbxConfigPath,
4901
+ );
4902
+ const services = shouldInstallStitch
4903
+ ? ["postman", "stitch"]
4904
+ : ["postman"];
4630
4905
  mcpCatalogSyncResults = await syncMcpToolCatalogs({
4631
4906
  scope: postmanSelection.mcpScope,
4632
4907
  services,
@@ -4934,6 +5209,7 @@ async function installBundleArtifacts({
4934
5209
  extraSkillIds = [],
4935
5210
  skillProfile = DEFAULT_SKILL_PROFILE,
4936
5211
  terminalVerifierSelection = null,
5212
+ useSymlinks = false,
4937
5213
  dryRun = false,
4938
5214
  cwd = process.cwd(),
4939
5215
  }) {
@@ -4983,6 +5259,9 @@ async function installBundleArtifacts({
4983
5259
  prompts: [],
4984
5260
  };
4985
5261
 
5262
+ // Bind useSymlinks into copyArtifact so every call site inherits it
5263
+ const copyArt = (args) => copyArtifact({ ...args, useSymlinks });
5264
+
4986
5265
  const workflowFiles = Array.isArray(platformSpec.workflows)
4987
5266
  ? platformSpec.workflows
4988
5267
  : [];
@@ -4997,7 +5276,7 @@ async function installBundleArtifacts({
4997
5276
  throw new Error(`Missing workflow source file: ${source}`);
4998
5277
  }
4999
5278
 
5000
- const result = await copyArtifact({
5279
+ const result = await copyArt({
5001
5280
  source,
5002
5281
  destination,
5003
5282
  overwrite,
@@ -5025,7 +5304,7 @@ async function installBundleArtifacts({
5025
5304
  throw new Error(`Missing agent source file: ${source}`);
5026
5305
  }
5027
5306
 
5028
- const result = await copyArtifact({
5307
+ const result = await copyArt({
5029
5308
  source,
5030
5309
  destination,
5031
5310
  overwrite,
@@ -5052,7 +5331,7 @@ async function installBundleArtifacts({
5052
5331
  throw new Error(`Missing command source file: ${source}`);
5053
5332
  }
5054
5333
 
5055
- const result = await copyArtifact({
5334
+ const result = await copyArt({
5056
5335
  source,
5057
5336
  destination,
5058
5337
  overwrite,
@@ -5079,7 +5358,7 @@ async function installBundleArtifacts({
5079
5358
  throw new Error(`Missing prompt source file: ${source}`);
5080
5359
  }
5081
5360
 
5082
- const result = await copyArtifact({
5361
+ const result = await copyArt({
5083
5362
  source,
5084
5363
  destination,
5085
5364
  overwrite,
@@ -5109,7 +5388,7 @@ async function installBundleArtifacts({
5109
5388
  );
5110
5389
  }
5111
5390
 
5112
- const result = await copyArtifact({
5391
+ const result = await copyArt({
5113
5392
  source,
5114
5393
  destination,
5115
5394
  overwrite,
@@ -5131,7 +5410,7 @@ async function installBundleArtifacts({
5131
5410
  profilePaths.skillsDir,
5132
5411
  "skills_index.json",
5133
5412
  );
5134
- const indexResult = await copyArtifact({
5413
+ const indexResult = await copyArt({
5135
5414
  source: skillsIndexSource,
5136
5415
  destination: skillsIndexDest,
5137
5416
  overwrite,
@@ -5612,9 +5891,13 @@ function printPostmanSetupSummary({ postmanSetup }) {
5612
5891
  console.log(`- MCP fallback: ${postmanSetup.mcpFallback}`);
5613
5892
  console.log(`- MCP image: ${postmanSetup.mcpImage}`);
5614
5893
  console.log(`- MCP update policy: ${postmanSetup.mcpUpdatePolicy}`);
5615
- console.log(`- MCP build local: ${postmanSetup.mcpBuildLocal ? "yes" : "no"}`);
5894
+ console.log(
5895
+ `- MCP build local: ${postmanSetup.mcpBuildLocal ? "yes" : "no"}`,
5896
+ );
5616
5897
  console.log(`- MCP image prepare: ${postmanSetup.dockerImageAction}`);
5617
- console.log(`- MCP tool sync: ${postmanSetup.mcpToolSync ? "enabled" : "disabled"}`);
5898
+ console.log(
5899
+ `- MCP tool sync: ${postmanSetup.mcpToolSync ? "enabled" : "disabled"}`,
5900
+ );
5618
5901
  console.log(
5619
5902
  `- Foundry MCP side-by-side: ${postmanSetup.foundryMcpEnabled ? "enabled" : "disabled"}`,
5620
5903
  );
@@ -5649,7 +5932,9 @@ function printPostmanSetupSummary({ postmanSetup }) {
5649
5932
  if (Array.isArray(postmanSetup.mcpCatalogSyncResults)) {
5650
5933
  for (const syncItem of postmanSetup.mcpCatalogSyncResults) {
5651
5934
  if (syncItem.action === "skipped") {
5652
- console.log(`- MCP catalog ${syncItem.service}: skipped (${syncItem.reason})`);
5935
+ console.log(
5936
+ `- MCP catalog ${syncItem.service}: skipped (${syncItem.reason})`,
5937
+ );
5653
5938
  } else {
5654
5939
  console.log(
5655
5940
  `- MCP catalog ${syncItem.service}: ${syncItem.action} (${syncItem.toolCount} tools)`,
@@ -6153,6 +6438,14 @@ function withInstallOptions(command) {
6153
6438
  DEFAULT_SKILL_PROFILE,
6154
6439
  )
6155
6440
  .option("--all-skills", "alias for --skill-profile full")
6441
+ .option(
6442
+ "--target <path>",
6443
+ "install into target project directory instead of cwd",
6444
+ )
6445
+ .option(
6446
+ "--link",
6447
+ "create symlinks instead of copies (edits in source are instantly reflected)",
6448
+ )
6156
6449
  .option("--dry-run", "preview install without writing files")
6157
6450
  .option("-y, --yes", "skip interactive confirmation");
6158
6451
  }
@@ -6416,7 +6709,14 @@ async function cleanupAntigravityTerminalIntegration({
6416
6709
 
6417
6710
  async function runWorkflowInstall(options) {
6418
6711
  try {
6419
- const cwd = process.cwd();
6712
+ const cwd = options.target ? path.resolve(options.target) : process.cwd();
6713
+ if (options.target) {
6714
+ const targetExists = await pathExists(cwd);
6715
+ if (!targetExists) {
6716
+ throw new Error(`Target directory does not exist: ${cwd}`);
6717
+ }
6718
+ }
6719
+ const useSymlinks = Boolean(options.link);
6420
6720
  const scope = normalizeScope(options.scope);
6421
6721
  const ruleScope = scope === "global" ? "project" : scope;
6422
6722
  const dryRun = Boolean(options.dryRun);
@@ -6467,6 +6767,7 @@ async function runWorkflowInstall(options) {
6467
6767
  : [],
6468
6768
  skillProfile: skillInstallOptions.skillProfile,
6469
6769
  terminalVerifierSelection,
6770
+ useSymlinks,
6470
6771
  dryRun,
6471
6772
  cwd,
6472
6773
  });
@@ -7157,7 +7458,8 @@ async function persistMcpRuntimePreference({
7157
7458
 
7158
7459
  function toProfileEnvSuffix(profileName) {
7159
7460
  const normalized =
7160
- normalizeCredentialProfileName(profileName) || DEFAULT_CREDENTIAL_PROFILE_NAME;
7461
+ normalizeCredentialProfileName(profileName) ||
7462
+ DEFAULT_CREDENTIAL_PROFILE_NAME;
7161
7463
  const suffix = normalized
7162
7464
  .replace(/[^A-Za-z0-9]+/g, "_")
7163
7465
  .replace(/^_+|_+$/g, "")
@@ -7173,13 +7475,16 @@ function profileEnvVarAlias(service, profileName) {
7173
7475
  function collectInlineCredentialFindings(configValue) {
7174
7476
  const findings = [];
7175
7477
  const record =
7176
- configValue && typeof configValue === "object" && !Array.isArray(configValue)
7478
+ configValue &&
7479
+ typeof configValue === "object" &&
7480
+ !Array.isArray(configValue)
7177
7481
  ? configValue
7178
7482
  : {};
7179
7483
 
7180
7484
  const scanService = (serviceId) => {
7181
7485
  const section = record[serviceId];
7182
- if (!section || typeof section !== "object" || Array.isArray(section)) return;
7486
+ if (!section || typeof section !== "object" || Array.isArray(section))
7487
+ return;
7183
7488
 
7184
7489
  if (normalizePostmanApiKey(section.apiKey)) {
7185
7490
  findings.push({
@@ -7190,7 +7495,8 @@ function collectInlineCredentialFindings(configValue) {
7190
7495
 
7191
7496
  if (Array.isArray(section.profiles)) {
7192
7497
  section.profiles.forEach((profile, index) => {
7193
- if (!profile || typeof profile !== "object" || Array.isArray(profile)) return;
7498
+ if (!profile || typeof profile !== "object" || Array.isArray(profile))
7499
+ return;
7194
7500
  if (normalizePostmanApiKey(profile.apiKey)) {
7195
7501
  findings.push({
7196
7502
  service: serviceId,
@@ -7209,7 +7515,8 @@ function collectInlineCredentialFindings(configValue) {
7209
7515
  !Array.isArray(section.profiles)
7210
7516
  ) {
7211
7517
  for (const [profileName, profile] of Object.entries(section.profiles)) {
7212
- if (!profile || typeof profile !== "object" || Array.isArray(profile)) continue;
7518
+ if (!profile || typeof profile !== "object" || Array.isArray(profile))
7519
+ continue;
7213
7520
  if (normalizePostmanApiKey(profile.apiKey)) {
7214
7521
  findings.push({
7215
7522
  service: serviceId,
@@ -7228,7 +7535,9 @@ function collectInlineCredentialFindings(configValue) {
7228
7535
 
7229
7536
  function migrateInlineCredentialsInConfig(configValue) {
7230
7537
  const next =
7231
- configValue && typeof configValue === "object" && !Array.isArray(configValue)
7538
+ configValue &&
7539
+ typeof configValue === "object" &&
7540
+ !Array.isArray(configValue)
7232
7541
  ? cloneJsonObject(configValue)
7233
7542
  : {};
7234
7543
  const findings = collectInlineCredentialFindings(next);
@@ -7260,9 +7569,13 @@ function migrateInlineCredentialsInConfig(configValue) {
7260
7569
  };
7261
7570
 
7262
7571
  const hasPostmanSection =
7263
- next.postman && typeof next.postman === "object" && !Array.isArray(next.postman);
7572
+ next.postman &&
7573
+ typeof next.postman === "object" &&
7574
+ !Array.isArray(next.postman);
7264
7575
  const hasStitchSection =
7265
- next.stitch && typeof next.stitch === "object" && !Array.isArray(next.stitch);
7576
+ next.stitch &&
7577
+ typeof next.stitch === "object" &&
7578
+ !Array.isArray(next.stitch);
7266
7579
 
7267
7580
  if (hasPostmanSection) normalizeSection("postman");
7268
7581
  if (hasStitchSection) normalizeSection("stitch");
@@ -7275,10 +7588,7 @@ function migrateInlineCredentialsInConfig(configValue) {
7275
7588
  };
7276
7589
  }
7277
7590
 
7278
- async function collectInlineHeaderFindings({
7279
- scope,
7280
- cwd = process.cwd(),
7281
- }) {
7591
+ async function collectInlineHeaderFindings({ scope, cwd = process.cwd() }) {
7282
7592
  const findings = [];
7283
7593
  const stitchDefinitionPath = resolveStitchMcpDefinitionPath({ scope, cwd });
7284
7594
  const geminiSettingsPath =
@@ -7942,11 +8252,7 @@ async function waitForMcpEndpointReady({
7942
8252
  );
7943
8253
  }
7944
8254
 
7945
- async function discoverUpstreamTools({
7946
- service,
7947
- url,
7948
- headers,
7949
- }) {
8255
+ async function discoverUpstreamTools({ service, url, headers }) {
7950
8256
  let sessionId = null;
7951
8257
  const init = await sendMcpJsonRpcRequest({
7952
8258
  url,
@@ -8051,11 +8357,7 @@ async function discoverUpstreamTools({
8051
8357
  return { tools, meta };
8052
8358
  }
8053
8359
 
8054
- function resolveServiceDiscoveryConfig({
8055
- service,
8056
- configValue,
8057
- scope,
8058
- }) {
8360
+ function resolveServiceDiscoveryConfig({ service, configValue, scope }) {
8059
8361
  const normalized = normalizeCredentialService(service);
8060
8362
  const serviceState = ensureCredentialServiceState(configValue, normalized);
8061
8363
  const activeProfile =
@@ -8145,7 +8447,11 @@ async function syncMcpToolCatalogs({
8145
8447
  };
8146
8448
  if (!dryRun) {
8147
8449
  await mkdir(path.dirname(catalogPath), { recursive: true });
8148
- await writeFile(catalogPath, `${JSON.stringify(catalog, null, 2)}\n`, "utf8");
8450
+ await writeFile(
8451
+ catalogPath,
8452
+ `${JSON.stringify(catalog, null, 2)}\n`,
8453
+ "utf8",
8454
+ );
8149
8455
  }
8150
8456
  syncResults.push({
8151
8457
  service: serviceId,
@@ -8234,9 +8540,7 @@ function normalizeMcpServeTransport(value, fallback = "stdio") {
8234
8540
  .toLowerCase();
8235
8541
  if (normalized === "stdio") return "stdio";
8236
8542
  if (normalized === "http" || normalized === "streamable-http") return "http";
8237
- throw new Error(
8238
- `Unknown MCP transport '${value}'. Use stdio|http.`,
8239
- );
8543
+ throw new Error(`Unknown MCP transport '${value}'. Use stdio|http.`);
8240
8544
  }
8241
8545
 
8242
8546
  function normalizeMcpServeScope(value, fallback = "auto") {
@@ -8250,9 +8554,7 @@ function normalizeMcpServeScope(value, fallback = "auto") {
8250
8554
  ) {
8251
8555
  return normalized;
8252
8556
  }
8253
- throw new Error(
8254
- `Unknown MCP scope '${value}'. Use auto|global|project.`,
8255
- );
8557
+ throw new Error(`Unknown MCP scope '${value}'. Use auto|global|project.`);
8256
8558
  }
8257
8559
 
8258
8560
  function resolveBundledMcpEntryPath() {
@@ -8401,7 +8703,9 @@ async function resolveMcpSkillRoot({
8401
8703
 
8402
8704
  function resolveMcpRuntimeDefaultsFromConfig(configValue) {
8403
8705
  const mcp =
8404
- configValue && typeof configValue.mcp === "object" && !Array.isArray(configValue.mcp)
8706
+ configValue &&
8707
+ typeof configValue.mcp === "object" &&
8708
+ !Array.isArray(configValue.mcp)
8405
8709
  ? configValue.mcp
8406
8710
  : {};
8407
8711
  const docker =
@@ -8472,7 +8776,9 @@ async function runMcpRuntimeStatus(options) {
8472
8776
  console.log(`Configured fallback: ${defaults.defaults.fallback}`);
8473
8777
  console.log(`Configured image: ${defaults.defaults.image}`);
8474
8778
  console.log(`Configured update policy: ${defaults.defaults.updatePolicy}`);
8475
- console.log(`Configured build local: ${defaults.defaults.buildLocal ? "yes" : "no"}`);
8779
+ console.log(
8780
+ `Configured build local: ${defaults.defaults.buildLocal ? "yes" : "no"}`,
8781
+ );
8476
8782
  console.log(`Requested skills root: ${explicitSkillsRoot || "(auto)"}`);
8477
8783
  if (skillsRootExists) {
8478
8784
  console.log(`Resolved host skills root: ${skillsRoot} (present)`);
@@ -8498,7 +8804,10 @@ async function runMcpRuntimeStatus(options) {
8498
8804
  const isRunning = container.status.toLowerCase().startsWith("up ");
8499
8805
  console.log(`Container status: ${container.status}`);
8500
8806
  console.log(`Container image: ${container.image}`);
8501
- const mounts = await inspectDockerContainerMounts({ name: containerName, cwd });
8807
+ const mounts = await inspectDockerContainerMounts({
8808
+ name: containerName,
8809
+ cwd,
8810
+ });
8502
8811
  const skillsMount = mounts.find(
8503
8812
  (mount) => String(mount.Destination || "") === "/workflows/skills",
8504
8813
  );
@@ -8556,8 +8865,7 @@ async function runMcpRuntimeUp(options) {
8556
8865
  const defaults = await loadMcpRuntimeDefaults({ scope, cwd });
8557
8866
  const containerName =
8558
8867
  normalizePostmanApiKey(opts.name) || DEFAULT_MCP_DOCKER_CONTAINER_NAME;
8559
- const image =
8560
- normalizePostmanApiKey(opts.image) || defaults.defaults.image;
8868
+ const image = normalizePostmanApiKey(opts.image) || defaults.defaults.image;
8561
8869
  const updatePolicy = normalizeMcpUpdatePolicy(
8562
8870
  opts.updatePolicy,
8563
8871
  defaults.defaults.updatePolicy,
@@ -8569,11 +8877,16 @@ async function runMcpRuntimeUp(options) {
8569
8877
  const buildLocal = hasCliFlag("--build-local")
8570
8878
  ? true
8571
8879
  : defaults.defaults.buildLocal;
8572
- const hostPort = normalizePortNumber(opts.port, DEFAULT_MCP_DOCKER_HOST_PORT);
8880
+ const hostPort = normalizePortNumber(
8881
+ opts.port,
8882
+ DEFAULT_MCP_DOCKER_HOST_PORT,
8883
+ );
8573
8884
  const replace = Boolean(opts.replace);
8574
8885
  const dockerAvailable = await checkDockerAvailable({ cwd });
8575
8886
  if (!dockerAvailable) {
8576
- throw new Error("Docker is unavailable. Start OrbStack/Docker and retry.");
8887
+ throw new Error(
8888
+ "Docker is unavailable. Start OrbStack/Docker and retry.",
8889
+ );
8577
8890
  }
8578
8891
 
8579
8892
  const prepared = await ensureMcpDockerImage({
@@ -8611,6 +8924,58 @@ async function runMcpRuntimeUp(options) {
8611
8924
  );
8612
8925
  }
8613
8926
  await mkdir(cbxRoot, { recursive: true });
8927
+
8928
+ // Forward configured API key env vars to Docker container
8929
+ const envVarsToForward = [];
8930
+ try {
8931
+ const cfgPath = resolveCbxConfigPath({ scope, cwd });
8932
+ const cfgData = await readJsonFileIfExists(cfgPath);
8933
+ if (cfgData.value && typeof cfgData.value === "object") {
8934
+ const postmanEnv = cfgData.value.postman?.apiKeyEnvVar;
8935
+ const stitchProfiles = cfgData.value.stitch?.profiles;
8936
+ if (postmanEnv && process.env[postmanEnv]) {
8937
+ envVarsToForward.push({
8938
+ name: postmanEnv,
8939
+ value: process.env[postmanEnv],
8940
+ });
8941
+ }
8942
+ if (Array.isArray(stitchProfiles)) {
8943
+ for (const profile of stitchProfiles) {
8944
+ const envName = profile?.apiKeyEnvVar;
8945
+ if (
8946
+ envName &&
8947
+ process.env[envName] &&
8948
+ !envVarsToForward.some((e) => e.name === envName)
8949
+ ) {
8950
+ envVarsToForward.push({
8951
+ name: envName,
8952
+ value: process.env[envName],
8953
+ });
8954
+ }
8955
+ }
8956
+ }
8957
+ // Also check common env var patterns
8958
+ for (const envName of [
8959
+ "POSTMAN_API_KEY",
8960
+ "POSTMAN_API_KEY_DEFAULT",
8961
+ "STITCH_API_KEY",
8962
+ "STITCH_API_KEY_DEFAULT",
8963
+ ]) {
8964
+ if (
8965
+ process.env[envName] &&
8966
+ !envVarsToForward.some((e) => e.name === envName)
8967
+ ) {
8968
+ envVarsToForward.push({
8969
+ name: envName,
8970
+ value: process.env[envName],
8971
+ });
8972
+ }
8973
+ }
8974
+ }
8975
+ } catch {
8976
+ // Config read failure is non-fatal for env var forwarding
8977
+ }
8978
+
8614
8979
  const dockerArgs = [
8615
8980
  "run",
8616
8981
  "-d",
@@ -8624,6 +8989,9 @@ async function runMcpRuntimeUp(options) {
8624
8989
  if (skillsRootExists) {
8625
8990
  dockerArgs.push("-v", `${skillsRoot}:/workflows/skills:ro`);
8626
8991
  }
8992
+ for (const envEntry of envVarsToForward) {
8993
+ dockerArgs.push("-e", `${envEntry.name}=${envEntry.value}`);
8994
+ }
8627
8995
  dockerArgs.push(
8628
8996
  image,
8629
8997
  "--transport",
@@ -8635,11 +9003,7 @@ async function runMcpRuntimeUp(options) {
8635
9003
  "--scope",
8636
9004
  "global",
8637
9005
  );
8638
- await execFile(
8639
- "docker",
8640
- dockerArgs,
8641
- { cwd },
8642
- );
9006
+ await execFile("docker", dockerArgs, { cwd });
8643
9007
 
8644
9008
  const running = await inspectDockerContainerByName({
8645
9009
  name: containerName,
@@ -8659,6 +9023,11 @@ async function runMcpRuntimeUp(options) {
8659
9023
  } else {
8660
9024
  console.log("Mount: /workflows/skills (not mounted - source missing)");
8661
9025
  }
9026
+ if (envVarsToForward.length > 0) {
9027
+ console.log(
9028
+ `Env vars forwarded: ${envVarsToForward.map((e) => e.name).join(", ")}`,
9029
+ );
9030
+ }
8662
9031
  console.log(`Port: ${hostPort}:${MCP_DOCKER_CONTAINER_PORT}`);
8663
9032
  console.log(`Status: ${running ? running.status : "started"}`);
8664
9033
  const endpoint = `http://127.0.0.1:${hostPort}/mcp`;
@@ -8699,7 +9068,9 @@ async function runMcpRuntimeUp(options) {
8699
9068
  );
8700
9069
  }
8701
9070
  console.log("Endpoint health: fallback-to-local");
8702
- console.log("Local command: cbx mcp serve --transport stdio --scope auto");
9071
+ console.log(
9072
+ "Local command: cbx mcp serve --transport stdio --scope auto",
9073
+ );
8703
9074
  } else {
8704
9075
  throw new Error(
8705
9076
  `MCP endpoint is unreachable at ${endpoint}. ${error.message}`,
@@ -8726,7 +9097,9 @@ async function runMcpRuntimeDown(options) {
8726
9097
  normalizePostmanApiKey(opts.name) || DEFAULT_MCP_DOCKER_CONTAINER_NAME;
8727
9098
  const dockerAvailable = await checkDockerAvailable({ cwd });
8728
9099
  if (!dockerAvailable) {
8729
- throw new Error("Docker is unavailable. Start OrbStack/Docker and retry.");
9100
+ throw new Error(
9101
+ "Docker is unavailable. Start OrbStack/Docker and retry.",
9102
+ );
8730
9103
  }
8731
9104
  const existing = await inspectDockerContainerByName({
8732
9105
  name: containerName,
@@ -9166,7 +9539,9 @@ const mcpCommand = program
9166
9539
 
9167
9540
  mcpCommand
9168
9541
  .command("serve")
9169
- .description("Launch bundled Cubis Foundry MCP server (canonical local entrypoint)")
9542
+ .description(
9543
+ "Launch bundled Cubis Foundry MCP server (canonical local entrypoint)",
9544
+ )
9170
9545
  .option("--transport <transport>", "stdio|http", "stdio")
9171
9546
  .option("--scope <scope>", "auto|global|project", "auto")
9172
9547
  .option("--port <port>", "HTTP port override")
@@ -9215,11 +9590,7 @@ mcpRuntimeCommand
9215
9590
  "config scope: project|workspace|global|user",
9216
9591
  "global",
9217
9592
  )
9218
- .option(
9219
- "--name <name>",
9220
- "container name",
9221
- DEFAULT_MCP_DOCKER_CONTAINER_NAME,
9222
- )
9593
+ .option("--name <name>", "container name", DEFAULT_MCP_DOCKER_CONTAINER_NAME)
9223
9594
  .option(
9224
9595
  "--skills-root <path>",
9225
9596
  "host skills directory to resolve/mount (default: auto-detect)",
@@ -9234,11 +9605,7 @@ mcpRuntimeCommand
9234
9605
  "config scope: project|workspace|global|user",
9235
9606
  "global",
9236
9607
  )
9237
- .option(
9238
- "--name <name>",
9239
- "container name",
9240
- DEFAULT_MCP_DOCKER_CONTAINER_NAME,
9241
- )
9608
+ .option("--name <name>", "container name", DEFAULT_MCP_DOCKER_CONTAINER_NAME)
9242
9609
  .option("--image <image:tag>", "docker image to run")
9243
9610
  .option("--update-policy <policy>", "pinned|latest")
9244
9611
  .option(
@@ -9260,11 +9627,7 @@ mcpRuntimeCommand
9260
9627
  mcpRuntimeCommand
9261
9628
  .command("down")
9262
9629
  .description("Stop and remove Docker runtime container")
9263
- .option(
9264
- "--name <name>",
9265
- "container name",
9266
- DEFAULT_MCP_DOCKER_CONTAINER_NAME,
9267
- )
9630
+ .option("--name <name>", "container name", DEFAULT_MCP_DOCKER_CONTAINER_NAME)
9268
9631
  .action(runMcpRuntimeDown);
9269
9632
 
9270
9633
  mcpRuntimeCommand.action(() => {