This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
A Postman/Insomnia-like TUI API client built in Go with Bubble Tea. Three-panel layout (sidebar, editor, response) with vim-style modal editing, collections stored as YAML, and 8+ theme support. Supports HTTP, GraphQL (including subscriptions), WebSocket, and gRPC (including streaming) protocols with environment variable interpolation, 7 auth methods (basic/bearer/apikey/oauth2/awsv4/digest/none), request history (SQLite), cURL/HAR/Postman/Insomnia/OpenAPI import/export, response diffing (line + word-level), pre/post-request JavaScript scripting, code generation (8 languages), request chaining/workflows, mock server, and a headless CLI runner.
make build # Build to bin/gottp (with version ldflags)
make run # Build and run
make test # go test ./...
make test-race # go test -race ./...
make test-cover # Coverage report with atomic mode
make lint # golangci-lint run
make bench # Benchmarks for HTTP client, scripting, diff, collection loader
make fuzz # Fuzz all import parsers (30s each)
make vulncheck # govulncheck ./...
make install # go install to $GOPATH/bin
make release-dry-run # GoReleaser snapshot (test release build)
# Single test / package
go test ./internal/protocol/http/ -run TestClient_GET
go test ./internal/runner/ -v
go test ./internal/auth/digest/
go test ./internal/mock/Launch TUI: ./bin/gottp --collection path/to/file.gottp.yaml
Headless CLI: ./bin/gottp run collection.gottp.yaml --env Production --output json
Mock server: ./bin/gottp mock collection.gottp.yaml --port 8080
CLI subcommands: run, init, validate, fmt, import, export, mock, completion, version, help
Environment files: place environments.yaml next to the collection file. The first environment is auto-selected on startup.
Bubble Tea MVU pattern: All UI components implement Update(msg) -> (Model, Cmd) and View() -> string. The root model is internal/app/app.go which orchestrates panels, overlays, and message routing.
internal/ui/msgs/msgs.go is a shared message types package created to avoid import cycles between app and UI components. All tea.Msg types and enums (AppMode, PanelFocus) live here. Both app and all UI packages import msgs — never import app from UI packages. When adding a new message type, add it to msgs.go, not to app or UI packages.
| Package | Role |
|---|---|
internal/app/ |
Root model, split into focused sub-modules (see below) |
internal/ui/msgs/ |
Shared message types (breaks import cycles) |
internal/ui/panels/{sidebar,editor,response}/ |
Three main panels |
internal/ui/components/ |
Reusable: KVTable, TabBar, StatusBar, CommandPalette, Help, Modal, Toast, JumpOverlay |
internal/ui/theme/ |
Theme catalog, lipgloss styles, custom YAML theme loader |
internal/ui/layout/ |
Responsive three-panel layout calculator |
internal/protocol/ |
Protocol interface, Registry, HTTP/GraphQL/WebSocket/gRPC clients |
internal/core/collection/ |
YAML collection model, loader, saver |
internal/core/environment/ |
Environment variables, {{var}} interpolation via Resolve(), AES-256-GCM encryption |
internal/core/{history,state,cookies,tls}/ |
SQLite history, central state, cookie jar, mTLS config |
internal/export/ |
curl/HAR/Postman/Insomnia export + codegen/ (8 languages) |
internal/import/ |
Format auto-detection + curl/Postman/Insomnia/OpenAPI/HAR importers |
internal/runner/ |
Headless CLI runner, perf baselines, workflow execution |
internal/auth/{oauth2,awsv4,digest}/ |
Auth implementations |
internal/diff/ |
Myers diff (line + word-level via DiffLinesWithWords()) |
internal/scripting/ |
JavaScript scripting via goja engine |
internal/mock/ |
Mock HTTP server with CORS, latency/error simulation |
internal/templates/ |
Pre-built request templates |
internal/config/ |
App config from ~/.config/gottp/config.yaml |
The internal/app/ package is split into focused files:
| File | Contents |
|---|---|
app.go |
App struct, New(), Init(), Update(), View(), resizePanels() |
app_keys.go |
handleGlobalKey(), handlePanelKey(), updateEditorInsert(), cycleFocus(), updateFocus() |
app_request.go |
sendRequest(), handleRequestSent(), initiateOAuth2(), introspection/reflection handlers |
app_overlays.go |
handleSwitchTheme(), handleImportFile(), handleSetBaseline(), openExternalEditor() |
app_tabs.go |
syncTabs(), loadActiveRequest(), loadHistory(), handleRequestSelected() |
app_save.go |
saveCollection(), copyAsCurl(), importCurl(), handleGenerateCode(), handleInsertTemplate() |
keymap.go |
KeyMap struct and DefaultKeyMap() |
Update() priority: overlays (command palette > help > modal > jump) → editor insert mode → global keys (Ctrl+Enter send, Ctrl+K palette, etc.) → panel-specific keys. The handleGlobalKey() and handlePanelKey() methods in app_keys.go handle this dispatch.
type Protocol interface {
Name() string
Execute(ctx context.Context, req *Request) (*Response, error)
Validate(req *Request) error
}Implemented: HTTP, GraphQL, WebSocket, gRPC. The Registry dispatches requests by req.Protocol field. Register new protocols via registry.Register(client) in app.New().
cmd/gottp/main.go routes to TUI mode (default) or subcommands. The runner in internal/runner/ loads the collection, resolves env vars, executes requests with scripting, and outputs results. Supports workflow execution (--workflow), performance baselines (--perf-save/--perf-baseline). Exit codes: 0=pass, 1=test assertion failure, 2=request error. Output formats: text, json, junit.
ModeNormal (navigate with j/k, Tab between panels) → ModeInsert (typing in text fields, Esc to return) → ModeJump (f key, type label to jump) → ModeSearch (/ in response body). i enters insert mode, Esc exits. Components track their own editing state via Editing() bool.
sendRequest() calls environment.Resolve() on URL, header values, param values, body, and auth fields before executing. Resolution priority: env vars > collection vars > OS env.
editor.Model wraps four protocol-specific forms (HTTPForm, GraphQLForm, WebSocketForm, GRPCForm) and a ProtocolSelector widget. Ctrl+P cycles protocols. All form access goes through delegation methods on editor.Model:
BuildRequest(),GetParams(),GetHeaders(),GetBodyContent(),SetBody(),BuildAuth(),FocusURL()LoadRequest()auto-detects protocol from collection request fields- Never use
editor.Form()directly — use the delegation methods instead
AuthSection in editor/auth_section.go supports none/basic/bearer/apikey/oauth2/awsv4/digest. BuildAuth() returns *protocol.AuthConfig, LoadAuth() populates from *collection.Auth. When adding a new auth type: update authTypes slice, add input fields, update BuildAuth()/LoadAuth()/View()/maxCursor().
internal/scripting/ provides a JavaScript (ES5.1+) engine via goja. Pre-scripts can mutate the request; post-scripts have read-only access to the response. The gottp global object provides: setEnvVar(), getEnvVar(), log(), test(name, fn), assert(), base64encode/decode(), sha256(), md5(), hmacSha256(), uuid(), timestamp(), timestampMs(), randomInt(), sleep(), readFile(). Each execution uses a fresh runtime with configurable timeout (default 5s).
The HTTP client (internal/protocol/http/client.go) supports:
- Proxy:
SetProxy(proxyURL, noProxy string)— HTTP/HTTPS/SOCKS5 proxy with per-request override - Cookie Jar:
SetCookieJar(jar)— auto-capturesSet-Cookieand sends cookies - mTLS:
SetTLSConfig(cfg)— client cert+key, CA bundles, skip-verify - Timing:
net/http/httptracefor DNS/TCP/TLS/TTFB/Transfer breakdown - Digest Auth: Automatically retries on 401 with
WWW-Authenticate: Digestheader
GitHub Actions are disabled (.github/workflows/*.yml.disabled). Tests and builds run locally. Releases are built manually via cross-compilation and uploaded with gh release create. The .goreleaser.yml config is kept for reference but not actively used.
To re-enable Actions: rename .yml.disabled back to .yml.
- Value receivers for
Update()andView(), pointer receivers for mutating helpers (startEditing,commitEdit,SetSize,SetPairs) - Sub-components return
(Model, Cmd)from Update, nottea.Model— they use concrete types - Responsive layout breakpoints: <60 cols = single panel, <100 cols = two panel, >=100 = three panel
- Collection files use
.gottp.yamlextension; environment files useenvironments.yaml - HTTP method colors: GET=green, POST=yellow, PUT=blue, PATCH=peach, DELETE=red (Catppuccin palette)
- Sub-models needing theme colors take both
theme.Themeandtheme.Stylesin constructors; storethfor colors andstylesfor pre-computed lipgloss styles - Config fields use
yaml:"...,omitempty"tags and zero-value defaults - Import format detection lives in
internal/import/detect.go— add new format checks there when adding importers saveCollection()syncs form state back tostore.ActiveRequest()before writing YAMLauthConfigToCollection()inapp_save.gomapsprotocol.AuthConfig→collection.Authfor persistence
- KVTable View() must build the cursor prefix (
"> "/" ") separately before joining with styled content. Slicing into styled strings breaks ANSI escape sequences. - Response body search uses plain text (not syntax-highlighted) for match highlighting to avoid ANSI escape interference.
- Command palette has dynamic mode:
OpenEnvPicker()/OpenThemePicker()replace commands temporarily;ResetCommands()restores defaults on close. - The HTTP client creates a new
http.Transportper-request when proxy/TLS config is present to support per-request proxy overrides. Without custom config, it reuses the default transport. - The
internal/core/tlspackage is imported asgotlsin config to avoid conflict withcrypto/tls. - Digest auth performs a transparent 401 retry — the first request intentionally gets a 401, then the response's
WWW-Authenticatechallenge is used to compute theAuthorizationheader for the retry. - The
diff.DiffLinesWithWords()function first computes line-level diff, then enriches consecutive Removed+Added pairs with word-level detail. TheWordsfield is nil for pure additions/removals.