@cubis/foundry 0.3.51 → 0.3.52

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.
Files changed (30) hide show
  1. package/README.md +26 -12
  2. package/bin/cubis.js +662 -80
  3. package/mcp/dist/index.js +108 -46
  4. package/mcp/src/server.ts +51 -39
  5. package/mcp/src/upstream/passthrough.test.ts +30 -0
  6. package/mcp/src/upstream/passthrough.ts +113 -17
  7. package/package.json +1 -1
  8. package/workflows/skills/postman/SKILL.md +31 -25
  9. package/workflows/skills/postman/references/full-mode-setup.md +36 -0
  10. package/workflows/skills/postman/references/troubleshooting.md +25 -0
  11. package/workflows/skills/postman/references/workspace-policy.md +20 -0
  12. package/workflows/workflows/agent-environment-setup/manifest.json +1 -1
  13. package/workflows/workflows/agent-environment-setup/platforms/antigravity/workflows/database.md +1 -1
  14. package/workflows/workflows/agent-environment-setup/platforms/codex/workflows/database.md +1 -1
  15. package/workflows/workflows/agent-environment-setup/platforms/copilot/rules/AGENTS.md +4 -3
  16. package/workflows/workflows/agent-environment-setup/platforms/copilot/rules/copilot-instructions.md +1 -1
  17. package/workflows/workflows/agent-environment-setup/platforms/copilot/skills/postman/SKILL.md +31 -25
  18. package/workflows/workflows/agent-environment-setup/platforms/copilot/skills/postman/references/full-mode-setup.md +36 -0
  19. package/workflows/workflows/agent-environment-setup/platforms/copilot/skills/postman/references/troubleshooting.md +25 -0
  20. package/workflows/workflows/agent-environment-setup/platforms/copilot/skills/postman/references/workspace-policy.md +20 -0
  21. package/workflows/workflows/agent-environment-setup/platforms/copilot/workflows/database.md +1 -1
  22. package/workflows/workflows/agent-environment-setup/platforms/cursor/skills/postman/SKILL.md +31 -25
  23. package/workflows/workflows/agent-environment-setup/platforms/cursor/skills/postman/references/full-mode-setup.md +36 -0
  24. package/workflows/workflows/agent-environment-setup/platforms/cursor/skills/postman/references/troubleshooting.md +25 -0
  25. package/workflows/workflows/agent-environment-setup/platforms/cursor/skills/postman/references/workspace-policy.md +20 -0
  26. package/workflows/workflows/agent-environment-setup/platforms/windsurf/skills/postman/SKILL.md +31 -25
  27. package/workflows/workflows/agent-environment-setup/platforms/windsurf/skills/postman/references/full-mode-setup.md +36 -0
  28. package/workflows/workflows/agent-environment-setup/platforms/windsurf/skills/postman/references/troubleshooting.md +25 -0
  29. package/workflows/workflows/agent-environment-setup/platforms/windsurf/skills/postman/references/workspace-policy.md +20 -0
  30. 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: ["AGENTS.md", ".github/copilot-instructions.md"],
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 rules are the default for this project.",
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
- "## 1) Build Only What Is Needed (YAGNI)",
891
+ "## 2) Simplicity First (KISS + YAGNI)",
859
892
  "",
860
- "- Implement only what current requirements need.",
861
- '- Do not add speculative abstractions, extension points, or feature flags "for future use."',
862
- "- If a helper/class is used only once and does not improve clarity, keep it inline.",
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
- "## 2) Readability First",
898
+ "## 3) SOLID, Used Pragmatically",
865
899
  "",
866
- "- Code should be understandable in one pass.",
867
- "- Prefer straightforward flow over clever tricks.",
868
- "- Reduce nesting and branching where possible.",
869
- "- Remove dead code and commented-out blocks.",
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
- "## 3) Precise Naming (One Look = Clear Intent)",
907
+ "## 4) Naming and Readability",
872
908
  "",
873
- "- Class names must say exactly what they represent.",
874
- " - Good: `AttendanceStatisticScreen`",
875
- " - Bad: `DataScreen`, `CommonManager`",
876
- "- Method names must say exactly what they do.",
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
- "## 4) Keep Functions and Classes Focused",
914
+ "## 5) Boundaries and Contracts",
883
915
  "",
884
- "- One function should do one clear job.",
885
- "- One class should own one clear responsibility.",
886
- "- Split when a file mixes unrelated concerns (UI + networking + mapping in one place).",
887
- "- Prefer small composable units over inheritance-heavy designs.",
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
- "## 5) Platform Implementation Rules",
921
+ "## 6) Testing and Verification",
890
922
  "",
891
- "- Keep providers/services focused; do not let one unit fetch unrelated feature data.",
892
- "- Prevent duplicate network calls (cache or in-flight dedupe) when multiple views depend on the same data.",
893
- "- Route/build functions must not return placeholder content in production flows.",
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
- "## 6) UI Migration Rule (Required for This Project)",
928
+ "## 7) Security and Reliability Baseline",
896
929
  "",
897
- "For each migrated screen:",
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
- "1. Copy legacy layout/behavior/state flow first (behavior parity).",
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
- "## 7) PR / Review Checklist",
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
- "Before merge, confirm:",
942
+ "## 9) Delivery Discipline",
907
943
  "",
908
- "- Naming is precise and intention-revealing.",
909
- "- No speculative abstraction was added.",
910
- "- Logic is simple enough for fast onboarding.",
911
- "- UI uses design system tokens/components, not ad-hoc sizing.",
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
- "## 8) Keep TECH.md Fresh",
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. Build only what is needed (YAGNI).",
944
- "2. Keep logic simple and readable.",
945
- "3. Use precise, intention-revealing names.",
946
- "4. Keep classes/functions focused on one responsibility.",
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
- const profilePaths = await resolveProfilePaths(profileId, scope, cwd);
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: "auto",
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: "auto",
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: "auto",
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: "auto",
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
- ...FOUNDRY_MCP_DEFAULT_ARGS,
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: POSTMAN_MCP_URL,
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 = installPostmanConfig?.mcpUrl || POSTMAN_MCP_URL;
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 = storedPostmanConfig.mcpUrl || POSTMAN_MCP_URL;
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: postmanState.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 sendMcpJsonRpcRequest({
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
- const message = String(error?.message || "").toLowerCase();
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
- "global",
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")