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.

Stars 15
Forks 7

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 .bru file 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 :param format.
  • 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-web in pom.xml or build.gradle
  • Node.js: look for express in package.json dependencies
  • Go: look for github.com/gin-gonic/gin in go.mod
  • Rust: look for actix-web or axum in Cargo.toml dependencies
  • Python: look for django or djangorestframework in 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.yml for server.port
  • Node.js: search for listen( calls, .env for PORT
  • Go: search for Run( or ListenAndServe( calls
  • Rust: search for bind( or listen( calls
  • Python: check settings.py for port, manage.py for 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:

  1. Read the class to find @RequestMapping at class level for path prefix
  2. Find all method-level mappings and combine with class prefix
  3. Read method parameters for @RequestBody, @PathVariable, @RequestParam, @RequestHeader
  4. Find the DTO/entity class used in @RequestBody and read its fields
  5. 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:

  1. Extract the route path string (first argument)
  2. Find app.use('/prefix', router) to resolve full paths for router-based routes
  3. Read the handler to find req.body, req.params, req.query usage
  4. If TypeScript, look for interface/type definitions for request body
  5. 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:

  1. Extract the route path string
  2. Resolve group prefixes by reading the Group() calls
  3. Read the handler function to find c.ShouldBindJSON, c.BindJSON, c.Param, c.Query
  4. Find the struct used in bind calls and read its fields (including json tags)
  5. 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:

  1. Extract path from macro attribute or resource/scope definition
  2. Resolve scope prefixes
  3. Read handler function signature for web::Json<T>, web::Path<T>, web::Query<T>
  4. Find the struct T and read its fields (including serde attributes)
  5. 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:

  1. Extract path from .route() first argument
  2. Resolve .nest() prefixes
  3. Read handler function for Json<T>, Path<T>, Query<T> extractors
  4. Find the struct T and 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:

  1. Read urls.py files to extract all path patterns
  2. Follow include() to resolve sub-URL configurations
  3. For ViewSets: auto-detect CRUD endpoints (list, create, retrieve, update, destroy)
  4. For @action decorators: extract custom action paths and methods
  5. Read the serializer class for request body fields
  6. 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):

  1. Find the DTO/struct/model/serializer class referenced in the handler
  2. Read ALL fields including their types
  3. 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
  1. For nested objects: follow the type reference, read the nested class/struct, and generate its fields recursively (up to 3 levels deep)
  2. For generics like List<T> or Vec<T>: resolve T and generate an array with one sample element
  3. Respect @JsonProperty, json:"tag", serde(rename), serializer field names over raw field names
  4. 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

json
{
  "version": "1",
  "name": "{project-name} API",
  "type": "collection",
  "ignore": ["node_modules", "target", "dist", "build"]
}

Derive {project-name} from:

  • Java: <artifactId> in pom.xml or rootProject.name in settings.gradle
  • Node.js: name in package.json
  • Go: module name from go.mod
  • Rust: name in Cargo.toml
  • Python: project directory name or name in 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.bru
  • get-user-by-id.bru
  • create-user.bru
  • update-user.bru
  • delete-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&param2=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:

  1. GET (list/all) endpoints first
  2. GET (by id/single) endpoints
  3. POST (create) endpoints
  4. PUT (update) endpoints
  5. PATCH (partial update) endpoints
  6. DELETE endpoints

Step 7 — Validation

After generating all files, validate the output:

  1. Verify bruno.json is valid JSON
  2. Check every .bru file has the required sections: meta {}, {method} {}, headers {}
  3. Verify all URLs use hardcoded http://localhost:{port} prefix (no {{base_url}} or variable interpolation)
  4. Verify all path variables use :param format (no {param} or <param> remaining)
  5. Verify body:json sections contain valid JSON
  6. Check no duplicate file names exist within the same folder
  7. 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 :param format
  • 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)

Expand your agent's capabilities with these related and highly-rated skills.

Didn't find tool you were looking for?

Be as detailed as possible for better results