@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 +470 -107
- package/mcp/Dockerfile +1 -0
- package/mcp/dist/index.js +277 -49
- package/mcp/src/index.ts +37 -15
- package/mcp/src/server.ts +17 -12
- package/mcp/src/telemetry/tokenBudget.ts +8 -2
- package/mcp/src/tools/skillBrowseCategory.ts +6 -1
- package/mcp/src/tools/skillBudgetReport.ts +18 -5
- package/mcp/src/tools/skillGet.ts +12 -0
- package/mcp/src/tools/skillListCategories.ts +4 -0
- package/mcp/src/tools/skillSearch.ts +6 -1
- package/mcp/src/transports/streamableHttp.ts +247 -4
- package/mcp/src/utils/logger.ts +5 -1
- package/mcp/src/vault/manifest.ts +22 -5
- package/mcp/src/vault/scanner.ts +16 -3
- package/package.json +1 -1
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
|
|
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("##
|
|
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("##
|
|
1701
|
-
if (snapshot.
|
|
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
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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 =
|
|
4390
|
+
const enabled =
|
|
4391
|
+
Boolean(options.postman) || hasWorkspaceOption || stitchRequested;
|
|
4129
4392
|
if (!enabled) return { enabled: false };
|
|
4130
4393
|
|
|
4131
|
-
const envApiKey = normalizePostmanApiKey(
|
|
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
|
|
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(
|
|
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(
|
|
4629
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
5894
|
+
console.log(
|
|
5895
|
+
`- MCP build local: ${postmanSetup.mcpBuildLocal ? "yes" : "no"}`,
|
|
5896
|
+
);
|
|
5616
5897
|
console.log(`- MCP image prepare: ${postmanSetup.dockerImageAction}`);
|
|
5617
|
-
console.log(
|
|
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(
|
|
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) ||
|
|
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 &&
|
|
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))
|
|
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))
|
|
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))
|
|
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 &&
|
|
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 &&
|
|
7572
|
+
next.postman &&
|
|
7573
|
+
typeof next.postman === "object" &&
|
|
7574
|
+
!Array.isArray(next.postman);
|
|
7264
7575
|
const hasStitchSection =
|
|
7265
|
-
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(
|
|
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 &&
|
|
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(
|
|
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({
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(() => {
|