@cubis/foundry 0.3.51 → 0.3.53
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 +26 -12
- package/bin/cubis.js +662 -80
- package/mcp/dist/index.js +108 -46
- package/mcp/src/server.ts +51 -39
- package/mcp/src/upstream/passthrough.test.ts +30 -0
- package/mcp/src/upstream/passthrough.ts +113 -17
- package/package.json +1 -1
- package/workflows/skills/postman/SKILL.md +31 -25
- package/workflows/skills/postman/references/full-mode-setup.md +36 -0
- package/workflows/skills/postman/references/troubleshooting.md +25 -0
- package/workflows/skills/postman/references/workspace-policy.md +20 -0
- package/workflows/workflows/agent-environment-setup/manifest.json +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/antigravity/workflows/database.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/codex/workflows/database.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/copilot/rules/AGENTS.md +4 -3
- package/workflows/workflows/agent-environment-setup/platforms/copilot/rules/copilot-instructions.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/copilot/skills/postman/SKILL.md +31 -25
- package/workflows/workflows/agent-environment-setup/platforms/copilot/skills/postman/references/full-mode-setup.md +36 -0
- package/workflows/workflows/agent-environment-setup/platforms/copilot/skills/postman/references/troubleshooting.md +25 -0
- package/workflows/workflows/agent-environment-setup/platforms/copilot/skills/postman/references/workspace-policy.md +20 -0
- package/workflows/workflows/agent-environment-setup/platforms/copilot/workflows/database.md +1 -1
- package/workflows/workflows/agent-environment-setup/platforms/cursor/skills/postman/SKILL.md +31 -25
- package/workflows/workflows/agent-environment-setup/platforms/cursor/skills/postman/references/full-mode-setup.md +36 -0
- package/workflows/workflows/agent-environment-setup/platforms/cursor/skills/postman/references/troubleshooting.md +25 -0
- package/workflows/workflows/agent-environment-setup/platforms/cursor/skills/postman/references/workspace-policy.md +20 -0
- package/workflows/workflows/agent-environment-setup/platforms/windsurf/skills/postman/SKILL.md +31 -25
- package/workflows/workflows/agent-environment-setup/platforms/windsurf/skills/postman/references/full-mode-setup.md +36 -0
- package/workflows/workflows/agent-environment-setup/platforms/windsurf/skills/postman/references/troubleshooting.md +25 -0
- package/workflows/workflows/agent-environment-setup/platforms/windsurf/skills/postman/references/workspace-policy.md +20 -0
- package/workflows/workflows/agent-environment-setup/shared/workflows/database.md +1 -1
package/bin/cubis.js
CHANGED
|
@@ -5,6 +5,7 @@ import { Command } from "commander";
|
|
|
5
5
|
import { parse as parseJsonc } from "jsonc-parser";
|
|
6
6
|
import { existsSync } from "node:fs";
|
|
7
7
|
import {
|
|
8
|
+
chmod,
|
|
8
9
|
cp,
|
|
9
10
|
mkdir,
|
|
10
11
|
readdir,
|
|
@@ -122,7 +123,7 @@ const WORKFLOW_PROFILES = {
|
|
|
122
123
|
agentDirs: [".github/agents"],
|
|
123
124
|
skillDirs: [".github/skills"],
|
|
124
125
|
promptDirs: [".github/prompts"],
|
|
125
|
-
ruleFilesByPriority: ["
|
|
126
|
+
ruleFilesByPriority: [".github/copilot-instructions.md", "AGENTS.md"],
|
|
126
127
|
},
|
|
127
128
|
global: {
|
|
128
129
|
workflowDirs: ["~/.copilot/workflows"],
|
|
@@ -149,25 +150,27 @@ const CODEX_AGENT_SKILL_PREFIX = "agent-";
|
|
|
149
150
|
const TERMINAL_VERIFIER_PROVIDERS = ["codex", "gemini"];
|
|
150
151
|
const DEFAULT_TERMINAL_VERIFIER = "codex";
|
|
151
152
|
const POSTMAN_API_KEY_ENV_VAR = "POSTMAN_API_KEY";
|
|
153
|
+
const POSTMAN_MODE_TO_URL = Object.freeze({
|
|
154
|
+
minimal: "https://mcp.postman.com/minimal",
|
|
155
|
+
code: "https://mcp.postman.com/code",
|
|
156
|
+
full: "https://mcp.postman.com/mcp",
|
|
157
|
+
});
|
|
158
|
+
const POSTMAN_VALID_MODES = new Set(Object.keys(POSTMAN_MODE_TO_URL));
|
|
159
|
+
const DEFAULT_POSTMAN_MODE = "minimal";
|
|
160
|
+
const DEFAULT_POSTMAN_INSTALL_MODE = "full";
|
|
161
|
+
const DEFAULT_POSTMAN_CONFIG_MODE = "minimal";
|
|
152
162
|
const POSTMAN_MCP_URL = "https://mcp.postman.com/minimal";
|
|
153
163
|
const POSTMAN_API_BASE_URL = "https://api.getpostman.com";
|
|
154
164
|
const POSTMAN_SKILL_ID = "postman";
|
|
155
165
|
const FOUNDRY_MCP_SERVER_ID = "cubis-foundry";
|
|
156
166
|
const FOUNDRY_MCP_COMMAND = "cbx";
|
|
157
|
-
const FOUNDRY_MCP_DEFAULT_ARGS = [
|
|
158
|
-
"mcp",
|
|
159
|
-
"serve",
|
|
160
|
-
"--transport",
|
|
161
|
-
"stdio",
|
|
162
|
-
"--scope",
|
|
163
|
-
"auto",
|
|
164
|
-
];
|
|
165
167
|
const STITCH_SKILL_ID = "stitch";
|
|
166
168
|
const STITCH_MCP_SERVER_ID = "StitchMCP";
|
|
167
169
|
const STITCH_API_KEY_ENV_VAR = "STITCH_API_KEY";
|
|
168
170
|
const STITCH_MCP_URL = "https://stitch.googleapis.com/mcp";
|
|
169
171
|
const POSTMAN_WORKSPACE_MANUAL_CHOICE = "__postman_workspace_manual__";
|
|
170
172
|
const CBX_CONFIG_FILENAME = "cbx_config.json";
|
|
173
|
+
const CBX_CREDENTIALS_ENV_FILENAME = "credentials.env";
|
|
171
174
|
const LEGACY_POSTMAN_CONFIG_FILENAME = ["postman", "setting.json"].join("_");
|
|
172
175
|
const DEFAULT_CREDENTIAL_PROFILE_NAME = "default";
|
|
173
176
|
const RESERVED_CREDENTIAL_PROFILE_NAMES = new Set(["all"]);
|
|
@@ -393,6 +396,28 @@ function normalizeMcpRuntime(value, fallback = DEFAULT_MCP_RUNTIME) {
|
|
|
393
396
|
return normalized;
|
|
394
397
|
}
|
|
395
398
|
|
|
399
|
+
function normalizePostmanMode(value, fallback = DEFAULT_POSTMAN_MODE) {
|
|
400
|
+
if (value === undefined || value === null || value === "") return fallback;
|
|
401
|
+
const normalized = String(value).trim().toLowerCase();
|
|
402
|
+
if (!POSTMAN_VALID_MODES.has(normalized)) {
|
|
403
|
+
throw new Error(`Unknown Postman mode '${value}'. Use minimal|code|full.`);
|
|
404
|
+
}
|
|
405
|
+
return normalized;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function resolvePostmanMcpUrlForMode(mode) {
|
|
409
|
+
const normalized = normalizePostmanMode(mode, DEFAULT_POSTMAN_MODE);
|
|
410
|
+
return POSTMAN_MODE_TO_URL[normalized] || POSTMAN_MCP_URL;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function resolvePostmanModeFromUrl(url, fallback = DEFAULT_POSTMAN_MODE) {
|
|
414
|
+
const normalizedUrl = String(url || "").trim();
|
|
415
|
+
for (const [mode, modeUrl] of Object.entries(POSTMAN_MODE_TO_URL)) {
|
|
416
|
+
if (normalizedUrl === modeUrl) return mode;
|
|
417
|
+
}
|
|
418
|
+
return normalizePostmanMode(fallback, DEFAULT_POSTMAN_MODE);
|
|
419
|
+
}
|
|
420
|
+
|
|
396
421
|
function normalizeMcpFallback(value, fallback = DEFAULT_MCP_FALLBACK) {
|
|
397
422
|
const normalized = String(value || fallback)
|
|
398
423
|
.trim()
|
|
@@ -853,65 +878,85 @@ function buildEngineeringRulesTemplate() {
|
|
|
853
878
|
return [
|
|
854
879
|
"# Engineering Rules",
|
|
855
880
|
"",
|
|
856
|
-
"These
|
|
881
|
+
"These are the default operating rules for this project.",
|
|
882
|
+
"Goal: ship useful outcomes quickly, safely, and with maintainable code.",
|
|
883
|
+
"",
|
|
884
|
+
"## 1) Product-First Planning (PM Lens)",
|
|
885
|
+
"",
|
|
886
|
+
"- Start every change by naming the user problem, expected outcome, and success signal.",
|
|
887
|
+
"- Define in-scope and out-of-scope before implementation.",
|
|
888
|
+
"- Prefer the smallest valuable slice over big-bang delivery.",
|
|
889
|
+
"- If a change does not improve user value, reliability, or delivery speed, do not do it.",
|
|
857
890
|
"",
|
|
858
|
-
"##
|
|
891
|
+
"## 2) Simplicity First (KISS + YAGNI)",
|
|
859
892
|
"",
|
|
860
|
-
"-
|
|
861
|
-
|
|
862
|
-
|
|
893
|
+
"- Keep architecture and code as simple as possible.",
|
|
894
|
+
"- Build only what current requirements need (YAGNI).",
|
|
895
|
+
'- Avoid speculative abstractions, extension points, and premature "future-proofing."',
|
|
896
|
+
"- Choose proven, understandable patterns unless complexity is clearly justified.",
|
|
863
897
|
"",
|
|
864
|
-
"##
|
|
898
|
+
"## 3) SOLID, Used Pragmatically",
|
|
865
899
|
"",
|
|
866
|
-
"-
|
|
867
|
-
"-
|
|
868
|
-
"-
|
|
869
|
-
"-
|
|
900
|
+
"- SRP: each module/function has one clear responsibility.",
|
|
901
|
+
"- OCP: extend behavior through composition when it prevents risky rewrites.",
|
|
902
|
+
"- LSP: child implementations must preserve parent contract behavior.",
|
|
903
|
+
"- ISP: prefer small focused interfaces over large catch-all contracts.",
|
|
904
|
+
"- DIP: depend on stable abstractions at boundaries, not concrete implementation details.",
|
|
905
|
+
"- Do not force SOLID patterns if they add ceremony without maintenance benefit.",
|
|
870
906
|
"",
|
|
871
|
-
"##
|
|
907
|
+
"## 4) Naming and Readability",
|
|
872
908
|
"",
|
|
873
|
-
"-
|
|
874
|
-
"
|
|
875
|
-
"
|
|
876
|
-
"-
|
|
877
|
-
" - Good: `loadCurrentUserSessions`",
|
|
878
|
-
" - Bad: `handleData`, `processThing`",
|
|
879
|
-
"- Boolean names must read as true/false facts: `isActive`, `hasError`, `canSubmit`.",
|
|
880
|
-
"- Avoid vague suffixes like `Helper`, `Util`, `Manager` unless the type has a narrow, clear responsibility.",
|
|
909
|
+
"- Use intention-revealing names for files, classes, functions, and variables.",
|
|
910
|
+
"- Avoid vague names like `Helper`, `Util`, `Manager` unless responsibility is explicit.",
|
|
911
|
+
"- Keep functions short and linear; reduce nesting and boolean complexity.",
|
|
912
|
+
"- Remove dead code, stale TODOs, and commented-out logic.",
|
|
881
913
|
"",
|
|
882
|
-
"##
|
|
914
|
+
"## 5) Boundaries and Contracts",
|
|
883
915
|
"",
|
|
884
|
-
"-
|
|
885
|
-
"-
|
|
886
|
-
"-
|
|
887
|
-
"-
|
|
916
|
+
"- Keep domain logic separate from UI/transport/framework code.",
|
|
917
|
+
"- Define API and data contracts explicitly with types/schemas at boundaries.",
|
|
918
|
+
"- Validate all external input and return actionable errors.",
|
|
919
|
+
"- Preserve backward compatibility for public contracts unless breaking change is explicit and planned.",
|
|
888
920
|
"",
|
|
889
|
-
"##
|
|
921
|
+
"## 6) Testing and Verification",
|
|
890
922
|
"",
|
|
891
|
-
"-
|
|
892
|
-
"-
|
|
893
|
-
"-
|
|
923
|
+
"- Match tests to risk: unit for logic, integration for boundaries, e2e for critical flows.",
|
|
924
|
+
"- Every behavior change should include or update tests.",
|
|
925
|
+
"- Every bug fix should add a regression test when practical.",
|
|
926
|
+
"- Do not merge with failing lint, type checks, or tests.",
|
|
894
927
|
"",
|
|
895
|
-
"##
|
|
928
|
+
"## 7) Security and Reliability Baseline",
|
|
896
929
|
"",
|
|
897
|
-
"
|
|
930
|
+
"- Never commit secrets, tokens, or sensitive test data.",
|
|
931
|
+
"- Enforce authentication/authorization checks at protected boundaries.",
|
|
932
|
+
"- Use structured logs with enough context for debugging, but never leak sensitive data.",
|
|
933
|
+
"- Design failure paths intentionally (timeouts, retries, fallback, rollback when needed).",
|
|
898
934
|
"",
|
|
899
|
-
"
|
|
900
|
-
"2. Replace legacy widgets/components with your project design system while preserving behavior.",
|
|
901
|
-
"3. Replace ad-hoc sizing with design tokens (spacing, radius, typography).",
|
|
902
|
-
"4. Verify on both small and large devices.",
|
|
935
|
+
"## 8) Performance and Cost Awareness",
|
|
903
936
|
"",
|
|
904
|
-
"
|
|
937
|
+
"- Measure before optimizing.",
|
|
938
|
+
"- Avoid obvious waste: repeated queries, duplicate network calls, and unnecessary hot-path work.",
|
|
939
|
+
"- Set lightweight budgets for critical paths (latency, memory, bundle size where relevant).",
|
|
940
|
+
"- Prefer incremental improvements over risky rewrites.",
|
|
905
941
|
"",
|
|
906
|
-
"
|
|
942
|
+
"## 9) Delivery Discipline",
|
|
907
943
|
"",
|
|
908
|
-
"-
|
|
909
|
-
"-
|
|
910
|
-
"-
|
|
911
|
-
"-
|
|
912
|
-
"- Lint/analyze/tests pass.",
|
|
944
|
+
"- Keep changes small, reviewable, and reversible.",
|
|
945
|
+
"- In PRs, clearly state problem, decision, tradeoffs, and validation evidence.",
|
|
946
|
+
"- For risky changes, include rollout and rollback notes.",
|
|
947
|
+
"- Documentation that affects usage or operations must be updated in the same change.",
|
|
913
948
|
"",
|
|
914
|
-
"##
|
|
949
|
+
"## 10) Definition of Done",
|
|
950
|
+
"",
|
|
951
|
+
"A task is done when:",
|
|
952
|
+
"",
|
|
953
|
+
"- Acceptance criteria are met.",
|
|
954
|
+
"- Code follows the rules above.",
|
|
955
|
+
"- Validation checks pass.",
|
|
956
|
+
"- Relevant docs are updated (including `TECH.md` when stack/architecture changed).",
|
|
957
|
+
"- No placeholder behavior remains in production paths.",
|
|
958
|
+
"",
|
|
959
|
+
"## 11) Keep TECH.md Fresh",
|
|
915
960
|
"",
|
|
916
961
|
"- `TECH.md` is generated from current codebase reality.",
|
|
917
962
|
"- Re-run `cbx rules tech-md --overwrite` after major stack or architecture changes.",
|
|
@@ -940,10 +985,11 @@ function buildEngineeringRulesManagedBlock({
|
|
|
940
985
|
`- Project tech map: \`${techRef}\``,
|
|
941
986
|
"",
|
|
942
987
|
"Hard policy:",
|
|
943
|
-
"1.
|
|
944
|
-
"2. Keep
|
|
945
|
-
"3.
|
|
946
|
-
"4.
|
|
988
|
+
"1. Start from product outcomes and ship the smallest valuable slice.",
|
|
989
|
+
"2. Keep architecture simple (KISS) and avoid speculative work (YAGNI).",
|
|
990
|
+
"3. Apply SOLID pragmatically to reduce change risk, not add ceremony.",
|
|
991
|
+
"4. Use clear naming with focused responsibilities and explicit boundaries.",
|
|
992
|
+
"5. Require validation evidence (lint/types/tests) before merge.",
|
|
947
993
|
"",
|
|
948
994
|
"<!-- cbx:engineering:auto:end -->",
|
|
949
995
|
].join("\n");
|
|
@@ -3066,7 +3112,9 @@ async function collectInstalledWorkflows(
|
|
|
3066
3112
|
scope,
|
|
3067
3113
|
cwd = process.cwd(),
|
|
3068
3114
|
) {
|
|
3069
|
-
|
|
3115
|
+
// Global install mode keeps workflows/agents in workspace paths.
|
|
3116
|
+
// Index using artifact-aware paths so sync-rules reflects installed workflows.
|
|
3117
|
+
const profilePaths = await resolveArtifactProfilePaths(profileId, scope, cwd);
|
|
3070
3118
|
if (!(await pathExists(profilePaths.workflowsDir))) return [];
|
|
3071
3119
|
|
|
3072
3120
|
const entries = await readdir(profilePaths.workflowsDir, {
|
|
@@ -3539,6 +3587,128 @@ function resolveMcpRootPath({ scope, cwd = process.cwd() }) {
|
|
|
3539
3587
|
return path.join(workspaceRoot, ".cbx", "mcp");
|
|
3540
3588
|
}
|
|
3541
3589
|
|
|
3590
|
+
function resolveManagedCredentialsEnvPath() {
|
|
3591
|
+
return path.join(os.homedir(), ".cbx", CBX_CREDENTIALS_ENV_FILENAME);
|
|
3592
|
+
}
|
|
3593
|
+
|
|
3594
|
+
function parseShellEnvValue(rawValue) {
|
|
3595
|
+
const value = String(rawValue || "").trim();
|
|
3596
|
+
if (!value) return "";
|
|
3597
|
+
if (
|
|
3598
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
3599
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
3600
|
+
) {
|
|
3601
|
+
return value.slice(1, -1);
|
|
3602
|
+
}
|
|
3603
|
+
return value;
|
|
3604
|
+
}
|
|
3605
|
+
|
|
3606
|
+
function parseEnvFileLine(line) {
|
|
3607
|
+
const trimmed = String(line || "").trim();
|
|
3608
|
+
if (!trimmed || trimmed.startsWith("#")) return null;
|
|
3609
|
+
const normalized = trimmed.startsWith("export ")
|
|
3610
|
+
? trimmed.slice("export ".length).trim()
|
|
3611
|
+
: trimmed;
|
|
3612
|
+
const match = normalized.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
|
|
3613
|
+
if (!match) return null;
|
|
3614
|
+
return {
|
|
3615
|
+
name: match[1],
|
|
3616
|
+
value: parseShellEnvValue(match[2]),
|
|
3617
|
+
};
|
|
3618
|
+
}
|
|
3619
|
+
|
|
3620
|
+
async function loadManagedCredentialsEnv() {
|
|
3621
|
+
const envPath = resolveManagedCredentialsEnvPath();
|
|
3622
|
+
if (!(await pathExists(envPath))) {
|
|
3623
|
+
return {
|
|
3624
|
+
envPath,
|
|
3625
|
+
loaded: [],
|
|
3626
|
+
skipped: [],
|
|
3627
|
+
exists: false,
|
|
3628
|
+
};
|
|
3629
|
+
}
|
|
3630
|
+
const raw = await readFile(envPath, "utf8");
|
|
3631
|
+
const loaded = [];
|
|
3632
|
+
const skipped = [];
|
|
3633
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
3634
|
+
const entry = parseEnvFileLine(line);
|
|
3635
|
+
if (!entry) continue;
|
|
3636
|
+
if (process.env[entry.name]) {
|
|
3637
|
+
skipped.push(entry.name);
|
|
3638
|
+
continue;
|
|
3639
|
+
}
|
|
3640
|
+
process.env[entry.name] = entry.value;
|
|
3641
|
+
loaded.push(entry.name);
|
|
3642
|
+
}
|
|
3643
|
+
return {
|
|
3644
|
+
envPath,
|
|
3645
|
+
loaded,
|
|
3646
|
+
skipped,
|
|
3647
|
+
exists: true,
|
|
3648
|
+
};
|
|
3649
|
+
}
|
|
3650
|
+
|
|
3651
|
+
function quoteShellEnvValue(value) {
|
|
3652
|
+
const normalized = String(value ?? "");
|
|
3653
|
+
// Single-quote with POSIX-safe escaping for embedded single quotes.
|
|
3654
|
+
return `'${normalized.replace(/'/g, `'\"'\"'`)}'`;
|
|
3655
|
+
}
|
|
3656
|
+
|
|
3657
|
+
async function persistManagedCredentialsEnv({ envVarNames, dryRun = false }) {
|
|
3658
|
+
const envPath = resolveManagedCredentialsEnvPath();
|
|
3659
|
+
const existingEntries = new Map();
|
|
3660
|
+
const existsBefore = await pathExists(envPath);
|
|
3661
|
+
|
|
3662
|
+
if (existsBefore) {
|
|
3663
|
+
const raw = await readFile(envPath, "utf8");
|
|
3664
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
3665
|
+
const entry = parseEnvFileLine(line);
|
|
3666
|
+
if (!entry) continue;
|
|
3667
|
+
existingEntries.set(entry.name, entry.value);
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3670
|
+
|
|
3671
|
+
const persisted = [];
|
|
3672
|
+
const missing = [];
|
|
3673
|
+
for (const envName of envVarNames || []) {
|
|
3674
|
+
const name = String(envName || "").trim();
|
|
3675
|
+
if (!name) continue;
|
|
3676
|
+
const value = normalizePostmanApiKey(process.env[name]);
|
|
3677
|
+
if (!value) {
|
|
3678
|
+
missing.push(name);
|
|
3679
|
+
continue;
|
|
3680
|
+
}
|
|
3681
|
+
existingEntries.set(name, value);
|
|
3682
|
+
persisted.push(name);
|
|
3683
|
+
}
|
|
3684
|
+
|
|
3685
|
+
const orderedNames = [...existingEntries.keys()].sort((a, b) =>
|
|
3686
|
+
a.localeCompare(b),
|
|
3687
|
+
);
|
|
3688
|
+
const body = [
|
|
3689
|
+
"# Managed by cbx workflows config keys persist-env",
|
|
3690
|
+
"# Stores credential env vars for future cbx sessions.",
|
|
3691
|
+
...orderedNames.map(
|
|
3692
|
+
(name) => `export ${name}=${quoteShellEnvValue(existingEntries.get(name))}`,
|
|
3693
|
+
),
|
|
3694
|
+
"",
|
|
3695
|
+
].join("\n");
|
|
3696
|
+
|
|
3697
|
+
if (!dryRun) {
|
|
3698
|
+
await mkdir(path.dirname(envPath), { recursive: true });
|
|
3699
|
+
await writeFile(envPath, body, "utf8");
|
|
3700
|
+
await chmod(envPath, 0o600);
|
|
3701
|
+
}
|
|
3702
|
+
|
|
3703
|
+
return {
|
|
3704
|
+
envPath,
|
|
3705
|
+
persisted,
|
|
3706
|
+
missing,
|
|
3707
|
+
dryRun,
|
|
3708
|
+
action: dryRun ? "would-update" : existsBefore ? "updated" : "created",
|
|
3709
|
+
};
|
|
3710
|
+
}
|
|
3711
|
+
|
|
3542
3712
|
function resolvePostmanMcpDefinitionPath({
|
|
3543
3713
|
platform,
|
|
3544
3714
|
scope,
|
|
@@ -3901,6 +4071,7 @@ function resolveCredentialEffectiveStatus({
|
|
|
3901
4071
|
}) {
|
|
3902
4072
|
if (!serviceConfig) return null;
|
|
3903
4073
|
const defaultEnvVar = defaultEnvVarForCredentialService(service);
|
|
4074
|
+
const defaultMcpUrl = defaultMcpUrlForCredentialService(service);
|
|
3904
4075
|
const activeProfile =
|
|
3905
4076
|
serviceConfig.activeProfile || serviceConfig.profiles?.[0] || null;
|
|
3906
4077
|
const apiKeyEnvVar =
|
|
@@ -3927,6 +4098,14 @@ function resolveCredentialEffectiveStatus({
|
|
|
3927
4098
|
service === "postman"
|
|
3928
4099
|
? normalizePostmanWorkspaceId(activeProfile?.workspaceId)
|
|
3929
4100
|
: null,
|
|
4101
|
+
mcpUrl: normalizePostmanApiKey(serviceConfig.mcpUrl) || defaultMcpUrl,
|
|
4102
|
+
mode:
|
|
4103
|
+
service === "postman"
|
|
4104
|
+
? resolvePostmanModeFromUrl(
|
|
4105
|
+
serviceConfig.mcpUrl,
|
|
4106
|
+
DEFAULT_POSTMAN_CONFIG_MODE,
|
|
4107
|
+
)
|
|
4108
|
+
: null,
|
|
3930
4109
|
};
|
|
3931
4110
|
}
|
|
3932
4111
|
|
|
@@ -4105,6 +4284,7 @@ async function applyPostmanMcpForPlatform({
|
|
|
4105
4284
|
}) {
|
|
4106
4285
|
const workspaceRoot = findWorkspaceRoot(cwd);
|
|
4107
4286
|
const warnings = [];
|
|
4287
|
+
const foundryScope = mcpScope === "global" ? "global" : "project";
|
|
4108
4288
|
|
|
4109
4289
|
if (platform === "antigravity") {
|
|
4110
4290
|
const settingsPath =
|
|
@@ -4127,7 +4307,7 @@ async function applyPostmanMcpForPlatform({
|
|
|
4127
4307
|
});
|
|
4128
4308
|
if (includeFoundryMcp) {
|
|
4129
4309
|
mcpServers[FOUNDRY_MCP_SERVER_ID] = buildGeminiFoundryServer({
|
|
4130
|
-
scope:
|
|
4310
|
+
scope: foundryScope,
|
|
4131
4311
|
});
|
|
4132
4312
|
} else {
|
|
4133
4313
|
delete mcpServers[FOUNDRY_MCP_SERVER_ID];
|
|
@@ -4174,7 +4354,7 @@ async function applyPostmanMcpForPlatform({
|
|
|
4174
4354
|
});
|
|
4175
4355
|
if (includeFoundryMcp) {
|
|
4176
4356
|
mcpServers[FOUNDRY_MCP_SERVER_ID] = buildCopilotCliFoundryServer({
|
|
4177
|
-
scope:
|
|
4357
|
+
scope: foundryScope,
|
|
4178
4358
|
});
|
|
4179
4359
|
} else {
|
|
4180
4360
|
delete mcpServers[FOUNDRY_MCP_SERVER_ID];
|
|
@@ -4195,7 +4375,7 @@ async function applyPostmanMcpForPlatform({
|
|
|
4195
4375
|
});
|
|
4196
4376
|
if (includeFoundryMcp) {
|
|
4197
4377
|
servers[FOUNDRY_MCP_SERVER_ID] = buildVsCodeFoundryServer({
|
|
4198
|
-
scope:
|
|
4378
|
+
scope: foundryScope,
|
|
4199
4379
|
});
|
|
4200
4380
|
} else {
|
|
4201
4381
|
delete servers[FOUNDRY_MCP_SERVER_ID];
|
|
@@ -4233,7 +4413,7 @@ async function applyPostmanMcpForPlatform({
|
|
|
4233
4413
|
});
|
|
4234
4414
|
if (includeFoundryMcp) {
|
|
4235
4415
|
servers[FOUNDRY_MCP_SERVER_ID] = buildVsCodeFoundryServer({
|
|
4236
|
-
scope:
|
|
4416
|
+
scope: foundryScope,
|
|
4237
4417
|
});
|
|
4238
4418
|
} else {
|
|
4239
4419
|
delete servers[FOUNDRY_MCP_SERVER_ID];
|
|
@@ -4313,7 +4493,7 @@ async function applyPostmanMcpForPlatform({
|
|
|
4313
4493
|
FOUNDRY_MCP_SERVER_ID,
|
|
4314
4494
|
"--",
|
|
4315
4495
|
FOUNDRY_MCP_COMMAND,
|
|
4316
|
-
...
|
|
4496
|
+
...buildFoundryServeArgs({ scope: foundryScope }),
|
|
4317
4497
|
],
|
|
4318
4498
|
{ cwd },
|
|
4319
4499
|
);
|
|
@@ -4390,6 +4570,11 @@ async function resolvePostmanInstallSelection({
|
|
|
4390
4570
|
const enabled =
|
|
4391
4571
|
Boolean(options.postman) || hasWorkspaceOption || stitchRequested;
|
|
4392
4572
|
if (!enabled) return { enabled: false };
|
|
4573
|
+
const requestedPostmanMode = normalizePostmanMode(
|
|
4574
|
+
options.postmanMode,
|
|
4575
|
+
DEFAULT_POSTMAN_INSTALL_MODE,
|
|
4576
|
+
);
|
|
4577
|
+
const requestedMcpUrl = resolvePostmanMcpUrlForMode(requestedPostmanMode);
|
|
4393
4578
|
|
|
4394
4579
|
const envApiKey = normalizePostmanApiKey(
|
|
4395
4580
|
process.env[POSTMAN_API_KEY_ENV_VAR],
|
|
@@ -4620,7 +4805,7 @@ async function resolvePostmanInstallSelection({
|
|
|
4620
4805
|
apiKeyEnvVar: defaultProfile.apiKeyEnvVar,
|
|
4621
4806
|
apiKeySource: storedCredentialSource(defaultProfile),
|
|
4622
4807
|
defaultWorkspaceId: defaultProfile.workspaceId,
|
|
4623
|
-
mcpUrl:
|
|
4808
|
+
mcpUrl: requestedMcpUrl,
|
|
4624
4809
|
},
|
|
4625
4810
|
};
|
|
4626
4811
|
if (stitchEnabled) {
|
|
@@ -4657,6 +4842,7 @@ async function resolvePostmanInstallSelection({
|
|
|
4657
4842
|
mcpToolSync,
|
|
4658
4843
|
foundryMcpEnabled,
|
|
4659
4844
|
runtimeSkipped,
|
|
4845
|
+
postmanMode: requestedPostmanMode,
|
|
4660
4846
|
defaultWorkspaceId: defaultWorkspaceId ?? null,
|
|
4661
4847
|
workspaceSelectionSource,
|
|
4662
4848
|
mcpScope,
|
|
@@ -4700,7 +4886,10 @@ async function configurePostmanInstallArtifacts({
|
|
|
4700
4886
|
installPostmanConfig?.defaultWorkspaceId ??
|
|
4701
4887
|
postmanSelection.defaultWorkspaceId ??
|
|
4702
4888
|
null;
|
|
4703
|
-
let effectiveMcpUrl =
|
|
4889
|
+
let effectiveMcpUrl =
|
|
4890
|
+
installPostmanConfig?.mcpUrl ||
|
|
4891
|
+
postmanSelection.cbxConfig?.postman?.mcpUrl ||
|
|
4892
|
+
POSTMAN_MCP_URL;
|
|
4704
4893
|
const shouldInstallStitch = Boolean(postmanSelection.stitchEnabled);
|
|
4705
4894
|
const installStitchConfig = shouldInstallStitch
|
|
4706
4895
|
? parseStoredStitchConfig(postmanSelection.cbxConfig)
|
|
@@ -4754,7 +4943,10 @@ async function configurePostmanInstallArtifacts({
|
|
|
4754
4943
|
effectiveApiKeyEnvVar =
|
|
4755
4944
|
storedPostmanConfig.apiKeyEnvVar || POSTMAN_API_KEY_ENV_VAR;
|
|
4756
4945
|
effectiveDefaultWorkspaceId = storedPostmanConfig.defaultWorkspaceId;
|
|
4757
|
-
effectiveMcpUrl =
|
|
4946
|
+
effectiveMcpUrl =
|
|
4947
|
+
storedPostmanConfig.mcpUrl ||
|
|
4948
|
+
postmanSelection.cbxConfig?.postman?.mcpUrl ||
|
|
4949
|
+
POSTMAN_MCP_URL;
|
|
4758
4950
|
} else {
|
|
4759
4951
|
warnings.push(
|
|
4760
4952
|
`Existing ${CBX_CONFIG_FILENAME} could not be parsed. Using install-time Postman values for MCP config.`,
|
|
@@ -4931,6 +5123,11 @@ async function configurePostmanInstallArtifacts({
|
|
|
4931
5123
|
dockerImageAction: postmanSelection.dockerImageAction,
|
|
4932
5124
|
mcpToolSync: postmanSelection.mcpToolSync,
|
|
4933
5125
|
foundryMcpEnabled: postmanSelection.foundryMcpEnabled,
|
|
5126
|
+
postmanMode: resolvePostmanModeFromUrl(
|
|
5127
|
+
effectiveMcpUrl,
|
|
5128
|
+
DEFAULT_POSTMAN_INSTALL_MODE,
|
|
5129
|
+
),
|
|
5130
|
+
postmanMcpUrl: effectiveMcpUrl,
|
|
4934
5131
|
apiKeySource: effectiveApiKeySource,
|
|
4935
5132
|
stitchApiKeySource: effectiveStitchApiKeySource,
|
|
4936
5133
|
defaultWorkspaceId: effectiveDefaultWorkspaceId,
|
|
@@ -4948,6 +5145,111 @@ async function configurePostmanInstallArtifacts({
|
|
|
4948
5145
|
};
|
|
4949
5146
|
}
|
|
4950
5147
|
|
|
5148
|
+
function resolveMcpScopeFromConfigDocument(configValue, fallbackScope) {
|
|
5149
|
+
try {
|
|
5150
|
+
if (
|
|
5151
|
+
configValue?.mcp &&
|
|
5152
|
+
typeof configValue.mcp === "object" &&
|
|
5153
|
+
!Array.isArray(configValue.mcp)
|
|
5154
|
+
) {
|
|
5155
|
+
return normalizeMcpScope(configValue.mcp.scope, fallbackScope);
|
|
5156
|
+
}
|
|
5157
|
+
} catch {
|
|
5158
|
+
// Fall through to fallback scope.
|
|
5159
|
+
}
|
|
5160
|
+
return normalizeMcpScope(fallbackScope, "global");
|
|
5161
|
+
}
|
|
5162
|
+
|
|
5163
|
+
async function applyPostmanConfigArtifacts({
|
|
5164
|
+
platform,
|
|
5165
|
+
mcpScope,
|
|
5166
|
+
configValue,
|
|
5167
|
+
dryRun = false,
|
|
5168
|
+
cwd = process.cwd(),
|
|
5169
|
+
}) {
|
|
5170
|
+
const warnings = [];
|
|
5171
|
+
const postmanState = ensureCredentialServiceState(configValue, "postman");
|
|
5172
|
+
const stitchState = parseStoredStitchConfig(configValue);
|
|
5173
|
+
const postmanApiKeyEnvVar =
|
|
5174
|
+
normalizePostmanApiKey(postmanState.apiKeyEnvVar) || POSTMAN_API_KEY_ENV_VAR;
|
|
5175
|
+
const postmanMcpUrl = postmanState.mcpUrl || POSTMAN_MCP_URL;
|
|
5176
|
+
const stitchEnabled = Boolean(stitchState);
|
|
5177
|
+
const stitchApiKeyEnvVar =
|
|
5178
|
+
normalizePostmanApiKey(stitchState?.apiKeyEnvVar) || STITCH_API_KEY_ENV_VAR;
|
|
5179
|
+
const stitchMcpUrl = stitchState?.mcpUrl || STITCH_MCP_URL;
|
|
5180
|
+
|
|
5181
|
+
const mcpDefinitionPath = resolvePostmanMcpDefinitionPath({
|
|
5182
|
+
platform,
|
|
5183
|
+
scope: mcpScope,
|
|
5184
|
+
cwd,
|
|
5185
|
+
});
|
|
5186
|
+
const mcpDefinitionContent = `${JSON.stringify(
|
|
5187
|
+
buildPostmanMcpDefinition({
|
|
5188
|
+
apiKeyEnvVar: postmanApiKeyEnvVar,
|
|
5189
|
+
mcpUrl: postmanMcpUrl,
|
|
5190
|
+
}),
|
|
5191
|
+
null,
|
|
5192
|
+
2,
|
|
5193
|
+
)}\n`;
|
|
5194
|
+
const mcpDefinitionResult = await writeGeneratedArtifact({
|
|
5195
|
+
destination: mcpDefinitionPath,
|
|
5196
|
+
content: mcpDefinitionContent,
|
|
5197
|
+
dryRun,
|
|
5198
|
+
});
|
|
5199
|
+
|
|
5200
|
+
let stitchMcpDefinitionPath = null;
|
|
5201
|
+
let stitchMcpDefinitionResult = null;
|
|
5202
|
+
if (stitchEnabled) {
|
|
5203
|
+
stitchMcpDefinitionPath = resolveStitchMcpDefinitionPath({
|
|
5204
|
+
scope: mcpScope,
|
|
5205
|
+
cwd,
|
|
5206
|
+
});
|
|
5207
|
+
const stitchMcpDefinitionContent = `${JSON.stringify(
|
|
5208
|
+
buildStitchMcpDefinition({
|
|
5209
|
+
apiKeyEnvVar: stitchApiKeyEnvVar,
|
|
5210
|
+
mcpUrl: stitchMcpUrl,
|
|
5211
|
+
}),
|
|
5212
|
+
null,
|
|
5213
|
+
2,
|
|
5214
|
+
)}\n`;
|
|
5215
|
+
stitchMcpDefinitionResult = await writeGeneratedArtifact({
|
|
5216
|
+
destination: stitchMcpDefinitionPath,
|
|
5217
|
+
content: stitchMcpDefinitionContent,
|
|
5218
|
+
dryRun,
|
|
5219
|
+
});
|
|
5220
|
+
}
|
|
5221
|
+
|
|
5222
|
+
let mcpRuntimeResult = null;
|
|
5223
|
+
if (!platform) {
|
|
5224
|
+
warnings.push(
|
|
5225
|
+
"Skipped platform runtime MCP target patch because platform could not be resolved. Re-run with --platform <codex|antigravity|copilot>.",
|
|
5226
|
+
);
|
|
5227
|
+
} else {
|
|
5228
|
+
mcpRuntimeResult = await applyPostmanMcpForPlatform({
|
|
5229
|
+
platform,
|
|
5230
|
+
mcpScope,
|
|
5231
|
+
apiKeyEnvVar: postmanApiKeyEnvVar,
|
|
5232
|
+
mcpUrl: postmanMcpUrl,
|
|
5233
|
+
stitchApiKeyEnvVar,
|
|
5234
|
+
stitchMcpUrl,
|
|
5235
|
+
includeStitchMcp: stitchEnabled,
|
|
5236
|
+
includeFoundryMcp: true,
|
|
5237
|
+
dryRun,
|
|
5238
|
+
cwd,
|
|
5239
|
+
});
|
|
5240
|
+
warnings.push(...(mcpRuntimeResult.warnings || []));
|
|
5241
|
+
}
|
|
5242
|
+
|
|
5243
|
+
return {
|
|
5244
|
+
mcpDefinitionPath,
|
|
5245
|
+
mcpDefinitionResult,
|
|
5246
|
+
stitchMcpDefinitionPath,
|
|
5247
|
+
stitchMcpDefinitionResult,
|
|
5248
|
+
mcpRuntimeResult,
|
|
5249
|
+
warnings,
|
|
5250
|
+
};
|
|
5251
|
+
}
|
|
5252
|
+
|
|
4951
5253
|
async function installAntigravityTerminalIntegrationArtifacts({
|
|
4952
5254
|
profilePaths,
|
|
4953
5255
|
provider,
|
|
@@ -5881,6 +6183,12 @@ function printPostmanSetupSummary({ postmanSetup }) {
|
|
|
5881
6183
|
|
|
5882
6184
|
console.log("\nPostman setup:");
|
|
5883
6185
|
console.log(`- MCP scope: ${postmanSetup.mcpScope}`);
|
|
6186
|
+
if (postmanSetup.postmanMode) {
|
|
6187
|
+
console.log(`- Postman mode: ${postmanSetup.postmanMode}`);
|
|
6188
|
+
}
|
|
6189
|
+
if (postmanSetup.postmanMcpUrl) {
|
|
6190
|
+
console.log(`- Postman MCP URL: ${postmanSetup.postmanMcpUrl}`);
|
|
6191
|
+
}
|
|
5884
6192
|
console.log(
|
|
5885
6193
|
`- Config file: ${postmanSetup.cbxConfigResult.action} (${postmanSetup.cbxConfigPath})`,
|
|
5886
6194
|
);
|
|
@@ -6371,6 +6679,10 @@ function withInstallOptions(command) {
|
|
|
6371
6679
|
"--postman",
|
|
6372
6680
|
"optional: install Postman skill and generate cbx_config.json",
|
|
6373
6681
|
)
|
|
6682
|
+
.option(
|
|
6683
|
+
"--postman-mode <mode>",
|
|
6684
|
+
"Postman MCP mode for --postman: minimal|code|full (default: full)",
|
|
6685
|
+
)
|
|
6374
6686
|
.option(
|
|
6375
6687
|
"--stitch",
|
|
6376
6688
|
"optional: include Stitch MCP profile/config alongside Postman",
|
|
@@ -6554,6 +6866,25 @@ function registerConfigKeysSubcommands(
|
|
|
6554
6866
|
"global",
|
|
6555
6867
|
)
|
|
6556
6868
|
.action(wrap(runWorkflowConfigKeysDoctor));
|
|
6869
|
+
|
|
6870
|
+
keysCommand
|
|
6871
|
+
.command("persist-env")
|
|
6872
|
+
.description(
|
|
6873
|
+
"Persist configured credential env vars into ~/.cbx/credentials.env (mode 600)",
|
|
6874
|
+
)
|
|
6875
|
+
.option("--service <service>", "postman|stitch|all", "all")
|
|
6876
|
+
.option("--profile <profile>", "persist only this profile name")
|
|
6877
|
+
.option(
|
|
6878
|
+
"--all-profiles",
|
|
6879
|
+
"persist all profiles for selected service(s) instead of active profile only",
|
|
6880
|
+
)
|
|
6881
|
+
.option(
|
|
6882
|
+
"--scope <scope>",
|
|
6883
|
+
"config scope: project|workspace|global|user",
|
|
6884
|
+
"global",
|
|
6885
|
+
)
|
|
6886
|
+
.option("--dry-run", "preview changes without writing files")
|
|
6887
|
+
.action(wrap(runWorkflowConfigKeysPersistEnv));
|
|
6557
6888
|
}
|
|
6558
6889
|
|
|
6559
6890
|
async function resolveAntigravityTerminalVerifierSelection({
|
|
@@ -6710,6 +7041,7 @@ async function cleanupAntigravityTerminalIntegration({
|
|
|
6710
7041
|
async function runWorkflowInstall(options) {
|
|
6711
7042
|
try {
|
|
6712
7043
|
const cwd = options.target ? path.resolve(options.target) : process.cwd();
|
|
7044
|
+
await loadManagedCredentialsEnv();
|
|
6713
7045
|
if (options.target) {
|
|
6714
7046
|
const targetExists = await pathExists(cwd);
|
|
6715
7047
|
if (!targetExists) {
|
|
@@ -7985,11 +8317,120 @@ async function runWorkflowConfigKeysDoctor(options) {
|
|
|
7985
8317
|
}
|
|
7986
8318
|
}
|
|
7987
8319
|
|
|
8320
|
+
async function runWorkflowConfigKeysPersistEnv(options) {
|
|
8321
|
+
try {
|
|
8322
|
+
const opts = resolveActionOptions(options);
|
|
8323
|
+
const cwd = process.cwd();
|
|
8324
|
+
const scopeArg = readCliOptionFromArgv("--scope");
|
|
8325
|
+
const scope = normalizeMcpScope(scopeArg ?? opts.scope, "global");
|
|
8326
|
+
const dryRun = hasCliFlag("--dry-run") || Boolean(opts.dryRun);
|
|
8327
|
+
const service = normalizeCredentialService(opts.service || "all", {
|
|
8328
|
+
allowAll: true,
|
|
8329
|
+
});
|
|
8330
|
+
const requestedProfileName = normalizeCredentialProfileName(opts.profile);
|
|
8331
|
+
const allProfiles = Boolean(opts.allProfiles);
|
|
8332
|
+
|
|
8333
|
+
if (requestedProfileName && allProfiles) {
|
|
8334
|
+
throw new Error(
|
|
8335
|
+
"Use either --profile <name> or --all-profiles, not both.",
|
|
8336
|
+
);
|
|
8337
|
+
}
|
|
8338
|
+
|
|
8339
|
+
await loadManagedCredentialsEnv();
|
|
8340
|
+
const { configPath, existing, existingValue } = await loadConfigForScope({
|
|
8341
|
+
scope,
|
|
8342
|
+
cwd,
|
|
8343
|
+
});
|
|
8344
|
+
if (!existing.exists || !existingValue) {
|
|
8345
|
+
throw new Error(`Config file is missing at ${configPath}.`);
|
|
8346
|
+
}
|
|
8347
|
+
|
|
8348
|
+
const services = service === "all" ? ["postman", "stitch"] : [service];
|
|
8349
|
+
const envVarNames = [];
|
|
8350
|
+
const warnings = [];
|
|
8351
|
+
|
|
8352
|
+
for (const serviceId of services) {
|
|
8353
|
+
const state = ensureCredentialServiceState(existingValue, serviceId);
|
|
8354
|
+
let selectedProfiles = [];
|
|
8355
|
+
|
|
8356
|
+
if (requestedProfileName) {
|
|
8357
|
+
const selected = findProfileByName(state.profiles, requestedProfileName);
|
|
8358
|
+
if (!selected) {
|
|
8359
|
+
warnings.push(
|
|
8360
|
+
`${serviceId}: profile '${requestedProfileName}' was not found`,
|
|
8361
|
+
);
|
|
8362
|
+
continue;
|
|
8363
|
+
}
|
|
8364
|
+
selectedProfiles = [selected];
|
|
8365
|
+
} else if (allProfiles) {
|
|
8366
|
+
selectedProfiles = [...state.profiles];
|
|
8367
|
+
} else if (state.activeProfile) {
|
|
8368
|
+
selectedProfiles = [state.activeProfile];
|
|
8369
|
+
}
|
|
8370
|
+
|
|
8371
|
+
if (selectedProfiles.length === 0) {
|
|
8372
|
+
warnings.push(
|
|
8373
|
+
`${serviceId}: no profile selected (service has no configured profiles)`,
|
|
8374
|
+
);
|
|
8375
|
+
continue;
|
|
8376
|
+
}
|
|
8377
|
+
|
|
8378
|
+
for (const profile of selectedProfiles) {
|
|
8379
|
+
const envVar = normalizePostmanApiKey(profile?.apiKeyEnvVar);
|
|
8380
|
+
if (!envVar || !isCredentialServiceEnvVar(envVar)) {
|
|
8381
|
+
warnings.push(
|
|
8382
|
+
`${serviceId}: profile '${profile?.name || "(unknown)"}' has invalid env var alias`,
|
|
8383
|
+
);
|
|
8384
|
+
continue;
|
|
8385
|
+
}
|
|
8386
|
+
envVarNames.push(envVar);
|
|
8387
|
+
}
|
|
8388
|
+
}
|
|
8389
|
+
|
|
8390
|
+
const uniqueEnvVarNames = unique(envVarNames);
|
|
8391
|
+
if (uniqueEnvVarNames.length === 0) {
|
|
8392
|
+
throw new Error(
|
|
8393
|
+
"No env var aliases were selected. Add profiles first or adjust --service/--profile.",
|
|
8394
|
+
);
|
|
8395
|
+
}
|
|
8396
|
+
|
|
8397
|
+
const persisted = await persistManagedCredentialsEnv({
|
|
8398
|
+
envVarNames: uniqueEnvVarNames,
|
|
8399
|
+
dryRun,
|
|
8400
|
+
});
|
|
8401
|
+
|
|
8402
|
+
console.log(`Config file: ${configPath}`);
|
|
8403
|
+
console.log(`Credentials env file: ${persisted.envPath}`);
|
|
8404
|
+
console.log(`Action: ${persisted.action}`);
|
|
8405
|
+
console.log(`Requested env vars: ${uniqueEnvVarNames.length}`);
|
|
8406
|
+
console.log(
|
|
8407
|
+
`Persisted env vars: ${persisted.persisted.length > 0 ? persisted.persisted.join(", ") : "(none)"}`,
|
|
8408
|
+
);
|
|
8409
|
+
if (persisted.missing.length > 0) {
|
|
8410
|
+
console.log(`Missing in current shell: ${persisted.missing.join(", ")}`);
|
|
8411
|
+
}
|
|
8412
|
+
if (warnings.length > 0) {
|
|
8413
|
+
console.log("Warnings:");
|
|
8414
|
+
for (const warning of warnings) {
|
|
8415
|
+
console.log(`- ${warning}`);
|
|
8416
|
+
}
|
|
8417
|
+
}
|
|
8418
|
+
} catch (error) {
|
|
8419
|
+
if (error?.name === "ExitPromptError") {
|
|
8420
|
+
console.error("\nCancelled.");
|
|
8421
|
+
process.exit(130);
|
|
8422
|
+
}
|
|
8423
|
+
console.error(`\nError: ${error.message}`);
|
|
8424
|
+
process.exit(1);
|
|
8425
|
+
}
|
|
8426
|
+
}
|
|
8427
|
+
|
|
7988
8428
|
async function runWorkflowConfig(options) {
|
|
7989
8429
|
try {
|
|
7990
8430
|
const opts = resolveActionOptions(options);
|
|
7991
8431
|
const cwd = process.cwd();
|
|
7992
8432
|
const scopeArg = readCliOptionFromArgv("--scope");
|
|
8433
|
+
await loadManagedCredentialsEnv();
|
|
7993
8434
|
const scope = normalizeMcpScope(scopeArg ?? opts.scope, "global");
|
|
7994
8435
|
const dryRun = Boolean(opts.dryRun);
|
|
7995
8436
|
const hasWorkspaceIdOption = opts.workspaceId !== undefined;
|
|
@@ -7997,6 +8438,7 @@ async function runWorkflowConfig(options) {
|
|
|
7997
8438
|
const wantsInteractiveEdit = Boolean(opts.edit);
|
|
7998
8439
|
const hasMcpRuntimeOption = opts.mcpRuntime !== undefined;
|
|
7999
8440
|
const hasMcpFallbackOption = opts.mcpFallback !== undefined;
|
|
8441
|
+
const hasPostmanModeOption = opts.postmanMode !== undefined;
|
|
8000
8442
|
|
|
8001
8443
|
if (hasWorkspaceIdOption && wantsClearWorkspaceId) {
|
|
8002
8444
|
throw new Error(
|
|
@@ -8009,7 +8451,8 @@ async function runWorkflowConfig(options) {
|
|
|
8009
8451
|
wantsClearWorkspaceId ||
|
|
8010
8452
|
wantsInteractiveEdit ||
|
|
8011
8453
|
hasMcpRuntimeOption ||
|
|
8012
|
-
hasMcpFallbackOption
|
|
8454
|
+
hasMcpFallbackOption ||
|
|
8455
|
+
hasPostmanModeOption;
|
|
8013
8456
|
const showOnly = Boolean(opts.show) || !wantsMutation;
|
|
8014
8457
|
const { configPath, existing, existingValue } = await loadConfigForScope({
|
|
8015
8458
|
scope,
|
|
@@ -8035,6 +8478,10 @@ async function runWorkflowConfig(options) {
|
|
|
8035
8478
|
|
|
8036
8479
|
const postmanState = ensureCredentialServiceState(next, "postman");
|
|
8037
8480
|
const activeProfile = { ...postmanState.activeProfile };
|
|
8481
|
+
const currentPostmanMode = resolvePostmanModeFromUrl(
|
|
8482
|
+
postmanState.mcpUrl,
|
|
8483
|
+
DEFAULT_POSTMAN_CONFIG_MODE,
|
|
8484
|
+
);
|
|
8038
8485
|
let workspaceId = normalizePostmanWorkspaceId(activeProfile.workspaceId);
|
|
8039
8486
|
|
|
8040
8487
|
if (wantsInteractiveEdit) {
|
|
@@ -8063,6 +8510,11 @@ async function runWorkflowConfig(options) {
|
|
|
8063
8510
|
normalizeMcpFallback(next.mcp?.fallback, DEFAULT_MCP_FALLBACK),
|
|
8064
8511
|
)
|
|
8065
8512
|
: null;
|
|
8513
|
+
const requestedPostmanMode = hasPostmanModeOption
|
|
8514
|
+
? normalizePostmanMode(opts.postmanMode, currentPostmanMode)
|
|
8515
|
+
: currentPostmanMode;
|
|
8516
|
+
const requestedPostmanMcpUrl =
|
|
8517
|
+
resolvePostmanMcpUrlForMode(requestedPostmanMode);
|
|
8066
8518
|
|
|
8067
8519
|
activeProfile.workspaceId = workspaceId;
|
|
8068
8520
|
const updatedProfiles = postmanState.profiles.map((profile) =>
|
|
@@ -8079,7 +8531,7 @@ async function runWorkflowConfig(options) {
|
|
|
8079
8531
|
: {}),
|
|
8080
8532
|
profiles: updatedProfiles,
|
|
8081
8533
|
activeProfileName: postmanState.activeProfileName,
|
|
8082
|
-
mcpUrl:
|
|
8534
|
+
mcpUrl: requestedPostmanMcpUrl,
|
|
8083
8535
|
},
|
|
8084
8536
|
});
|
|
8085
8537
|
upsertNormalizedPostmanConfig(next, updatedPostmanState);
|
|
@@ -8105,6 +8557,48 @@ async function runWorkflowConfig(options) {
|
|
|
8105
8557
|
existingExists: existing.exists,
|
|
8106
8558
|
dryRun,
|
|
8107
8559
|
});
|
|
8560
|
+
const effectivePostmanState = ensureCredentialServiceState(next, "postman");
|
|
8561
|
+
const effectivePostmanMode = resolvePostmanModeFromUrl(
|
|
8562
|
+
effectivePostmanState.mcpUrl,
|
|
8563
|
+
DEFAULT_POSTMAN_CONFIG_MODE,
|
|
8564
|
+
);
|
|
8565
|
+
|
|
8566
|
+
let postmanArtifacts = null;
|
|
8567
|
+
if (hasPostmanModeOption) {
|
|
8568
|
+
const mcpScope = resolveMcpScopeFromConfigDocument(next, scope);
|
|
8569
|
+
let platform = null;
|
|
8570
|
+
const explicitPlatform = normalizePlatform(opts.platform);
|
|
8571
|
+
const configuredPlatform = normalizePlatform(next?.mcp?.platform);
|
|
8572
|
+
if (
|
|
8573
|
+
explicitPlatform &&
|
|
8574
|
+
WORKFLOW_PROFILES[explicitPlatform]
|
|
8575
|
+
) {
|
|
8576
|
+
platform = explicitPlatform;
|
|
8577
|
+
} else if (
|
|
8578
|
+
configuredPlatform &&
|
|
8579
|
+
WORKFLOW_PROFILES[configuredPlatform]
|
|
8580
|
+
) {
|
|
8581
|
+
platform = configuredPlatform;
|
|
8582
|
+
} else {
|
|
8583
|
+
try {
|
|
8584
|
+
platform = await resolvePlatform(opts.platform, scope, cwd);
|
|
8585
|
+
} catch (error) {
|
|
8586
|
+
// Keep config mutation successful; surface patch guidance.
|
|
8587
|
+
if (!dryRun) {
|
|
8588
|
+
console.log(
|
|
8589
|
+
`Warning: platform could not be resolved for runtime patch (${error.message})`,
|
|
8590
|
+
);
|
|
8591
|
+
}
|
|
8592
|
+
}
|
|
8593
|
+
}
|
|
8594
|
+
postmanArtifacts = await applyPostmanConfigArtifacts({
|
|
8595
|
+
platform,
|
|
8596
|
+
mcpScope,
|
|
8597
|
+
configValue: next,
|
|
8598
|
+
dryRun,
|
|
8599
|
+
cwd,
|
|
8600
|
+
});
|
|
8601
|
+
}
|
|
8108
8602
|
|
|
8109
8603
|
console.log(`Config file: ${configPath}`);
|
|
8110
8604
|
console.log(`Action: ${action}`);
|
|
@@ -8118,6 +8612,31 @@ async function runWorkflowConfig(options) {
|
|
|
8118
8612
|
if (hasMcpFallbackOption) {
|
|
8119
8613
|
console.log(`mcp.fallback: ${mcpFallback}`);
|
|
8120
8614
|
}
|
|
8615
|
+
if (hasPostmanModeOption) {
|
|
8616
|
+
console.log(`postman.mode: ${effectivePostmanMode}`);
|
|
8617
|
+
console.log(`postman.mcpUrl: ${effectivePostmanState.mcpUrl}`);
|
|
8618
|
+
if (postmanArtifacts) {
|
|
8619
|
+
console.log(
|
|
8620
|
+
`postman.definition: ${postmanArtifacts.mcpDefinitionResult.action} (${postmanArtifacts.mcpDefinitionPath})`,
|
|
8621
|
+
);
|
|
8622
|
+
if (
|
|
8623
|
+
postmanArtifacts.stitchMcpDefinitionPath &&
|
|
8624
|
+
postmanArtifacts.stitchMcpDefinitionResult
|
|
8625
|
+
) {
|
|
8626
|
+
console.log(
|
|
8627
|
+
`stitch.definition: ${postmanArtifacts.stitchMcpDefinitionResult.action} (${postmanArtifacts.stitchMcpDefinitionPath})`,
|
|
8628
|
+
);
|
|
8629
|
+
}
|
|
8630
|
+
if (postmanArtifacts.mcpRuntimeResult) {
|
|
8631
|
+
console.log(
|
|
8632
|
+
`platform.mcp.target: ${postmanArtifacts.mcpRuntimeResult.action} (${postmanArtifacts.mcpRuntimeResult.path || "n/a"})`,
|
|
8633
|
+
);
|
|
8634
|
+
}
|
|
8635
|
+
for (const warning of postmanArtifacts.warnings) {
|
|
8636
|
+
console.log(`Warning: ${warning}`);
|
|
8637
|
+
}
|
|
8638
|
+
}
|
|
8639
|
+
}
|
|
8121
8640
|
if (Boolean(opts.showAfter)) {
|
|
8122
8641
|
const payload = buildConfigShowPayload(next);
|
|
8123
8642
|
console.log(JSON.stringify(payload, null, 2));
|
|
@@ -8213,6 +8732,72 @@ function sleepMs(ms) {
|
|
|
8213
8732
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
8214
8733
|
}
|
|
8215
8734
|
|
|
8735
|
+
function isMcpHandshakeRelatedError(error) {
|
|
8736
|
+
const message = String(error?.message || "").toLowerCase();
|
|
8737
|
+
return (
|
|
8738
|
+
message.includes("server not initialized") ||
|
|
8739
|
+
message.includes("already initialized") ||
|
|
8740
|
+
message.includes("mcp-session-id header is required") ||
|
|
8741
|
+
message.includes("missing mcp-session-id") ||
|
|
8742
|
+
message.includes("unknown mcp-session-id") ||
|
|
8743
|
+
message.includes("invalid mcp-session-id") ||
|
|
8744
|
+
message.includes("unknown session")
|
|
8745
|
+
);
|
|
8746
|
+
}
|
|
8747
|
+
|
|
8748
|
+
async function probeMcpEndpointReady({ url, headers = {} }) {
|
|
8749
|
+
let sessionId = null;
|
|
8750
|
+
let initError = null;
|
|
8751
|
+
|
|
8752
|
+
try {
|
|
8753
|
+
const init = await sendMcpJsonRpcRequest({
|
|
8754
|
+
url,
|
|
8755
|
+
method: "initialize",
|
|
8756
|
+
id: `cbx-ready-init-${Date.now()}`,
|
|
8757
|
+
params: {
|
|
8758
|
+
protocolVersion: "2025-06-18",
|
|
8759
|
+
capabilities: {},
|
|
8760
|
+
clientInfo: { name: "cbx-cli", version: CLI_VERSION },
|
|
8761
|
+
},
|
|
8762
|
+
headers,
|
|
8763
|
+
});
|
|
8764
|
+
sessionId = init.sessionId || null;
|
|
8765
|
+
if (sessionId) {
|
|
8766
|
+
// Best effort notification. Some servers ignore this safely.
|
|
8767
|
+
await sendMcpJsonRpcRequest({
|
|
8768
|
+
url,
|
|
8769
|
+
method: "notifications/initialized",
|
|
8770
|
+
id: null,
|
|
8771
|
+
params: {},
|
|
8772
|
+
headers,
|
|
8773
|
+
sessionId,
|
|
8774
|
+
}).catch(() => {});
|
|
8775
|
+
}
|
|
8776
|
+
} catch (error) {
|
|
8777
|
+
initError = error;
|
|
8778
|
+
}
|
|
8779
|
+
|
|
8780
|
+
try {
|
|
8781
|
+
await sendMcpJsonRpcRequest({
|
|
8782
|
+
url,
|
|
8783
|
+
method: "tools/list",
|
|
8784
|
+
id: `cbx-runtime-ready-${Date.now()}`,
|
|
8785
|
+
params: {},
|
|
8786
|
+
headers,
|
|
8787
|
+
sessionId,
|
|
8788
|
+
});
|
|
8789
|
+
return;
|
|
8790
|
+
} catch (toolsError) {
|
|
8791
|
+
if (isMcpHandshakeRelatedError(toolsError)) {
|
|
8792
|
+
return;
|
|
8793
|
+
}
|
|
8794
|
+
if (initError && !isMcpHandshakeRelatedError(initError)) {
|
|
8795
|
+
throw initError;
|
|
8796
|
+
}
|
|
8797
|
+
throw toolsError;
|
|
8798
|
+
}
|
|
8799
|
+
}
|
|
8800
|
+
|
|
8216
8801
|
async function waitForMcpEndpointReady({
|
|
8217
8802
|
url,
|
|
8218
8803
|
headers = {},
|
|
@@ -8224,21 +8809,10 @@ async function waitForMcpEndpointReady({
|
|
|
8224
8809
|
|
|
8225
8810
|
while (Date.now() - startedAt < timeoutMs) {
|
|
8226
8811
|
try {
|
|
8227
|
-
await
|
|
8228
|
-
url,
|
|
8229
|
-
method: "tools/list",
|
|
8230
|
-
id: `cbx-runtime-ready-${Date.now()}`,
|
|
8231
|
-
params: {},
|
|
8232
|
-
headers,
|
|
8233
|
-
});
|
|
8812
|
+
await probeMcpEndpointReady({ url, headers });
|
|
8234
8813
|
return true;
|
|
8235
8814
|
} catch (error) {
|
|
8236
|
-
|
|
8237
|
-
if (
|
|
8238
|
-
message.includes("server not initialized") ||
|
|
8239
|
-
message.includes("mcp-session-id header is required") ||
|
|
8240
|
-
message.includes("already initialized")
|
|
8241
|
-
) {
|
|
8815
|
+
if (isMcpHandshakeRelatedError(error)) {
|
|
8242
8816
|
return true;
|
|
8243
8817
|
}
|
|
8244
8818
|
lastError = error;
|
|
@@ -8467,6 +9041,7 @@ async function runMcpToolsSync(options) {
|
|
|
8467
9041
|
try {
|
|
8468
9042
|
const opts = resolveActionOptions(options);
|
|
8469
9043
|
const cwd = process.cwd();
|
|
9044
|
+
await loadManagedCredentialsEnv();
|
|
8470
9045
|
const scope = normalizeMcpScope(opts.scope, "global");
|
|
8471
9046
|
const service = normalizeCredentialService(opts.service || "all", {
|
|
8472
9047
|
allowAll: true,
|
|
@@ -8565,6 +9140,7 @@ async function runMcpServe(options) {
|
|
|
8565
9140
|
try {
|
|
8566
9141
|
const opts = resolveActionOptions(options);
|
|
8567
9142
|
const cwd = process.cwd();
|
|
9143
|
+
await loadManagedCredentialsEnv();
|
|
8568
9144
|
const entryPath = resolveBundledMcpEntryPath();
|
|
8569
9145
|
if (!(await pathExists(entryPath))) {
|
|
8570
9146
|
throw new Error(
|
|
@@ -8752,6 +9328,7 @@ async function runMcpRuntimeStatus(options) {
|
|
|
8752
9328
|
try {
|
|
8753
9329
|
const opts = resolveActionOptions(options);
|
|
8754
9330
|
const cwd = process.cwd();
|
|
9331
|
+
await loadManagedCredentialsEnv();
|
|
8755
9332
|
const scope = normalizeMcpScope(opts.scope, "global");
|
|
8756
9333
|
const explicitSkillsRoot = normalizePostmanApiKey(opts.skillsRoot);
|
|
8757
9334
|
const defaults = await loadMcpRuntimeDefaults({ scope, cwd });
|
|
@@ -8860,6 +9437,7 @@ async function runMcpRuntimeUp(options) {
|
|
|
8860
9437
|
try {
|
|
8861
9438
|
const opts = resolveActionOptions(options);
|
|
8862
9439
|
const cwd = process.cwd();
|
|
9440
|
+
await loadManagedCredentialsEnv();
|
|
8863
9441
|
const scope = normalizeMcpScope(opts.scope, "global");
|
|
8864
9442
|
const explicitSkillsRoot = normalizePostmanApiKey(opts.skillsRoot);
|
|
8865
9443
|
const defaults = await loadMcpRuntimeDefaults({ scope, cwd });
|
|
@@ -9001,7 +9579,7 @@ async function runMcpRuntimeUp(options) {
|
|
|
9001
9579
|
"--port",
|
|
9002
9580
|
String(MCP_DOCKER_CONTAINER_PORT),
|
|
9003
9581
|
"--scope",
|
|
9004
|
-
|
|
9582
|
+
scope,
|
|
9005
9583
|
);
|
|
9006
9584
|
await execFile("docker", dockerArgs, { cwd });
|
|
9007
9585
|
|
|
@@ -9407,6 +9985,7 @@ withWorkflowBaseOptions(
|
|
|
9407
9985
|
const workflowsConfigCommand = workflowsCommand
|
|
9408
9986
|
.command("config")
|
|
9409
9987
|
.description("View or edit cbx_config.json from terminal")
|
|
9988
|
+
.option("-p, --platform <platform>", "target platform id for MCP target patch")
|
|
9410
9989
|
.option(
|
|
9411
9990
|
"--scope <scope>",
|
|
9412
9991
|
"config scope: project|workspace|global|user",
|
|
@@ -9416,6 +9995,7 @@ const workflowsConfigCommand = workflowsCommand
|
|
|
9416
9995
|
.option("--edit", "edit Postman default workspace ID interactively")
|
|
9417
9996
|
.option("--workspace-id <id|null>", "set postman.defaultWorkspaceId")
|
|
9418
9997
|
.option("--clear-workspace-id", "set postman.defaultWorkspaceId to null")
|
|
9998
|
+
.option("--postman-mode <mode>", "set postman.mcpUrl via mode: minimal|code|full")
|
|
9419
9999
|
.option("--mcp-runtime <runtime>", "set mcp.runtime: docker|local")
|
|
9420
10000
|
.option("--mcp-fallback <fallback>", "set mcp.fallback: local|fail|skip")
|
|
9421
10001
|
.option("--show-after", "print JSON after update")
|
|
@@ -9509,6 +10089,7 @@ withWorkflowBaseOptions(
|
|
|
9509
10089
|
const skillsConfigCommand = skillsCommand
|
|
9510
10090
|
.command("config")
|
|
9511
10091
|
.description("Alias for workflows config")
|
|
10092
|
+
.option("-p, --platform <platform>", "target platform id for MCP target patch")
|
|
9512
10093
|
.option(
|
|
9513
10094
|
"--scope <scope>",
|
|
9514
10095
|
"config scope: project|workspace|global|user",
|
|
@@ -9518,6 +10099,7 @@ const skillsConfigCommand = skillsCommand
|
|
|
9518
10099
|
.option("--edit", "edit Postman default workspace ID interactively")
|
|
9519
10100
|
.option("--workspace-id <id|null>", "set postman.defaultWorkspaceId")
|
|
9520
10101
|
.option("--clear-workspace-id", "set postman.defaultWorkspaceId to null")
|
|
10102
|
+
.option("--postman-mode <mode>", "set postman.mcpUrl via mode: minimal|code|full")
|
|
9521
10103
|
.option("--mcp-runtime <runtime>", "set mcp.runtime: docker|local")
|
|
9522
10104
|
.option("--mcp-fallback <fallback>", "set mcp.fallback: local|fail|skip")
|
|
9523
10105
|
.option("--show-after", "print JSON after update")
|