agentleFS
jkanindajkaninda/okapi-skills/http_client/SKILL.md

http_client

Skill3 starsChanged 3 months agoMITView on GitHub

github.com/jkaninda/okapi/client is a small fluent HTTP client with: Do() and Send() are aliases. For the common "do, decode, fail on non-2xx" path use Decode: Decode chooses JSON / XML / YAML based on the response Content-Type. Each verb returns a *RequestBuilder: Available verbs: Get, Post, Put, Patch, Delete, Head, Options. For anything else use c.Request(method, path). Two escape hatches on the client itself: Builders can override client defaults for a single call:

The file

We split this skill into the rules it gives an agent

1 rule. About 0 are common, 1 is distinctive, and 0 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 0

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Default + per-request middleware chain

    line 6

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Okapi HTTP Client (okapi/client package)

github.com/jkaninda/okapi/client is a small fluent HTTP client with:

  • A request builder per verb
  • Default + per-request middleware chain
  • Built-in retry policy with exponential backoff
  • Body encoders for JSON, XML, YAML, form, multipart, raw
  • Response decoders driven by Content-Type
  • Zero dependency on the Okapi server package — usable against any REST API

Quick Start

import "github.com/jkaninda/okapi/client"

c := client.New("https://api.example.com",
    client.WithBearerToken(token),
    client.WithUserAgent("my-app/1.0"),
    client.WithTimeout(10*time.Second),
)

var user User
resp, err := c.Get("/users/42").
    WithContext(ctx).
    QueryParam("expand", "profile").
    Do()
if err != nil { return err }
if err := resp.Error(); err != nil { return err } // *client.HTTPError on non-2xx
if err := resp.JSON(&user); err != nil { return err }

Do() and Send() are aliases. For the common "do, decode, fail on non-2xx" path use Decode:

var user User
err := c.Get("/users/42").Decode(&user)

Decode chooses JSON / XML / YAML based on the response Content-Type.

Client Options

OptionPurpose
WithHTTPClient(*http.Client)Provide a pre-configured http.Client (TLS, transport)
WithTimeout(d)Default per-request timeout
WithHeader(k, v)Add one default header
WithHeaders(map)Merge multiple default headers
WithBearerToken(token)Sets Authorization: Bearer <token>
WithBasicAuth(u, p)Sets Authorization: Basic ...
WithUserAgent(ua)Sets the default User-Agent
WithMiddleware(mw...)Append middleware to the chain
WithRetry(policy)Default retry policy

Request Builder

Each verb returns a *RequestBuilder:

resp, err := c.Post("/items").
    WithContext(ctx).
    Header("X-Trace-Id", traceID).
    QueryParam("dry_run", "true").
    JSONBody(Item{Title: "hello"}).
    Timeout(5*time.Second).
    Do()

Available verbs: Get, Post, Put, Patch, Delete, Head, Options. For anything else use c.Request(method, path).

Two escape hatches on the client itself:

c.BaseURL()                          // the configured base URL
c.Do(ctx, req *http.Request)         // dispatch a hand-built *http.Request through the middleware chain

Terminal Methods

MethodBehavior
Do()Issue the request, return (*Response, error)
Send()Alias for Do()
Decode(target)Do() + decode into target; returns *HTTPError on non-2xx

Body Encoders

MethodContent-Type
JSONBody(v any)application/json
XMLBody(v any)application/xml
YAMLBody(v any)application/yaml
FormBody(map[string]string)application/x-www-form-urlencoded
Multipart(func(*multipart.Writer) error)multipart/form-data; boundary=…
RawBody([]byte)unset (use Header to set)
Body(io.Reader)unset (use Header to set)

Per-Request Auth Shortcuts

c.Get("/me").BearerToken(jwt).Send()
c.Get("/admin").BasicAuth("user", "pass").Send()

Per-Request Overrides

Builders can override client defaults for a single call:

c.Get("/big").
    Timeout(30 * time.Second).
    Retry(client.RetryPolicy{MaxAttempts: 5, BaseDelay: 100 * time.Millisecond}).
    Middleware(client.LoggingMiddleware(os.Stdout)).
    Do()

Response

resp.IsSuccess()             // 2xx?
resp.Error()                 // *HTTPError on non-2xx, nil otherwise
resp.String()                // body as string
resp.Body                    // []byte
resp.Decode(&target)         // format chosen from Content-Type
resp.JSON(&target)
resp.XML(&target)
resp.YAML(&target)
resp.JSONPath("user.profile.name") // (any, bool) — dot-path lookup in a JSON object
resp.Cookie("sid")           // *http.Cookie or nil
resp.Method / resp.URL       // originating method and final URL
resp.Header                  // http.Header  (from the embedded *http.Response)
resp.StatusCode              // int          (from the embedded *http.Response)

Response embeds *http.Response and exposes the body as Body []byte — already read and closed when the response is returned, so decode from Body rather than reading a stream.

Middleware

type RoundTripFunc func(*http.Request) (*http.Response, error)
type Middleware    func(next RoundTripFunc) RoundTripFunc

Order: client middlewares are outermost; per-request middlewares run next; the retry middleware sits innermost.

Built-in middlewares:

MiddlewareBehavior
LoggingMiddleware(io.Writer)One line per request (method, URL, status, duration)
UserAgentMiddleware(ua)Forces User-Agent on every request
RequestIDMiddleware()Sets X-Request-Id (random hex) if absent

Custom middleware:

auth := func(next client.RoundTripFunc) client.RoundTripFunc {
    return func(req *http.Request) (*http.Response, error) {
        req.Header.Set("X-Service-Token", currentServiceToken())
        return next(req)
    }
}
c := client.New(baseURL, client.WithMiddleware(auth))

Retry Policy

c := client.New(baseURL, client.WithRetry(client.RetryPolicy{
    MaxAttempts: 4,
    BaseDelay:   100 * time.Millisecond,
    MaxDelay:    2 * time.Second,
}))

Defaults:

  • MaxAttempts <= 1 → no retries
  • RetryOnStatus nil → retries on 408, 429, 500, 502, 503, 504
  • MaxDelay == 0 → backoff doubles indefinitely
  • Transport errors (network failures) always retry while attempts remain

Custom retry predicate:

client.RetryPolicy{
    MaxAttempts: 3,
    BaseDelay:   50 * time.Millisecond,
    ShouldRetry: func(resp *http.Response, err error) bool {
        return err != nil || (resp != nil && resp.StatusCode == http.StatusBadGateway)
    },
}

Request bodies are buffered once and rewound between attempts, so retries work for POST/PUT/PATCH. Backoff is interrupted when the request context is cancelled.

Errors

resp, err := c.Get("/missing").Do()
if err != nil {
    return err // transport / build error
}
if err := resp.Error(); err != nil {
    var hErr *client.HTTPError
    if errors.As(err, &hErr) {
        fmt.Println(hErr.StatusCode, string(hErr.Body))
    }
    return err
}

Do (and its alias Send) never returns HTTPError — a non-2xx response is a valid response. Opt in via resp.Error() or Decode, which calls Error() internally.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.