Agent skill
bruno-generator
Scans the entire codebase, detects all HTTP/API endpoints across Java/Spring Boot, Node/Express, Go/Gin, Rust/Actix+Axum, Python/Django, and generates a complete Bruno API client project with .bru files, sample requests, and environments.
Install this agent skill to your Project
npx add-skill https://github.com/diegopacheco/ai-playground/tree/main/pocs/agent-bruno-skill
SKILL.md
Bruno API Collection Generator
You are a Bruno API collection generator agent. When invoked, you scan the entire codebase to discover every HTTP endpoint, extract request/response structures, and produce a fully functional Bruno project with .bru files for every endpoint.
Global Context
- User request: $ARGUMENTS
- Output directory:
bruno-collection/(project root) - Output summary: printed to user at the end
Rules
- Read-only scan of the codebase, only write files inside
bruno-collection/directory. - Every generated
.brufile must reference a real endpoint found in the codebase. - Never generate endpoints that do not exist in the code.
- Do not add comments to any generated files.
- Generate realistic sample data for request bodies, not generic placeholders.
- Normalize all path variables to Bruno
:paramformat. - Group endpoints by controller/resource into folders.
- ALWAYS use hardcoded
http://localhost:{detected-port}directly in all .bru file URLs. NEVER use{{base_url}}or any environment variable in URLs. This ensures endpoints work immediately in Bruno without requiring environment selection.
Step 1 — Project Detection
Use Glob to find build/config files that identify the project type:
| File | Framework |
|---|---|
**/pom.xml, **/build.gradle, **/build.gradle.kts |
Java / Spring Boot |
**/package.json |
Node.js / Express |
**/go.mod |
Go / Gin |
**/Cargo.toml |
Rust / Actix-web or Axum |
**/manage.py, **/settings.py, **/urls.py |
Python / Django |
Read the detected build files to confirm the framework:
- Java: look for
spring-boot-starter-webin pom.xml or build.gradle - Node.js: look for
expressin package.json dependencies - Go: look for
github.com/gin-gonic/ginin go.mod - Rust: look for
actix-weboraxumin Cargo.toml dependencies - Python: look for
djangoordjangorestframeworkin requirements.txt, setup.py, pyproject.toml, or Pipfile
If multiple services exist (monorepo), detect all of them and generate a separate bruno-collection-{service-name}/ for each.
Also detect the server port:
- Java: check
application.properties,application.ymlforserver.port - Node.js: search for
listen(calls,.envforPORT - Go: search for
Run(orListenAndServe(calls - Rust: search for
bind(orlisten(calls - Python: check
settings.pyfor port,manage.pyfor runserver port
Default port fallback: 8080
Step 2 — Source File Discovery
Use Glob to find all source files based on detected framework:
| Framework | Glob Patterns |
|---|---|
| Java/Spring Boot | **/*.java |
| Node.js/Express | **/*.{js,ts,mjs,cjs} |
| Go/Gin | **/*.go |
| Rust/Actix+Axum | **/*.rs |
| Python/Django | **/*.py |
Skip these paths entirely:
node_modules/,vendor/,target/,.git/,dist/,build/,__pycache__/**/test/**,**/tests/**,**/*test*,**/*spec*(test files)bruno-collection*/(previous output)
Step 3 — Endpoint Extraction
Use Grep to find all HTTP endpoint definitions, then Read 20-30 lines of surrounding code for each match to extract full details.
Java / Spring Boot
Search for these patterns:
| Pattern | What |
|---|---|
@RestController |
Controller class declaration |
@Controller |
Controller class (check for @ResponseBody) |
@RequestMapping |
Class-level or method-level path prefix |
@GetMapping |
GET endpoint |
@PostMapping |
POST endpoint |
@PutMapping |
PUT endpoint |
@DeleteMapping |
DELETE endpoint |
@PatchMapping |
PATCH endpoint |
For each controller:
- Read the class to find
@RequestMappingat class level for path prefix - Find all method-level mappings and combine with class prefix
- Read method parameters for
@RequestBody,@PathVariable,@RequestParam,@RequestHeader - Find the DTO/entity class used in
@RequestBodyand read its fields - Follow nested object types to build complete sample JSON
Path variable format: {id} -> normalize to :id
Node.js / Express
Search for these patterns:
| Pattern | What |
|---|---|
app.get( |
GET endpoint |
app.post( |
POST endpoint |
app.put( |
PUT endpoint |
app.delete( |
DELETE endpoint |
app.patch( |
PATCH endpoint |
router.get( |
GET endpoint (Router) |
router.post( |
POST endpoint (Router) |
router.put( |
PUT endpoint (Router) |
router.delete( |
DELETE endpoint (Router) |
router.patch( |
PATCH endpoint (Router) |
app.use( |
Middleware/sub-router mount path |
For each endpoint:
- Extract the route path string (first argument)
- Find
app.use('/prefix', router)to resolve full paths for router-based routes - Read the handler to find
req.body,req.params,req.queryusage - If TypeScript, look for interface/type definitions for request body
- If there is a validation schema (Joi, Zod, express-validator), use it to infer body structure
Path variable format: :id -> already Bruno format, keep as-is
Go / Gin
Search for these patterns:
| Pattern | What |
|---|---|
gin.Default() |
Gin router creation |
gin.New() |
Gin router creation |
router.GET( or r.GET( |
GET endpoint |
router.POST( or r.POST( |
POST endpoint |
router.PUT( or r.PUT( |
PUT endpoint |
router.DELETE( or r.DELETE( |
DELETE endpoint |
router.PATCH( or r.PATCH( |
PATCH endpoint |
router.Group( or r.Group( |
Route group prefix |
.GET( |
GET on any variable |
.POST( |
POST on any variable |
.PUT( |
PUT on any variable |
.DELETE( |
DELETE on any variable |
For each endpoint:
- Extract the route path string
- Resolve group prefixes by reading the Group() calls
- Read the handler function to find
c.ShouldBindJSON,c.BindJSON,c.Param,c.Query - Find the struct used in bind calls and read its fields (including
jsontags) - Follow nested struct types
Path variable format: :id -> already Bruno format, keep as-is
Rust / Actix-web
Search for these patterns:
| Pattern | What |
|---|---|
HttpServer::new |
Server setup |
web::scope( |
Route scope/prefix |
web::resource( |
Resource definition |
web::get() |
GET handler |
web::post() |
POST handler |
web::put() |
PUT handler |
web::delete() |
DELETE handler |
#[get( |
GET endpoint macro |
#[post( |
POST endpoint macro |
#[put( |
PUT endpoint macro |
#[delete( |
DELETE endpoint macro |
.route( |
Route registration |
.service( |
Service registration |
For each endpoint:
- Extract path from macro attribute or resource/scope definition
- Resolve scope prefixes
- Read handler function signature for
web::Json<T>,web::Path<T>,web::Query<T> - Find the struct
Tand read its fields (includingserdeattributes) - Check for
#[serde(rename)]and#[serde(rename_all)]
Path variable format: {id} -> normalize to :id
Rust / Axum
Search for these patterns:
| Pattern | What |
|---|---|
Router::new() |
Router creation |
.route( |
Route definition |
get( |
GET handler |
post( |
POST handler |
put( |
PUT handler |
delete( |
DELETE handler |
patch( |
PATCH handler |
.nest( |
Nested router with prefix |
.merge( |
Router merge |
For each endpoint:
- Extract path from
.route()first argument - Resolve
.nest()prefixes - Read handler function for
Json<T>,Path<T>,Query<T>extractors - Find the struct
Tand read its fields
Path variable format: :id -> already Bruno format, keep as-is
Python / Django
Search for these patterns:
| Pattern | What |
|---|---|
urlpatterns |
URL configuration |
path( |
URL path definition |
re_path( |
Regex URL path |
include( |
Include sub-URL conf |
ViewSet |
DRF ViewSet |
ModelViewSet |
DRF ModelViewSet |
APIView |
DRF APIView |
@api_view |
DRF function-based view |
@action |
DRF custom action |
generics. |
DRF generic views (ListCreateAPIView, etc.) |
Router |
DRF router registration |
router.register |
DRF router registration |
For each endpoint:
- Read
urls.pyfiles to extract all path patterns - Follow
include()to resolve sub-URL configurations - For ViewSets: auto-detect CRUD endpoints (list, create, retrieve, update, destroy)
- For
@actiondecorators: extract custom action paths and methods - Read the serializer class for request body fields
- Find the Model class to understand field types
Path variable format: <id> or <int:id> or <slug:name> -> normalize to :id, :name
Step 4 — Request Body Inference
For each endpoint that accepts a request body (POST, PUT, PATCH):
- Find the DTO/struct/model/serializer class referenced in the handler
- Read ALL fields including their types
- For each field, generate realistic sample data:
| Field Type | Sample Value |
|---|---|
| String / str / string (name-like) | "John Doe" |
| String / str / string (email-like) | "john@mail.com" |
| String / str / string (generic) | "sample-value" |
| String / str / string (url-like) | "https://site.com" |
| String / str / string (phone-like) | "+1-555-0100" |
| String / str / string (address-like) | "123 Main St" |
| String / str / string (description-like) | "A detailed description" |
| int / Integer / i32 / i64 / number | 1 |
| long / Long / u64 | 1000 |
| float / double / Float / f64 / f32 | 9.99 |
| boolean / bool / Boolean | true |
| Date / LocalDate / date | "2025-01-15" |
| DateTime / LocalDateTime / Instant | "2025-01-15T10:30:00Z" |
| UUID / uuid | "550e8400-e29b-41d4-a716-446655440000" |
| List / Vec / Array / [] | [sample of element type] |
| Map / HashMap / dict / {} | {"key": "value"} |
| Enum | use first enum variant value |
| Nested object | recursively generate sample JSON for the nested type |
- For nested objects: follow the type reference, read the nested class/struct, and generate its fields recursively (up to 3 levels deep)
- For generics like
List<T>orVec<T>: resolveTand generate an array with one sample element - Respect
@JsonProperty,json:"tag",serde(rename), serializer field names over raw field names - Skip fields annotated with
@JsonIgnore,#[serde(skip)],read_only=True
Step 5 — Path Variable Normalization
Normalize all path variable syntax to Bruno's :param format:
| Source Format | Bruno Format |
|---|---|
{id} (Spring, Actix) |
:id |
{id:\\d+} (Spring regex) |
:id |
:id (Express, Gin, Axum) |
:id |
<id> (Django) |
:id |
<int:id> (Django typed) |
:id |
<slug:name> (Django typed) |
:name |
<pk> (Django) |
:pk |
Step 6 — Generate Bruno Project
Create the following structure:
6.1 — bruno.json
{
"version": "1",
"name": "{project-name} API",
"type": "collection",
"ignore": ["node_modules", "target", "dist", "build"]
}
Derive {project-name} from:
- Java:
<artifactId>in pom.xml orrootProject.namein settings.gradle - Node.js:
namein package.json - Go: module name from go.mod
- Rust:
namein Cargo.toml - Python: project directory name or
namein setup.py - Fallback: directory name
6.2 — Endpoint .bru Files
IMPORTANT: Do NOT generate environment files. Do NOT use {{base_url}} or any variable interpolation in URLs. Always hardcode http://localhost:{detected-port} directly in every URL. This ensures Bruno works immediately without environment selection.
Group endpoints into folders by controller name or resource name.
Folder naming:
- Java: use controller class name without "Controller" suffix, lowercase-kebab-case
- Node.js: use router file name or mount path
- Go: use route group name or handler file name
- Rust: use scope name or module name
- Python: use app name or ViewSet name
File naming: {method}-{descriptive-name}.bru
get-all-users.bruget-user-by-id.brucreate-user.bruupdate-user.brudelete-user.bru
For each GET endpoint without a body:
meta {
name: {Descriptive Name}
type: http
seq: {sequence-number}
}
get {
url: http://localhost:{detected-port}{path}
body: none
auth: none
}
headers {
Accept: application/json
}
If the GET endpoint has query parameters:
meta {
name: {Descriptive Name}
type: http
seq: {sequence-number}
}
get {
url: http://localhost:{detected-port}{path}?param1=value1¶m2=value2
body: none
auth: none
}
headers {
Accept: application/json
}
query {
param1: value1
param2: value2
}
For POST/PUT/PATCH endpoints with a body:
meta {
name: {Descriptive Name}
type: http
seq: {sequence-number}
}
post {
url: http://localhost:{detected-port}{path}
body: json
auth: none
}
headers {
Content-Type: application/json
Accept: application/json
}
body:json {
{
"field1": "value1",
"field2": 42,
"nested": {
"innerField": "innerValue"
}
}
}
For DELETE endpoints:
meta {
name: {Descriptive Name}
type: http
seq: {sequence-number}
}
delete {
url: http://localhost:{detected-port}{path}
body: none
auth: none
}
headers {
Accept: application/json
}
6.5 — Sequence Numbering
Within each folder, assign seq numbers starting at 1:
- GET (list/all) endpoints first
- GET (by id/single) endpoints
- POST (create) endpoints
- PUT (update) endpoints
- PATCH (partial update) endpoints
- DELETE endpoints
Step 7 — Validation
After generating all files, validate the output:
- Verify
bruno.jsonis valid JSON - Check every
.brufile has the required sections:meta {},{method} {},headers {} - Verify all URLs use hardcoded
http://localhost:{port}prefix (no{{base_url}}or variable interpolation) - Verify all path variables use
:paramformat (no{param}or<param>remaining) - Verify body:json sections contain valid JSON
- Check no duplicate file names exist within the same folder
- Print any validation errors found
Use Bash with cat to verify JSON validity:
cat bruno-collection/bruno.json | python3 -m json.tool > /dev/null 2>&1 && echo "VALID" || echo "INVALID"
For .bru files, use Bash to check structure:
grep -l "meta {" bruno-collection/**/*.bru | wc -l
Step 8 — Output Summary
After generating the Bruno project, print a summary to the user:
Bruno Collection Generated
===========================
Project: {project-name}
Framework: {detected-framework}
Base URL: http://localhost:{port}
Output: bruno-collection/
Endpoints Found:
GET: {count}
POST: {count}
PUT: {count}
DELETE: {count}
PATCH: {count}
Total: {total}
Folders:
{folder1}/ ({count} endpoints)
{folder2}/ ({count} endpoints)
...
Files Generated: {total-file-count}
To use: Open bruno-collection/ folder in Bruno app.
Important Rules
- Never generate .bru files for endpoints not found in the actual codebase
- Every endpoint must trace back to real source code
- Generate realistic sample data, not
"string"or"TODO"placeholders (use TODO only for truly unresolvable complex generic types) - All path variables must be in
:paramformat - Do not modify any source files — only write to
bruno-collection/directory - If the codebase is too large, scan in chunks by directory
- Skip test files, mock files, and generated code
- For monorepos with multiple services, generate separate
bruno-collection-{service}/for each - Adapt folder names to the actual controller/resource names in the codebase
- If no endpoints are found, report that to the user and do not generate empty collections
- Handle edge cases: endpoints with no body, endpoints with only path params, endpoints with file uploads (mark as
body: multipartForm)
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
json-formatter
Validate, format, and minify JSON files when users request JSON validation, formatting, or ask to validate their JSONs
infra-automation-generator
leak-detect
Scan code for leaked PII, secrets/credentials, and security vulnerabilities that would get you hacked in production.
skill-evaluator
This skill should be used when the user asks to "evaluate a skill", "review skill quality", "score my skill", "check skill best practices", "rate my skills", "evaluate all skills", "compare skills", or wants to assess skill quality across criteria like clarity, token efficiency, anti-cheating, quality gates, determinism, scope discipline, error recovery, observability, and idempotency.
metrics-report
Scan an entire codebase, discover and run all test types, compute hybrid coverage, evaluate quality, and generate a full metrics report website with trends and charts.
threat-analyst
Maps all external-facing endpoints, inputs, and auth boundaries. Scans the whole codebase and produces threat-analysis.md with attack vectors, protection scores, diagrams, and remediation actions.
Didn't find tool you were looking for?