Gurl-cli is a high-performance, stateful HTTP, gRPC & WebSocket client designed for the command line.
Unlike standard tools (curl, postman, grpcurl, websocat), gurl-cli is engineered for request chaining and automated flow testing. It allows you to execute sequences of requests where the output of one (e.g., Auth Tokens, session cookies, generated UUIDs) automatically feeds into the next. Everything is defined in a single, readable file using a custom, zero-allocation configuration format (.gurlf) that completely eliminates JSON escaping hell.
- β‘ Quickstart
- π Installation
- π Configuration Types
- π§© Dynamic Macros & Flow Control
- π§ͺ Integration Testing
β οΈ Core Concepts & Constraints- π Architecture & Performance
- π License
gurl-cli operates via a clean command-line interface, allowing you to run files or raw configurations directly.
# Run a specific configuration file
gurl-cli run config.gurlf
# Run a raw configuration directly from the terminal (no file required)
gurl-cli run "
[http_config]
URL:http://localhost:8080
Type:http
ID:0
[\http_config]"
# Create a template or get help
gurl-cli create config.gurlf http
gurl-cli helpEnsure you have Go installed, then run:
go install https://github.com/Votline/Gurl-cli@latestOr download from github Releases
Configs use the custom .gurlf syntax. The tool writes responses back into the configuration file automatically, making debugging instant.
Standard HTTP requests support automatic cookie jars, headers, and body payloads.
[reg]
URL:http://localhost:8080/api/users/reg
Method:POST
Body:`
{
"name": "Viza",
"email": "some@mail.com",
"role":"admin",
"password":"eightpswd"
}
`
Headers:Content-Type: application/json
ID:0
Type:http
[\reg]
Easily test your microservices by pointing directly to your .proto files or using reflection inside the grpc of your servers.
[new_course]
Target:localhost:50052
Endpoint:courses.CoursesService/NewCourse
Data:`
{
"user_id": "12345",
"name": "some_name",
"description": "cool desc",
"price": "1347"
}
`
ProtoPath:../protos/courses.proto
ID:0
Type:grpc
[\new_course]
Full support for interactive WebSocket connections natively through the HTTP type.
- Use
ws://to send a single configured payload. - Use
while:ws://to open an interactive session and stream messages directly from your terminal'sstdin.
[chat_session]
URL:while:ws://localhost:8080/ws
ID:1
Type:http
[\chat_session]
Don't rewrite identical payloads. Inherit from a previous request using TargetID and patch only the fields you need using Replace.
[log_upd_user]
TargetID:1
Replace:`
[rep]
Body:`
{
"name": "a62893bc-70af-4fb3-aca6-b663ad35404f",
"email": "upd@mail.com",
"password": "updatepaswd"
}`
[\rep]
`
ID:8
Type:repeat
[\log_upd_user]
Keep your configs clean by importing base templates or fallback configurations. Variables can be passed down to the imported scope.
[import_config]
TargetPath:fallback.gurlf
ID:2
Type:import
SetVariables:`
[vars]
UserID: FALLBACK {RANDOM oneof=uuid}
[\vars]
`
[\import_config]
gurl-cli becomes powerful when you link requests together using in-place macros.
Extract data from previous requests or stateful fields:
- JSON Extraction:
{RESPONSE id=1 json:token}- Extracts a field from the response body of configID:1. - Stateful Cookies: Use the
CookieInfield to inject session data:{COOKIES id=1}- Injects all cookies captured in theCookieOutfield of configID:1.{COOKIES id=file}- Tells the transport to use cookies defined directly within the currentCookieInblock.
Manage state across different .gurlf files or system sessions. This is the heavy lifting for cross-file communication.
- Save State: Use
SetEnvironmentsto persist values to a local.env_tempfile or OS env. - Retrieve State:
{ENVIRONMENT key=Token ; from=.env_temp}or{ENVIRONMENT key=USER ; from=os}. - Defaults: You can now provide fallback values using
; default=...if the environment or variable is missing.
Example
[config] SetEnvironments:` [envs] <- there is no such file, the environment will be in-memory. OsEnvName:os env {RANDOM oneof=uuid} [\envs] [.env] <- this file exists. The old values will be overwritten by the new ones in case of a collision. FileEnvName:file env {RANDOM oneof=uuid} [\.env] ` Body:` { "host": "{ENVIRONMENT key=DB_HOST ; from=os ; default=localhost}" <- will be used 'localhost' "osname": "{ENVIRONMENT key=OsEnvName ; from=os ; default=nop}" <- will be used value from os.GetEnv "filename": "{ENVIRONMENT key=FileEnvName ; from=.env ; default=nop}" <- will be used value from file '.env' } ` [\config]
Variables are designed for in-memory state management during a single execution. They do not persist to disk.
- In-Place Usage:
{VARIABLE key=Name ; default=guest}- Get a variable with a fallback. - Import config Pattern: The primary use case is passing data into import configs.
Example
[config] TargetPath: fallback.gurlf SetVariables:` [vars] UserID: FALLBACK - {RANDOM oneof=uuid} [\vars] ` Type:import [\config]Note:
fallback.gurlfwill now be able to resolve{VARIABLE key=UserID}.
Generate dynamic data in 0 allocs/op:
{RANDOM oneof=uuid}- High-speed UUID.{RANDOM oneof=user,admin}- Random pick from a list.{RANDOM oneof=int(10,100)}- Random integer in range.{RANDOM oneof=int}- Random integer.
- Wait:
Wait: 5s- Delays execution (supportsms, s, m, h). - Timeout:
Timeout: 15s- Sets a dynamic context deadline for the request (stops hanging connections). - Expect: Define success criteria and branching logic:
Expect: 200;fail=crash- Hard stop on failure.Expect: 200;fail=5- If not 200, jump to configID:5and stop.Expect: 0- (gRPC) ExpectsOKstatus.
- The
IgnoreCertField: AddingIgnoreCert: trueto a config (or inherited via repeat) will skip TLS/SSL verification for that request. Usefalseor omit to enforce secure connections. - Context Timeouts: The
Timeoutfield sets a timeout for the context. Automatically inherited by childrepeatconfigs.
Because gurl-cli persists its state (Responses, Cookies, Envs) back into files, it is perfect for complex integration scenarios.
You can run a chain of requests where file2.gurlf depends on the output of file1.gurlf.
Step 1: auth.gurlf
Captures cookies and saves the UserID to a temporary environment.
[login]
URL: http://localhost:8080/login
SetEnvironments: `
[.env_temp]
UserID: {RESPONSE id=0 json:id}
Cookies: "{COOKIES id=0}"
[\.env_temp]
`
ID: 0
Type: http
[\login]
Step 2: delete_account.gurlf
Uses the previously saved cookies and ID.
[del]
URL: http://localhost:8080/user/{ENVIRONMENT key=UserID ; from=.env_temp}
CookieIn: `
{COOKIES id=file}
{ENVIRONMENT key=Cookies ; from=.env_temp}
`
Type: http
[\del]
Step 3: check.gurlf
Combine two files into one for easy startup.
[import_login]
TargetPath:auth.gurlf
ID:0
Type:import
[\import_login]
[import_delete]
TargetPath:delete_account.gurlf
ID:1
Type:import
[\import_delete]Step 4: run it
gcli run check.gurlfThe .gurlf parser uses a high-speed, zero-allocation scanning logic. It identifies the end of a multi-line field by looking for a backtick strictly surrounded by newlines (\n + ` + \n).
Caution
A backtick on a new line always terminates the top-level block. When nesting configs, ensure the inner backticks do not mimic this pattern.
- CORRECT: Keep the inner closing backtick on the same line as your data.
- INCORRECT: Putting the inner closing backtick on a new line will break the outer parser.
| Syntax | Result |
|---|---|
Body: `value` |
One-line string. Value is value |
Body: "value\n" |
Multi-line string. Values is "value\n" |
Replace: `[patch]\n Body:`data`\n[\patch]\n`\n |
Nested Correct. The last backtick is surrounded by \n, which means the end of the Replace key value |
Replace: `[patch]\n Body:`data\n`\n [\patch]\n`\n |
Nested Incorrect. There is a \n\n` in the value, which means the end of the value for the 'Replace' field, but this is not the case. |
Note
Correct config:
[config]
GlobalKey:`
[inner_cfg]
LocalKey:`
LocalValue` <- end of 'LocalKey'. Syntax: '`\n'
[\inner_cfg]
` <- end of 'GlobalKey'. Syntax: '\n`\n'
[config]
- The
ReplaceField: Strictly exclusive to therepeatconfiguration type. Used to patch fields from aTargetID. - gRPC Reflection vs. ProtoPath: Omit
ProtoPathto use Server Reflection. If provided, it parses the local.protofile.
Macros now use ; as a separator to allow for clean default fallbacks (e.g., {ENVIRONMENT key=API_KEY ; from=os ; default=12345}). You can escape semicolons inside values if needed.
gurl-cli is explicitly engineered for low latency and zero-allocation execution, making it viable for high-throughput testing and microservice orchestration. The core architecture relies on aggressive object pooling and deep Go runtime optimizations.
Interface Hacking & Pre-allocation: To avoid heap allocations, the config package uses pre-allocated pools (10 objects per type). The parser peeks at the Type field and uses unsafe.Pointer to manually swap itab and data pointers.
Lock-Free Pipeline: Data flows between the Parser, Core, File Writer and Console Writer via isolated, capped Ring Buffers. This decoupled architecture ensures the executor never blocks on the parser or disk I/O.
Standard library functions (bytes.Split, json, strconv) are replaced with custom, allocation-free iterators. fastUUID and manual byte-slice scanning keep the hot path garbage-free.
The file writer uses a detached seek/flush mechanism to inject responses directly into .gurlf files. This context-free operation prevents file corruption even during abrupt process termination.
Tested on AMD Ryzen 7 5800U (linux/amd64). The hot paths are garbage-free.
| Component / Function | Time (ns/op) | Allocations | Notes |
|---|---|---|---|
| ParseStream | ~362 ns/op | 0 allocs/op | Full multi-line config streaming. |
| HandleType | ~274 ns/op | 0 allocs/op | unsafe interface casting + mapping. |
| HandleInstructions | ~106 ns/op | 0 allocs/op | Finds macros (aka instructions) |
| ParseFindConfig | ~317 ns/op | 0 allocs/op | Finds config by target id, anmarshall it (import configs logic) |
| fastExtract | ~12.6 ns/op | 0 allocs/op | Rapid field extraction. |
| fastUUID | ~48 ns/op | 0 allocs/op | Generate UUID (libraries allocate memory) |
for 0 alloc in ParseStream, you need to comment out log.Debug