@cubis/foundry 0.3.23 → 0.3.25

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/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  All notable changes to this project are documented in this file.
4
4
 
5
+ ## [0.3.25] - 2026-02-26
6
+
7
+ ### Added
8
+
9
+ - Added `cbx workflows config` command to view and edit `cbx_config.json` from terminal.
10
+ - Added `cbx skills config` alias for compatibility with deprecated `skills` command group.
11
+ - Added docs for showing/updating/clearing Postman `defaultWorkspaceId` after install.
12
+
13
+ ### Changed
14
+
15
+ - Improved Postman install warnings when workspace selection is not persisted because existing `cbx_config.json` was skipped.
16
+ - Included actionable follow-up command in warnings (`cbx workflows config ...`).
17
+
5
18
  ## [0.3.23] - 2026-02-26
6
19
 
7
20
  ### Added
package/README.md CHANGED
@@ -59,6 +59,7 @@ cbx workflows install --platform antigravity --dry-run
59
59
  cbx workflows install --platform antigravity --terminal-integration --terminal-verifier codex
60
60
  cbx workflows install --platform codex --postman
61
61
  cbx workflows install --platform codex --postman --postman-workspace-id null
62
+ cbx workflows install --platform codex --postman --postman-workspace-id "<workspace-id>"
62
63
  cbx workflows install --platform codex --postman --postman-api-key "<key>"
63
64
  cbx workflows install --platform codex --postman --mcp-scope global
64
65
  cbx workflows install --platform copilot --postman --mcp-scope project
@@ -83,10 +84,56 @@ Postman + Antigravity Stitch setup behavior:
83
84
  - Env-first auth is supported: when `POSTMAN_API_KEY` is set, generated settings keep `apiKey: null` and MCP config uses `Bearer ${POSTMAN_API_KEY}`.
84
85
  - Inline auth is supported with `--postman-api-key <key>`.
85
86
  - `--postman-workspace-id null` writes JSON `null` for `defaultWorkspaceId`.
87
+ - If `POSTMAN_API_KEY` is available and install is interactive (no `--yes`) without `--postman-workspace-id`, `cbx` fetches your Postman workspaces and prompts you to pick one as default.
86
88
  - Antigravity gets an additional managed file at `.cbx/mcp/antigravity/stitch.json` (or `~/.cbx/mcp/antigravity/stitch.json` in global MCP scope).
87
89
  - Stitch key source priority for Antigravity: `--stitch-api-key` then `STITCH_API_KEY`; when unset, generated config keeps placeholder `X-Goog-Api-Key: ur stitch key`.
88
90
  - In project MCP scope, `cbx_config.json` and `.cbx/mcp/` are auto-added to `.gitignore` (no duplicate entries).
89
91
 
92
+ Codex Postman workspace selection:
93
+
94
+ ```bash
95
+ # Set Postman API key first
96
+ export POSTMAN_API_KEY="<your-postman-api-key>"
97
+
98
+ # Interactive workspace selector (recommended)
99
+ cbx workflows install --platform codex --bundle agent-environment-setup --postman
100
+ ```
101
+
102
+ - Do not use `--yes` here (it skips prompts).
103
+ - You will get a workspace selector and the selected value is saved as `defaultWorkspaceId`.
104
+
105
+ Direct/manual workspace ID:
106
+
107
+ ```bash
108
+ cbx workflows install --platform codex --bundle agent-environment-setup --postman --postman-workspace-id "<workspace-id>" --yes
109
+ ```
110
+
111
+ Clear default workspace:
112
+
113
+ ```bash
114
+ cbx workflows install --platform codex --bundle agent-environment-setup --postman --postman-workspace-id null --yes
115
+ ```
116
+
117
+ Important:
118
+ - If `cbx_config.json` already exists and you want to change the saved workspace, use `--overwrite` (or edit config manually), because existing config is preserved by default.
119
+ - If install output shows `Config file: skipped (...)`, your newly selected workspace is not persisted. Use `--overwrite` or edit with the config command below.
120
+
121
+ View/edit config in terminal:
122
+
123
+ ```bash
124
+ # View config
125
+ cbx workflows config --scope global --show
126
+
127
+ # Interactive edit (workspace ID)
128
+ cbx workflows config --scope global --edit
129
+
130
+ # Direct set workspace ID
131
+ cbx workflows config --scope global --workspace-id "<workspace-id>"
132
+
133
+ # Clear workspace ID
134
+ cbx workflows config --scope global --clear-workspace-id
135
+ ```
136
+
90
137
  Platform runtime MCP placement:
91
138
  - Codex:
92
139
  - Global MCP scope: `~/.codex/config.toml` via `codex mcp add`.
package/bin/cubis.js CHANGED
@@ -127,11 +127,13 @@ const TERMINAL_VERIFIER_PROVIDERS = ["codex", "gemini"];
127
127
  const DEFAULT_TERMINAL_VERIFIER = "codex";
128
128
  const POSTMAN_API_KEY_ENV_VAR = "POSTMAN_API_KEY";
129
129
  const POSTMAN_MCP_URL = "https://mcp.postman.com/minimal";
130
+ const POSTMAN_API_BASE_URL = "https://api.getpostman.com";
130
131
  const POSTMAN_SKILL_ID = "postman";
131
132
  const STITCH_MCP_SERVER_ID = "StitchMCP";
132
133
  const STITCH_API_KEY_ENV_VAR = "STITCH_API_KEY";
133
134
  const STITCH_MCP_URL = "https://stitch.googleapis.com/mcp";
134
135
  const STITCH_API_KEY_PLACEHOLDER = "ur stitch key";
136
+ const POSTMAN_WORKSPACE_MANUAL_CHOICE = "__postman_workspace_manual__";
135
137
  const CBX_CONFIG_FILENAME = "cbx_config.json";
136
138
  const POSTMAN_SETTINGS_FILENAME = "postman_setting.json";
137
139
  const POSTMAN_API_KEY_MISSING_WARNING =
@@ -2428,6 +2430,57 @@ function parseStoredPostmanConfig(raw) {
2428
2430
  };
2429
2431
  }
2430
2432
 
2433
+ async function fetchPostmanWorkspaces({
2434
+ apiKey,
2435
+ apiBaseUrl = POSTMAN_API_BASE_URL,
2436
+ timeoutMs = 12000
2437
+ }) {
2438
+ if (!apiKey) return [];
2439
+
2440
+ const controller = new AbortController();
2441
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
2442
+ const endpoint = `${apiBaseUrl.replace(/\/$/, "")}/workspaces`;
2443
+
2444
+ try {
2445
+ const response = await fetch(endpoint, {
2446
+ method: "GET",
2447
+ headers: {
2448
+ "X-Api-Key": apiKey,
2449
+ Accept: "application/json"
2450
+ },
2451
+ signal: controller.signal
2452
+ });
2453
+
2454
+ const payload = await response.json().catch(() => null);
2455
+ if (!response.ok) {
2456
+ const message =
2457
+ payload?.error?.message ||
2458
+ payload?.error?.name ||
2459
+ `Postman API request failed (${response.status} ${response.statusText}).`;
2460
+ throw new Error(message);
2461
+ }
2462
+
2463
+ const workspaces = Array.isArray(payload?.workspaces) ? payload.workspaces : [];
2464
+ return workspaces
2465
+ .map((workspace) => {
2466
+ const id = normalizePostmanWorkspaceId(workspace?.id);
2467
+ if (!id) return null;
2468
+ const name = String(workspace?.name || "").trim() || id;
2469
+ const type = String(workspace?.type || "").trim() || null;
2470
+ const visibility = String(workspace?.visibility || "").trim() || null;
2471
+ return { id, name, type, visibility };
2472
+ })
2473
+ .filter(Boolean);
2474
+ } catch (error) {
2475
+ if (error?.name === "AbortError") {
2476
+ throw new Error("Postman workspace lookup timed out.");
2477
+ }
2478
+ throw error;
2479
+ } finally {
2480
+ clearTimeout(timeout);
2481
+ }
2482
+ }
2483
+
2431
2484
  function parseStoredStitchConfig(raw) {
2432
2485
  if (!raw || typeof raw !== "object") return null;
2433
2486
  const source = raw.stitch && typeof raw.stitch === "object" ? raw.stitch : null;
@@ -2739,6 +2792,7 @@ async function resolvePostmanInstallSelection({
2739
2792
  let defaultWorkspaceId = hasWorkspaceOption
2740
2793
  ? normalizePostmanWorkspaceId(options.postmanWorkspaceId)
2741
2794
  : null;
2795
+ let workspaceSelectionSource = hasWorkspaceOption ? "option" : "none";
2742
2796
  const requestedMcpScope = options.mcpScope
2743
2797
  ? normalizeMcpScope(options.mcpScope, normalizeMcpScope(scope, "project"))
2744
2798
  : null;
@@ -2762,11 +2816,60 @@ async function resolvePostmanInstallSelection({
2762
2816
  }
2763
2817
 
2764
2818
  if (canPrompt && !hasWorkspaceOption) {
2765
- const promptedWorkspaceId = await input({
2766
- message: "Default Postman workspace ID (optional, leave blank for null):",
2767
- default: ""
2768
- });
2769
- defaultWorkspaceId = normalizePostmanWorkspaceId(promptedWorkspaceId);
2819
+ const selectableApiKey = normalizePostmanApiKey(apiKey || envApiKey);
2820
+ let usedWorkspaceSelector = false;
2821
+ let selectedWorkspaceId = defaultWorkspaceId;
2822
+
2823
+ if (selectableApiKey) {
2824
+ try {
2825
+ const fetchedWorkspaces = await fetchPostmanWorkspaces({
2826
+ apiKey: selectableApiKey
2827
+ });
2828
+ if (fetchedWorkspaces.length > 0) {
2829
+ usedWorkspaceSelector = true;
2830
+ const sortedWorkspaces = [...fetchedWorkspaces].sort((a, b) => a.name.localeCompare(b.name));
2831
+ const workspaceChoice = await select({
2832
+ message: "Choose default Postman workspace for this install:",
2833
+ choices: [
2834
+ { name: "No default workspace (null)", value: null },
2835
+ ...sortedWorkspaces.map((workspace) => {
2836
+ const details = [workspace.type, workspace.visibility].filter(Boolean).join(", ");
2837
+ const suffix = details ? ` - ${details}` : "";
2838
+ return {
2839
+ name: `${workspace.name} (${workspace.id})${suffix}`,
2840
+ value: workspace.id
2841
+ };
2842
+ }),
2843
+ { name: "Enter workspace ID manually", value: POSTMAN_WORKSPACE_MANUAL_CHOICE }
2844
+ ],
2845
+ default: null
2846
+ });
2847
+
2848
+ if (workspaceChoice === POSTMAN_WORKSPACE_MANUAL_CHOICE) {
2849
+ const promptedWorkspaceId = await input({
2850
+ message: "Default Postman workspace ID (optional, leave blank for null):",
2851
+ default: ""
2852
+ });
2853
+ selectedWorkspaceId = normalizePostmanWorkspaceId(promptedWorkspaceId);
2854
+ } else {
2855
+ selectedWorkspaceId = normalizePostmanWorkspaceId(workspaceChoice);
2856
+ }
2857
+ }
2858
+ } catch (error) {
2859
+ warnings.push(`Could not load Postman workspaces for selection: ${error.message}`);
2860
+ }
2861
+ }
2862
+
2863
+ if (!usedWorkspaceSelector) {
2864
+ const promptedWorkspaceId = await input({
2865
+ message: "Default Postman workspace ID (optional, leave blank for null):",
2866
+ default: ""
2867
+ });
2868
+ selectedWorkspaceId = normalizePostmanWorkspaceId(promptedWorkspaceId);
2869
+ }
2870
+
2871
+ defaultWorkspaceId = selectedWorkspaceId;
2872
+ workspaceSelectionSource = "interactive";
2770
2873
  }
2771
2874
 
2772
2875
  if (canPrompt && stitchEnabled && !hasStitchApiKeyOption && !stitchApiKey && !envStitchApiKey) {
@@ -2844,6 +2947,7 @@ async function resolvePostmanInstallSelection({
2844
2947
  stitchApiKey,
2845
2948
  stitchApiKeySource,
2846
2949
  defaultWorkspaceId: defaultWorkspaceId ?? null,
2950
+ workspaceSelectionSource,
2847
2951
  mcpScope,
2848
2952
  warnings,
2849
2953
  cbxConfig,
@@ -2912,6 +3016,21 @@ async function configurePostmanInstallArtifacts({
2912
3016
  effectiveStitchApiKey = storedStitchConfig.apiKey;
2913
3017
  effectiveStitchMcpUrl = storedStitchConfig.mcpUrl || STITCH_MCP_URL;
2914
3018
  }
3019
+
3020
+ if (postmanSelection.workspaceSelectionSource && postmanSelection.workspaceSelectionSource !== "none") {
3021
+ const requestedWorkspaceId = postmanSelection.defaultWorkspaceId ?? null;
3022
+ const persistedWorkspaceId = effectiveDefaultWorkspaceId ?? null;
3023
+ if (requestedWorkspaceId !== persistedWorkspaceId) {
3024
+ const configScope = postmanSelection.mcpScope === "global" ? "global" : "project";
3025
+ const configHint =
3026
+ requestedWorkspaceId === null
3027
+ ? `cbx workflows config --scope ${configScope} --clear-workspace-id`
3028
+ : `cbx workflows config --scope ${configScope} --workspace-id \"${requestedWorkspaceId}\"`;
3029
+ warnings.push(
3030
+ `Selected Postman workspace (${requestedWorkspaceId === null ? "null" : requestedWorkspaceId}) was not saved because ${CBX_CONFIG_FILENAME} already exists. Re-run with --overwrite or run '${configHint}'.`
3031
+ );
3032
+ }
3033
+ }
2915
3034
  }
2916
3035
 
2917
3036
  const envApiKey = normalizePostmanApiKey(process.env[POSTMAN_API_KEY_ENV_VAR]);
@@ -4418,6 +4537,105 @@ async function runWorkflowDoctor(platformArg, options) {
4418
4537
  }
4419
4538
  }
4420
4539
 
4540
+ async function runWorkflowConfig(options) {
4541
+ try {
4542
+ const cwd = process.cwd();
4543
+ const scope = normalizeMcpScope(options.scope, "global");
4544
+ const dryRun = Boolean(options.dryRun);
4545
+ const hasWorkspaceIdOption = options.workspaceId !== undefined;
4546
+ const wantsClearWorkspaceId = Boolean(options.clearWorkspaceId);
4547
+ const wantsInteractiveEdit = Boolean(options.edit);
4548
+
4549
+ if (hasWorkspaceIdOption && wantsClearWorkspaceId) {
4550
+ throw new Error("Use either --workspace-id or --clear-workspace-id, not both.");
4551
+ }
4552
+
4553
+ const wantsMutation = hasWorkspaceIdOption || wantsClearWorkspaceId || wantsInteractiveEdit;
4554
+ const showOnly = Boolean(options.show) || !wantsMutation;
4555
+ const configPath = resolveCbxConfigPath({ scope, cwd });
4556
+ const existing = await readJsonFileIfExists(configPath);
4557
+ const existingValue =
4558
+ existing.value && typeof existing.value === "object" && !Array.isArray(existing.value) ? existing.value : null;
4559
+
4560
+ if (showOnly) {
4561
+ console.log(`Config file: ${configPath}`);
4562
+ if (!existing.exists) {
4563
+ console.log("Status: missing");
4564
+ return;
4565
+ }
4566
+ if (!existingValue) {
4567
+ throw new Error(`Existing config at ${configPath} is not valid JSON object.`);
4568
+ }
4569
+ console.log(`Status: ${existing.exists ? "exists" : "missing"}`);
4570
+ console.log(JSON.stringify(existingValue, null, 2));
4571
+ return;
4572
+ }
4573
+
4574
+ if (existing.exists && !existingValue) {
4575
+ throw new Error(`Existing config at ${configPath} is not valid JSON object.`);
4576
+ }
4577
+
4578
+ const next = existingValue ? JSON.parse(JSON.stringify(existingValue)) : {};
4579
+ if (!next.schemaVersion || typeof next.schemaVersion !== "number") next.schemaVersion = 1;
4580
+ next.generatedBy = "cbx workflows config";
4581
+ next.generatedAt = new Date().toISOString();
4582
+
4583
+ if (!next.mcp || typeof next.mcp !== "object" || Array.isArray(next.mcp)) next.mcp = {};
4584
+ next.mcp.scope = scope;
4585
+ if (!next.mcp.server) next.mcp.server = POSTMAN_SKILL_ID;
4586
+
4587
+ if (!next.postman || typeof next.postman !== "object" || Array.isArray(next.postman)) next.postman = {};
4588
+ next.postman.apiKey = normalizePostmanApiKey(next.postman.apiKey);
4589
+ next.postman.apiKeyEnvVar = String(next.postman.apiKeyEnvVar || POSTMAN_API_KEY_ENV_VAR).trim() || POSTMAN_API_KEY_ENV_VAR;
4590
+ next.postman.mcpUrl = String(next.postman.mcpUrl || POSTMAN_MCP_URL).trim() || POSTMAN_MCP_URL;
4591
+
4592
+ let workspaceId = normalizePostmanWorkspaceId(next.postman.defaultWorkspaceId);
4593
+
4594
+ if (wantsInteractiveEdit) {
4595
+ const promptedWorkspaceId = await input({
4596
+ message: "Postman default workspace ID (optional, leave blank or 'null' to clear):",
4597
+ default: workspaceId || ""
4598
+ });
4599
+ workspaceId = normalizePostmanWorkspaceId(promptedWorkspaceId);
4600
+ }
4601
+
4602
+ if (hasWorkspaceIdOption) {
4603
+ workspaceId = normalizePostmanWorkspaceId(options.workspaceId);
4604
+ }
4605
+
4606
+ if (wantsClearWorkspaceId) {
4607
+ workspaceId = null;
4608
+ }
4609
+
4610
+ next.postman.defaultWorkspaceId = workspaceId;
4611
+ const envApiKey = normalizePostmanApiKey(process.env[POSTMAN_API_KEY_ENV_VAR]);
4612
+ next.postman.apiKeySource = getPostmanApiKeySource({
4613
+ apiKey: next.postman.apiKey,
4614
+ envApiKey
4615
+ });
4616
+
4617
+ const content = `${JSON.stringify(next, null, 2)}\n`;
4618
+ if (!dryRun) {
4619
+ await mkdir(path.dirname(configPath), { recursive: true });
4620
+ await writeFile(configPath, content, "utf8");
4621
+ }
4622
+
4623
+ console.log(`Config file: ${configPath}`);
4624
+ console.log(`Action: ${dryRun ? (existing.exists ? "would-update" : "would-create") : existing.exists ? "updated" : "created"}`);
4625
+ console.log(`postman.defaultWorkspaceId: ${workspaceId === null ? "null" : workspaceId}`);
4626
+ if (Boolean(options.showAfter)) {
4627
+ console.log(JSON.stringify(next, null, 2));
4628
+ }
4629
+ } catch (error) {
4630
+ if (error?.name === "ExitPromptError") {
4631
+ console.error("\nCancelled.");
4632
+ process.exit(130);
4633
+ }
4634
+ console.error(`\nError: ${error.message}`);
4635
+ process.exit(1);
4636
+ }
4637
+ }
4638
+
4421
4639
  function printRulesInitSummary({
4422
4640
  platform,
4423
4641
  scope,
@@ -4649,6 +4867,18 @@ withWorkflowBaseOptions(
4649
4867
  .option("--json", "output JSON")
4650
4868
  ).action(runWorkflowDoctor);
4651
4869
 
4870
+ workflowsCommand
4871
+ .command("config")
4872
+ .description("View or edit cbx_config.json from terminal")
4873
+ .option("--scope <scope>", "config scope: project|workspace|global|user", "global")
4874
+ .option("--show", "show current config (default when no edit flags)")
4875
+ .option("--edit", "edit Postman default workspace ID interactively")
4876
+ .option("--workspace-id <id|null>", "set postman.defaultWorkspaceId")
4877
+ .option("--clear-workspace-id", "set postman.defaultWorkspaceId to null")
4878
+ .option("--show-after", "print JSON after update")
4879
+ .option("--dry-run", "preview changes without writing files")
4880
+ .action(runWorkflowConfig);
4881
+
4652
4882
  workflowsCommand.action(() => {
4653
4883
  workflowsCommand.help();
4654
4884
  });
@@ -4708,6 +4938,21 @@ withWorkflowBaseOptions(
4708
4938
  await runWorkflowDoctor(platform, options);
4709
4939
  });
4710
4940
 
4941
+ skillsCommand
4942
+ .command("config")
4943
+ .description("Alias for workflows config")
4944
+ .option("--scope <scope>", "config scope: project|workspace|global|user", "global")
4945
+ .option("--show", "show current config (default when no edit flags)")
4946
+ .option("--edit", "edit Postman default workspace ID interactively")
4947
+ .option("--workspace-id <id|null>", "set postman.defaultWorkspaceId")
4948
+ .option("--clear-workspace-id", "set postman.defaultWorkspaceId to null")
4949
+ .option("--show-after", "print JSON after update")
4950
+ .option("--dry-run", "preview changes without writing files")
4951
+ .action(async (options) => {
4952
+ printSkillsDeprecation();
4953
+ await runWorkflowConfig(options);
4954
+ });
4955
+
4711
4956
  skillsCommand.action(() => {
4712
4957
  printSkillsDeprecation();
4713
4958
  skillsCommand.help();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cubis/foundry",
3
- "version": "0.3.23",
3
+ "version": "0.3.25",
4
4
  "description": "Cubis Foundry CLI for workflow-first AI agent environments",
5
5
  "type": "module",
6
6
  "bin": {