@cubis/foundry 0.3.48 → 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/README.md +15 -0
- package/bin/cubis.js +559 -113
- 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() {
|
|
@@ -8341,9 +8643,69 @@ function resolveCbxRootPath({ scope, cwd = process.cwd() }) {
|
|
|
8341
8643
|
return path.join(workspaceRoot, ".cbx");
|
|
8342
8644
|
}
|
|
8343
8645
|
|
|
8646
|
+
function dedupePaths(paths) {
|
|
8647
|
+
const seen = new Set();
|
|
8648
|
+
const out = [];
|
|
8649
|
+
for (const value of paths) {
|
|
8650
|
+
const normalized = path.resolve(value);
|
|
8651
|
+
if (seen.has(normalized)) continue;
|
|
8652
|
+
seen.add(normalized);
|
|
8653
|
+
out.push(normalized);
|
|
8654
|
+
}
|
|
8655
|
+
return out;
|
|
8656
|
+
}
|
|
8657
|
+
|
|
8658
|
+
function resolveMcpSkillRootCandidates({
|
|
8659
|
+
scope,
|
|
8660
|
+
cwd = process.cwd(),
|
|
8661
|
+
explicitSkillsRoot = null,
|
|
8662
|
+
}) {
|
|
8663
|
+
if (explicitSkillsRoot) {
|
|
8664
|
+
return dedupePaths([path.resolve(cwd, explicitSkillsRoot)]);
|
|
8665
|
+
}
|
|
8666
|
+
|
|
8667
|
+
const workspaceRoot = findWorkspaceRoot(cwd);
|
|
8668
|
+
const workspaceCandidates = [
|
|
8669
|
+
path.join(workspaceRoot, ".agents", "skills"),
|
|
8670
|
+
path.join(workspaceRoot, ".github", "skills"),
|
|
8671
|
+
path.join(workspaceRoot, ".agent", "skills"),
|
|
8672
|
+
];
|
|
8673
|
+
const homeCandidates = [
|
|
8674
|
+
path.join(os.homedir(), ".agents", "skills"),
|
|
8675
|
+
path.join(os.homedir(), ".copilot", "skills"),
|
|
8676
|
+
path.join(os.homedir(), ".gemini", "antigravity", "skills"),
|
|
8677
|
+
];
|
|
8678
|
+
|
|
8679
|
+
return dedupePaths(
|
|
8680
|
+
scope === "global"
|
|
8681
|
+
? [...homeCandidates, ...workspaceCandidates]
|
|
8682
|
+
: [...workspaceCandidates, ...homeCandidates],
|
|
8683
|
+
);
|
|
8684
|
+
}
|
|
8685
|
+
|
|
8686
|
+
async function resolveMcpSkillRoot({
|
|
8687
|
+
scope,
|
|
8688
|
+
cwd = process.cwd(),
|
|
8689
|
+
explicitSkillsRoot = null,
|
|
8690
|
+
}) {
|
|
8691
|
+
const candidates = resolveMcpSkillRootCandidates({
|
|
8692
|
+
scope,
|
|
8693
|
+
cwd,
|
|
8694
|
+
explicitSkillsRoot,
|
|
8695
|
+
});
|
|
8696
|
+
for (const candidate of candidates) {
|
|
8697
|
+
if (await pathExists(candidate)) {
|
|
8698
|
+
return { resolvedPath: candidate, candidates };
|
|
8699
|
+
}
|
|
8700
|
+
}
|
|
8701
|
+
return { resolvedPath: null, candidates };
|
|
8702
|
+
}
|
|
8703
|
+
|
|
8344
8704
|
function resolveMcpRuntimeDefaultsFromConfig(configValue) {
|
|
8345
8705
|
const mcp =
|
|
8346
|
-
configValue &&
|
|
8706
|
+
configValue &&
|
|
8707
|
+
typeof configValue.mcp === "object" &&
|
|
8708
|
+
!Array.isArray(configValue.mcp)
|
|
8347
8709
|
? configValue.mcp
|
|
8348
8710
|
: {};
|
|
8349
8711
|
const docker =
|
|
@@ -8391,15 +8753,21 @@ async function runMcpRuntimeStatus(options) {
|
|
|
8391
8753
|
const opts = resolveActionOptions(options);
|
|
8392
8754
|
const cwd = process.cwd();
|
|
8393
8755
|
const scope = normalizeMcpScope(opts.scope, "global");
|
|
8756
|
+
const explicitSkillsRoot = normalizePostmanApiKey(opts.skillsRoot);
|
|
8394
8757
|
const defaults = await loadMcpRuntimeDefaults({ scope, cwd });
|
|
8395
8758
|
const containerName =
|
|
8396
8759
|
normalizePostmanApiKey(opts.name) || DEFAULT_MCP_DOCKER_CONTAINER_NAME;
|
|
8397
8760
|
const dockerAvailable = await checkDockerAvailable({ cwd });
|
|
8761
|
+
const { resolvedPath: skillsRoot, candidates: skillRootCandidates } =
|
|
8762
|
+
await resolveMcpSkillRoot({
|
|
8763
|
+
scope,
|
|
8764
|
+
cwd,
|
|
8765
|
+
explicitSkillsRoot,
|
|
8766
|
+
});
|
|
8398
8767
|
const container = dockerAvailable
|
|
8399
8768
|
? await inspectDockerContainerByName({ name: containerName, cwd })
|
|
8400
8769
|
: null;
|
|
8401
|
-
const
|
|
8402
|
-
const skillsRootExists = await pathExists(skillsRoot);
|
|
8770
|
+
const skillsRootExists = Boolean(skillsRoot);
|
|
8403
8771
|
|
|
8404
8772
|
console.log(`Scope: ${scope}`);
|
|
8405
8773
|
console.log(`Config file: ${defaults.configPath}`);
|
|
@@ -8408,10 +8776,16 @@ async function runMcpRuntimeStatus(options) {
|
|
|
8408
8776
|
console.log(`Configured fallback: ${defaults.defaults.fallback}`);
|
|
8409
8777
|
console.log(`Configured image: ${defaults.defaults.image}`);
|
|
8410
8778
|
console.log(`Configured update policy: ${defaults.defaults.updatePolicy}`);
|
|
8411
|
-
console.log(`Configured build local: ${defaults.defaults.buildLocal ? "yes" : "no"}`);
|
|
8412
8779
|
console.log(
|
|
8413
|
-
`
|
|
8780
|
+
`Configured build local: ${defaults.defaults.buildLocal ? "yes" : "no"}`,
|
|
8414
8781
|
);
|
|
8782
|
+
console.log(`Requested skills root: ${explicitSkillsRoot || "(auto)"}`);
|
|
8783
|
+
if (skillsRootExists) {
|
|
8784
|
+
console.log(`Resolved host skills root: ${skillsRoot} (present)`);
|
|
8785
|
+
} else {
|
|
8786
|
+
console.log("Resolved host skills root: not found");
|
|
8787
|
+
console.log(`Skill root candidates: ${skillRootCandidates.join(", ")}`);
|
|
8788
|
+
}
|
|
8415
8789
|
console.log(`Docker available: ${dockerAvailable ? "yes" : "no"}`);
|
|
8416
8790
|
console.log(`Container name: ${containerName}`);
|
|
8417
8791
|
if (!dockerAvailable) {
|
|
@@ -8422,7 +8796,7 @@ async function runMcpRuntimeStatus(options) {
|
|
|
8422
8796
|
console.log("Container status: not found");
|
|
8423
8797
|
if (!skillsRootExists) {
|
|
8424
8798
|
console.log(
|
|
8425
|
-
"Hint:
|
|
8799
|
+
"Hint: no host skill directory was found from auto-detect candidates. Pass --skills-root <path> to force a mount source.",
|
|
8426
8800
|
);
|
|
8427
8801
|
}
|
|
8428
8802
|
return;
|
|
@@ -8430,7 +8804,10 @@ async function runMcpRuntimeStatus(options) {
|
|
|
8430
8804
|
const isRunning = container.status.toLowerCase().startsWith("up ");
|
|
8431
8805
|
console.log(`Container status: ${container.status}`);
|
|
8432
8806
|
console.log(`Container image: ${container.image}`);
|
|
8433
|
-
const mounts = await inspectDockerContainerMounts({
|
|
8807
|
+
const mounts = await inspectDockerContainerMounts({
|
|
8808
|
+
name: containerName,
|
|
8809
|
+
cwd,
|
|
8810
|
+
});
|
|
8434
8811
|
const skillsMount = mounts.find(
|
|
8435
8812
|
(mount) => String(mount.Destination || "") === "/workflows/skills",
|
|
8436
8813
|
);
|
|
@@ -8445,7 +8822,7 @@ async function runMcpRuntimeStatus(options) {
|
|
|
8445
8822
|
} else {
|
|
8446
8823
|
console.log("Skills mount: missing");
|
|
8447
8824
|
console.log(
|
|
8448
|
-
"Hint: recreate runtime with a skills mount (
|
|
8825
|
+
"Hint: recreate runtime with a skills mount (for example: cbx mcp runtime up --replace --skills-root <path>).",
|
|
8449
8826
|
);
|
|
8450
8827
|
}
|
|
8451
8828
|
if (isRunning) {
|
|
@@ -8484,11 +8861,11 @@ async function runMcpRuntimeUp(options) {
|
|
|
8484
8861
|
const opts = resolveActionOptions(options);
|
|
8485
8862
|
const cwd = process.cwd();
|
|
8486
8863
|
const scope = normalizeMcpScope(opts.scope, "global");
|
|
8864
|
+
const explicitSkillsRoot = normalizePostmanApiKey(opts.skillsRoot);
|
|
8487
8865
|
const defaults = await loadMcpRuntimeDefaults({ scope, cwd });
|
|
8488
8866
|
const containerName =
|
|
8489
8867
|
normalizePostmanApiKey(opts.name) || DEFAULT_MCP_DOCKER_CONTAINER_NAME;
|
|
8490
|
-
const image =
|
|
8491
|
-
normalizePostmanApiKey(opts.image) || defaults.defaults.image;
|
|
8868
|
+
const image = normalizePostmanApiKey(opts.image) || defaults.defaults.image;
|
|
8492
8869
|
const updatePolicy = normalizeMcpUpdatePolicy(
|
|
8493
8870
|
opts.updatePolicy,
|
|
8494
8871
|
defaults.defaults.updatePolicy,
|
|
@@ -8500,11 +8877,16 @@ async function runMcpRuntimeUp(options) {
|
|
|
8500
8877
|
const buildLocal = hasCliFlag("--build-local")
|
|
8501
8878
|
? true
|
|
8502
8879
|
: defaults.defaults.buildLocal;
|
|
8503
|
-
const hostPort = normalizePortNumber(
|
|
8880
|
+
const hostPort = normalizePortNumber(
|
|
8881
|
+
opts.port,
|
|
8882
|
+
DEFAULT_MCP_DOCKER_HOST_PORT,
|
|
8883
|
+
);
|
|
8504
8884
|
const replace = Boolean(opts.replace);
|
|
8505
8885
|
const dockerAvailable = await checkDockerAvailable({ cwd });
|
|
8506
8886
|
if (!dockerAvailable) {
|
|
8507
|
-
throw new Error(
|
|
8887
|
+
throw new Error(
|
|
8888
|
+
"Docker is unavailable. Start OrbStack/Docker and retry.",
|
|
8889
|
+
);
|
|
8508
8890
|
}
|
|
8509
8891
|
|
|
8510
8892
|
const prepared = await ensureMcpDockerImage({
|
|
@@ -8528,15 +8910,72 @@ async function runMcpRuntimeUp(options) {
|
|
|
8528
8910
|
}
|
|
8529
8911
|
|
|
8530
8912
|
const cbxRoot = resolveCbxRootPath({ scope, cwd });
|
|
8531
|
-
const
|
|
8532
|
-
|
|
8913
|
+
const { resolvedPath: skillsRoot, candidates: skillRootCandidates } =
|
|
8914
|
+
await resolveMcpSkillRoot({
|
|
8915
|
+
scope,
|
|
8916
|
+
cwd,
|
|
8917
|
+
explicitSkillsRoot,
|
|
8918
|
+
});
|
|
8919
|
+
const skillsRootExists = Boolean(skillsRoot);
|
|
8533
8920
|
const runtimeWarnings = [];
|
|
8534
8921
|
if (!skillsRootExists) {
|
|
8535
8922
|
runtimeWarnings.push(
|
|
8536
|
-
`
|
|
8923
|
+
`No skill mount source found from candidates (${skillRootCandidates.join(", ")}). Runtime will start without /workflows/skills and vault discovery can return zero skills.`,
|
|
8537
8924
|
);
|
|
8538
8925
|
}
|
|
8539
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
|
+
|
|
8540
8979
|
const dockerArgs = [
|
|
8541
8980
|
"run",
|
|
8542
8981
|
"-d",
|
|
@@ -8550,6 +8989,9 @@ async function runMcpRuntimeUp(options) {
|
|
|
8550
8989
|
if (skillsRootExists) {
|
|
8551
8990
|
dockerArgs.push("-v", `${skillsRoot}:/workflows/skills:ro`);
|
|
8552
8991
|
}
|
|
8992
|
+
for (const envEntry of envVarsToForward) {
|
|
8993
|
+
dockerArgs.push("-e", `${envEntry.name}=${envEntry.value}`);
|
|
8994
|
+
}
|
|
8553
8995
|
dockerArgs.push(
|
|
8554
8996
|
image,
|
|
8555
8997
|
"--transport",
|
|
@@ -8561,11 +9003,7 @@ async function runMcpRuntimeUp(options) {
|
|
|
8561
9003
|
"--scope",
|
|
8562
9004
|
"global",
|
|
8563
9005
|
);
|
|
8564
|
-
await execFile(
|
|
8565
|
-
"docker",
|
|
8566
|
-
dockerArgs,
|
|
8567
|
-
{ cwd },
|
|
8568
|
-
);
|
|
9006
|
+
await execFile("docker", dockerArgs, { cwd });
|
|
8569
9007
|
|
|
8570
9008
|
const running = await inspectDockerContainerByName({
|
|
8571
9009
|
name: containerName,
|
|
@@ -8579,11 +9017,17 @@ async function runMcpRuntimeUp(options) {
|
|
|
8579
9017
|
console.log(`Fallback: ${fallback}`);
|
|
8580
9018
|
console.log(`Build local: ${buildLocal ? "yes" : "no"}`);
|
|
8581
9019
|
console.log(`Mount: ${cbxRoot} -> /root/.cbx`);
|
|
9020
|
+
console.log(`Requested skills root: ${explicitSkillsRoot || "(auto)"}`);
|
|
8582
9021
|
if (skillsRootExists) {
|
|
8583
9022
|
console.log(`Mount: ${skillsRoot} -> /workflows/skills (ro)`);
|
|
8584
9023
|
} else {
|
|
8585
9024
|
console.log("Mount: /workflows/skills (not mounted - source missing)");
|
|
8586
9025
|
}
|
|
9026
|
+
if (envVarsToForward.length > 0) {
|
|
9027
|
+
console.log(
|
|
9028
|
+
`Env vars forwarded: ${envVarsToForward.map((e) => e.name).join(", ")}`,
|
|
9029
|
+
);
|
|
9030
|
+
}
|
|
8587
9031
|
console.log(`Port: ${hostPort}:${MCP_DOCKER_CONTAINER_PORT}`);
|
|
8588
9032
|
console.log(`Status: ${running ? running.status : "started"}`);
|
|
8589
9033
|
const endpoint = `http://127.0.0.1:${hostPort}/mcp`;
|
|
@@ -8624,7 +9068,9 @@ async function runMcpRuntimeUp(options) {
|
|
|
8624
9068
|
);
|
|
8625
9069
|
}
|
|
8626
9070
|
console.log("Endpoint health: fallback-to-local");
|
|
8627
|
-
console.log(
|
|
9071
|
+
console.log(
|
|
9072
|
+
"Local command: cbx mcp serve --transport stdio --scope auto",
|
|
9073
|
+
);
|
|
8628
9074
|
} else {
|
|
8629
9075
|
throw new Error(
|
|
8630
9076
|
`MCP endpoint is unreachable at ${endpoint}. ${error.message}`,
|
|
@@ -8651,7 +9097,9 @@ async function runMcpRuntimeDown(options) {
|
|
|
8651
9097
|
normalizePostmanApiKey(opts.name) || DEFAULT_MCP_DOCKER_CONTAINER_NAME;
|
|
8652
9098
|
const dockerAvailable = await checkDockerAvailable({ cwd });
|
|
8653
9099
|
if (!dockerAvailable) {
|
|
8654
|
-
throw new Error(
|
|
9100
|
+
throw new Error(
|
|
9101
|
+
"Docker is unavailable. Start OrbStack/Docker and retry.",
|
|
9102
|
+
);
|
|
8655
9103
|
}
|
|
8656
9104
|
const existing = await inspectDockerContainerByName({
|
|
8657
9105
|
name: containerName,
|
|
@@ -9091,7 +9539,9 @@ const mcpCommand = program
|
|
|
9091
9539
|
|
|
9092
9540
|
mcpCommand
|
|
9093
9541
|
.command("serve")
|
|
9094
|
-
.description(
|
|
9542
|
+
.description(
|
|
9543
|
+
"Launch bundled Cubis Foundry MCP server (canonical local entrypoint)",
|
|
9544
|
+
)
|
|
9095
9545
|
.option("--transport <transport>", "stdio|http", "stdio")
|
|
9096
9546
|
.option("--scope <scope>", "auto|global|project", "auto")
|
|
9097
9547
|
.option("--port <port>", "HTTP port override")
|
|
@@ -9140,10 +9590,10 @@ mcpRuntimeCommand
|
|
|
9140
9590
|
"config scope: project|workspace|global|user",
|
|
9141
9591
|
"global",
|
|
9142
9592
|
)
|
|
9593
|
+
.option("--name <name>", "container name", DEFAULT_MCP_DOCKER_CONTAINER_NAME)
|
|
9143
9594
|
.option(
|
|
9144
|
-
"--
|
|
9145
|
-
"
|
|
9146
|
-
DEFAULT_MCP_DOCKER_CONTAINER_NAME,
|
|
9595
|
+
"--skills-root <path>",
|
|
9596
|
+
"host skills directory to resolve/mount (default: auto-detect)",
|
|
9147
9597
|
)
|
|
9148
9598
|
.action(runMcpRuntimeStatus);
|
|
9149
9599
|
|
|
@@ -9155,11 +9605,7 @@ mcpRuntimeCommand
|
|
|
9155
9605
|
"config scope: project|workspace|global|user",
|
|
9156
9606
|
"global",
|
|
9157
9607
|
)
|
|
9158
|
-
.option(
|
|
9159
|
-
"--name <name>",
|
|
9160
|
-
"container name",
|
|
9161
|
-
DEFAULT_MCP_DOCKER_CONTAINER_NAME,
|
|
9162
|
-
)
|
|
9608
|
+
.option("--name <name>", "container name", DEFAULT_MCP_DOCKER_CONTAINER_NAME)
|
|
9163
9609
|
.option("--image <image:tag>", "docker image to run")
|
|
9164
9610
|
.option("--update-policy <policy>", "pinned|latest")
|
|
9165
9611
|
.option(
|
|
@@ -9171,17 +9617,17 @@ mcpRuntimeCommand
|
|
|
9171
9617
|
"build MCP Docker image from local package mcp/ directory instead of pulling",
|
|
9172
9618
|
)
|
|
9173
9619
|
.option("--port <port>", "host port to map to container :3100")
|
|
9620
|
+
.option(
|
|
9621
|
+
"--skills-root <path>",
|
|
9622
|
+
"host skills directory to mount into /workflows/skills (default: auto-detect)",
|
|
9623
|
+
)
|
|
9174
9624
|
.option("--replace", "remove existing container with same name before start")
|
|
9175
9625
|
.action(runMcpRuntimeUp);
|
|
9176
9626
|
|
|
9177
9627
|
mcpRuntimeCommand
|
|
9178
9628
|
.command("down")
|
|
9179
9629
|
.description("Stop and remove Docker runtime container")
|
|
9180
|
-
.option(
|
|
9181
|
-
"--name <name>",
|
|
9182
|
-
"container name",
|
|
9183
|
-
DEFAULT_MCP_DOCKER_CONTAINER_NAME,
|
|
9184
|
-
)
|
|
9630
|
+
.option("--name <name>", "container name", DEFAULT_MCP_DOCKER_CONTAINER_NAME)
|
|
9185
9631
|
.action(runMcpRuntimeDown);
|
|
9186
9632
|
|
|
9187
9633
|
mcpRuntimeCommand.action(() => {
|