@cubis/foundry 0.3.13 → 0.3.15
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 +3 -3
- package/Ai Agent Workflow/skills/postman/SKILL.md +2 -2
- package/Ai Agent Workflow/workflows/agent-environment-setup/platforms/antigravity/agents/backend-specialist.md +1 -1
- package/Ai Agent Workflow/workflows/agent-environment-setup/platforms/antigravity/workflows/backend.md +11 -1
- package/Ai Agent Workflow/workflows/agent-environment-setup/platforms/codex/agents/backend-specialist.md +1 -1
- package/Ai Agent Workflow/workflows/agent-environment-setup/platforms/codex/workflows/backend.md +5 -1
- package/Ai Agent Workflow/workflows/agent-environment-setup/platforms/copilot/agents/backend-specialist.md +1 -1
- package/Ai Agent Workflow/workflows/agent-environment-setup/platforms/copilot/workflows/backend.md +5 -1
- package/README.md +4 -0
- package/bin/cubis.js +699 -42
- package/package.json +2 -1
|
@@ -12,7 +12,7 @@ Before proceeding, validate that the user has completed the following steps befo
|
|
|
12
12
|
|
|
13
13
|
## Step 1
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
Generate and maintain `postman_setting.json` as the primary Postman configuration. Use env-first authentication by default: keep `apiKey` as `null`, set `apiKeyEnvVar` to `POSTMAN_API_KEY`, and read the key from environment variables. Only store `apiKey` inline when the user explicitly requests file-based key storage. Keep `defaultWorkspaceId` nullable (`null` when unknown) so workflows can run without a preselected workspace.
|
|
16
16
|
|
|
17
17
|
## Step 2
|
|
18
18
|
|
|
@@ -68,7 +68,7 @@ Create a hook that runs anytime the source code or configuration file has been c
|
|
|
68
68
|
|
|
69
69
|
Automate API testing and collection management with Postman. Create workspaces, collections, environments, and run tests programmatically.
|
|
70
70
|
|
|
71
|
-
**Authentication**:
|
|
71
|
+
**Authentication**: Env-first via `postman_setting.json` + `POSTMAN_API_KEY`; inline `apiKey` is optional.
|
|
72
72
|
|
|
73
73
|
## Available MCP Servers
|
|
74
74
|
|
|
@@ -10,7 +10,7 @@ Before proceeding, validate that the user has completed the following steps befo
|
|
|
10
10
|
|
|
11
11
|
## Step 1
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Generate and maintain `postman_setting.json` as the primary Postman configuration. Use env-first authentication by default: keep `apiKey` as `null`, set `apiKeyEnvVar` to `POSTMAN_API_KEY`, and read the key from environment variables. Only store `apiKey` inline when the user explicitly requests file-based key storage. Keep `defaultWorkspaceId` nullable (`null` when unknown) so workflows can run without a preselected workspace.
|
|
14
14
|
|
|
15
15
|
## Step 2
|
|
16
16
|
|
|
@@ -66,7 +66,7 @@ Create a hook that runs anytime the source code or configuration file has been c
|
|
|
66
66
|
|
|
67
67
|
Automate API testing and collection management with Postman. Create workspaces, collections, environments, and run tests programmatically.
|
|
68
68
|
|
|
69
|
-
**Authentication**:
|
|
69
|
+
**Authentication**: Env-first via `postman_setting.json` + `POSTMAN_API_KEY`; inline `apiKey` is optional.
|
|
70
70
|
|
|
71
71
|
## Available MCP Servers
|
|
72
72
|
|
|
@@ -12,7 +12,7 @@ Before proceeding, validate that the user has completed the following steps befo
|
|
|
12
12
|
|
|
13
13
|
## Step 1
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
Generate and maintain `postman_setting.json` as the primary Postman configuration. Use env-first authentication by default: keep `apiKey` as `null`, set `apiKeyEnvVar` to `POSTMAN_API_KEY`, and read the key from environment variables. Only store `apiKey` inline when the user explicitly requests file-based key storage. Keep `defaultWorkspaceId` nullable (`null` when unknown) so workflows can run without a preselected workspace.
|
|
16
16
|
|
|
17
17
|
## Step 2
|
|
18
18
|
|
|
@@ -68,7 +68,7 @@ Create a hook that runs anytime the source code or configuration file has been c
|
|
|
68
68
|
|
|
69
69
|
Automate API testing and collection management with Postman. Create workspaces, collections, environments, and run tests programmatically.
|
|
70
70
|
|
|
71
|
-
**Authentication**:
|
|
71
|
+
**Authentication**: Env-first via `postman_setting.json` + `POSTMAN_API_KEY`; inline `apiKey` is optional.
|
|
72
72
|
|
|
73
73
|
## Available MCP Servers
|
|
74
74
|
|
|
@@ -239,4 +239,4 @@ for (const collection of collections) {
|
|
|
239
239
|
|
|
240
240
|
**Full mode (112 tools):** Change URL to `https://mcp.postman.com/full`
|
|
241
241
|
|
|
242
|
-
**API Key Permissions:** Workspace management, collection read/write, environment read/write, collection runs
|
|
242
|
+
**API Key Permissions:** Workspace management, collection read/write, environment read/write, collection runs
|
|
@@ -10,7 +10,7 @@ Before proceeding, validate that the user has completed the following steps befo
|
|
|
10
10
|
|
|
11
11
|
## Step 1
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Generate and maintain `postman_setting.json` as the primary Postman configuration. Use env-first authentication by default: keep `apiKey` as `null`, set `apiKeyEnvVar` to `POSTMAN_API_KEY`, and read the key from environment variables. Only store `apiKey` inline when the user explicitly requests file-based key storage. Keep `defaultWorkspaceId` nullable (`null` when unknown) so workflows can run without a preselected workspace.
|
|
14
14
|
|
|
15
15
|
## Step 2
|
|
16
16
|
|
|
@@ -66,7 +66,7 @@ Create a hook that runs anytime the source code or configuration file has been c
|
|
|
66
66
|
|
|
67
67
|
Automate API testing and collection management with Postman. Create workspaces, collections, environments, and run tests programmatically.
|
|
68
68
|
|
|
69
|
-
**Authentication**:
|
|
69
|
+
**Authentication**: Env-first via `postman_setting.json` + `POSTMAN_API_KEY`; inline `apiKey` is optional.
|
|
70
70
|
|
|
71
71
|
## Available MCP Servers
|
|
72
72
|
|
|
@@ -3,7 +3,7 @@ name: backend-specialist
|
|
|
3
3
|
description: Expert backend architect for Node.js, Python, and modern serverless/edge systems. Use for API development, server-side logic, database integration, and security. Triggers on backend, server, api, endpoint, database, auth.
|
|
4
4
|
tools: Read, Grep, Glob, Bash, Edit, Write
|
|
5
5
|
model: inherit
|
|
6
|
-
skills: clean-code, nodejs-best-practices, python-patterns, api-patterns, database-design, database-optimizer, database-skills, mcp-builder, lint-and-validate, powershell-windows, bash-linux, rust-pro, api-designer, architecture-designer, typescript-pro, nestjs-expert, fastapi-expert, secure-code-guardian, test-master
|
|
6
|
+
skills: clean-code, nodejs-best-practices, python-patterns, api-patterns, openapi-docs, database-design, database-optimizer, database-skills, mcp-builder, lint-and-validate, powershell-windows, bash-linux, rust-pro, api-designer, architecture-designer, typescript-pro, nestjs-expert, fastapi-expert, secure-code-guardian, test-master
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Backend Development Architect
|
|
@@ -14,4 +14,14 @@ Use this workflow when backend architecture or implementation is primary.
|
|
|
14
14
|
1. Ask `@backend-specialist` for solution outline.
|
|
15
15
|
2. Validate API contracts, data model, and failure handling.
|
|
16
16
|
3. Implement backend changes with tests and observability.
|
|
17
|
-
4.
|
|
17
|
+
4. Always update API docs: OpenAPI spec, Swagger UI route, and Stoplight Elements route/component.
|
|
18
|
+
5. Return summary including migration and rollout notes.
|
|
19
|
+
|
|
20
|
+
## Output Contract
|
|
21
|
+
- API/contract changes
|
|
22
|
+
- OpenAPI spec path
|
|
23
|
+
- Swagger UI route
|
|
24
|
+
- Stoplight route/component status
|
|
25
|
+
- Migration impact
|
|
26
|
+
- Reliability/security notes
|
|
27
|
+
- Verification evidence
|
|
@@ -3,7 +3,7 @@ name: backend-specialist
|
|
|
3
3
|
description: Expert backend architect for Node.js, Python, and modern serverless/edge systems. Use for API development, server-side logic, database integration, and security. Triggers on backend, server, api, endpoint, database, auth.
|
|
4
4
|
tools: Read, Grep, Glob, Bash, Edit, Write
|
|
5
5
|
model: inherit
|
|
6
|
-
skills: clean-code, nodejs-best-practices, python-patterns, api-patterns, database-design, database-optimizer, database-skills, mcp-builder, lint-and-validate, powershell-windows, bash-linux, rust-pro, api-designer, architecture-designer, typescript-pro, nestjs-expert, fastapi-expert, secure-code-guardian, test-master
|
|
6
|
+
skills: clean-code, nodejs-best-practices, python-patterns, api-patterns, openapi-docs, database-design, database-optimizer, database-skills, mcp-builder, lint-and-validate, powershell-windows, bash-linux, rust-pro, api-designer, architecture-designer, typescript-pro, nestjs-expert, fastapi-expert, secure-code-guardian, test-master
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Backend Development Architect
|
package/Ai Agent Workflow/workflows/agent-environment-setup/platforms/codex/workflows/backend.md
CHANGED
|
@@ -16,10 +16,14 @@ Use this when backend architecture or service logic is primary.
|
|
|
16
16
|
1. Ask specialist(s) for design and risk assessment.
|
|
17
17
|
2. Validate contracts, data model, and failure handling.
|
|
18
18
|
3. Implement backend changes with observability.
|
|
19
|
-
4.
|
|
19
|
+
4. Always update API docs: OpenAPI spec, Swagger UI route, and Stoplight Elements route/component.
|
|
20
|
+
5. Run targeted tests and summarize rollout notes.
|
|
20
21
|
|
|
21
22
|
## Output Contract
|
|
22
23
|
- API/contract changes
|
|
24
|
+
- OpenAPI spec path
|
|
25
|
+
- Swagger UI route
|
|
26
|
+
- Stoplight route/component status
|
|
23
27
|
- Migration impact
|
|
24
28
|
- Reliability/security notes
|
|
25
29
|
- Verification evidence
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: backend-specialist
|
|
3
3
|
description: Expert backend architect for Node.js, Python, and modern serverless/edge systems. Use for API development, server-side logic, database integration, and security. Triggers on backend, server, api, endpoint, database, auth.
|
|
4
4
|
tools: Read, Grep, Glob, Bash, Edit, Write
|
|
5
|
-
skills: clean-code, nodejs-best-practices, python-patterns, api-patterns, database-design, database-optimizer, database-skills, mcp-builder, lint-and-validate, powershell-windows, bash-linux, rust-pro, api-designer, architecture-designer, typescript-pro, nestjs-expert, fastapi-expert, secure-code-guardian, test-master
|
|
5
|
+
skills: clean-code, nodejs-best-practices, python-patterns, api-patterns, openapi-docs, database-design, database-optimizer, database-skills, mcp-builder, lint-and-validate, powershell-windows, bash-linux, rust-pro, api-designer, architecture-designer, typescript-pro, nestjs-expert, fastapi-expert, secure-code-guardian, test-master
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Backend Development Architect
|
package/Ai Agent Workflow/workflows/agent-environment-setup/platforms/copilot/workflows/backend.md
CHANGED
|
@@ -16,10 +16,14 @@ Use this when backend architecture or service logic is primary.
|
|
|
16
16
|
1. Ask specialist(s) for design and risk assessment.
|
|
17
17
|
2. Validate contracts, data model, and failure handling.
|
|
18
18
|
3. Implement backend changes with observability.
|
|
19
|
-
4.
|
|
19
|
+
4. Always update API docs: OpenAPI spec, Swagger UI route, and Stoplight Elements route/component.
|
|
20
|
+
5. Run targeted tests and summarize rollout notes.
|
|
20
21
|
|
|
21
22
|
## Output Contract
|
|
22
23
|
- API/contract changes
|
|
24
|
+
- OpenAPI spec path
|
|
25
|
+
- Swagger UI route
|
|
26
|
+
- Stoplight route/component status
|
|
23
27
|
- Migration impact
|
|
24
28
|
- Reliability/security notes
|
|
25
29
|
- Verification evidence
|
package/README.md
CHANGED
|
@@ -34,10 +34,13 @@ cbx workflows doctor codex
|
|
|
34
34
|
cbx workflows platforms
|
|
35
35
|
cbx workflows install --platform antigravity --dry-run
|
|
36
36
|
cbx workflows install --platform antigravity --terminal-integration --terminal-verifier codex
|
|
37
|
+
cbx workflows install --platform codex --postman
|
|
38
|
+
cbx workflows install --platform codex --postman --postman-workspace-id null
|
|
37
39
|
```
|
|
38
40
|
|
|
39
41
|
Install bootstrap behavior:
|
|
40
42
|
- `cbx workflows install` now also bootstraps `ENGINEERING_RULES.md` and `TECH.md` (creates when missing; keeps existing files unless explicitly regenerated).
|
|
43
|
+
- Optional `--postman` bootstrap creates `postman_setting.json` and installs/configures the Postman skill.
|
|
41
44
|
- Use `cbx rules init --platform <platform> --overwrite` to force-regenerate both files.
|
|
42
45
|
|
|
43
46
|
`rules` manages strict engineering policy and a generated codebase tech map:
|
|
@@ -112,6 +115,7 @@ Routing behavior:
|
|
|
112
115
|
- Antigravity/Copilot: workflow + agent markdown can be routed by platform conventions.
|
|
113
116
|
- Codex: use generated callable wrapper skills (`$workflow-*`, `$agent-*`).
|
|
114
117
|
- Example for backend intent in Codex: `$workflow-backend` or `$agent-backend-specialist`.
|
|
118
|
+
- Backend workflow policy: always include OpenAPI updates plus Swagger UI and Stoplight Elements status in output.
|
|
115
119
|
|
|
116
120
|
### Codex Runtime Mode
|
|
117
121
|
|
package/bin/cubis.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { confirm, select } from "@inquirer/prompts";
|
|
3
|
+
import { confirm, input, select } from "@inquirer/prompts";
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { existsSync } from "node:fs";
|
|
6
6
|
import {
|
|
@@ -122,6 +122,12 @@ const CODEX_WORKFLOW_SKILL_PREFIX = "workflow-";
|
|
|
122
122
|
const CODEX_AGENT_SKILL_PREFIX = "agent-";
|
|
123
123
|
const TERMINAL_VERIFIER_PROVIDERS = ["codex", "gemini"];
|
|
124
124
|
const DEFAULT_TERMINAL_VERIFIER = "codex";
|
|
125
|
+
const POSTMAN_API_KEY_ENV_VAR = "POSTMAN_API_KEY";
|
|
126
|
+
const POSTMAN_MCP_URL = "https://mcp.postman.com/minimal";
|
|
127
|
+
const POSTMAN_SKILL_ID = "postman";
|
|
128
|
+
const POSTMAN_SETTINGS_FILENAME = "postman_setting.json";
|
|
129
|
+
const POSTMAN_API_KEY_MISSING_WARNING =
|
|
130
|
+
`Postman API key is not configured. Set ${POSTMAN_API_KEY_ENV_VAR} or update ${POSTMAN_SETTINGS_FILENAME}.`;
|
|
125
131
|
const TECH_SCAN_MAX_FILES = 5000;
|
|
126
132
|
const TECH_SCAN_IGNORED_DIRS = new Set([
|
|
127
133
|
".git",
|
|
@@ -166,6 +172,54 @@ const TECH_LANGUAGE_BY_EXTENSION = new Map([
|
|
|
166
172
|
[".sh", "Shell"],
|
|
167
173
|
[".ps1", "PowerShell"]
|
|
168
174
|
]);
|
|
175
|
+
const TECH_PACKAGE_PREVIEW_LIMIT = 40;
|
|
176
|
+
const TECH_JS_FRAMEWORK_SIGNALS = [
|
|
177
|
+
["next", "Next.js"],
|
|
178
|
+
["react", "React"],
|
|
179
|
+
["vue", "Vue"],
|
|
180
|
+
["nuxt", "Nuxt"],
|
|
181
|
+
["svelte", "Svelte"],
|
|
182
|
+
["@nestjs/core", "NestJS"],
|
|
183
|
+
["express", "Express"],
|
|
184
|
+
["fastify", "Fastify"],
|
|
185
|
+
["hono", "Hono"],
|
|
186
|
+
["tailwindcss", "Tailwind CSS"],
|
|
187
|
+
["prisma", "Prisma"],
|
|
188
|
+
["drizzle-orm", "Drizzle ORM"],
|
|
189
|
+
["mongoose", "Mongoose"],
|
|
190
|
+
["typeorm", "TypeORM"],
|
|
191
|
+
["@playwright/test", "Playwright"],
|
|
192
|
+
["vitest", "Vitest"],
|
|
193
|
+
["jest", "Jest"],
|
|
194
|
+
["cypress", "Cypress"]
|
|
195
|
+
];
|
|
196
|
+
const TECH_DART_FRAMEWORK_SIGNALS = [
|
|
197
|
+
["flutter_riverpod", "Riverpod"],
|
|
198
|
+
["riverpod", "Riverpod"],
|
|
199
|
+
["go_router", "go_router"],
|
|
200
|
+
["dio", "Dio"],
|
|
201
|
+
["freezed", "Freezed"],
|
|
202
|
+
["bloc", "BLoC"]
|
|
203
|
+
];
|
|
204
|
+
const TECH_GO_FRAMEWORK_SIGNALS = [
|
|
205
|
+
["github.com/gofiber/fiber/v2", "Go Fiber"],
|
|
206
|
+
["github.com/gin-gonic/gin", "Gin"],
|
|
207
|
+
["github.com/labstack/echo/v4", "Echo"],
|
|
208
|
+
["github.com/go-chi/chi/v5", "Chi"]
|
|
209
|
+
];
|
|
210
|
+
const TECH_PYTHON_FRAMEWORK_SIGNALS = [
|
|
211
|
+
["fastapi", "FastAPI"],
|
|
212
|
+
["django", "Django"],
|
|
213
|
+
["flask", "Flask"],
|
|
214
|
+
["pydantic", "Pydantic"],
|
|
215
|
+
["sqlalchemy", "SQLAlchemy"]
|
|
216
|
+
];
|
|
217
|
+
const TECH_RUST_FRAMEWORK_SIGNALS = [
|
|
218
|
+
["axum", "Axum"],
|
|
219
|
+
["actix-web", "Actix Web"],
|
|
220
|
+
["rocket", "Rocket"],
|
|
221
|
+
["tokio", "Tokio"]
|
|
222
|
+
];
|
|
169
223
|
|
|
170
224
|
function platformInstallsCustomAgents(platformId) {
|
|
171
225
|
const profile = WORKFLOW_PROFILES[platformId];
|
|
@@ -231,6 +285,13 @@ function normalizeTerminalVerifier(value) {
|
|
|
231
285
|
return TERMINAL_VERIFIER_ALIASES[normalized] || null;
|
|
232
286
|
}
|
|
233
287
|
|
|
288
|
+
function normalizePostmanWorkspaceId(value) {
|
|
289
|
+
if (value === undefined || value === null) return null;
|
|
290
|
+
const normalized = String(value).trim();
|
|
291
|
+
if (!normalized || normalized.toLowerCase() === "null") return null;
|
|
292
|
+
return normalized;
|
|
293
|
+
}
|
|
294
|
+
|
|
234
295
|
function defaultState() {
|
|
235
296
|
return {
|
|
236
297
|
schemaVersion: 1,
|
|
@@ -572,6 +633,198 @@ async function upsertEngineeringRulesBlock({
|
|
|
572
633
|
};
|
|
573
634
|
}
|
|
574
635
|
|
|
636
|
+
function normalizeTechPackageName(value) {
|
|
637
|
+
if (value === undefined || value === null) return null;
|
|
638
|
+
const normalized = String(value).trim().replace(/^['"]|['"]$/g, "").toLowerCase();
|
|
639
|
+
return normalized || null;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function parseTomlSections(content) {
|
|
643
|
+
const sections = new Map();
|
|
644
|
+
let currentSection = "";
|
|
645
|
+
sections.set(currentSection, []);
|
|
646
|
+
|
|
647
|
+
for (const line of content.split(/\r?\n/)) {
|
|
648
|
+
const sectionMatch = line.match(/^\s*\[([^\]]+)\]\s*$/);
|
|
649
|
+
if (sectionMatch) {
|
|
650
|
+
currentSection = sectionMatch[1].trim();
|
|
651
|
+
if (!sections.has(currentSection)) sections.set(currentSection, []);
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
sections.get(currentSection).push(line);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
return sections;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function parsePubspecDependencyNames(content) {
|
|
661
|
+
const packages = new Set();
|
|
662
|
+
let currentSection = null;
|
|
663
|
+
|
|
664
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
665
|
+
const line = rawLine.replace(/\t/g, " ");
|
|
666
|
+
const trimmed = line.trim();
|
|
667
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
668
|
+
|
|
669
|
+
if (!line.startsWith(" ")) {
|
|
670
|
+
const sectionMatch = trimmed.match(/^([a-zA-Z0-9_]+):\s*$/);
|
|
671
|
+
if (!sectionMatch) {
|
|
672
|
+
currentSection = null;
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
currentSection = sectionMatch[1];
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
if (currentSection !== "dependencies" && currentSection !== "dev_dependencies") continue;
|
|
680
|
+
const depMatch = trimmed.match(/^([a-zA-Z0-9_]+):/);
|
|
681
|
+
if (!depMatch) continue;
|
|
682
|
+
const packageName = normalizeTechPackageName(depMatch[1]);
|
|
683
|
+
if (!packageName || packageName === "flutter" || packageName === "sdk") continue;
|
|
684
|
+
packages.add(packageName);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
return packages;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function parseGoModuleNames(content) {
|
|
691
|
+
const modules = new Set();
|
|
692
|
+
let inRequireBlock = false;
|
|
693
|
+
|
|
694
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
695
|
+
const trimmed = rawLine.trim();
|
|
696
|
+
if (!trimmed || trimmed.startsWith("//")) continue;
|
|
697
|
+
|
|
698
|
+
if (trimmed.startsWith("require (")) {
|
|
699
|
+
inRequireBlock = true;
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
if (inRequireBlock && trimmed === ")") {
|
|
703
|
+
inRequireBlock = false;
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
if (inRequireBlock) {
|
|
708
|
+
const moduleName = normalizeTechPackageName(trimmed.split(/\s+/)[0]);
|
|
709
|
+
if (moduleName) modules.add(moduleName);
|
|
710
|
+
continue;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
if (trimmed.startsWith("require ")) {
|
|
714
|
+
const moduleName = normalizeTechPackageName(trimmed.slice("require ".length).trim().split(/\s+/)[0]);
|
|
715
|
+
if (moduleName) modules.add(moduleName);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
return modules;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function parseRequirementsPackageNames(content) {
|
|
723
|
+
const packages = new Set();
|
|
724
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
725
|
+
const withoutComment = rawLine.split("#")[0].trim();
|
|
726
|
+
if (!withoutComment) continue;
|
|
727
|
+
if (withoutComment.startsWith("-")) continue;
|
|
728
|
+
|
|
729
|
+
const cleaned = withoutComment.split(";")[0].trim();
|
|
730
|
+
const match = cleaned.match(/^([A-Za-z0-9_.-]+)/);
|
|
731
|
+
if (!match) continue;
|
|
732
|
+
const packageName = normalizeTechPackageName(match[1]);
|
|
733
|
+
if (packageName) packages.add(packageName);
|
|
734
|
+
}
|
|
735
|
+
return packages;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
function parsePyprojectPackageNames(content) {
|
|
739
|
+
const packages = new Set();
|
|
740
|
+
const sections = parseTomlSections(content);
|
|
741
|
+
|
|
742
|
+
const projectSection = sections.get("project");
|
|
743
|
+
if (projectSection) {
|
|
744
|
+
const projectBody = projectSection.join("\n");
|
|
745
|
+
const dependenciesArrayMatch = projectBody.match(/dependencies\s*=\s*\[([\s\S]*?)\]/m);
|
|
746
|
+
if (dependenciesArrayMatch) {
|
|
747
|
+
const entries = dependenciesArrayMatch[1].match(/"([^"]+)"|'([^']+)'/g) || [];
|
|
748
|
+
for (const entry of entries) {
|
|
749
|
+
const normalizedEntry = normalizeTechPackageName(entry);
|
|
750
|
+
if (!normalizedEntry) continue;
|
|
751
|
+
const packageName = normalizeTechPackageName(normalizedEntry.split(/[<>=!~\s\[]/)[0]);
|
|
752
|
+
if (packageName) packages.add(packageName);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
for (const line of projectSection) {
|
|
757
|
+
const trimmed = line.trim();
|
|
758
|
+
const optionalDepsMatch = trimmed.match(
|
|
759
|
+
/^([A-Za-z0-9_.-]+)\s*=\s*\[\s*("([^"]+)"|'([^']+)')/
|
|
760
|
+
);
|
|
761
|
+
if (!optionalDepsMatch) continue;
|
|
762
|
+
const entry = optionalDepsMatch[3] || optionalDepsMatch[4];
|
|
763
|
+
const packageName = normalizeTechPackageName(String(entry).split(/[<>=!~\s\[]/)[0]);
|
|
764
|
+
if (packageName) packages.add(packageName);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
for (const [sectionName, lines] of sections.entries()) {
|
|
769
|
+
const isPoetryDependencySection =
|
|
770
|
+
sectionName === "tool.poetry.dependencies" ||
|
|
771
|
+
/^tool\.poetry\.group\.[^.]+\.dependencies$/.test(sectionName);
|
|
772
|
+
if (!isPoetryDependencySection) continue;
|
|
773
|
+
|
|
774
|
+
for (const line of lines) {
|
|
775
|
+
const trimmed = line.trim();
|
|
776
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
777
|
+
const depMatch = trimmed.match(/^([A-Za-z0-9_.-]+)\s*=/);
|
|
778
|
+
if (!depMatch) continue;
|
|
779
|
+
const packageName = normalizeTechPackageName(depMatch[1]);
|
|
780
|
+
if (!packageName || packageName === "python") continue;
|
|
781
|
+
packages.add(packageName);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
return packages;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
function parseCargoCrateNames(content) {
|
|
789
|
+
const crates = new Set();
|
|
790
|
+
const sections = parseTomlSections(content);
|
|
791
|
+
|
|
792
|
+
for (const [sectionName, lines] of sections.entries()) {
|
|
793
|
+
const isDependencySection =
|
|
794
|
+
sectionName === "dependencies" ||
|
|
795
|
+
sectionName === "dev-dependencies" ||
|
|
796
|
+
sectionName === "build-dependencies" ||
|
|
797
|
+
sectionName === "workspace.dependencies" ||
|
|
798
|
+
/\.dependencies$/.test(sectionName) ||
|
|
799
|
+
/\.dev-dependencies$/.test(sectionName) ||
|
|
800
|
+
/\.build-dependencies$/.test(sectionName);
|
|
801
|
+
if (!isDependencySection) continue;
|
|
802
|
+
|
|
803
|
+
for (const line of lines) {
|
|
804
|
+
const trimmed = line.trim();
|
|
805
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
806
|
+
const depMatch = trimmed.match(/^([A-Za-z0-9_-]+)\s*=/);
|
|
807
|
+
if (!depMatch) continue;
|
|
808
|
+
const crateName = normalizeTechPackageName(depMatch[1]);
|
|
809
|
+
if (crateName) crates.add(crateName);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
return crates;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
function addFrameworkSignalsFromPackages({ packages, frameworks, signals }) {
|
|
817
|
+
for (const [signal, frameworkLabel] of signals) {
|
|
818
|
+
if (packages.has(signal.toLowerCase())) {
|
|
819
|
+
frameworks.add(frameworkLabel);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
function toSortedArray(values) {
|
|
825
|
+
return [...values].sort((a, b) => a.localeCompare(b));
|
|
826
|
+
}
|
|
827
|
+
|
|
575
828
|
async function collectTechSnapshot(rootDir) {
|
|
576
829
|
const discoveredFiles = [];
|
|
577
830
|
const queue = [rootDir];
|
|
@@ -601,6 +854,12 @@ async function collectTechSnapshot(rootDir) {
|
|
|
601
854
|
|
|
602
855
|
const languageCounts = new Map();
|
|
603
856
|
const topDirs = new Set();
|
|
857
|
+
const packageJsonFiles = [];
|
|
858
|
+
const pubspecFiles = [];
|
|
859
|
+
const goModFiles = [];
|
|
860
|
+
const pyprojectFiles = [];
|
|
861
|
+
const requirementsFiles = [];
|
|
862
|
+
const cargoTomlFiles = [];
|
|
604
863
|
|
|
605
864
|
for (const fullPath of discoveredFiles) {
|
|
606
865
|
const rel = toPosixPath(path.relative(rootDir, fullPath));
|
|
@@ -609,15 +868,31 @@ async function collectTechSnapshot(rootDir) {
|
|
|
609
868
|
|
|
610
869
|
const extension = path.extname(fullPath).toLowerCase();
|
|
611
870
|
const language = TECH_LANGUAGE_BY_EXTENSION.get(extension);
|
|
612
|
-
if (
|
|
613
|
-
|
|
871
|
+
if (language) {
|
|
872
|
+
languageCounts.set(language, (languageCounts.get(language) || 0) + 1);
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
const baseName = path.basename(fullPath).toLowerCase();
|
|
876
|
+
if (baseName === "package.json") packageJsonFiles.push(fullPath);
|
|
877
|
+
if (baseName === "pubspec.yaml") pubspecFiles.push(fullPath);
|
|
878
|
+
if (baseName === "go.mod") goModFiles.push(fullPath);
|
|
879
|
+
if (baseName === "pyproject.toml") pyprojectFiles.push(fullPath);
|
|
880
|
+
if (baseName === "cargo.toml") cargoTomlFiles.push(fullPath);
|
|
881
|
+
if (baseName === "requirements.txt" || /^requirements(?:[-_.].+)?\.txt$/.test(baseName)) {
|
|
882
|
+
requirementsFiles.push(fullPath);
|
|
883
|
+
}
|
|
614
884
|
}
|
|
615
885
|
|
|
616
886
|
const fileExists = (name) => existsSync(path.join(rootDir, name));
|
|
617
|
-
const
|
|
887
|
+
const rootPackageJsonPath = path.join(rootDir, "package.json");
|
|
618
888
|
const packageScripts = new Map();
|
|
619
889
|
const frameworks = new Set();
|
|
620
890
|
const lockfiles = [];
|
|
891
|
+
const javascriptPackages = new Set();
|
|
892
|
+
const dartPackages = new Set();
|
|
893
|
+
const goModules = new Set();
|
|
894
|
+
const pythonPackages = new Set();
|
|
895
|
+
const rustCrates = new Set();
|
|
621
896
|
|
|
622
897
|
if (fileExists("bun.lock") || fileExists("bun.lockb")) lockfiles.push("bun");
|
|
623
898
|
if (fileExists("pnpm-lock.yaml")) lockfiles.push("pnpm");
|
|
@@ -628,54 +903,118 @@ async function collectTechSnapshot(rootDir) {
|
|
|
628
903
|
if (fileExists("go.sum")) lockfiles.push("go");
|
|
629
904
|
if (fileExists("pubspec.lock")) lockfiles.push("pub");
|
|
630
905
|
|
|
631
|
-
if (
|
|
632
|
-
if (
|
|
633
|
-
if (
|
|
634
|
-
if (
|
|
906
|
+
if (pubspecFiles.length > 0) frameworks.add("Flutter");
|
|
907
|
+
if (goModFiles.length > 0) frameworks.add("Go Modules");
|
|
908
|
+
if (cargoTomlFiles.length > 0) frameworks.add("Rust Cargo");
|
|
909
|
+
if (requirementsFiles.length > 0 || pyprojectFiles.length > 0) frameworks.add("Python");
|
|
635
910
|
|
|
636
|
-
|
|
911
|
+
for (const packageJsonFile of packageJsonFiles) {
|
|
637
912
|
try {
|
|
638
|
-
const parsed = JSON.parse(await readFile(
|
|
639
|
-
const scripts = parsed.scripts && typeof parsed.scripts === "object" ? parsed.scripts : {};
|
|
640
|
-
for (const [name, command] of Object.entries(scripts)) {
|
|
641
|
-
if (typeof command !== "string") continue;
|
|
642
|
-
packageScripts.set(name, command);
|
|
643
|
-
}
|
|
644
|
-
|
|
913
|
+
const parsed = JSON.parse(await readFile(packageJsonFile, "utf8"));
|
|
645
914
|
const deps = {
|
|
646
915
|
...(parsed.dependencies || {}),
|
|
647
916
|
...(parsed.devDependencies || {}),
|
|
648
|
-
...(parsed.peerDependencies || {})
|
|
917
|
+
...(parsed.peerDependencies || {}),
|
|
918
|
+
...(parsed.optionalDependencies || {})
|
|
649
919
|
};
|
|
650
|
-
const
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
[
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
["tailwindcss", "Tailwind CSS"],
|
|
662
|
-
["prisma", "Prisma"],
|
|
663
|
-
["drizzle-orm", "Drizzle ORM"],
|
|
664
|
-
["mongoose", "Mongoose"],
|
|
665
|
-
["typeorm", "TypeORM"],
|
|
666
|
-
["@playwright/test", "Playwright"],
|
|
667
|
-
["vitest", "Vitest"],
|
|
668
|
-
["jest", "Jest"],
|
|
669
|
-
["cypress", "Cypress"]
|
|
670
|
-
];
|
|
671
|
-
for (const [signal, label] of frameworkSignals) {
|
|
672
|
-
if (depNames.has(signal)) frameworks.add(label);
|
|
920
|
+
for (const depName of Object.keys(deps)) {
|
|
921
|
+
const normalized = normalizeTechPackageName(depName);
|
|
922
|
+
if (normalized) javascriptPackages.add(normalized);
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
if (path.resolve(packageJsonFile) === path.resolve(rootPackageJsonPath)) {
|
|
926
|
+
const scripts = parsed.scripts && typeof parsed.scripts === "object" ? parsed.scripts : {};
|
|
927
|
+
for (const [name, command] of Object.entries(scripts)) {
|
|
928
|
+
if (typeof command !== "string") continue;
|
|
929
|
+
packageScripts.set(name, command);
|
|
930
|
+
}
|
|
673
931
|
}
|
|
674
932
|
} catch {
|
|
675
933
|
// ignore malformed package.json
|
|
676
934
|
}
|
|
677
935
|
}
|
|
678
936
|
|
|
937
|
+
for (const pubspecFile of pubspecFiles) {
|
|
938
|
+
try {
|
|
939
|
+
const content = await readFile(pubspecFile, "utf8");
|
|
940
|
+
for (const packageName of parsePubspecDependencyNames(content)) {
|
|
941
|
+
dartPackages.add(packageName);
|
|
942
|
+
}
|
|
943
|
+
} catch {
|
|
944
|
+
// ignore malformed pubspec.yaml
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
for (const goModFile of goModFiles) {
|
|
949
|
+
try {
|
|
950
|
+
const content = await readFile(goModFile, "utf8");
|
|
951
|
+
for (const moduleName of parseGoModuleNames(content)) {
|
|
952
|
+
goModules.add(moduleName);
|
|
953
|
+
}
|
|
954
|
+
} catch {
|
|
955
|
+
// ignore unreadable go.mod
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
for (const pyprojectFile of pyprojectFiles) {
|
|
960
|
+
try {
|
|
961
|
+
const content = await readFile(pyprojectFile, "utf8");
|
|
962
|
+
for (const packageName of parsePyprojectPackageNames(content)) {
|
|
963
|
+
pythonPackages.add(packageName);
|
|
964
|
+
}
|
|
965
|
+
} catch {
|
|
966
|
+
// ignore malformed pyproject.toml
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
for (const requirementsFile of requirementsFiles) {
|
|
971
|
+
try {
|
|
972
|
+
const content = await readFile(requirementsFile, "utf8");
|
|
973
|
+
for (const packageName of parseRequirementsPackageNames(content)) {
|
|
974
|
+
pythonPackages.add(packageName);
|
|
975
|
+
}
|
|
976
|
+
} catch {
|
|
977
|
+
// ignore unreadable requirements file
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
for (const cargoTomlFile of cargoTomlFiles) {
|
|
982
|
+
try {
|
|
983
|
+
const content = await readFile(cargoTomlFile, "utf8");
|
|
984
|
+
for (const crateName of parseCargoCrateNames(content)) {
|
|
985
|
+
rustCrates.add(crateName);
|
|
986
|
+
}
|
|
987
|
+
} catch {
|
|
988
|
+
// ignore malformed Cargo.toml
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
addFrameworkSignalsFromPackages({
|
|
993
|
+
packages: javascriptPackages,
|
|
994
|
+
frameworks,
|
|
995
|
+
signals: TECH_JS_FRAMEWORK_SIGNALS
|
|
996
|
+
});
|
|
997
|
+
addFrameworkSignalsFromPackages({
|
|
998
|
+
packages: dartPackages,
|
|
999
|
+
frameworks,
|
|
1000
|
+
signals: TECH_DART_FRAMEWORK_SIGNALS
|
|
1001
|
+
});
|
|
1002
|
+
addFrameworkSignalsFromPackages({
|
|
1003
|
+
packages: goModules,
|
|
1004
|
+
frameworks,
|
|
1005
|
+
signals: TECH_GO_FRAMEWORK_SIGNALS
|
|
1006
|
+
});
|
|
1007
|
+
addFrameworkSignalsFromPackages({
|
|
1008
|
+
packages: pythonPackages,
|
|
1009
|
+
frameworks,
|
|
1010
|
+
signals: TECH_PYTHON_FRAMEWORK_SIGNALS
|
|
1011
|
+
});
|
|
1012
|
+
addFrameworkSignalsFromPackages({
|
|
1013
|
+
packages: rustCrates,
|
|
1014
|
+
frameworks,
|
|
1015
|
+
signals: TECH_RUST_FRAMEWORK_SIGNALS
|
|
1016
|
+
});
|
|
1017
|
+
|
|
679
1018
|
const sortedLanguages = [...languageCounts.entries()].sort((a, b) => b[1] - a[1]);
|
|
680
1019
|
const sortedFrameworks = [...frameworks].sort((a, b) => a.localeCompare(b));
|
|
681
1020
|
const sortedTopDirs = [...topDirs].sort((a, b) => a.localeCompare(b)).slice(0, 12);
|
|
@@ -704,10 +1043,33 @@ async function collectTechSnapshot(rootDir) {
|
|
|
704
1043
|
frameworks: sortedFrameworks,
|
|
705
1044
|
lockfiles: sortedLockfiles,
|
|
706
1045
|
topDirs: sortedTopDirs,
|
|
707
|
-
keyScripts
|
|
1046
|
+
keyScripts,
|
|
1047
|
+
packageSignals: {
|
|
1048
|
+
javascript: toSortedArray(javascriptPackages),
|
|
1049
|
+
dart: toSortedArray(dartPackages),
|
|
1050
|
+
go: toSortedArray(goModules),
|
|
1051
|
+
python: toSortedArray(pythonPackages),
|
|
1052
|
+
rust: toSortedArray(rustCrates)
|
|
1053
|
+
}
|
|
708
1054
|
};
|
|
709
1055
|
}
|
|
710
1056
|
|
|
1057
|
+
function appendTechPackageSection(lines, heading, packages) {
|
|
1058
|
+
lines.push(`### ${heading}`);
|
|
1059
|
+
if (!packages || packages.length === 0) {
|
|
1060
|
+
lines.push("- None detected.");
|
|
1061
|
+
} else {
|
|
1062
|
+
const preview = packages.slice(0, TECH_PACKAGE_PREVIEW_LIMIT);
|
|
1063
|
+
for (const packageName of preview) {
|
|
1064
|
+
lines.push(`- \`${packageName}\``);
|
|
1065
|
+
}
|
|
1066
|
+
if (packages.length > TECH_PACKAGE_PREVIEW_LIMIT) {
|
|
1067
|
+
lines.push(`- ... (+${packages.length - TECH_PACKAGE_PREVIEW_LIMIT} more)`);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
lines.push("");
|
|
1071
|
+
}
|
|
1072
|
+
|
|
711
1073
|
function buildTechMd(snapshot) {
|
|
712
1074
|
const lines = [];
|
|
713
1075
|
lines.push("# TECH.md");
|
|
@@ -737,6 +1099,13 @@ function buildTechMd(snapshot) {
|
|
|
737
1099
|
}
|
|
738
1100
|
lines.push("");
|
|
739
1101
|
|
|
1102
|
+
lines.push("## Package Signals");
|
|
1103
|
+
appendTechPackageSection(lines, "JavaScript / TypeScript (package.json)", snapshot.packageSignals.javascript);
|
|
1104
|
+
appendTechPackageSection(lines, "Dart / Flutter (pubspec.yaml)", snapshot.packageSignals.dart);
|
|
1105
|
+
appendTechPackageSection(lines, "Go Modules (go.mod)", snapshot.packageSignals.go);
|
|
1106
|
+
appendTechPackageSection(lines, "Python Packages (requirements / pyproject)", snapshot.packageSignals.python);
|
|
1107
|
+
appendTechPackageSection(lines, "Rust Crates (Cargo.toml)", snapshot.packageSignals.rust);
|
|
1108
|
+
|
|
740
1109
|
lines.push("## Tooling and Lockfiles");
|
|
741
1110
|
if (snapshot.lockfiles.length === 0) {
|
|
742
1111
|
lines.push("- No lockfiles detected.");
|
|
@@ -1872,6 +2241,237 @@ async function writeGeneratedArtifact({ destination, content, dryRun = false })
|
|
|
1872
2241
|
return { action: exists ? "replaced" : "installed", path: destination };
|
|
1873
2242
|
}
|
|
1874
2243
|
|
|
2244
|
+
function resolvePostmanSettingsPath({ scope, cwd = process.cwd() }) {
|
|
2245
|
+
if (scope === "global") {
|
|
2246
|
+
return path.join(os.homedir(), ".cbx", POSTMAN_SETTINGS_FILENAME);
|
|
2247
|
+
}
|
|
2248
|
+
const workspaceRoot = findWorkspaceRoot(cwd);
|
|
2249
|
+
return path.join(workspaceRoot, POSTMAN_SETTINGS_FILENAME);
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
function buildPostmanMcpConfig({ apiKey = null, mcpUrl = POSTMAN_MCP_URL }) {
|
|
2253
|
+
const authHeader = apiKey
|
|
2254
|
+
? `Bearer ${apiKey}`
|
|
2255
|
+
: `Bearer \${${POSTMAN_API_KEY_ENV_VAR}}`;
|
|
2256
|
+
return {
|
|
2257
|
+
mcpServers: {
|
|
2258
|
+
postman: {
|
|
2259
|
+
url: mcpUrl,
|
|
2260
|
+
headers: {
|
|
2261
|
+
Authorization: authHeader
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
},
|
|
2265
|
+
disabled: false
|
|
2266
|
+
};
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
function getPostmanApiKeySource({ apiKey, envApiKey }) {
|
|
2270
|
+
if (apiKey) return "inline";
|
|
2271
|
+
if (envApiKey) return "env";
|
|
2272
|
+
return "unset";
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
function normalizePostmanApiKey(value) {
|
|
2276
|
+
if (value === undefined || value === null) return null;
|
|
2277
|
+
const normalized = String(value).trim();
|
|
2278
|
+
return normalized || null;
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2281
|
+
async function ensureGitIgnoreEntry({
|
|
2282
|
+
filePath,
|
|
2283
|
+
entry,
|
|
2284
|
+
dryRun = false
|
|
2285
|
+
}) {
|
|
2286
|
+
const exists = await pathExists(filePath);
|
|
2287
|
+
const original = exists ? await readFile(filePath, "utf8") : "";
|
|
2288
|
+
const lines = original.split(/\r?\n/).map((line) => line.trim());
|
|
2289
|
+
const alreadyPresent = lines.includes(entry);
|
|
2290
|
+
|
|
2291
|
+
if (alreadyPresent) {
|
|
2292
|
+
return { action: "unchanged", filePath };
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
if (dryRun) {
|
|
2296
|
+
return {
|
|
2297
|
+
action: exists ? "would-patch" : "would-create",
|
|
2298
|
+
filePath
|
|
2299
|
+
};
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
const suffix = original.endsWith("\n") || original.length === 0 ? "" : "\n";
|
|
2303
|
+
const nextContent = `${original}${suffix}${entry}\n`;
|
|
2304
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
2305
|
+
await writeFile(filePath, nextContent, "utf8");
|
|
2306
|
+
return {
|
|
2307
|
+
action: exists ? "patched" : "created",
|
|
2308
|
+
filePath
|
|
2309
|
+
};
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2312
|
+
async function resolvePostmanInstallSelection({
|
|
2313
|
+
scope,
|
|
2314
|
+
options,
|
|
2315
|
+
cwd = process.cwd()
|
|
2316
|
+
}) {
|
|
2317
|
+
const hasApiKeyOption = options.postmanApiKey !== undefined;
|
|
2318
|
+
const hasWorkspaceOption = options.postmanWorkspaceId !== undefined;
|
|
2319
|
+
const enabled = Boolean(options.postman) || hasApiKeyOption || hasWorkspaceOption;
|
|
2320
|
+
if (!enabled) return { enabled: false };
|
|
2321
|
+
|
|
2322
|
+
const explicitApiKey = hasApiKeyOption ? String(options.postmanApiKey || "").trim() : "";
|
|
2323
|
+
let apiKey = explicitApiKey || null;
|
|
2324
|
+
const envApiKey = String(process.env[POSTMAN_API_KEY_ENV_VAR] || "").trim();
|
|
2325
|
+
let defaultWorkspaceId = hasWorkspaceOption
|
|
2326
|
+
? normalizePostmanWorkspaceId(options.postmanWorkspaceId)
|
|
2327
|
+
: null;
|
|
2328
|
+
const warnings = [];
|
|
2329
|
+
|
|
2330
|
+
const canPrompt = !options.yes && process.stdin.isTTY;
|
|
2331
|
+
if (canPrompt && !hasApiKeyOption && !apiKey && !envApiKey) {
|
|
2332
|
+
const promptedApiKey = String(
|
|
2333
|
+
await input({
|
|
2334
|
+
message: `Postman API key (optional, leave blank to keep ${POSTMAN_API_KEY_ENV_VAR} env mode):`,
|
|
2335
|
+
default: ""
|
|
2336
|
+
})
|
|
2337
|
+
).trim();
|
|
2338
|
+
if (promptedApiKey) {
|
|
2339
|
+
apiKey = promptedApiKey;
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
if (canPrompt && !hasWorkspaceOption) {
|
|
2344
|
+
const promptedWorkspaceId = await input({
|
|
2345
|
+
message: "Default Postman workspace ID (optional, leave blank for null):",
|
|
2346
|
+
default: ""
|
|
2347
|
+
});
|
|
2348
|
+
defaultWorkspaceId = normalizePostmanWorkspaceId(promptedWorkspaceId);
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
const apiKeySource = getPostmanApiKeySource({ apiKey, envApiKey });
|
|
2352
|
+
if (apiKeySource === "unset") {
|
|
2353
|
+
warnings.push(POSTMAN_API_KEY_MISSING_WARNING);
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
const settingsPath = resolvePostmanSettingsPath({ scope, cwd });
|
|
2357
|
+
const settings = {
|
|
2358
|
+
apiKey: apiKey || null,
|
|
2359
|
+
apiKeyEnvVar: POSTMAN_API_KEY_ENV_VAR,
|
|
2360
|
+
apiKeySource,
|
|
2361
|
+
defaultWorkspaceId: defaultWorkspaceId ?? null,
|
|
2362
|
+
mcpUrl: POSTMAN_MCP_URL,
|
|
2363
|
+
generatedBy: "cbx workflows install --postman",
|
|
2364
|
+
generatedAt: new Date().toISOString()
|
|
2365
|
+
};
|
|
2366
|
+
|
|
2367
|
+
return {
|
|
2368
|
+
enabled: true,
|
|
2369
|
+
apiKey,
|
|
2370
|
+
apiKeySource,
|
|
2371
|
+
defaultWorkspaceId: defaultWorkspaceId ?? null,
|
|
2372
|
+
warnings,
|
|
2373
|
+
settings,
|
|
2374
|
+
settingsPath
|
|
2375
|
+
};
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
async function configurePostmanInstallArtifacts({
|
|
2379
|
+
scope,
|
|
2380
|
+
profilePaths,
|
|
2381
|
+
postmanSelection,
|
|
2382
|
+
overwrite = false,
|
|
2383
|
+
dryRun = false,
|
|
2384
|
+
cwd = process.cwd()
|
|
2385
|
+
}) {
|
|
2386
|
+
if (!postmanSelection?.enabled) return null;
|
|
2387
|
+
|
|
2388
|
+
let warnings = postmanSelection.warnings.filter((warning) => warning !== POSTMAN_API_KEY_MISSING_WARNING);
|
|
2389
|
+
const settingsContent = `${JSON.stringify(postmanSelection.settings, null, 2)}\n`;
|
|
2390
|
+
const settingsResult = await writeTextFile({
|
|
2391
|
+
targetPath: postmanSelection.settingsPath,
|
|
2392
|
+
content: settingsContent,
|
|
2393
|
+
overwrite,
|
|
2394
|
+
dryRun
|
|
2395
|
+
});
|
|
2396
|
+
|
|
2397
|
+
let effectiveApiKey = normalizePostmanApiKey(postmanSelection.settings.apiKey);
|
|
2398
|
+
let effectiveDefaultWorkspaceId = postmanSelection.defaultWorkspaceId ?? null;
|
|
2399
|
+
let effectiveMcpUrl = postmanSelection.settings.mcpUrl || POSTMAN_MCP_URL;
|
|
2400
|
+
|
|
2401
|
+
if (settingsResult.action === "skipped" || settingsResult.action === "would-skip") {
|
|
2402
|
+
try {
|
|
2403
|
+
const existingSettingsRaw = await readFile(postmanSelection.settingsPath, "utf8");
|
|
2404
|
+
const existingSettings = JSON.parse(existingSettingsRaw);
|
|
2405
|
+
effectiveApiKey = normalizePostmanApiKey(existingSettings?.apiKey);
|
|
2406
|
+
effectiveDefaultWorkspaceId = normalizePostmanWorkspaceId(existingSettings?.defaultWorkspaceId);
|
|
2407
|
+
const existingMcpUrl = String(existingSettings?.mcpUrl || "").trim();
|
|
2408
|
+
if (existingMcpUrl) {
|
|
2409
|
+
effectiveMcpUrl = existingMcpUrl;
|
|
2410
|
+
}
|
|
2411
|
+
} catch {
|
|
2412
|
+
warnings.push(
|
|
2413
|
+
`Existing ${POSTMAN_SETTINGS_FILENAME} could not be parsed. Using install-time Postman values for MCP config.`
|
|
2414
|
+
);
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
|
|
2418
|
+
const envApiKey = normalizePostmanApiKey(process.env[POSTMAN_API_KEY_ENV_VAR]);
|
|
2419
|
+
const effectiveApiKeySource = getPostmanApiKeySource({
|
|
2420
|
+
apiKey: effectiveApiKey,
|
|
2421
|
+
envApiKey
|
|
2422
|
+
});
|
|
2423
|
+
if (effectiveApiKeySource === "unset") {
|
|
2424
|
+
warnings.push(POSTMAN_API_KEY_MISSING_WARNING);
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
let gitIgnoreResult = null;
|
|
2428
|
+
if (scope === "project") {
|
|
2429
|
+
const workspaceRoot = findWorkspaceRoot(cwd);
|
|
2430
|
+
const gitIgnorePath = path.join(workspaceRoot, ".gitignore");
|
|
2431
|
+
gitIgnoreResult = await ensureGitIgnoreEntry({
|
|
2432
|
+
filePath: gitIgnorePath,
|
|
2433
|
+
entry: POSTMAN_SETTINGS_FILENAME,
|
|
2434
|
+
dryRun
|
|
2435
|
+
});
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
const postmanSkillDir = path.join(profilePaths.skillsDir, POSTMAN_SKILL_ID);
|
|
2439
|
+
const postmanMcpPath = path.join(postmanSkillDir, "mcp.json");
|
|
2440
|
+
let mcpResult = null;
|
|
2441
|
+
const postmanSkillExists = await pathExists(postmanSkillDir);
|
|
2442
|
+
if (!dryRun && !postmanSkillExists) {
|
|
2443
|
+
mcpResult = {
|
|
2444
|
+
action: "missing-postman-skill",
|
|
2445
|
+
path: postmanMcpPath
|
|
2446
|
+
};
|
|
2447
|
+
} else {
|
|
2448
|
+
const mcpConfigContent = `${JSON.stringify(
|
|
2449
|
+
buildPostmanMcpConfig({
|
|
2450
|
+
apiKey: effectiveApiKey,
|
|
2451
|
+
mcpUrl: effectiveMcpUrl
|
|
2452
|
+
}),
|
|
2453
|
+
null,
|
|
2454
|
+
2
|
|
2455
|
+
)}\n`;
|
|
2456
|
+
mcpResult = await writeGeneratedArtifact({
|
|
2457
|
+
destination: postmanMcpPath,
|
|
2458
|
+
content: mcpConfigContent,
|
|
2459
|
+
dryRun
|
|
2460
|
+
});
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2463
|
+
return {
|
|
2464
|
+
enabled: true,
|
|
2465
|
+
apiKeySource: effectiveApiKeySource,
|
|
2466
|
+
defaultWorkspaceId: effectiveDefaultWorkspaceId,
|
|
2467
|
+
warnings,
|
|
2468
|
+
settingsPath: postmanSelection.settingsPath,
|
|
2469
|
+
settingsResult,
|
|
2470
|
+
gitIgnoreResult,
|
|
2471
|
+
mcpResult
|
|
2472
|
+
};
|
|
2473
|
+
}
|
|
2474
|
+
|
|
1875
2475
|
async function installAntigravityTerminalIntegrationArtifacts({
|
|
1876
2476
|
profilePaths,
|
|
1877
2477
|
provider,
|
|
@@ -2032,6 +2632,7 @@ async function installBundleArtifacts({
|
|
|
2032
2632
|
platform,
|
|
2033
2633
|
scope,
|
|
2034
2634
|
overwrite,
|
|
2635
|
+
extraSkillIds = [],
|
|
2035
2636
|
terminalVerifierSelection = null,
|
|
2036
2637
|
dryRun = false,
|
|
2037
2638
|
cwd = process.cwd()
|
|
@@ -2092,7 +2693,8 @@ async function installBundleArtifacts({
|
|
|
2092
2693
|
else installed.push(destination);
|
|
2093
2694
|
}
|
|
2094
2695
|
|
|
2095
|
-
const
|
|
2696
|
+
const manifestSkillIds = Array.isArray(platformSpec.skills) ? platformSpec.skills : [];
|
|
2697
|
+
const skillIds = unique([...manifestSkillIds, ...extraSkillIds.filter(Boolean)]);
|
|
2096
2698
|
for (const skillId of skillIds) {
|
|
2097
2699
|
const source = path.join(agentAssetsRoot(), "skills", skillId);
|
|
2098
2700
|
const destination = path.join(profilePaths.skillsDir, skillId);
|
|
@@ -2389,6 +2991,32 @@ function printInstallSummary({
|
|
|
2389
2991
|
}
|
|
2390
2992
|
}
|
|
2391
2993
|
|
|
2994
|
+
function printPostmanSetupSummary({ postmanSetup }) {
|
|
2995
|
+
if (!postmanSetup?.enabled) return;
|
|
2996
|
+
|
|
2997
|
+
console.log("\nPostman setup:");
|
|
2998
|
+
console.log(`- Settings file: ${postmanSetup.settingsResult.action} (${postmanSetup.settingsPath})`);
|
|
2999
|
+
console.log(`- API key source: ${postmanSetup.apiKeySource}`);
|
|
3000
|
+
console.log(
|
|
3001
|
+
`- Default workspace ID: ${postmanSetup.defaultWorkspaceId === null ? "null" : postmanSetup.defaultWorkspaceId}`
|
|
3002
|
+
);
|
|
3003
|
+
if (postmanSetup.gitIgnoreResult) {
|
|
3004
|
+
console.log(
|
|
3005
|
+
`- .gitignore (${postmanSetup.gitIgnoreResult.filePath}): ${postmanSetup.gitIgnoreResult.action}`
|
|
3006
|
+
);
|
|
3007
|
+
}
|
|
3008
|
+
if (postmanSetup.mcpResult) {
|
|
3009
|
+
console.log(`- Postman MCP config (${postmanSetup.mcpResult.path}): ${postmanSetup.mcpResult.action}`);
|
|
3010
|
+
}
|
|
3011
|
+
|
|
3012
|
+
if (postmanSetup.warnings.length > 0) {
|
|
3013
|
+
console.log("- Warnings:");
|
|
3014
|
+
for (const warning of postmanSetup.warnings) {
|
|
3015
|
+
console.log(` - ${warning}`);
|
|
3016
|
+
}
|
|
3017
|
+
}
|
|
3018
|
+
}
|
|
3019
|
+
|
|
2392
3020
|
function printRemoveSummary({
|
|
2393
3021
|
platform,
|
|
2394
3022
|
scope,
|
|
@@ -2694,6 +3322,18 @@ function withInstallOptions(command) {
|
|
|
2694
3322
|
return withWorkflowBaseOptions(command)
|
|
2695
3323
|
.option("-b, --bundle <bundle>", "bundle id (default: agent-environment-setup)")
|
|
2696
3324
|
.option("--overwrite", "overwrite existing files")
|
|
3325
|
+
.option(
|
|
3326
|
+
"--postman",
|
|
3327
|
+
"optional: install Postman skill and generate postman_setting.json"
|
|
3328
|
+
)
|
|
3329
|
+
.option(
|
|
3330
|
+
"--postman-api-key <key>",
|
|
3331
|
+
"optional: set Postman API key inline for generated postman_setting.json and installed Postman MCP config"
|
|
3332
|
+
)
|
|
3333
|
+
.option(
|
|
3334
|
+
"--postman-workspace-id <id|null>",
|
|
3335
|
+
"optional: set default Postman workspace ID (use 'null' for no default)"
|
|
3336
|
+
)
|
|
2697
3337
|
.option(
|
|
2698
3338
|
"--terminal-integration",
|
|
2699
3339
|
"Antigravity only: enable terminal verification integration (prompts for verifier when interactive)"
|
|
@@ -2854,6 +3494,11 @@ async function runWorkflowInstall(options) {
|
|
|
2854
3494
|
platform,
|
|
2855
3495
|
options
|
|
2856
3496
|
});
|
|
3497
|
+
const postmanSelection = await resolvePostmanInstallSelection({
|
|
3498
|
+
scope,
|
|
3499
|
+
options,
|
|
3500
|
+
cwd: process.cwd()
|
|
3501
|
+
});
|
|
2857
3502
|
|
|
2858
3503
|
const installResult = await installBundleArtifacts({
|
|
2859
3504
|
bundleId,
|
|
@@ -2861,6 +3506,7 @@ async function runWorkflowInstall(options) {
|
|
|
2861
3506
|
platform,
|
|
2862
3507
|
scope,
|
|
2863
3508
|
overwrite: Boolean(options.overwrite),
|
|
3509
|
+
extraSkillIds: postmanSelection.enabled ? [POSTMAN_SKILL_ID] : [],
|
|
2864
3510
|
terminalVerifierSelection,
|
|
2865
3511
|
dryRun,
|
|
2866
3512
|
cwd: process.cwd()
|
|
@@ -2890,6 +3536,14 @@ async function runWorkflowInstall(options) {
|
|
|
2890
3536
|
skipTech: false,
|
|
2891
3537
|
cwd: process.cwd()
|
|
2892
3538
|
});
|
|
3539
|
+
const postmanSetupResult = await configurePostmanInstallArtifacts({
|
|
3540
|
+
scope,
|
|
3541
|
+
profilePaths: installResult.profilePaths,
|
|
3542
|
+
postmanSelection,
|
|
3543
|
+
overwrite: Boolean(options.overwrite),
|
|
3544
|
+
dryRun,
|
|
3545
|
+
cwd: process.cwd()
|
|
3546
|
+
});
|
|
2893
3547
|
|
|
2894
3548
|
const terminalVerificationRuleResult =
|
|
2895
3549
|
platform === "antigravity" && installResult.terminalIntegration
|
|
@@ -2930,6 +3584,9 @@ async function runWorkflowInstall(options) {
|
|
|
2930
3584
|
engineeringResults: engineeringArtifactsResult.engineeringResults,
|
|
2931
3585
|
techResult: engineeringArtifactsResult.techResult
|
|
2932
3586
|
});
|
|
3587
|
+
printPostmanSetupSummary({
|
|
3588
|
+
postmanSetup: postmanSetupResult
|
|
3589
|
+
});
|
|
2933
3590
|
if (dryRun) {
|
|
2934
3591
|
console.log("\nDry-run complete. Re-run without `--dry-run` to apply changes.");
|
|
2935
3592
|
} else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cubis/foundry",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.15",
|
|
4
4
|
"description": "Cubis Foundry CLI for workflow-first AI agent environments",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"check": "node bin/cubis.js --help",
|
|
19
19
|
"test:attributes": "node scripts/validate-platform-attributes.mjs",
|
|
20
20
|
"test:attributes:strict": "node scripts/validate-platform-attributes.mjs --strict",
|
|
21
|
+
"test:tech-md": "node scripts/test-tech-md-scanner.mjs",
|
|
21
22
|
"test:smoke": "bash scripts/smoke-workflows.sh",
|
|
22
23
|
"test:all": "npm run test:attributes && npm run test:smoke",
|
|
23
24
|
"test:all:strict": "npm run test:attributes:strict && npm run test:smoke",
|