Files
Mester Gábor 2dd6519168 Initial commit
2026-03-19 07:12:03 +01:00

342 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# greq
**greq** — a git-native, TUI-based API client for the terminal. Like Postman, but it lives in your `.greq/` folder, is version-controllable, and never opens a browser.
```
╭──────────────────────────╮╭──────────────────────────────────────────────────╮
│ GREQ [1:default] ││ → POST https://api.example.com/auth/login │
├──────────────────────────┤│──────────────────────────────────────────────────│
│ ▶ GET Health Check ││ { │
│ ▸ AUTH ││ "token": "eyJhbGciOiJIUzI1NiJ9...", │
│ ▶ POST Login ││ "user_id": 42 │
│ GET Refresh ││ } │
│ ▸ USERS ││ │
│ GET List Users ││──────────────────────────────────────────────────│
│ GET Get User ││ ● 200 OK │ ⚡ 213ms │ ◎ 1.2 KB │ app/json │
╰──────────────────────────╯╰──────────────────────────────────────────────────╯
Enter:run s:stress c:curl i:import d:diff S:snapshot h:history n:new e:edit
```
---
## Installation
```bash
git clone <repo>
cd greq
go build -o greq .
./greq # looks for .greq/ in the current directory
```
> **Dependencies:** Go 1.21+. For clipboard support on Linux you may need `xclip` (X11) or `wl-clipboard` (Wayland).
---
## Project structure
```
greq/
├── main.go # entry point
├── internal/
│ ├── api/
│ │ ├── client.go # HTTP client, variable resolution, token extraction
│ │ ├── chain.go # request chaining ({{last_FIELD}})
│ │ ├── curl.go # cURL import / export
│ │ ├── lua_test_runner.go # Lua test script runner (gopher-lua)
│ │ ├── script.go # pre_script shell execution
│ │ └── stress.go # load test runner (100×, 10 workers)
│ ├── storage/
│ │ ├── types.go # RequestFile, RequestItem structs
│ │ ├── loader.go # YAML loading, env list
│ │ ├── writer.go # YAML saving
│ │ └── config.go # .greq/config.yaml (e.g. editor setting)
│ └── tui/
│ ├── model.go # Bubble Tea Model, states, item types
│ ├── update.go # Update logic, key handlers, commands
│ ├── view.go # View rendering (header, panels, overlays)
│ ├── styles.go # Lipgloss styles, colours, env colour
│ ├── delegate.go # Custom list delegate (folder headers)
│ ├── history.go # HistoryEntry, stats bar, history table
│ ├── diff.go # LCS line diff, snapshot save/load
│ └── editor_picker.go # Available editor detection
└── .greq/ # project-level data (can be committed to git!)
├── config.yaml # user settings
├── env.yaml # default environment
├── env.local.yaml # optional: local env
├── env.staging.yaml # optional: staging env
├── env.prod.yaml # optional: prod env
├── requests/
│ ├── health.yaml # root-level request
│ ├── auth/
│ │ └── login.yaml # auth folder
│ └── users/
│ ├── list.yaml
│ └── get_user.yaml
└── snapshots/ # response baselines (d/S keys)
└── login.json
```
---
## .greq/ folder layout
### `env.yaml` — variables
```yaml
base_url: https://api.example.com
user_id: "42"
api_key: secret123
```
Reference them in any request with `{{base_url}}`, `{{user_id}}`, etc.
### Request YAML schema
```yaml
name: Login # display name in the list
method: POST # GET / POST / PUT / DELETE / PATCH
url: "{{base_url}}/auth/login" # variables allowed
headers:
Content-Type: application/json
X-Api-Key: "{{api_key}}"
body: |
{
"username": "admin",
"password": "secret"
}
# Shell script — runs BEFORE the request (to generate extra headers)
pre_script: |
TS=$(date +%s)
echo "X-Timestamp: $TS"
# Lua script — runs AFTER the response (for assertions)
test_script: |
assert_status(200)
assert_eq(json_body.success, true, "login successful")
if json_body.token then pass("token present") else fail("no token") end
```
### `config.yaml` — settings
```yaml
editor: nvim # default editor for the 'e' key
```
---
## Keybindings
### Main view
| Key | Action |
|---|---|
| `Enter` | Run the selected request |
| `s` | Load test (100 requests, Min/Max/Avg/error rate) |
| `e` | Edit request in `$EDITOR` (or configured editor) |
| `n` | New request wizard |
| `i` | Import a cURL command |
| `c` | Copy selected request as cURL |
| `d` | Diff: current response vs. saved baseline |
| `S` | Save current response as baseline |
| `h` | History overlay (all requests in the session) |
| `r` | Reload request list and env |
| `/` | Fuzzy search across requests |
| `Tab` | Toggle focus: list ↔ response panel |
| `1``9` | Switch environment (env.local.yaml, env.staging.yaml, etc.) |
| `q` / `Ctrl+C` | Quit |
### Response panel (after Tab)
| Key | Action |
|---|---|
| `↑` / `↓` | Scroll |
| `PgUp` / `PgDn` | Page scroll |
| `Tab` | Return to list |
### Overlays (History, Diff)
| Key | Action |
|---|---|
| `↑` / `↓` | Scroll |
| `Esc` / `h` / `d` | Close |
---
## Features
### Variables and Request Chaining
Values from `env.yaml` can be used as `{{key}}` placeholders in URLs, headers, and bodies.
If a response contains JSON, every top-level field is automatically available in the next request as `{{last_FIELD}}`:
```
Login response: { "id": 42, "token": "abc" }
→ {{last_id}} = "42"
→ {{last_token}} = "abc"
Next request URL: {{base_url}}/users/{{last_id}}
```
The `token`, `access_token`, and `jwt` fields are also automatically injected as `Authorization: Bearer …` headers in subsequent requests.
### Lua test scripts
`test_script` runs in Lua after the response arrives.
**Available variables:**
| Variable | Type | Description |
|---|---|---|
| `status` | number | HTTP status code (e.g. `200`) |
| `status_text` | string | Full status string (e.g. `"200 OK"`) |
| `body` | string | Raw response body |
| `json_body` | table / nil | Parsed JSON, or `nil` |
| `headers` | table | Response headers (lowercase keys) |
| `duration_ms` | number | Response time in milliseconds |
| `size` | number | Body size in bytes |
**Helper functions:**
```lua
pass("message") -- green assertion
fail("message") -- red assertion
assert_eq(a, b, "message") -- fail if a ~= b
assert_status(200) -- fail if status does not match
json_decode("...") -- JSON string → Lua table
```
**Example:**
```lua
assert_status(200)
if json_body == nil then
fail("response is not JSON")
return
end
assert_eq(json_body.role, "admin", "admin permission")
if duration_ms > 500 then
fail("too slow: " .. duration_ms .. "ms")
else
pass("response time OK: " .. duration_ms .. "ms")
end
```
Results appear in the stats bar badge: `✓ 3 tests` or `✗ 1/3 failed`.
### Shell pre-script
`pre_script` runs **before** the request, in a shell. Lines printed to stdout matching `Key: Value` are injected as extra request headers.
```yaml
pre_script: |
TS=$(date +%s)
SIG=$(echo -n "POST${TS}" | openssl dgst -sha256 -hmac "$HMAC_SECRET" -binary | base64)
echo "X-Timestamp: $TS"
echo "X-Signature: $SIG"
```
If the script exits with a non-zero code, the request is aborted.
### cURL Import / Export
**Export (`c`):** Copies the selected request as a ready-to-use `curl` command to the clipboard (variables resolved). If no clipboard tool is available, the command is displayed in the right panel.
**Import (`i`):** Paste a `curl` command (e.g. from Chrome DevTools → Copy as cURL) and greq parses it automatically, opening the wizard pre-filled.
### Load Testing ⚡
Press `s` on any selected request and greq fires it **100 times** concurrently (10 workers at a time).
The response panel shows the aggregated stats:
```
⚡ LOAD TEST RESULTS
Requests 100
Concurrency 10 workers
Min 43ms
Avg 97ms
Max 312ms
Errors 0 (0.0%)
```
If any requests failed, the `Errors` line is highlighted in red. Press `Enter` afterwards to go back to normal request mode.
### Response Diff
```
S → saves the current response as a baseline (.greq/snapshots/)
d → shows what changed since the baseline (red/green per line)
```
Useful when hitting the same endpoint at different times and wanting to see exactly what changed in the response.
### Environments (`1``9`)
Multiple env files can coexist:
```
.greq/env.yaml → 1 (default, purple header)
.greq/env.local.yaml → 2 (green header)
.greq/env.staging.yaml → 3 (yellow header)
.greq/env.prod.yaml → 4 (RED header — warning)
```
The header colour instantly signals which environment is active, so you don't accidentally delete production data.
---
## Quick start
```bash
# 1. Create the .greq folder at your project root
mkdir -p .greq/requests/auth
# 2. Write an env file
cat > .greq/env.yaml << EOF
base_url: https://api.example.com
EOF
# 3. Create a request
cat > .greq/requests/auth/login.yaml << EOF
name: Login
method: POST
url: "{{base_url}}/auth/login"
headers:
Content-Type: application/json
body: |
{"username": "admin", "password": "secret"}
test_script: |
assert_status(200)
if json_body.token then pass("token OK") else fail("no token") end
EOF
# 4. Run it
./greq
```
`greq` always looks for the `.greq/` folder in the **current directory**, so each git repo can have its own request set.
---
## Dependencies
| Package | Purpose |
|---|---|
| `charmbracelet/bubbletea` | TUI framework |
| `charmbracelet/bubbles` | List, textinput, viewport components |
| `charmbracelet/lipgloss` | Styles, colours, layout |
| `alecthomas/chroma/v2` | JSON syntax highlighting |
| `yuin/gopher-lua` | Lua interpreter for test scripts |
| `atotto/clipboard` | Clipboard support |
| `gopkg.in/yaml.v3` | YAML read/write |