@cubis/foundry 0.3.28 → 0.3.29
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/Ai Agent Workflow/powers/postman/POWER.md +2 -2
- package/Ai Agent Workflow/powers/postman/SKILL.md +2 -2
- package/Ai Agent Workflow/skills/postman/POWER.md +2 -2
- package/Ai Agent Workflow/skills/postman/SKILL.md +2 -2
- package/CHANGELOG.md +26 -1
- package/README.md +241 -399
- package/bin/cubis.js +876 -107
- package/package.json +1 -1
package/bin/cubis.js
CHANGED
|
@@ -136,7 +136,10 @@ const STITCH_MCP_URL = "https://stitch.googleapis.com/mcp";
|
|
|
136
136
|
const STITCH_API_KEY_PLACEHOLDER = "ur stitch key";
|
|
137
137
|
const POSTMAN_WORKSPACE_MANUAL_CHOICE = "__postman_workspace_manual__";
|
|
138
138
|
const CBX_CONFIG_FILENAME = "cbx_config.json";
|
|
139
|
-
const
|
|
139
|
+
const LEGACY_POSTMAN_CONFIG_FILENAME = ["postman", "setting.json"].join("_");
|
|
140
|
+
const DEFAULT_CREDENTIAL_PROFILE_NAME = "default";
|
|
141
|
+
const RESERVED_CREDENTIAL_PROFILE_NAMES = new Set(["all"]);
|
|
142
|
+
const CREDENTIAL_SERVICES = new Set(["postman", "stitch"]);
|
|
140
143
|
const POSTMAN_API_KEY_MISSING_WARNING =
|
|
141
144
|
`Postman API key is not configured. Set ${POSTMAN_API_KEY_ENV_VAR} or update ${CBX_CONFIG_FILENAME}.`;
|
|
142
145
|
const STITCH_API_KEY_MISSING_WARNING =
|
|
@@ -313,6 +316,77 @@ function normalizePostmanWorkspaceId(value) {
|
|
|
313
316
|
return normalized;
|
|
314
317
|
}
|
|
315
318
|
|
|
319
|
+
function normalizeCredentialProfileName(value) {
|
|
320
|
+
if (value === undefined || value === null) return null;
|
|
321
|
+
const normalized = String(value).trim();
|
|
322
|
+
return normalized || null;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function credentialProfileNameKey(value) {
|
|
326
|
+
const normalized = normalizeCredentialProfileName(value);
|
|
327
|
+
return normalized ? normalized.toLowerCase() : null;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function normalizeCredentialService(value, { allowAll = false } = {}) {
|
|
331
|
+
if (!value) return allowAll ? "all" : "postman";
|
|
332
|
+
const normalized = String(value).trim().toLowerCase();
|
|
333
|
+
if (allowAll && normalized === "all") return "all";
|
|
334
|
+
if (!CREDENTIAL_SERVICES.has(normalized)) {
|
|
335
|
+
throw new Error(
|
|
336
|
+
`Unknown credential service '${value}'. Use ${allowAll ? "postman|stitch|all" : "postman|stitch"}.`
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
return normalized;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function defaultEnvVarForCredentialService(service) {
|
|
343
|
+
return service === "stitch" ? STITCH_API_KEY_ENV_VAR : POSTMAN_API_KEY_ENV_VAR;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function defaultMcpUrlForCredentialService(service) {
|
|
347
|
+
return service === "stitch" ? STITCH_MCP_URL : POSTMAN_MCP_URL;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function isCredentialServiceEnvVar(value) {
|
|
351
|
+
return typeof value === "string" && /^[A-Za-z_][A-Za-z0-9_]*$/.test(value.trim());
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function normalizeCredentialProfileRecord(service, rawProfile, fallbackName = DEFAULT_CREDENTIAL_PROFILE_NAME) {
|
|
355
|
+
const defaultEnvVar = defaultEnvVarForCredentialService(service);
|
|
356
|
+
const source = rawProfile && typeof rawProfile === "object" && !Array.isArray(rawProfile) ? rawProfile : {};
|
|
357
|
+
const name = normalizeCredentialProfileName(source.name) || fallbackName;
|
|
358
|
+
const apiKey = normalizePostmanApiKey(source.apiKey);
|
|
359
|
+
const envVarCandidate = normalizePostmanApiKey(source.apiKeyEnvVar) || defaultEnvVar;
|
|
360
|
+
const apiKeyEnvVar = isCredentialServiceEnvVar(envVarCandidate) ? envVarCandidate : defaultEnvVar;
|
|
361
|
+
const profile = {
|
|
362
|
+
name,
|
|
363
|
+
apiKey,
|
|
364
|
+
apiKeyEnvVar
|
|
365
|
+
};
|
|
366
|
+
if (service === "postman") {
|
|
367
|
+
profile.workspaceId = normalizePostmanWorkspaceId(source.workspaceId ?? source.defaultWorkspaceId);
|
|
368
|
+
}
|
|
369
|
+
return profile;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function dedupeCredentialProfiles(profiles) {
|
|
373
|
+
const seen = new Set();
|
|
374
|
+
const deduped = [];
|
|
375
|
+
for (const profile of profiles) {
|
|
376
|
+
const key = credentialProfileNameKey(profile?.name);
|
|
377
|
+
if (!key || seen.has(key)) continue;
|
|
378
|
+
seen.add(key);
|
|
379
|
+
deduped.push(profile);
|
|
380
|
+
}
|
|
381
|
+
return deduped;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function storedCredentialSource(profile) {
|
|
385
|
+
if (normalizePostmanApiKey(profile?.apiKey)) return "inline";
|
|
386
|
+
if (normalizePostmanApiKey(profile?.apiKeyEnvVar)) return "env";
|
|
387
|
+
return "unset";
|
|
388
|
+
}
|
|
389
|
+
|
|
316
390
|
function defaultState() {
|
|
317
391
|
return {
|
|
318
392
|
schemaVersion: 1,
|
|
@@ -1368,6 +1442,79 @@ async function readBundleManifest(bundleId) {
|
|
|
1368
1442
|
return parsed;
|
|
1369
1443
|
}
|
|
1370
1444
|
|
|
1445
|
+
function extractSkillIdFromIndexPath(indexPathValue) {
|
|
1446
|
+
const raw = String(indexPathValue || "").trim();
|
|
1447
|
+
if (!raw) return null;
|
|
1448
|
+
const normalized = raw.replace(/\\/g, "/");
|
|
1449
|
+
const marker = "/skills/";
|
|
1450
|
+
const markerIndex = normalized.indexOf(marker);
|
|
1451
|
+
if (markerIndex === -1) return null;
|
|
1452
|
+
const remainder = normalized.slice(markerIndex + marker.length);
|
|
1453
|
+
const [skillId] = remainder.split("/");
|
|
1454
|
+
const normalizedSkillId = String(skillId || "").trim();
|
|
1455
|
+
return normalizedSkillId || null;
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
async function resolveTopLevelSkillIdsFromIndex() {
|
|
1459
|
+
const skillsRoot = path.join(agentAssetsRoot(), "skills");
|
|
1460
|
+
const indexPath = path.join(skillsRoot, "skills_index.json");
|
|
1461
|
+
if (!(await pathExists(indexPath))) return [];
|
|
1462
|
+
|
|
1463
|
+
let parsed;
|
|
1464
|
+
try {
|
|
1465
|
+
parsed = JSON.parse(await readFile(indexPath, "utf8"));
|
|
1466
|
+
} catch {
|
|
1467
|
+
parsed = [];
|
|
1468
|
+
}
|
|
1469
|
+
const entries = Array.isArray(parsed) ? parsed : [];
|
|
1470
|
+
|
|
1471
|
+
const candidates = [];
|
|
1472
|
+
for (const entry of entries) {
|
|
1473
|
+
if (!entry || typeof entry !== "object") continue;
|
|
1474
|
+
const byName = normalizeCredentialProfileName(entry.name);
|
|
1475
|
+
if (byName) candidates.push(byName);
|
|
1476
|
+
const byPath = extractSkillIdFromIndexPath(entry.path);
|
|
1477
|
+
if (byPath) candidates.push(byPath);
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
const topLevelIds = [];
|
|
1481
|
+
const seen = new Set();
|
|
1482
|
+
for (const rawCandidate of candidates) {
|
|
1483
|
+
const skillId = String(rawCandidate || "").trim();
|
|
1484
|
+
if (!skillId || skillId.startsWith(".")) continue;
|
|
1485
|
+
if (skillId === ".system" || skillId.startsWith(".system/")) continue;
|
|
1486
|
+
const key = skillId.toLowerCase();
|
|
1487
|
+
if (seen.has(key)) continue;
|
|
1488
|
+
|
|
1489
|
+
const skillDir = path.join(skillsRoot, skillId);
|
|
1490
|
+
const skillFile = path.join(skillDir, "SKILL.md");
|
|
1491
|
+
if (!(await pathExists(skillDir)) || !(await pathExists(skillFile))) continue;
|
|
1492
|
+
|
|
1493
|
+
seen.add(key);
|
|
1494
|
+
topLevelIds.push(skillId);
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
topLevelIds.sort((a, b) => a.localeCompare(b));
|
|
1498
|
+
return topLevelIds;
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
async function resolveInstallSkillIds({ platformSpec, extraSkillIds = [] }) {
|
|
1502
|
+
const indexedSkillIds = await resolveTopLevelSkillIdsFromIndex();
|
|
1503
|
+
const fallbackManifestSkillIds = Array.isArray(platformSpec.skills) ? platformSpec.skills : [];
|
|
1504
|
+
|
|
1505
|
+
let selected = indexedSkillIds.length > 0 ? indexedSkillIds : fallbackManifestSkillIds;
|
|
1506
|
+
if (Array.isArray(platformSpec.skillAllowList) && platformSpec.skillAllowList.length > 0) {
|
|
1507
|
+
const allow = new Set(platformSpec.skillAllowList.map((item) => String(item).toLowerCase()));
|
|
1508
|
+
selected = selected.filter((skillId) => allow.has(String(skillId).toLowerCase()));
|
|
1509
|
+
}
|
|
1510
|
+
if (Array.isArray(platformSpec.skillBlockList) && platformSpec.skillBlockList.length > 0) {
|
|
1511
|
+
const blocked = new Set(platformSpec.skillBlockList.map((item) => String(item).toLowerCase()));
|
|
1512
|
+
selected = selected.filter((skillId) => !blocked.has(String(skillId).toLowerCase()));
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
return unique([...selected, ...extraSkillIds.filter(Boolean)]);
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1371
1518
|
async function chooseBundle(bundleOption) {
|
|
1372
1519
|
const bundleIds = await listBundleIds();
|
|
1373
1520
|
if (bundleIds.length === 0) {
|
|
@@ -2275,12 +2422,12 @@ async function writeGeneratedArtifact({ destination, content, dryRun = false })
|
|
|
2275
2422
|
return { action: exists ? "replaced" : "installed", path: destination };
|
|
2276
2423
|
}
|
|
2277
2424
|
|
|
2278
|
-
function
|
|
2425
|
+
function resolveLegacyPostmanConfigPath({ scope, cwd = process.cwd() }) {
|
|
2279
2426
|
if (scope === "global") {
|
|
2280
|
-
return path.join(os.homedir(), ".cbx",
|
|
2427
|
+
return path.join(os.homedir(), ".cbx", LEGACY_POSTMAN_CONFIG_FILENAME);
|
|
2281
2428
|
}
|
|
2282
2429
|
const workspaceRoot = findWorkspaceRoot(cwd);
|
|
2283
|
-
return path.join(workspaceRoot,
|
|
2430
|
+
return path.join(workspaceRoot, LEGACY_POSTMAN_CONFIG_FILENAME);
|
|
2284
2431
|
}
|
|
2285
2432
|
|
|
2286
2433
|
function resolveCbxConfigPath({ scope, cwd = process.cwd() }) {
|
|
@@ -2291,6 +2438,18 @@ function resolveCbxConfigPath({ scope, cwd = process.cwd() }) {
|
|
|
2291
2438
|
return path.join(workspaceRoot, CBX_CONFIG_FILENAME);
|
|
2292
2439
|
}
|
|
2293
2440
|
|
|
2441
|
+
async function assertNoLegacyOnlyPostmanConfig({ scope, cwd = process.cwd() }) {
|
|
2442
|
+
const configPath = resolveCbxConfigPath({ scope, cwd });
|
|
2443
|
+
if (await pathExists(configPath)) return;
|
|
2444
|
+
|
|
2445
|
+
const legacyPath = resolveLegacyPostmanConfigPath({ scope, cwd });
|
|
2446
|
+
if (!(await pathExists(legacyPath))) return;
|
|
2447
|
+
|
|
2448
|
+
throw new Error(
|
|
2449
|
+
`Legacy Postman config detected at ${legacyPath}. Create ${configPath} first (for example: cbx workflows config --scope ${scope} --clear-workspace-id).`
|
|
2450
|
+
);
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2294
2453
|
function resolveMcpRootPath({ scope, cwd = process.cwd() }) {
|
|
2295
2454
|
if (scope === "global") {
|
|
2296
2455
|
return path.join(os.homedir(), ".cbx", "mcp");
|
|
@@ -2414,20 +2573,158 @@ function normalizePostmanApiKey(value) {
|
|
|
2414
2573
|
return normalized || null;
|
|
2415
2574
|
}
|
|
2416
2575
|
|
|
2576
|
+
function parseStoredCredentialServiceConfig({
|
|
2577
|
+
service,
|
|
2578
|
+
rawService
|
|
2579
|
+
}) {
|
|
2580
|
+
if (!rawService || typeof rawService !== "object" || Array.isArray(rawService)) {
|
|
2581
|
+
return null;
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
const defaultEnvVar = defaultEnvVarForCredentialService(service);
|
|
2585
|
+
const defaultMcpUrl = defaultMcpUrlForCredentialService(service);
|
|
2586
|
+
const mcpUrl = String(rawService.mcpUrl || defaultMcpUrl).trim() || defaultMcpUrl;
|
|
2587
|
+
const profiles = [];
|
|
2588
|
+
const rawProfiles = Array.isArray(rawService.profiles) ? rawService.profiles : [];
|
|
2589
|
+
|
|
2590
|
+
for (const rawProfile of rawProfiles) {
|
|
2591
|
+
const name = normalizeCredentialProfileName(rawProfile?.name);
|
|
2592
|
+
if (!name) continue;
|
|
2593
|
+
profiles.push(normalizeCredentialProfileRecord(service, rawProfile, name));
|
|
2594
|
+
}
|
|
2595
|
+
|
|
2596
|
+
if (profiles.length === 0) {
|
|
2597
|
+
profiles.push(
|
|
2598
|
+
normalizeCredentialProfileRecord(
|
|
2599
|
+
service,
|
|
2600
|
+
{
|
|
2601
|
+
name: DEFAULT_CREDENTIAL_PROFILE_NAME,
|
|
2602
|
+
apiKey: rawService.apiKey,
|
|
2603
|
+
apiKeyEnvVar: rawService.apiKeyEnvVar,
|
|
2604
|
+
workspaceId: service === "postman" ? rawService.defaultWorkspaceId : null
|
|
2605
|
+
},
|
|
2606
|
+
DEFAULT_CREDENTIAL_PROFILE_NAME
|
|
2607
|
+
)
|
|
2608
|
+
);
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2611
|
+
const dedupedProfiles = dedupeCredentialProfiles(profiles);
|
|
2612
|
+
const activeProfileNameCandidate =
|
|
2613
|
+
normalizeCredentialProfileName(rawService.activeProfileName) || dedupedProfiles[0].name;
|
|
2614
|
+
const activeProfile =
|
|
2615
|
+
dedupedProfiles.find(
|
|
2616
|
+
(profile) => credentialProfileNameKey(profile.name) === credentialProfileNameKey(activeProfileNameCandidate)
|
|
2617
|
+
) || dedupedProfiles[0];
|
|
2618
|
+
const activeProfileName = activeProfile.name;
|
|
2619
|
+
|
|
2620
|
+
if (service === "postman" && activeProfile.workspaceId === null) {
|
|
2621
|
+
const legacyWorkspaceId = normalizePostmanWorkspaceId(rawService.defaultWorkspaceId);
|
|
2622
|
+
if (legacyWorkspaceId) {
|
|
2623
|
+
activeProfile.workspaceId = legacyWorkspaceId;
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
|
|
2627
|
+
return {
|
|
2628
|
+
mcpUrl,
|
|
2629
|
+
profiles: dedupedProfiles,
|
|
2630
|
+
activeProfileName,
|
|
2631
|
+
activeProfile,
|
|
2632
|
+
apiKey: normalizePostmanApiKey(activeProfile.apiKey),
|
|
2633
|
+
apiKeyEnvVar: String(activeProfile.apiKeyEnvVar || defaultEnvVar).trim() || defaultEnvVar,
|
|
2634
|
+
defaultWorkspaceId: service === "postman" ? normalizePostmanWorkspaceId(activeProfile.workspaceId) : null
|
|
2635
|
+
};
|
|
2636
|
+
}
|
|
2637
|
+
|
|
2417
2638
|
function parseStoredPostmanConfig(raw) {
|
|
2418
2639
|
if (!raw || typeof raw !== "object") return null;
|
|
2419
2640
|
const source = raw.postman && typeof raw.postman === "object" ? raw.postman : raw;
|
|
2641
|
+
return parseStoredCredentialServiceConfig({ service: "postman", rawService: source });
|
|
2642
|
+
}
|
|
2420
2643
|
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
const
|
|
2424
|
-
|
|
2644
|
+
function parseStoredStitchConfig(raw) {
|
|
2645
|
+
if (!raw || typeof raw !== "object") return null;
|
|
2646
|
+
const source = raw.stitch && typeof raw.stitch === "object" ? raw.stitch : raw;
|
|
2647
|
+
if (!source || typeof source !== "object" || Array.isArray(source)) return null;
|
|
2648
|
+
const hasStitchShape =
|
|
2649
|
+
source.profiles !== undefined ||
|
|
2650
|
+
source.apiKey !== undefined ||
|
|
2651
|
+
source.apiKeyEnvVar !== undefined ||
|
|
2652
|
+
source.mcpUrl !== undefined ||
|
|
2653
|
+
source.activeProfileName !== undefined;
|
|
2654
|
+
if (!hasStitchShape) return null;
|
|
2655
|
+
return parseStoredCredentialServiceConfig({ service: "stitch", rawService: source, allowMissing: true });
|
|
2656
|
+
}
|
|
2657
|
+
|
|
2658
|
+
function upsertNormalizedPostmanConfig(target, postmanState) {
|
|
2659
|
+
const next = target && typeof target === "object" && !Array.isArray(target) ? target : {};
|
|
2660
|
+
const existingPostman =
|
|
2661
|
+
next.postman && typeof next.postman === "object" && !Array.isArray(next.postman) ? next.postman : {};
|
|
2662
|
+
const serviceConfig = postmanState || parseStoredPostmanConfig({ postman: existingPostman });
|
|
2663
|
+
if (!serviceConfig) return next;
|
|
2664
|
+
|
|
2665
|
+
next.postman = {
|
|
2666
|
+
...existingPostman,
|
|
2667
|
+
profiles: serviceConfig.profiles,
|
|
2668
|
+
activeProfileName: serviceConfig.activeProfileName,
|
|
2669
|
+
apiKey: serviceConfig.apiKey,
|
|
2670
|
+
apiKeyEnvVar: serviceConfig.apiKeyEnvVar,
|
|
2671
|
+
apiKeySource: storedCredentialSource(serviceConfig.activeProfile),
|
|
2672
|
+
defaultWorkspaceId: serviceConfig.defaultWorkspaceId,
|
|
2673
|
+
mcpUrl: serviceConfig.mcpUrl
|
|
2674
|
+
};
|
|
2675
|
+
return next;
|
|
2676
|
+
}
|
|
2677
|
+
|
|
2678
|
+
function upsertNormalizedStitchConfig(target, stitchState) {
|
|
2679
|
+
const next = target && typeof target === "object" && !Array.isArray(target) ? target : {};
|
|
2680
|
+
const existingStitch =
|
|
2681
|
+
next.stitch && typeof next.stitch === "object" && !Array.isArray(next.stitch) ? next.stitch : {};
|
|
2682
|
+
const serviceConfig =
|
|
2683
|
+
stitchState ||
|
|
2684
|
+
parseStoredStitchConfig({ stitch: existingStitch }) ||
|
|
2685
|
+
parseStoredCredentialServiceConfig({ service: "stitch", rawService: existingStitch, allowMissing: true });
|
|
2686
|
+
if (!serviceConfig) return next;
|
|
2687
|
+
|
|
2688
|
+
next.stitch = {
|
|
2689
|
+
...existingStitch,
|
|
2690
|
+
server: existingStitch.server || STITCH_MCP_SERVER_ID,
|
|
2691
|
+
profiles: serviceConfig.profiles,
|
|
2692
|
+
activeProfileName: serviceConfig.activeProfileName,
|
|
2693
|
+
apiKey: serviceConfig.apiKey,
|
|
2694
|
+
apiKeyEnvVar: serviceConfig.apiKeyEnvVar,
|
|
2695
|
+
apiKeySource: storedCredentialSource(serviceConfig.activeProfile),
|
|
2696
|
+
mcpUrl: serviceConfig.mcpUrl
|
|
2697
|
+
};
|
|
2698
|
+
return next;
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2701
|
+
function resolveCredentialEffectiveStatus({
|
|
2702
|
+
service,
|
|
2703
|
+
serviceConfig,
|
|
2704
|
+
env = process.env
|
|
2705
|
+
}) {
|
|
2706
|
+
if (!serviceConfig) return null;
|
|
2707
|
+
const defaultEnvVar = defaultEnvVarForCredentialService(service);
|
|
2708
|
+
const activeProfile = serviceConfig.activeProfile || serviceConfig.profiles?.[0] || null;
|
|
2709
|
+
const apiKey = normalizePostmanApiKey(activeProfile?.apiKey);
|
|
2710
|
+
const apiKeyEnvVar = String(activeProfile?.apiKeyEnvVar || defaultEnvVar).trim() || defaultEnvVar;
|
|
2711
|
+
const envApiKey = normalizePostmanApiKey(env?.[apiKeyEnvVar]);
|
|
2712
|
+
const storedSource = storedCredentialSource(activeProfile);
|
|
2713
|
+
const effectiveSource =
|
|
2714
|
+
service === "stitch"
|
|
2715
|
+
? getStitchApiKeySource({ apiKey, envApiKey })
|
|
2716
|
+
: getPostmanApiKeySource({ apiKey, envApiKey });
|
|
2425
2717
|
|
|
2426
2718
|
return {
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2719
|
+
service,
|
|
2720
|
+
activeProfileName: serviceConfig.activeProfileName,
|
|
2721
|
+
activeProfile,
|
|
2722
|
+
profileCount: Array.isArray(serviceConfig.profiles) ? serviceConfig.profiles.length : 0,
|
|
2723
|
+
storedSource,
|
|
2724
|
+
effectiveSource,
|
|
2725
|
+
effectiveEnvVar: apiKeyEnvVar,
|
|
2726
|
+
envVarPresent: Boolean(envApiKey),
|
|
2727
|
+
workspaceId: service === "postman" ? normalizePostmanWorkspaceId(activeProfile?.workspaceId) : null
|
|
2431
2728
|
};
|
|
2432
2729
|
}
|
|
2433
2730
|
|
|
@@ -2482,22 +2779,6 @@ async function fetchPostmanWorkspaces({
|
|
|
2482
2779
|
}
|
|
2483
2780
|
}
|
|
2484
2781
|
|
|
2485
|
-
function parseStoredStitchConfig(raw) {
|
|
2486
|
-
if (!raw || typeof raw !== "object") return null;
|
|
2487
|
-
const source = raw.stitch && typeof raw.stitch === "object" ? raw.stitch : null;
|
|
2488
|
-
if (!source) return null;
|
|
2489
|
-
|
|
2490
|
-
const apiKey = normalizePostmanApiKey(source.apiKey);
|
|
2491
|
-
const apiKeyEnvVar = String(source.apiKeyEnvVar || STITCH_API_KEY_ENV_VAR).trim() || STITCH_API_KEY_ENV_VAR;
|
|
2492
|
-
const mcpUrl = String(source.mcpUrl || STITCH_MCP_URL).trim() || STITCH_MCP_URL;
|
|
2493
|
-
|
|
2494
|
-
return {
|
|
2495
|
-
apiKey,
|
|
2496
|
-
apiKeyEnvVar,
|
|
2497
|
-
mcpUrl
|
|
2498
|
-
};
|
|
2499
|
-
}
|
|
2500
|
-
|
|
2501
2782
|
function parseJsonLenient(raw) {
|
|
2502
2783
|
try {
|
|
2503
2784
|
return {
|
|
@@ -2947,7 +3228,12 @@ async function resolvePostmanInstallSelection({
|
|
|
2947
3228
|
}
|
|
2948
3229
|
|
|
2949
3230
|
const cbxConfigPath = resolveCbxConfigPath({ scope: mcpScope, cwd });
|
|
2950
|
-
const
|
|
3231
|
+
const defaultProfile = normalizeCredentialProfileRecord("postman", {
|
|
3232
|
+
name: DEFAULT_CREDENTIAL_PROFILE_NAME,
|
|
3233
|
+
apiKey: apiKey || null,
|
|
3234
|
+
apiKeyEnvVar: POSTMAN_API_KEY_ENV_VAR,
|
|
3235
|
+
workspaceId: defaultWorkspaceId ?? null
|
|
3236
|
+
});
|
|
2951
3237
|
const cbxConfig = {
|
|
2952
3238
|
schemaVersion: 1,
|
|
2953
3239
|
generatedBy: "cbx workflows install --postman",
|
|
@@ -2958,19 +3244,28 @@ async function resolvePostmanInstallSelection({
|
|
|
2958
3244
|
platform
|
|
2959
3245
|
},
|
|
2960
3246
|
postman: {
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
3247
|
+
profiles: [defaultProfile],
|
|
3248
|
+
activeProfileName: defaultProfile.name,
|
|
3249
|
+
apiKey: defaultProfile.apiKey,
|
|
3250
|
+
apiKeyEnvVar: defaultProfile.apiKeyEnvVar,
|
|
3251
|
+
apiKeySource: storedCredentialSource(defaultProfile),
|
|
3252
|
+
defaultWorkspaceId: defaultProfile.workspaceId,
|
|
2965
3253
|
mcpUrl: POSTMAN_MCP_URL
|
|
2966
3254
|
}
|
|
2967
3255
|
};
|
|
2968
3256
|
if (stitchEnabled) {
|
|
3257
|
+
const defaultStitchProfile = normalizeCredentialProfileRecord("stitch", {
|
|
3258
|
+
name: DEFAULT_CREDENTIAL_PROFILE_NAME,
|
|
3259
|
+
apiKey: stitchApiKey || null,
|
|
3260
|
+
apiKeyEnvVar: STITCH_API_KEY_ENV_VAR
|
|
3261
|
+
});
|
|
2969
3262
|
cbxConfig.stitch = {
|
|
2970
3263
|
server: STITCH_MCP_SERVER_ID,
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
3264
|
+
profiles: [defaultStitchProfile],
|
|
3265
|
+
activeProfileName: defaultStitchProfile.name,
|
|
3266
|
+
apiKey: defaultStitchProfile.apiKey,
|
|
3267
|
+
apiKeyEnvVar: defaultStitchProfile.apiKeyEnvVar,
|
|
3268
|
+
apiKeySource: storedCredentialSource(defaultStitchProfile),
|
|
2974
3269
|
mcpUrl: STITCH_MCP_URL
|
|
2975
3270
|
};
|
|
2976
3271
|
}
|
|
@@ -2987,8 +3282,7 @@ async function resolvePostmanInstallSelection({
|
|
|
2987
3282
|
mcpScope,
|
|
2988
3283
|
warnings,
|
|
2989
3284
|
cbxConfig,
|
|
2990
|
-
cbxConfigPath
|
|
2991
|
-
legacySettingsPath
|
|
3285
|
+
cbxConfigPath
|
|
2992
3286
|
};
|
|
2993
3287
|
}
|
|
2994
3288
|
|
|
@@ -3006,6 +3300,7 @@ async function configurePostmanInstallArtifacts({
|
|
|
3006
3300
|
let warnings = postmanSelection.warnings.filter(
|
|
3007
3301
|
(warning) => warning !== POSTMAN_API_KEY_MISSING_WARNING && warning !== STITCH_API_KEY_MISSING_WARNING
|
|
3008
3302
|
);
|
|
3303
|
+
await assertNoLegacyOnlyPostmanConfig({ scope: postmanSelection.mcpScope, cwd });
|
|
3009
3304
|
const cbxConfigContent = `${JSON.stringify(postmanSelection.cbxConfig, null, 2)}\n`;
|
|
3010
3305
|
const cbxConfigResult = await writeTextFile({
|
|
3011
3306
|
targetPath: postmanSelection.cbxConfigPath,
|
|
@@ -3014,28 +3309,27 @@ async function configurePostmanInstallArtifacts({
|
|
|
3014
3309
|
dryRun
|
|
3015
3310
|
});
|
|
3016
3311
|
|
|
3017
|
-
|
|
3018
|
-
let
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
let
|
|
3022
|
-
let effectiveMcpUrl = postmanSelection.cbxConfig?.postman?.mcpUrl || POSTMAN_MCP_URL;
|
|
3312
|
+
const installPostmanConfig = parseStoredPostmanConfig(postmanSelection.cbxConfig);
|
|
3313
|
+
let effectiveApiKey = normalizePostmanApiKey(installPostmanConfig?.apiKey);
|
|
3314
|
+
let effectiveApiKeyEnvVar = String(installPostmanConfig?.apiKeyEnvVar || POSTMAN_API_KEY_ENV_VAR).trim();
|
|
3315
|
+
let effectiveDefaultWorkspaceId = installPostmanConfig?.defaultWorkspaceId ?? postmanSelection.defaultWorkspaceId ?? null;
|
|
3316
|
+
let effectiveMcpUrl = installPostmanConfig?.mcpUrl || POSTMAN_MCP_URL;
|
|
3023
3317
|
const shouldInstallStitch = Boolean(postmanSelection.stitchEnabled);
|
|
3318
|
+
const installStitchConfig = shouldInstallStitch ? parseStoredStitchConfig(postmanSelection.cbxConfig) : null;
|
|
3024
3319
|
let effectiveStitchApiKey = shouldInstallStitch
|
|
3025
|
-
? normalizePostmanApiKey(
|
|
3320
|
+
? normalizePostmanApiKey(installStitchConfig?.apiKey)
|
|
3026
3321
|
: null;
|
|
3322
|
+
let effectiveStitchApiKeyEnvVar = shouldInstallStitch
|
|
3323
|
+
? String(installStitchConfig?.apiKeyEnvVar || STITCH_API_KEY_ENV_VAR).trim() || STITCH_API_KEY_ENV_VAR
|
|
3324
|
+
: STITCH_API_KEY_ENV_VAR;
|
|
3027
3325
|
let effectiveStitchMcpUrl = shouldInstallStitch
|
|
3028
|
-
?
|
|
3326
|
+
? installStitchConfig?.mcpUrl || STITCH_MCP_URL
|
|
3029
3327
|
: STITCH_MCP_URL;
|
|
3030
3328
|
|
|
3031
3329
|
if (cbxConfigResult.action === "skipped" || cbxConfigResult.action === "would-skip") {
|
|
3032
3330
|
const existingCbxConfig = await readJsonFileIfExists(postmanSelection.cbxConfigPath);
|
|
3033
|
-
const
|
|
3034
|
-
const
|
|
3035
|
-
parseStoredPostmanConfig(existingCbxConfig.value) || parseStoredPostmanConfig(existingLegacySettings.value);
|
|
3036
|
-
const storedStitchConfig =
|
|
3037
|
-
shouldInstallStitch &&
|
|
3038
|
-
(parseStoredStitchConfig(existingCbxConfig.value) || parseStoredStitchConfig(existingLegacySettings.value));
|
|
3331
|
+
const storedPostmanConfig = parseStoredPostmanConfig(existingCbxConfig.value);
|
|
3332
|
+
const storedStitchConfig = shouldInstallStitch ? parseStoredStitchConfig(existingCbxConfig.value) : null;
|
|
3039
3333
|
|
|
3040
3334
|
if (storedPostmanConfig) {
|
|
3041
3335
|
effectiveApiKey = storedPostmanConfig.apiKey;
|
|
@@ -3044,12 +3338,14 @@ async function configurePostmanInstallArtifacts({
|
|
|
3044
3338
|
effectiveMcpUrl = storedPostmanConfig.mcpUrl || POSTMAN_MCP_URL;
|
|
3045
3339
|
} else {
|
|
3046
3340
|
warnings.push(
|
|
3047
|
-
`Existing ${CBX_CONFIG_FILENAME}
|
|
3341
|
+
`Existing ${CBX_CONFIG_FILENAME} could not be parsed. Using install-time Postman values for MCP config.`
|
|
3048
3342
|
);
|
|
3049
3343
|
}
|
|
3050
3344
|
|
|
3051
3345
|
if (storedStitchConfig) {
|
|
3052
3346
|
effectiveStitchApiKey = storedStitchConfig.apiKey;
|
|
3347
|
+
effectiveStitchApiKeyEnvVar =
|
|
3348
|
+
String(storedStitchConfig.apiKeyEnvVar || STITCH_API_KEY_ENV_VAR).trim() || STITCH_API_KEY_ENV_VAR;
|
|
3053
3349
|
effectiveStitchMcpUrl = storedStitchConfig.mcpUrl || STITCH_MCP_URL;
|
|
3054
3350
|
}
|
|
3055
3351
|
|
|
@@ -3069,7 +3365,7 @@ async function configurePostmanInstallArtifacts({
|
|
|
3069
3365
|
}
|
|
3070
3366
|
}
|
|
3071
3367
|
|
|
3072
|
-
const envApiKey = normalizePostmanApiKey(process.env[POSTMAN_API_KEY_ENV_VAR]);
|
|
3368
|
+
const envApiKey = normalizePostmanApiKey(process.env[effectiveApiKeyEnvVar || POSTMAN_API_KEY_ENV_VAR]);
|
|
3073
3369
|
const effectiveApiKeySource = getPostmanApiKeySource({
|
|
3074
3370
|
apiKey: effectiveApiKey,
|
|
3075
3371
|
envApiKey
|
|
@@ -3077,7 +3373,7 @@ async function configurePostmanInstallArtifacts({
|
|
|
3077
3373
|
if (effectiveApiKeySource === "unset") {
|
|
3078
3374
|
warnings.push(POSTMAN_API_KEY_MISSING_WARNING);
|
|
3079
3375
|
}
|
|
3080
|
-
const envStitchApiKey = normalizePostmanApiKey(process.env[
|
|
3376
|
+
const envStitchApiKey = normalizePostmanApiKey(process.env[effectiveStitchApiKeyEnvVar]);
|
|
3081
3377
|
const effectiveStitchApiKeySource = shouldInstallStitch
|
|
3082
3378
|
? getStitchApiKeySource({
|
|
3083
3379
|
apiKey: effectiveStitchApiKey,
|
|
@@ -3340,6 +3636,62 @@ async function validateCopilotAgentsSchema(agentsDir) {
|
|
|
3340
3636
|
return findings;
|
|
3341
3637
|
}
|
|
3342
3638
|
|
|
3639
|
+
async function findNestedSkillDirs(rootDir, depth = 0, nested = []) {
|
|
3640
|
+
if (!(await pathExists(rootDir))) return nested;
|
|
3641
|
+
const entries = await readdir(rootDir, { withFileTypes: true });
|
|
3642
|
+
for (const entry of entries) {
|
|
3643
|
+
if (!entry.isDirectory()) continue;
|
|
3644
|
+
const childDir = path.join(rootDir, entry.name);
|
|
3645
|
+
const skillFile = path.join(childDir, "SKILL.md");
|
|
3646
|
+
if (depth >= 1 && (await pathExists(skillFile))) {
|
|
3647
|
+
nested.push(childDir);
|
|
3648
|
+
}
|
|
3649
|
+
await findNestedSkillDirs(childDir, depth + 1, nested);
|
|
3650
|
+
}
|
|
3651
|
+
return nested;
|
|
3652
|
+
}
|
|
3653
|
+
|
|
3654
|
+
async function cleanupNestedDuplicateSkills({
|
|
3655
|
+
skillsRootDir,
|
|
3656
|
+
installedSkillDirs,
|
|
3657
|
+
dryRun = false
|
|
3658
|
+
}) {
|
|
3659
|
+
if (!skillsRootDir || !Array.isArray(installedSkillDirs) || installedSkillDirs.length === 0) return [];
|
|
3660
|
+
|
|
3661
|
+
const topLevelSkillIds = new Set();
|
|
3662
|
+
for (const skillDir of installedSkillDirs) {
|
|
3663
|
+
const skillId = path.basename(skillDir);
|
|
3664
|
+
if (!skillId || skillId.startsWith(".")) continue;
|
|
3665
|
+
const topLevelSkillFile = path.join(skillsRootDir, skillId, "SKILL.md");
|
|
3666
|
+
if (await pathExists(topLevelSkillFile)) {
|
|
3667
|
+
topLevelSkillIds.add(skillId.toLowerCase());
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3670
|
+
|
|
3671
|
+
if (topLevelSkillIds.size === 0) return [];
|
|
3672
|
+
|
|
3673
|
+
const cleanup = [];
|
|
3674
|
+
for (const topLevelSkillDir of installedSkillDirs) {
|
|
3675
|
+
if (!(await pathExists(topLevelSkillDir))) continue;
|
|
3676
|
+
const nestedSkillDirs = await findNestedSkillDirs(topLevelSkillDir);
|
|
3677
|
+
for (const nestedDir of nestedSkillDirs) {
|
|
3678
|
+
const nestedSkillId = path.basename(nestedDir);
|
|
3679
|
+
if (!topLevelSkillIds.has(nestedSkillId.toLowerCase())) continue;
|
|
3680
|
+
if (!dryRun) {
|
|
3681
|
+
await rm(nestedDir, { recursive: true, force: true });
|
|
3682
|
+
}
|
|
3683
|
+
cleanup.push({
|
|
3684
|
+
nestedSkillId,
|
|
3685
|
+
path: nestedDir,
|
|
3686
|
+
ownerSkillId: path.basename(topLevelSkillDir),
|
|
3687
|
+
action: dryRun ? "would-remove" : "removed"
|
|
3688
|
+
});
|
|
3689
|
+
}
|
|
3690
|
+
}
|
|
3691
|
+
|
|
3692
|
+
return cleanup;
|
|
3693
|
+
}
|
|
3694
|
+
|
|
3343
3695
|
async function installBundleArtifacts({
|
|
3344
3696
|
bundleId,
|
|
3345
3697
|
manifest,
|
|
@@ -3408,8 +3760,10 @@ async function installBundleArtifacts({
|
|
|
3408
3760
|
else installed.push(destination);
|
|
3409
3761
|
}
|
|
3410
3762
|
|
|
3411
|
-
const
|
|
3412
|
-
|
|
3763
|
+
const skillIds = await resolveInstallSkillIds({
|
|
3764
|
+
platformSpec,
|
|
3765
|
+
extraSkillIds
|
|
3766
|
+
});
|
|
3413
3767
|
for (const skillId of skillIds) {
|
|
3414
3768
|
const source = path.join(agentAssetsRoot(), "skills", skillId);
|
|
3415
3769
|
const destination = path.join(profilePaths.skillsDir, skillId);
|
|
@@ -3458,6 +3812,12 @@ async function installBundleArtifacts({
|
|
|
3458
3812
|
installed.push(...terminalIntegration.installedPaths);
|
|
3459
3813
|
}
|
|
3460
3814
|
|
|
3815
|
+
const duplicateSkillCleanup = await cleanupNestedDuplicateSkills({
|
|
3816
|
+
skillsRootDir: profilePaths.skillsDir,
|
|
3817
|
+
installedSkillDirs: artifacts.skills,
|
|
3818
|
+
dryRun
|
|
3819
|
+
});
|
|
3820
|
+
|
|
3461
3821
|
const sanitizedSkills = await sanitizeInstalledSkillsForPlatform({
|
|
3462
3822
|
platform,
|
|
3463
3823
|
skillDirs: artifacts.skills,
|
|
@@ -3476,6 +3836,7 @@ async function installBundleArtifacts({
|
|
|
3476
3836
|
artifacts,
|
|
3477
3837
|
terminalIntegration,
|
|
3478
3838
|
generatedWrapperSkills,
|
|
3839
|
+
duplicateSkillCleanup,
|
|
3479
3840
|
sanitizedSkills,
|
|
3480
3841
|
sanitizedAgents
|
|
3481
3842
|
};
|
|
@@ -3608,7 +3969,8 @@ async function removeBundleArtifacts({
|
|
|
3608
3969
|
if (await safeRemove(destination, dryRun)) removed.push(destination);
|
|
3609
3970
|
}
|
|
3610
3971
|
|
|
3611
|
-
|
|
3972
|
+
const skillIds = await resolveInstallSkillIds({ platformSpec, extraSkillIds: [] });
|
|
3973
|
+
for (const skillId of skillIds) {
|
|
3612
3974
|
const destination = path.join(profilePaths.skillsDir, skillId);
|
|
3613
3975
|
if (await safeRemove(destination, dryRun)) removed.push(destination);
|
|
3614
3976
|
}
|
|
@@ -3676,6 +4038,7 @@ function printInstallSummary({
|
|
|
3676
4038
|
installed,
|
|
3677
4039
|
skipped,
|
|
3678
4040
|
generatedWrapperSkills = [],
|
|
4041
|
+
duplicateSkillCleanup = [],
|
|
3679
4042
|
sanitizedSkills = [],
|
|
3680
4043
|
sanitizedAgents = [],
|
|
3681
4044
|
terminalIntegration = null,
|
|
@@ -3725,6 +4088,18 @@ function printInstallSummary({
|
|
|
3725
4088
|
}
|
|
3726
4089
|
}
|
|
3727
4090
|
|
|
4091
|
+
if (duplicateSkillCleanup.length > 0) {
|
|
4092
|
+
console.log(`\nNested duplicate skill cleanup (${duplicateSkillCleanup.length}):`);
|
|
4093
|
+
for (const item of duplicateSkillCleanup.slice(0, 10)) {
|
|
4094
|
+
console.log(
|
|
4095
|
+
`- ${item.path}: ${item.action} duplicate skill '${item.nestedSkillId}' nested under '${item.ownerSkillId}'`
|
|
4096
|
+
);
|
|
4097
|
+
}
|
|
4098
|
+
if (duplicateSkillCleanup.length > 10) {
|
|
4099
|
+
console.log(`- ...and ${duplicateSkillCleanup.length - 10} more duplicate skill folder(s).`);
|
|
4100
|
+
}
|
|
4101
|
+
}
|
|
4102
|
+
|
|
3728
4103
|
if (!dryRun && sanitizedSkills.length > 0) {
|
|
3729
4104
|
console.log(`\nCopilot skill schema normalization (${sanitizedSkills.length}):`);
|
|
3730
4105
|
for (const item of sanitizedSkills.slice(0, 8)) {
|
|
@@ -4135,6 +4510,56 @@ function printSkillsDeprecation() {
|
|
|
4135
4510
|
console.log("[deprecation] 'cbx skills ...' is now an alias. Use 'cbx workflows ...'.");
|
|
4136
4511
|
}
|
|
4137
4512
|
|
|
4513
|
+
function registerConfigKeysSubcommands(configCommand, { aliasMode = false } = {}) {
|
|
4514
|
+
const wrap = (handler) =>
|
|
4515
|
+
aliasMode
|
|
4516
|
+
? async (options) => {
|
|
4517
|
+
printSkillsDeprecation();
|
|
4518
|
+
await handler(options);
|
|
4519
|
+
}
|
|
4520
|
+
: handler;
|
|
4521
|
+
|
|
4522
|
+
const keysCommand = configCommand
|
|
4523
|
+
.command("keys")
|
|
4524
|
+
.description("Manage named key profiles in cbx_config.json for Postman/Stitch");
|
|
4525
|
+
|
|
4526
|
+
keysCommand
|
|
4527
|
+
.command("list")
|
|
4528
|
+
.description("List key profiles")
|
|
4529
|
+
.option("--service <service>", "postman|stitch|all", "all")
|
|
4530
|
+
.option("--scope <scope>", "config scope: project|workspace|global|user", "global")
|
|
4531
|
+
.action(wrap(runWorkflowConfigKeysList));
|
|
4532
|
+
|
|
4533
|
+
keysCommand
|
|
4534
|
+
.command("add")
|
|
4535
|
+
.description("Add key profile")
|
|
4536
|
+
.option("--service <service>", "postman|stitch", "postman")
|
|
4537
|
+
.requiredOption("--name <profile>", "profile name")
|
|
4538
|
+
.requiredOption("--env-var <envVar>", "environment variable alias")
|
|
4539
|
+
.option("--workspace-id <id|null>", "optional: Postman profile workspace ID")
|
|
4540
|
+
.option("--scope <scope>", "config scope: project|workspace|global|user", "global")
|
|
4541
|
+
.option("--dry-run", "preview changes without writing files")
|
|
4542
|
+
.action(wrap(runWorkflowConfigKeysAdd));
|
|
4543
|
+
|
|
4544
|
+
keysCommand
|
|
4545
|
+
.command("use")
|
|
4546
|
+
.description("Set active key profile")
|
|
4547
|
+
.option("--service <service>", "postman|stitch", "postman")
|
|
4548
|
+
.requiredOption("--name <profile>", "profile name")
|
|
4549
|
+
.option("--scope <scope>", "config scope: project|workspace|global|user", "global")
|
|
4550
|
+
.option("--dry-run", "preview changes without writing files")
|
|
4551
|
+
.action(wrap(runWorkflowConfigKeysUse));
|
|
4552
|
+
|
|
4553
|
+
keysCommand
|
|
4554
|
+
.command("remove")
|
|
4555
|
+
.description("Remove key profile")
|
|
4556
|
+
.option("--service <service>", "postman|stitch", "postman")
|
|
4557
|
+
.requiredOption("--name <profile>", "profile name")
|
|
4558
|
+
.option("--scope <scope>", "config scope: project|workspace|global|user", "global")
|
|
4559
|
+
.option("--dry-run", "preview changes without writing files")
|
|
4560
|
+
.action(wrap(runWorkflowConfigKeysRemove));
|
|
4561
|
+
}
|
|
4562
|
+
|
|
4138
4563
|
async function resolveAntigravityTerminalVerifierSelection({ platform, options }) {
|
|
4139
4564
|
const verifierRaw = options.terminalVerifier;
|
|
4140
4565
|
const hasTerminalIntegrationFlag = Boolean(options.terminalIntegration);
|
|
@@ -4379,6 +4804,7 @@ async function runWorkflowInstall(options) {
|
|
|
4379
4804
|
installed: installResult.installed,
|
|
4380
4805
|
skipped: installResult.skipped,
|
|
4381
4806
|
generatedWrapperSkills: installResult.generatedWrapperSkills,
|
|
4807
|
+
duplicateSkillCleanup: installResult.duplicateSkillCleanup,
|
|
4382
4808
|
sanitizedSkills: installResult.sanitizedSkills,
|
|
4383
4809
|
sanitizedAgents: installResult.sanitizedAgents,
|
|
4384
4810
|
terminalIntegration: installResult.terminalIntegration,
|
|
@@ -4573,25 +4999,365 @@ async function runWorkflowDoctor(platformArg, options) {
|
|
|
4573
4999
|
}
|
|
4574
5000
|
}
|
|
4575
5001
|
|
|
5002
|
+
function cloneJsonObject(value) {
|
|
5003
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
5004
|
+
return JSON.parse(JSON.stringify(value));
|
|
5005
|
+
}
|
|
5006
|
+
|
|
5007
|
+
function resolveActionOptions(options) {
|
|
5008
|
+
if (!options) return {};
|
|
5009
|
+
if (typeof options.optsWithGlobals === "function") {
|
|
5010
|
+
const resolved = options.optsWithGlobals();
|
|
5011
|
+
return resolved && typeof resolved === "object" ? resolved : {};
|
|
5012
|
+
}
|
|
5013
|
+
if (typeof options.opts === "function") {
|
|
5014
|
+
const resolved = options.opts();
|
|
5015
|
+
return resolved && typeof resolved === "object" ? resolved : {};
|
|
5016
|
+
}
|
|
5017
|
+
return options;
|
|
5018
|
+
}
|
|
5019
|
+
|
|
5020
|
+
function readCliOptionFromArgv(optionName) {
|
|
5021
|
+
const argv = process.argv.slice(2);
|
|
5022
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
5023
|
+
const token = argv[i];
|
|
5024
|
+
if (token === optionName) {
|
|
5025
|
+
return argv[i + 1] ?? null;
|
|
5026
|
+
}
|
|
5027
|
+
if (token.startsWith(`${optionName}=`)) {
|
|
5028
|
+
return token.slice(optionName.length + 1);
|
|
5029
|
+
}
|
|
5030
|
+
}
|
|
5031
|
+
return null;
|
|
5032
|
+
}
|
|
5033
|
+
|
|
5034
|
+
function hasCliFlag(optionName) {
|
|
5035
|
+
const argv = process.argv.slice(2);
|
|
5036
|
+
return argv.includes(optionName);
|
|
5037
|
+
}
|
|
5038
|
+
|
|
5039
|
+
function prepareConfigDocument(existingValue, { scope, generatedBy }) {
|
|
5040
|
+
const next = cloneJsonObject(existingValue);
|
|
5041
|
+
if (!next.schemaVersion || typeof next.schemaVersion !== "number") next.schemaVersion = 1;
|
|
5042
|
+
next.generatedBy = generatedBy;
|
|
5043
|
+
next.generatedAt = new Date().toISOString();
|
|
5044
|
+
if (!next.mcp || typeof next.mcp !== "object" || Array.isArray(next.mcp)) next.mcp = {};
|
|
5045
|
+
next.mcp.scope = scope;
|
|
5046
|
+
if (!next.mcp.server) next.mcp.server = POSTMAN_SKILL_ID;
|
|
5047
|
+
return next;
|
|
5048
|
+
}
|
|
5049
|
+
|
|
5050
|
+
function ensureCredentialServiceState(configValue, service) {
|
|
5051
|
+
if (service === "postman") {
|
|
5052
|
+
return parseStoredPostmanConfig(configValue) || parseStoredCredentialServiceConfig({ service, rawService: {} });
|
|
5053
|
+
}
|
|
5054
|
+
return (
|
|
5055
|
+
parseStoredStitchConfig(configValue) || parseStoredCredentialServiceConfig({ service: "stitch", rawService: {} })
|
|
5056
|
+
);
|
|
5057
|
+
}
|
|
5058
|
+
|
|
5059
|
+
function upsertCredentialServiceConfig(configValue, service, serviceState) {
|
|
5060
|
+
if (service === "postman") {
|
|
5061
|
+
return upsertNormalizedPostmanConfig(configValue, serviceState);
|
|
5062
|
+
}
|
|
5063
|
+
return upsertNormalizedStitchConfig(configValue, serviceState);
|
|
5064
|
+
}
|
|
5065
|
+
|
|
5066
|
+
function buildConfigShowPayload(rawConfig) {
|
|
5067
|
+
const payload = cloneJsonObject(rawConfig);
|
|
5068
|
+
const postmanState = ensureCredentialServiceState(payload, "postman");
|
|
5069
|
+
upsertNormalizedPostmanConfig(payload, postmanState);
|
|
5070
|
+
|
|
5071
|
+
const stitchState = parseStoredStitchConfig(payload);
|
|
5072
|
+
if (stitchState) {
|
|
5073
|
+
upsertNormalizedStitchConfig(payload, stitchState);
|
|
5074
|
+
}
|
|
5075
|
+
|
|
5076
|
+
payload.status = {
|
|
5077
|
+
postman: resolveCredentialEffectiveStatus({
|
|
5078
|
+
service: "postman",
|
|
5079
|
+
serviceConfig: postmanState
|
|
5080
|
+
})
|
|
5081
|
+
};
|
|
5082
|
+
if (stitchState) {
|
|
5083
|
+
payload.status.stitch = resolveCredentialEffectiveStatus({
|
|
5084
|
+
service: "stitch",
|
|
5085
|
+
serviceConfig: stitchState
|
|
5086
|
+
});
|
|
5087
|
+
}
|
|
5088
|
+
|
|
5089
|
+
return payload;
|
|
5090
|
+
}
|
|
5091
|
+
|
|
5092
|
+
function normalizeProfileNameOrThrow(name) {
|
|
5093
|
+
const normalizedName = normalizeCredentialProfileName(name);
|
|
5094
|
+
if (!normalizedName) {
|
|
5095
|
+
throw new Error("Missing required profile name. Use --name <profile>.");
|
|
5096
|
+
}
|
|
5097
|
+
if (RESERVED_CREDENTIAL_PROFILE_NAMES.has(credentialProfileNameKey(normalizedName))) {
|
|
5098
|
+
throw new Error(`Profile name '${normalizedName}' is reserved.`);
|
|
5099
|
+
}
|
|
5100
|
+
return normalizedName;
|
|
5101
|
+
}
|
|
5102
|
+
|
|
5103
|
+
function findProfileByName(profiles, profileName) {
|
|
5104
|
+
const key = credentialProfileNameKey(profileName);
|
|
5105
|
+
return profiles.find((profile) => credentialProfileNameKey(profile.name) === key) || null;
|
|
5106
|
+
}
|
|
5107
|
+
|
|
5108
|
+
async function loadConfigForScope({ scope, cwd = process.cwd() }) {
|
|
5109
|
+
const configPath = resolveCbxConfigPath({ scope, cwd });
|
|
5110
|
+
await assertNoLegacyOnlyPostmanConfig({ scope, cwd });
|
|
5111
|
+
const existing = await readJsonFileIfExists(configPath);
|
|
5112
|
+
const existingValue =
|
|
5113
|
+
existing.value && typeof existing.value === "object" && !Array.isArray(existing.value) ? existing.value : null;
|
|
5114
|
+
if (existing.exists && !existingValue) {
|
|
5115
|
+
throw new Error(`Existing config at ${configPath} is not valid JSON object.`);
|
|
5116
|
+
}
|
|
5117
|
+
return { configPath, existing, existingValue };
|
|
5118
|
+
}
|
|
5119
|
+
|
|
5120
|
+
async function writeConfigFile({ configPath, nextConfig, existingExists, dryRun }) {
|
|
5121
|
+
const content = `${JSON.stringify(nextConfig, null, 2)}\n`;
|
|
5122
|
+
if (!dryRun) {
|
|
5123
|
+
await mkdir(path.dirname(configPath), { recursive: true });
|
|
5124
|
+
await writeFile(configPath, content, "utf8");
|
|
5125
|
+
}
|
|
5126
|
+
return dryRun ? (existingExists ? "would-update" : "would-create") : existingExists ? "updated" : "created";
|
|
5127
|
+
}
|
|
5128
|
+
|
|
5129
|
+
async function runWorkflowConfigKeysList(options) {
|
|
5130
|
+
try {
|
|
5131
|
+
const opts = resolveActionOptions(options);
|
|
5132
|
+
const cwd = process.cwd();
|
|
5133
|
+
const scopeArg = readCliOptionFromArgv("--scope");
|
|
5134
|
+
const scope = normalizeMcpScope(scopeArg ?? opts.scope, "global");
|
|
5135
|
+
const service = normalizeCredentialService(opts.service, { allowAll: true });
|
|
5136
|
+
const { configPath, existing, existingValue } = await loadConfigForScope({ scope, cwd });
|
|
5137
|
+
|
|
5138
|
+
console.log(`Config file: ${configPath}`);
|
|
5139
|
+
if (!existing.exists) {
|
|
5140
|
+
console.log("Status: missing");
|
|
5141
|
+
return;
|
|
5142
|
+
}
|
|
5143
|
+
|
|
5144
|
+
const services = service === "all" ? ["postman", "stitch"] : [service];
|
|
5145
|
+
for (const serviceId of services) {
|
|
5146
|
+
const serviceState = ensureCredentialServiceState(existingValue, serviceId);
|
|
5147
|
+
if (serviceId === "stitch" && !parseStoredStitchConfig(existingValue)) {
|
|
5148
|
+
console.log(`\n${serviceId}: not configured`);
|
|
5149
|
+
continue;
|
|
5150
|
+
}
|
|
5151
|
+
const effective = resolveCredentialEffectiveStatus({
|
|
5152
|
+
service: serviceId,
|
|
5153
|
+
serviceConfig: serviceState
|
|
5154
|
+
});
|
|
5155
|
+
console.log(`\n${serviceId}: active=${serviceState.activeProfileName} profiles=${serviceState.profiles.length}`);
|
|
5156
|
+
console.log(`- Stored source: ${effective.storedSource}`);
|
|
5157
|
+
console.log(`- Effective source: ${effective.effectiveSource}`);
|
|
5158
|
+
console.log(`- Effective env var: ${effective.effectiveEnvVar}`);
|
|
5159
|
+
for (const profile of serviceState.profiles) {
|
|
5160
|
+
const marker = credentialProfileNameKey(profile.name) === credentialProfileNameKey(serviceState.activeProfileName) ? "*" : " ";
|
|
5161
|
+
const workspaceSuffix =
|
|
5162
|
+
serviceId === "postman"
|
|
5163
|
+
? ` workspace=${normalizePostmanWorkspaceId(profile.workspaceId) ?? "null"}`
|
|
5164
|
+
: "";
|
|
5165
|
+
console.log(` ${marker} ${profile.name} env=${profile.apiKeyEnvVar}${workspaceSuffix}`);
|
|
5166
|
+
}
|
|
5167
|
+
}
|
|
5168
|
+
} catch (error) {
|
|
5169
|
+
if (error?.name === "ExitPromptError") {
|
|
5170
|
+
console.error("\nCancelled.");
|
|
5171
|
+
process.exit(130);
|
|
5172
|
+
}
|
|
5173
|
+
console.error(`\nError: ${error.message}`);
|
|
5174
|
+
process.exit(1);
|
|
5175
|
+
}
|
|
5176
|
+
}
|
|
5177
|
+
|
|
5178
|
+
async function runWorkflowConfigKeysAdd(options) {
|
|
5179
|
+
try {
|
|
5180
|
+
const opts = resolveActionOptions(options);
|
|
5181
|
+
const cwd = process.cwd();
|
|
5182
|
+
const scopeArg = readCliOptionFromArgv("--scope");
|
|
5183
|
+
const scope = normalizeMcpScope(scopeArg ?? opts.scope, "global");
|
|
5184
|
+
const dryRun = hasCliFlag("--dry-run") || Boolean(opts.dryRun);
|
|
5185
|
+
const service = normalizeCredentialService(opts.service);
|
|
5186
|
+
const profileName = normalizeProfileNameOrThrow(opts.name);
|
|
5187
|
+
const envVar = normalizePostmanApiKey(opts.envVar);
|
|
5188
|
+
if (!envVar || !isCredentialServiceEnvVar(envVar)) {
|
|
5189
|
+
throw new Error("Missing or invalid --env-var. Example: --env-var POSTMAN_API_KEY");
|
|
5190
|
+
}
|
|
5191
|
+
|
|
5192
|
+
const { configPath, existing, existingValue } = await loadConfigForScope({ scope, cwd });
|
|
5193
|
+
const next = prepareConfigDocument(existingValue, {
|
|
5194
|
+
scope,
|
|
5195
|
+
generatedBy: "cbx workflows config keys add"
|
|
5196
|
+
});
|
|
5197
|
+
|
|
5198
|
+
const serviceState = ensureCredentialServiceState(next, service);
|
|
5199
|
+
if (findProfileByName(serviceState.profiles, profileName)) {
|
|
5200
|
+
throw new Error(`Profile '${profileName}' already exists for ${service}.`);
|
|
5201
|
+
}
|
|
5202
|
+
|
|
5203
|
+
const newProfile = normalizeCredentialProfileRecord(service, {
|
|
5204
|
+
name: profileName,
|
|
5205
|
+
apiKey: null,
|
|
5206
|
+
apiKeyEnvVar: envVar,
|
|
5207
|
+
workspaceId: service === "postman" ? normalizePostmanWorkspaceId(opts.workspaceId) : undefined
|
|
5208
|
+
});
|
|
5209
|
+
const updatedServiceState = {
|
|
5210
|
+
...serviceState,
|
|
5211
|
+
profiles: dedupeCredentialProfiles([...serviceState.profiles, newProfile])
|
|
5212
|
+
};
|
|
5213
|
+
upsertCredentialServiceConfig(next, service, updatedServiceState);
|
|
5214
|
+
|
|
5215
|
+
const action = await writeConfigFile({
|
|
5216
|
+
configPath,
|
|
5217
|
+
nextConfig: next,
|
|
5218
|
+
existingExists: existing.exists,
|
|
5219
|
+
dryRun
|
|
5220
|
+
});
|
|
5221
|
+
console.log(`Config file: ${configPath}`);
|
|
5222
|
+
console.log(`Action: ${action}`);
|
|
5223
|
+
console.log(`Added profile '${profileName}' for ${service} (env var ${envVar}).`);
|
|
5224
|
+
console.log(`Active profile remains '${updatedServiceState.activeProfileName}'.`);
|
|
5225
|
+
} catch (error) {
|
|
5226
|
+
if (error?.name === "ExitPromptError") {
|
|
5227
|
+
console.error("\nCancelled.");
|
|
5228
|
+
process.exit(130);
|
|
5229
|
+
}
|
|
5230
|
+
console.error(`\nError: ${error.message}`);
|
|
5231
|
+
process.exit(1);
|
|
5232
|
+
}
|
|
5233
|
+
}
|
|
5234
|
+
|
|
5235
|
+
async function runWorkflowConfigKeysUse(options) {
|
|
5236
|
+
try {
|
|
5237
|
+
const opts = resolveActionOptions(options);
|
|
5238
|
+
const cwd = process.cwd();
|
|
5239
|
+
const scopeArg = readCliOptionFromArgv("--scope");
|
|
5240
|
+
const scope = normalizeMcpScope(scopeArg ?? opts.scope, "global");
|
|
5241
|
+
const dryRun = hasCliFlag("--dry-run") || Boolean(opts.dryRun);
|
|
5242
|
+
const service = normalizeCredentialService(opts.service);
|
|
5243
|
+
const profileName = normalizeProfileNameOrThrow(opts.name);
|
|
5244
|
+
|
|
5245
|
+
const { configPath, existing, existingValue } = await loadConfigForScope({ scope, cwd });
|
|
5246
|
+
if (!existing.exists) {
|
|
5247
|
+
throw new Error(`Config file is missing at ${configPath}.`);
|
|
5248
|
+
}
|
|
5249
|
+
|
|
5250
|
+
const next = prepareConfigDocument(existingValue, {
|
|
5251
|
+
scope,
|
|
5252
|
+
generatedBy: "cbx workflows config keys use"
|
|
5253
|
+
});
|
|
5254
|
+
const serviceState = ensureCredentialServiceState(next, service);
|
|
5255
|
+
const selectedProfile = findProfileByName(serviceState.profiles, profileName);
|
|
5256
|
+
if (!selectedProfile) {
|
|
5257
|
+
throw new Error(`Profile '${profileName}' does not exist for ${service}.`);
|
|
5258
|
+
}
|
|
5259
|
+
|
|
5260
|
+
const updatedServiceState = {
|
|
5261
|
+
...serviceState,
|
|
5262
|
+
activeProfileName: selectedProfile.name
|
|
5263
|
+
};
|
|
5264
|
+
upsertCredentialServiceConfig(next, service, updatedServiceState);
|
|
5265
|
+
|
|
5266
|
+
const action = await writeConfigFile({
|
|
5267
|
+
configPath,
|
|
5268
|
+
nextConfig: next,
|
|
5269
|
+
existingExists: existing.exists,
|
|
5270
|
+
dryRun
|
|
5271
|
+
});
|
|
5272
|
+
console.log(`Config file: ${configPath}`);
|
|
5273
|
+
console.log(`Action: ${action}`);
|
|
5274
|
+
console.log(`Active ${service} profile: ${selectedProfile.name}`);
|
|
5275
|
+
} catch (error) {
|
|
5276
|
+
if (error?.name === "ExitPromptError") {
|
|
5277
|
+
console.error("\nCancelled.");
|
|
5278
|
+
process.exit(130);
|
|
5279
|
+
}
|
|
5280
|
+
console.error(`\nError: ${error.message}`);
|
|
5281
|
+
process.exit(1);
|
|
5282
|
+
}
|
|
5283
|
+
}
|
|
5284
|
+
|
|
5285
|
+
async function runWorkflowConfigKeysRemove(options) {
|
|
5286
|
+
try {
|
|
5287
|
+
const opts = resolveActionOptions(options);
|
|
5288
|
+
const cwd = process.cwd();
|
|
5289
|
+
const scopeArg = readCliOptionFromArgv("--scope");
|
|
5290
|
+
const scope = normalizeMcpScope(scopeArg ?? opts.scope, "global");
|
|
5291
|
+
const dryRun = hasCliFlag("--dry-run") || Boolean(opts.dryRun);
|
|
5292
|
+
const service = normalizeCredentialService(opts.service);
|
|
5293
|
+
const profileName = normalizeProfileNameOrThrow(opts.name);
|
|
5294
|
+
|
|
5295
|
+
const { configPath, existing, existingValue } = await loadConfigForScope({ scope, cwd });
|
|
5296
|
+
if (!existing.exists) {
|
|
5297
|
+
throw new Error(`Config file is missing at ${configPath}.`);
|
|
5298
|
+
}
|
|
5299
|
+
|
|
5300
|
+
const next = prepareConfigDocument(existingValue, {
|
|
5301
|
+
scope,
|
|
5302
|
+
generatedBy: "cbx workflows config keys remove"
|
|
5303
|
+
});
|
|
5304
|
+
const serviceState = ensureCredentialServiceState(next, service);
|
|
5305
|
+
const selectedProfile = findProfileByName(serviceState.profiles, profileName);
|
|
5306
|
+
if (!selectedProfile) {
|
|
5307
|
+
throw new Error(`Profile '${profileName}' does not exist for ${service}.`);
|
|
5308
|
+
}
|
|
5309
|
+
if (
|
|
5310
|
+
credentialProfileNameKey(serviceState.activeProfileName) === credentialProfileNameKey(selectedProfile.name)
|
|
5311
|
+
) {
|
|
5312
|
+
throw new Error(`Cannot remove active profile '${selectedProfile.name}'. Switch active profile first.`);
|
|
5313
|
+
}
|
|
5314
|
+
|
|
5315
|
+
const updatedProfiles = serviceState.profiles.filter(
|
|
5316
|
+
(profile) => credentialProfileNameKey(profile.name) !== credentialProfileNameKey(selectedProfile.name)
|
|
5317
|
+
);
|
|
5318
|
+
const updatedServiceState = {
|
|
5319
|
+
...serviceState,
|
|
5320
|
+
profiles: updatedProfiles
|
|
5321
|
+
};
|
|
5322
|
+
upsertCredentialServiceConfig(next, service, updatedServiceState);
|
|
5323
|
+
|
|
5324
|
+
const action = await writeConfigFile({
|
|
5325
|
+
configPath,
|
|
5326
|
+
nextConfig: next,
|
|
5327
|
+
existingExists: existing.exists,
|
|
5328
|
+
dryRun
|
|
5329
|
+
});
|
|
5330
|
+
console.log(`Config file: ${configPath}`);
|
|
5331
|
+
console.log(`Action: ${action}`);
|
|
5332
|
+
console.log(`Removed profile '${selectedProfile.name}' from ${service}.`);
|
|
5333
|
+
} catch (error) {
|
|
5334
|
+
if (error?.name === "ExitPromptError") {
|
|
5335
|
+
console.error("\nCancelled.");
|
|
5336
|
+
process.exit(130);
|
|
5337
|
+
}
|
|
5338
|
+
console.error(`\nError: ${error.message}`);
|
|
5339
|
+
process.exit(1);
|
|
5340
|
+
}
|
|
5341
|
+
}
|
|
5342
|
+
|
|
4576
5343
|
async function runWorkflowConfig(options) {
|
|
4577
5344
|
try {
|
|
5345
|
+
const opts = resolveActionOptions(options);
|
|
4578
5346
|
const cwd = process.cwd();
|
|
4579
|
-
const
|
|
4580
|
-
const
|
|
4581
|
-
const
|
|
4582
|
-
const
|
|
4583
|
-
const
|
|
5347
|
+
const scopeArg = readCliOptionFromArgv("--scope");
|
|
5348
|
+
const scope = normalizeMcpScope(scopeArg ?? opts.scope, "global");
|
|
5349
|
+
const dryRun = Boolean(opts.dryRun);
|
|
5350
|
+
const hasWorkspaceIdOption = opts.workspaceId !== undefined;
|
|
5351
|
+
const wantsClearWorkspaceId = Boolean(opts.clearWorkspaceId);
|
|
5352
|
+
const wantsInteractiveEdit = Boolean(opts.edit);
|
|
4584
5353
|
|
|
4585
5354
|
if (hasWorkspaceIdOption && wantsClearWorkspaceId) {
|
|
4586
5355
|
throw new Error("Use either --workspace-id or --clear-workspace-id, not both.");
|
|
4587
5356
|
}
|
|
4588
5357
|
|
|
4589
5358
|
const wantsMutation = hasWorkspaceIdOption || wantsClearWorkspaceId || wantsInteractiveEdit;
|
|
4590
|
-
const showOnly = Boolean(
|
|
4591
|
-
const configPath =
|
|
4592
|
-
const existing = await readJsonFileIfExists(configPath);
|
|
4593
|
-
const existingValue =
|
|
4594
|
-
existing.value && typeof existing.value === "object" && !Array.isArray(existing.value) ? existing.value : null;
|
|
5359
|
+
const showOnly = Boolean(opts.show) || !wantsMutation;
|
|
5360
|
+
const { configPath, existing, existingValue } = await loadConfigForScope({ scope, cwd });
|
|
4595
5361
|
|
|
4596
5362
|
if (showOnly) {
|
|
4597
5363
|
console.log(`Config file: ${configPath}`);
|
|
@@ -4599,33 +5365,20 @@ async function runWorkflowConfig(options) {
|
|
|
4599
5365
|
console.log("Status: missing");
|
|
4600
5366
|
return;
|
|
4601
5367
|
}
|
|
4602
|
-
if (!existingValue) {
|
|
4603
|
-
throw new Error(`Existing config at ${configPath} is not valid JSON object.`);
|
|
4604
|
-
}
|
|
4605
5368
|
console.log(`Status: ${existing.exists ? "exists" : "missing"}`);
|
|
4606
|
-
|
|
5369
|
+
const payload = buildConfigShowPayload(existingValue);
|
|
5370
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
4607
5371
|
return;
|
|
4608
5372
|
}
|
|
4609
5373
|
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
const next = existingValue ? JSON.parse(JSON.stringify(existingValue)) : {};
|
|
4615
|
-
if (!next.schemaVersion || typeof next.schemaVersion !== "number") next.schemaVersion = 1;
|
|
4616
|
-
next.generatedBy = "cbx workflows config";
|
|
4617
|
-
next.generatedAt = new Date().toISOString();
|
|
4618
|
-
|
|
4619
|
-
if (!next.mcp || typeof next.mcp !== "object" || Array.isArray(next.mcp)) next.mcp = {};
|
|
4620
|
-
next.mcp.scope = scope;
|
|
4621
|
-
if (!next.mcp.server) next.mcp.server = POSTMAN_SKILL_ID;
|
|
4622
|
-
|
|
4623
|
-
if (!next.postman || typeof next.postman !== "object" || Array.isArray(next.postman)) next.postman = {};
|
|
4624
|
-
next.postman.apiKey = normalizePostmanApiKey(next.postman.apiKey);
|
|
4625
|
-
next.postman.apiKeyEnvVar = String(next.postman.apiKeyEnvVar || POSTMAN_API_KEY_ENV_VAR).trim() || POSTMAN_API_KEY_ENV_VAR;
|
|
4626
|
-
next.postman.mcpUrl = String(next.postman.mcpUrl || POSTMAN_MCP_URL).trim() || POSTMAN_MCP_URL;
|
|
5374
|
+
const next = prepareConfigDocument(existingValue, {
|
|
5375
|
+
scope,
|
|
5376
|
+
generatedBy: "cbx workflows config"
|
|
5377
|
+
});
|
|
4627
5378
|
|
|
4628
|
-
|
|
5379
|
+
const postmanState = ensureCredentialServiceState(next, "postman");
|
|
5380
|
+
const activeProfile = { ...postmanState.activeProfile };
|
|
5381
|
+
let workspaceId = normalizePostmanWorkspaceId(activeProfile.workspaceId);
|
|
4629
5382
|
|
|
4630
5383
|
if (wantsInteractiveEdit) {
|
|
4631
5384
|
const promptedWorkspaceId = await input({
|
|
@@ -4634,33 +5387,47 @@ async function runWorkflowConfig(options) {
|
|
|
4634
5387
|
});
|
|
4635
5388
|
workspaceId = normalizePostmanWorkspaceId(promptedWorkspaceId);
|
|
4636
5389
|
}
|
|
4637
|
-
|
|
4638
5390
|
if (hasWorkspaceIdOption) {
|
|
4639
|
-
workspaceId = normalizePostmanWorkspaceId(
|
|
5391
|
+
workspaceId = normalizePostmanWorkspaceId(opts.workspaceId);
|
|
4640
5392
|
}
|
|
4641
|
-
|
|
4642
5393
|
if (wantsClearWorkspaceId) {
|
|
4643
5394
|
workspaceId = null;
|
|
4644
5395
|
}
|
|
4645
5396
|
|
|
4646
|
-
|
|
4647
|
-
const
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
5397
|
+
activeProfile.workspaceId = workspaceId;
|
|
5398
|
+
const updatedProfiles = postmanState.profiles.map((profile) =>
|
|
5399
|
+
credentialProfileNameKey(profile.name) === credentialProfileNameKey(postmanState.activeProfileName)
|
|
5400
|
+
? activeProfile
|
|
5401
|
+
: profile
|
|
5402
|
+
);
|
|
5403
|
+
const updatedPostmanState = parseStoredCredentialServiceConfig({
|
|
5404
|
+
service: "postman",
|
|
5405
|
+
rawService: {
|
|
5406
|
+
...(next.postman && typeof next.postman === "object" ? next.postman : {}),
|
|
5407
|
+
profiles: updatedProfiles,
|
|
5408
|
+
activeProfileName: postmanState.activeProfileName,
|
|
5409
|
+
mcpUrl: postmanState.mcpUrl
|
|
5410
|
+
}
|
|
4651
5411
|
});
|
|
5412
|
+
upsertNormalizedPostmanConfig(next, updatedPostmanState);
|
|
4652
5413
|
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
await mkdir(path.dirname(configPath), { recursive: true });
|
|
4656
|
-
await writeFile(configPath, content, "utf8");
|
|
5414
|
+
if (parseStoredStitchConfig(next)) {
|
|
5415
|
+
upsertNormalizedStitchConfig(next, parseStoredStitchConfig(next));
|
|
4657
5416
|
}
|
|
4658
5417
|
|
|
5418
|
+
const action = await writeConfigFile({
|
|
5419
|
+
configPath,
|
|
5420
|
+
nextConfig: next,
|
|
5421
|
+
existingExists: existing.exists,
|
|
5422
|
+
dryRun
|
|
5423
|
+
});
|
|
5424
|
+
|
|
4659
5425
|
console.log(`Config file: ${configPath}`);
|
|
4660
|
-
console.log(`Action: ${
|
|
5426
|
+
console.log(`Action: ${action}`);
|
|
4661
5427
|
console.log(`postman.defaultWorkspaceId: ${workspaceId === null ? "null" : workspaceId}`);
|
|
4662
|
-
if (Boolean(
|
|
4663
|
-
|
|
5428
|
+
if (Boolean(opts.showAfter)) {
|
|
5429
|
+
const payload = buildConfigShowPayload(next);
|
|
5430
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
4664
5431
|
}
|
|
4665
5432
|
} catch (error) {
|
|
4666
5433
|
if (error?.name === "ExitPromptError") {
|
|
@@ -4903,7 +5670,7 @@ withWorkflowBaseOptions(
|
|
|
4903
5670
|
.option("--json", "output JSON")
|
|
4904
5671
|
).action(runWorkflowDoctor);
|
|
4905
5672
|
|
|
4906
|
-
workflowsCommand
|
|
5673
|
+
const workflowsConfigCommand = workflowsCommand
|
|
4907
5674
|
.command("config")
|
|
4908
5675
|
.description("View or edit cbx_config.json from terminal")
|
|
4909
5676
|
.option("--scope <scope>", "config scope: project|workspace|global|user", "global")
|
|
@@ -4914,6 +5681,7 @@ workflowsCommand
|
|
|
4914
5681
|
.option("--show-after", "print JSON after update")
|
|
4915
5682
|
.option("--dry-run", "preview changes without writing files")
|
|
4916
5683
|
.action(runWorkflowConfig);
|
|
5684
|
+
registerConfigKeysSubcommands(workflowsConfigCommand);
|
|
4917
5685
|
|
|
4918
5686
|
workflowsCommand.action(() => {
|
|
4919
5687
|
workflowsCommand.help();
|
|
@@ -4974,7 +5742,7 @@ withWorkflowBaseOptions(
|
|
|
4974
5742
|
await runWorkflowDoctor(platform, options);
|
|
4975
5743
|
});
|
|
4976
5744
|
|
|
4977
|
-
skillsCommand
|
|
5745
|
+
const skillsConfigCommand = skillsCommand
|
|
4978
5746
|
.command("config")
|
|
4979
5747
|
.description("Alias for workflows config")
|
|
4980
5748
|
.option("--scope <scope>", "config scope: project|workspace|global|user", "global")
|
|
@@ -4988,6 +5756,7 @@ skillsCommand
|
|
|
4988
5756
|
printSkillsDeprecation();
|
|
4989
5757
|
await runWorkflowConfig(options);
|
|
4990
5758
|
});
|
|
5759
|
+
registerConfigKeysSubcommands(skillsConfigCommand, { aliasMode: true });
|
|
4991
5760
|
|
|
4992
5761
|
skillsCommand.action(() => {
|
|
4993
5762
|
printSkillsDeprecation();
|