@matimo/bruno 0.1.0

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/README.md ADDED
@@ -0,0 +1,174 @@
1
+ # @matimo/bruno
2
+
3
+ Bruno CLI tools for Matimo — Enable AI agents to autonomously manage, execute, and validate API test collections.
4
+
5
+ ## 📦 Installation
6
+
7
+ ```bash
8
+ npm install @matimo/bruno
9
+ ```
10
+
11
+ ## 🚀 Quick Start
12
+
13
+ ### TypeScript / JavaScript
14
+
15
+ ```typescript
16
+ import { MatimoInstance } from '@matimo/core';
17
+
18
+ const matimo = await MatimoInstance.init('./packages/bruno/tools');
19
+
20
+ // List all collections in workspace
21
+ const collections = await matimo.execute('bruno_list_collections', {
22
+ workspace_path: './collections'
23
+ });
24
+
25
+ // Get collection metadata before running
26
+ const info = await matimo.execute('bruno_get_collection_info', {
27
+ collection_path: './collections/payment-api'
28
+ });
29
+
30
+ // Run entire collection
31
+ const result = await matimo.execute('bruno_run_collection', {
32
+ collection_path: './collections/payment-api',
33
+ environment: 'staging',
34
+ report_path: './reports/staging-results.json'
35
+ });
36
+
37
+ // Run single request for debugging
38
+ const response = await matimo.execute('bruno_run_request', {
39
+ collection_path: './collections/auth',
40
+ request_name: 'Login',
41
+ environment: 'staging'
42
+ });
43
+
44
+ // Bootstrap from OpenAPI spec
45
+ const imported = await matimo.execute('bruno_import_openapi', {
46
+ spec_source: 'https://api.example.com/openapi.json',
47
+ output_directory: './collections',
48
+ collection_name: 'Generated API Tests'
49
+ });
50
+
51
+ // Create new collection
52
+ const created = await matimo.execute('bruno_create_collection', {
53
+ collection_path: './collections/new-service',
54
+ collection_name: 'New Service Tests'
55
+ });
56
+ ```
57
+
58
+ ## 📋 Available Tools
59
+
60
+ ### 1. `bruno_run_collection`
61
+ Execute a Bruno API collection with configurable environments, data files, and reporting.
62
+
63
+ **Parameters:**
64
+ - `collection_path` (required) — Path to collection file or directory
65
+ - `environment` — Environment name (dev, staging, prod)
66
+ - `env_file` — Path to environment file override
67
+ - `data_file` — CSV/JSON data file for data-driven testing
68
+ - `iteration_count` — Number of iterations to run
69
+ - `delay_ms` — Delay between requests
70
+ - `tags` — Comma-separated tags (run requests with ALL tags)
71
+ - `exclude_tags` — Comma-separated tags (skip requests with ANY tags)
72
+ - `tests_only` — Run only requests with tests/assertions
73
+ - `bail_on_failure` — Stop on first failure
74
+ - `parallel` — Run requests in parallel
75
+ - `sandbox_mode` — JavaScript execution mode ('safe' or 'developer')
76
+ - `report_format` — Report format (json, junit, html)
77
+ - `report_path` — Path to write report file
78
+
79
+ **Returns:** Collection execution results, summary, and report path
80
+
81
+ ### 2. `bruno_run_request`
82
+ Execute a single request for targeted debugging and validation.
83
+
84
+ **Parameters:**
85
+ - `collection_path` (required) — Collection directory
86
+ - `request_name` (required) — Request name to execute
87
+ - `environment` — Environment name override
88
+ - `env_file` — Environment file override
89
+ - `sandbox_mode` — JavaScript execution mode
90
+
91
+ **Returns:** Request/response details and assertion results
92
+
93
+ ### 3. `bruno_list_collections`
94
+ Discover all collections in a workspace.
95
+
96
+ **Parameters:**
97
+ - `workspace_path` (required) — Workspace directory
98
+ - `filter` — Filter by collection name (substring)
99
+
100
+ **Returns:** Array of collection metadata
101
+
102
+ ### 4. `bruno_get_collection_info`
103
+ Introspect collection structure before execution.
104
+
105
+ **Parameters:**
106
+ - `collection_path` (required) — Collection path
107
+
108
+ **Returns:** Collection structure, requests, environments, variables
109
+
110
+ ### 5. `bruno_import_openapi`
111
+ Bootstrap a collection from OpenAPI 3.0 specification.
112
+
113
+ **Parameters:**
114
+ - `spec_source` (required) — Path or URL to OpenAPI spec
115
+ - `output_directory` (required) — Where to create collection
116
+ - `collection_name` — Collection name
117
+ - `collection_format` — 'bru' or 'opencollection'
118
+ - `group_by` — Group by 'tags' or 'path'
119
+ - `insecure` — Skip TLS verification
120
+
121
+ **Returns:** Collection path and metadata
122
+
123
+ ### 6. `bruno_create_collection`
124
+ Create a new empty collection scaffold.
125
+
126
+ **Parameters:**
127
+ - `collection_path` (required) — Collection creation path
128
+ - `collection_name` (required) — Collection name
129
+
130
+ **Returns:** Creation status and path
131
+
132
+ ## 🔄 Agent Workflows
133
+
134
+ ### Autonomous Test Execution
135
+ ```
136
+ Agent discovers spec → import_openapi → set environment → run_collection → parse results
137
+ ```
138
+
139
+ ### Multi-Environment Validation
140
+ ```
141
+ list_collections → for each environment: set_env + run_collection → compare results
142
+ ```
143
+
144
+ ### Targeted Debugging
145
+ ```
146
+ get_collection_info → run_request (single endpoint) → analyze response
147
+ ```
148
+
149
+ ### Data-Driven Testing
150
+ ```
151
+ run_collection with CSV file → multiple iterations → aggregate metrics
152
+ ```
153
+
154
+ ## 🔐 Authentication
155
+
156
+ Tools use environment variables for credentials. Bruno CLI manages environment setup — tools wrap CLI execution.
157
+
158
+ ## 📖 Prerequisites
159
+
160
+ - **Bruno CLI** installed globally: `npm install -g @usebruno/cli` (or `pnpm install -g @usebruno/cli`)
161
+ - **Node.js** 18+ required
162
+ - Bruno collections in `.bru` format or OpenAPI specs
163
+
164
+ ## 🤝 Integration
165
+
166
+ Works with:
167
+ - **LangChain** — Convert to StructuredTool
168
+ - **CrewAI** — Convert to BaseTool
169
+ - **MCP** — Expose via JSON-RPC to Claude / other MCP clients
170
+ - **Native** — Direct SDK usage
171
+
172
+ ## 📝 License
173
+
174
+ MIT
@@ -0,0 +1,33 @@
1
+ # Bruno CLI Provider Definition
2
+ #
3
+ # This file defines the configuration for Bruno CLI tools.
4
+ # All Bruno tools reference this provider definition.
5
+
6
+ name: bruno-provider
7
+ type: provider
8
+ version: '1.0.0'
9
+
10
+ description: |
11
+ Bruno CLI Provider Configuration
12
+
13
+ All Bruno tools use the locally-installed Bruno CLI.
14
+
15
+ Setup:
16
+ 1. Install Bruno CLI:
17
+ npm install -g @usebruno/cli
18
+ or
19
+ brew install bruno
20
+
21
+ 2. Bruno CLI v3+ provides:
22
+ - Collection management (create, list, import)
23
+ - Request execution (run single or full suite)
24
+ - OpenAPI spec import and generation
25
+ - Test result reporting
26
+
27
+ Authentication:
28
+ Bruno CLI tools run locally and don't require credentials.
29
+ API keys for tested endpoints should be provided in environment variables.
30
+
31
+ Documentation:
32
+ - Bruno Docs: https://www.usebruno.com/
33
+ - CLI Reference: https://www.usebruno.com/docs/cli/cli-overview
package/package.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "@matimo/bruno",
3
+ "version": "0.1.0",
4
+ "description": "Bruno CLI tools for Matimo — API collection execution, import, and validation",
5
+ "files": [
6
+ "tools",
7
+ "README.md",
8
+ "definition.yaml"
9
+ ],
10
+ "dependencies": {
11
+ "@usebruno/cli": "^3.3.0",
12
+ "@matimo/core": "0.1.0"
13
+ },
14
+ "peerDependencies": {
15
+ "matimo": "0.1.0-alpha.14"
16
+ }
17
+ }
@@ -0,0 +1,51 @@
1
+ import { execFileSync } from 'child_process';
2
+
3
+ export const BRU_MIN_VERSION_STR = '1.0.0';
4
+ const BRU_MIN_VERSION = [1, 0, 0] as const;
5
+
6
+ /**
7
+ * Verify the Bruno CLI is installed and meets the minimum required version.
8
+ *
9
+ * - Throws if `bru` is not found in PATH (ENOENT).
10
+ * - Throws if the installed version is below {@link BRU_MIN_VERSION_STR}.
11
+ * - Skips silently if the version string cannot be parsed (graceful degradation).
12
+ */
13
+ export function checkBruVersion(): void {
14
+ let versionOutput: string;
15
+ try {
16
+ versionOutput = execFileSync('bru', ['--version'], { encoding: 'utf-8', stdio: 'pipe' });
17
+ } catch (err) {
18
+ if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
19
+ throw new Error(
20
+ "Bruno CLI ('bru') is not installed or not in PATH. " +
21
+ 'Install it with: npm install -g @usebruno/cli',
22
+ );
23
+ }
24
+ // Other error — skip version check (bru is installed but --version failed for another reason)
25
+ return;
26
+ }
27
+
28
+ const match = versionOutput.trim().match(/^(\d+)\.(\d+)\.(\d+)/);
29
+ if (!match) return; // Unparseable output — skip check
30
+
31
+ const installed: [number, number, number] = [
32
+ parseInt(match[1], 10),
33
+ parseInt(match[2], 10),
34
+ parseInt(match[3], 10),
35
+ ];
36
+
37
+ const [iMaj, iMin, iPatch] = installed;
38
+ const [minMaj, minMin, minPatch] = BRU_MIN_VERSION;
39
+
40
+ const belowMin =
41
+ iMaj < minMaj ||
42
+ (iMaj === minMaj && iMin < minMin) ||
43
+ (iMaj === minMaj && iMin === minMin && iPatch < minPatch);
44
+
45
+ if (belowMin) {
46
+ throw new Error(
47
+ `Bruno CLI version ${versionOutput.trim()} is below the minimum required version ` +
48
+ `${BRU_MIN_VERSION_STR}. Upgrade with: npm install -g @usebruno/cli`,
49
+ );
50
+ }
51
+ }
@@ -0,0 +1,108 @@
1
+ name: bruno_add_request
2
+ description: Add a new HTTP request to a Bruno collection programmatically. Creates a .bru file with the specified method, URL, headers, body, and test assertions.
3
+ version: '1.0.0'
4
+ status: approved
5
+
6
+ parameters:
7
+ collection_path:
8
+ type: string
9
+ required: true
10
+ description: Path to Bruno collection directory
11
+ example: './api-tests/payment-api'
12
+
13
+ request_name:
14
+ type: string
15
+ required: true
16
+ description: Request name (becomes .bru filename, alphanumeric + hyphens)
17
+ example: 'get-users'
18
+
19
+ method:
20
+ type: string
21
+ required: true
22
+ description: HTTP method
23
+ enum: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']
24
+ example: 'GET'
25
+
26
+ url:
27
+ type: string
28
+ required: true
29
+ description: Full request URL (can contain {variables})
30
+ example: 'https://api.example.com/users/{user_id}'
31
+
32
+ headers:
33
+ type: object
34
+ required: false
35
+ description: HTTP headers as key-value pairs
36
+ example:
37
+ Content-Type: 'application/json'
38
+ Authorization: 'Bearer {api_token}'
39
+
40
+ body:
41
+ type: string
42
+ required: false
43
+ description: Request body (JSON or raw text)
44
+ example: '{"name": "John", "email": "john@example.com"}'
45
+
46
+ tests:
47
+ type: string
48
+ required: false
49
+ description: Bruno test script (JavaScript assertions)
50
+ example: |
51
+ test("Status is 200", function() {
52
+ expect(res.getStatus()).to.equal(200);
53
+ });
54
+
55
+ documentation:
56
+ type: string
57
+ required: false
58
+ description: Request documentation/description
59
+ example: 'Fetch list of all users from the system'
60
+
61
+ execution:
62
+ type: function
63
+ code: index.ts
64
+ timeout: 10000
65
+
66
+ output_schema:
67
+ type: object
68
+ properties:
69
+ success:
70
+ type: boolean
71
+ description: Whether request was created successfully
72
+ request_path:
73
+ type: string
74
+ description: Full path to created .bru file
75
+ request_name:
76
+ type: string
77
+ description: Request name
78
+ message:
79
+ type: string
80
+ description: Success or error message
81
+
82
+ examples:
83
+ - name: "Add GET request with headers"
84
+ params:
85
+ collection_path: "./api-tests/payment-api"
86
+ request_name: "get-transactions"
87
+ method: "GET"
88
+ url: "https://api.payment.com/v1/transactions"
89
+ headers:
90
+ Authorization: "Bearer {api_token}"
91
+ Accept: "application/json"
92
+
93
+ - name: "Add POST request with body and tests"
94
+ params:
95
+ collection_path: "./api-tests/payment-api"
96
+ request_name: "create-payment"
97
+ method: "POST"
98
+ url: "https://api.payment.com/v1/payments"
99
+ headers:
100
+ Content-Type: "application/json"
101
+ Authorization: "Bearer {api_token}"
102
+ body: '{"amount": 100, "currency": "USD", "description": "Payment for order"}'
103
+ tests: |
104
+ test("Payment created with 201", function() {
105
+ expect(res.getStatus()).to.equal(201);
106
+ expect(res.getBody().id).to.exist;
107
+ });
108
+ documentation: "Create a new payment transaction"
@@ -0,0 +1,101 @@
1
+ import { getGlobalMatimoLogger } from '@matimo/core';
2
+ import { promises as fs } from 'fs';
3
+ import * as path from 'path';
4
+
5
+ const logger = getGlobalMatimoLogger();
6
+
7
+ function generateBruContent(params: Record<string, unknown>): string {
8
+ const requestName = params.request_name as string;
9
+ const method = (params.method as string).toLowerCase();
10
+ const url = params.url as string;
11
+ const headers = (params.headers as Record<string, string>) || {};
12
+ const body = params.body as string | undefined;
13
+ const tests = params.tests as string | undefined;
14
+ const documentation = params.documentation as string | undefined;
15
+
16
+ let content = '';
17
+
18
+ content += `meta {\n name: ${requestName}\n type: http\n seq: 1\n}\n\n`;
19
+
20
+ if (documentation) {
21
+ content += `docs {\n ${documentation}\n}\n\n`;
22
+ }
23
+
24
+ content += `${method} {\n url: ${url}\n body: ${body ? 'json' : 'none'}\n auth: inherit\n}\n\n`;
25
+
26
+ if (Object.keys(headers).length > 0) {
27
+ content += `headers {\n`;
28
+ for (const [k, v] of Object.entries(headers)) {
29
+ content += ` ${k}: ${v}\n`;
30
+ }
31
+ content += `}\n\n`;
32
+ }
33
+
34
+ if (body) {
35
+ content += `body:json {\n`;
36
+ content += body
37
+ .split('\n')
38
+ .map((line) => ` ${line}`)
39
+ .join('\n');
40
+ content += `\n}\n\n`;
41
+ }
42
+
43
+ if (tests) {
44
+ content += `tests {\n`;
45
+ content += tests
46
+ .split('\n')
47
+ .map((line) => ` ${line}`)
48
+ .join('\n');
49
+ content += `\n}\n`;
50
+ }
51
+
52
+ return content;
53
+ }
54
+
55
+ export default async function execute(params: Record<string, unknown>): Promise<unknown> {
56
+ const collectionPath = params.collection_path as string;
57
+ const requestName = params.request_name as string;
58
+
59
+ if (!collectionPath || !requestName) {
60
+ return {
61
+ success: false,
62
+ request_path: '',
63
+ request_name: '',
64
+ message: 'collection_path and request_name are required',
65
+ };
66
+ }
67
+
68
+ try {
69
+ logger.info(`Adding request ${requestName} to collection at ${collectionPath}`);
70
+
71
+ const absoluteCollectionPath = path.resolve(collectionPath);
72
+ // Write requests into a dedicated requests/ subfolder (consistent with Bruno convention)
73
+ const requestsDir = path.join(absoluteCollectionPath, 'requests');
74
+ await fs.mkdir(requestsDir, { recursive: true });
75
+
76
+ const filename = `${requestName.toLowerCase().replace(/\s+/g, '-')}.bru`;
77
+ const requestPath = path.join(requestsDir, filename);
78
+
79
+ const content = generateBruContent(params);
80
+ await fs.writeFile(requestPath, content, 'utf-8');
81
+
82
+ logger.info(`Request written to ${requestPath}`);
83
+
84
+ return {
85
+ success: true,
86
+ request_path: requestPath,
87
+ request_name: requestName,
88
+ message: `Request '${requestName}' added to collection successfully`,
89
+ };
90
+ } catch (error) {
91
+ logger.error(
92
+ `Add request failed: ${error instanceof Error ? error.message : String(error)}`
93
+ );
94
+ return {
95
+ success: false,
96
+ request_path: '',
97
+ request_name: requestName,
98
+ message: `Failed to add request: ${error instanceof Error ? error.message : String(error)}`,
99
+ };
100
+ }
101
+ }
@@ -0,0 +1,46 @@
1
+ name: bruno_create_collection
2
+ description: Create a new empty Bruno collection scaffold with directory structure and configuration. Enables agents to initialize new collections for API testing projects.
3
+ version: '1.0.0'
4
+ status: approved
5
+
6
+ parameters:
7
+ collection_path:
8
+ type: string
9
+ required: true
10
+ description: Path where new collection directory will be created
11
+
12
+ collection_name:
13
+ type: string
14
+ required: true
15
+ description: Name for the new collection
16
+
17
+ execution:
18
+ type: function
19
+ code: index.ts
20
+ timeout: 15000
21
+
22
+ output_schema:
23
+ type: object
24
+ properties:
25
+ success:
26
+ type: boolean
27
+ collection_path:
28
+ type: string
29
+ message:
30
+ type: string
31
+ errors:
32
+ type: array
33
+ items:
34
+ type: string
35
+
36
+
37
+ examples:
38
+ - name: "Create new collection for microservice testing"
39
+ params:
40
+ collection_path: "./collections/user-service"
41
+ collection_name: "User Service API Tests"
42
+
43
+ - name: "Bootstrap collection for integration tests"
44
+ params:
45
+ collection_path: "./collections/e2e-workflows"
46
+ collection_name: "End-to-End Workflows"
@@ -0,0 +1,55 @@
1
+ import { getGlobalMatimoLogger } from '@matimo/core';
2
+ import { promises as fs } from 'fs';
3
+ import * as path from 'path';
4
+
5
+ const logger = getGlobalMatimoLogger();
6
+
7
+ export default async function execute(params: Record<string, unknown>): Promise<unknown> {
8
+ const collectionPath = params.collection_path as string;
9
+ const collectionName = params.collection_name as string;
10
+
11
+ if (!collectionPath || !collectionName) {
12
+ return {
13
+ success: false,
14
+ collection_path: '',
15
+ message: 'collection_path and collection_name parameters are required',
16
+ errors: ['collection_path and collection_name parameters are required'],
17
+ };
18
+ }
19
+
20
+ try {
21
+ logger.info(`Creating collection: ${collectionName} at ${collectionPath}`);
22
+
23
+ const absolutePath = path.resolve(collectionPath);
24
+ await fs.mkdir(absolutePath, { recursive: true });
25
+
26
+ const brunoJson = {
27
+ version: '1',
28
+ name: collectionName,
29
+ type: 'collection',
30
+ ignore: [] as string[],
31
+ };
32
+
33
+ const brunoJsonPath = path.join(absolutePath, 'bruno.json');
34
+ await fs.writeFile(brunoJsonPath, JSON.stringify(brunoJson, null, 2), 'utf-8');
35
+
36
+ logger.info('Collection created successfully');
37
+
38
+ return {
39
+ success: true,
40
+ collection_path: absolutePath,
41
+ message: `Collection "${collectionName}" created at ${absolutePath}`,
42
+ errors: [],
43
+ };
44
+ } catch (error) {
45
+ logger.error(
46
+ `Create collection failed: ${error instanceof Error ? error.message : String(error)}`
47
+ );
48
+ return {
49
+ success: false,
50
+ collection_path: collectionPath,
51
+ message: 'Collection creation failed',
52
+ errors: [error instanceof Error ? error.message : String(error)],
53
+ };
54
+ }
55
+ }
@@ -0,0 +1,61 @@
1
+ name: bruno_get_collection_info
2
+ description: Introspect a Bruno collection to get its structure, requests, environments, and configuration. Returns complete collection metadata for pre-flight validation before execution.
3
+ version: '1.0.0'
4
+ status: approved
5
+
6
+ parameters:
7
+ collection_path:
8
+ type: string
9
+ required: true
10
+ description: Path to Bruno collection file or directory
11
+
12
+ execution:
13
+ type: function
14
+ code: index.ts
15
+ timeout: 10000
16
+
17
+ output_schema:
18
+ type: object
19
+ properties:
20
+ success:
21
+ type: boolean
22
+ collection:
23
+ type: object
24
+ properties:
25
+ name:
26
+ type: string
27
+ path:
28
+ type: string
29
+ requests:
30
+ type: array
31
+ items:
32
+ type: object
33
+ properties:
34
+ name:
35
+ type: string
36
+ method:
37
+ type: string
38
+ url:
39
+ type: string
40
+ path:
41
+ type: string
42
+ tags:
43
+ type: array
44
+ items:
45
+ type: string
46
+ has_tests:
47
+ type: boolean
48
+ errors:
49
+ type: array
50
+ items:
51
+ type: string
52
+
53
+
54
+ examples:
55
+ - name: "Inspect collection structure"
56
+ params:
57
+ collection_path: "./collections/payment-api"
58
+
59
+ - name: "Get full metadata before running"
60
+ params:
61
+ collection_path: "./collections/auth-flow.bru"