Initial commit

This commit is contained in:
Mester Gábor
2026-03-19 07:12:03 +01:00
commit 2dd6519168
25 changed files with 3645 additions and 0 deletions
+341
View File
@@ -0,0 +1,341 @@
# 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 |