Help us shape the future of Go! Take the 2026 Go Developer Survey

tap

package
v1.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 22, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

Documentation

Overview

Package tap is a local reverse proxy that records the requests AI coding CLIs (Claude Code, Codex, Gemini CLI, Grok) send to their model providers.

Each CLI is pointed at the proxy through its own base URL environment variable, so no CA certificate is needed: the CLI talks plain HTTP to 127.0.0.1 and the proxy talks HTTPS to the real provider.

Index

Constants

View Source
const (
	PingPath   = "/_acurl/ping"   // GET: Info, so wrappers and viewers can tell the proxy is up
	EventsPath = "/_acurl/events" // GET: a JSON line per Event until the proxy stops
	StopPath   = "/_acurl/stop"   // POST: stop the proxy
)

Control endpoints served next to the provider routes.

View Source
const DefaultMaxBody = 1 << 20

DefaultMaxBody is how much of each body a capture keeps.

View Source
const Redacted = "[redacted]"

Redacted replaces every removed value.

Variables

View Source
var CLIs = []CLI{
	{
		Name:     "claude",
		Bindings: []Binding{{Env: "ANTHROPIC_BASE_URL", Route: "anthropic"}},
		Note:     "API key and claude.ai login both work",
	},
	{
		Name:     "codex",
		Bindings: []Binding{{Env: "OPENAI_BASE_URL", Route: "openai", Suffix: "/v1"}},
		Note:     "API key login only; ChatGPT login talks to chatgpt.com and bypasses the base URL",
	},
	{
		Name: "gemini",
		Bindings: []Binding{
			{Env: "GOOGLE_GEMINI_BASE_URL", Route: "gemini"},
			{Env: "CODE_ASSIST_ENDPOINT", Route: "code-assist"},
		},
		Note: "Gemini API key and Google login; Vertex AI is not routed",
	},
	{
		Name:     "grok",
		Bindings: []Binding{{Env: "GROK_CLI_CHAT_PROXY_BASE_URL", Route: "grok", Suffix: "/v1"}},
		Note:     "Grok Build CLI with grok.com login",
	},
}

CLIs are the tools `acurl tap env` writes wrappers for.

View Source
var DefaultRoutes = []Route{
	{Name: "anthropic", Prefix: "/anthropic", Upstream: "https://api.anthropic.com"},
	{Name: "openai", Prefix: "/openai", Upstream: "https://api.openai.com"},
	{Name: "gemini", Prefix: "/gemini", Upstream: "https://generativelanguage.googleapis.com"},
	{Name: "code-assist", Prefix: "/code-assist", Upstream: "https://cloudcode-pa.googleapis.com"},
	{Name: "grok", Prefix: "/grok", Upstream: "https://cli-chat-proxy.grok.com"},
}

DefaultRoutes are the provider APIs the proxy forwards to.

View Source
var Shells = []string{"bash", "zsh", "fish", "powershell"}

Shells that `acurl tap env` can write wrappers for.

Functions

func CLINames

func CLINames() []string

CLINames lists the known CLI names.

func Redact

func Redact(x *Exchange, secrets []string)

Redact replaces each of secrets in x's headers, URL and bodies, and records where it did.

func WriteBodies

func WriteBodies(w io.Writer, x *Exchange) error

WriteBodies writes the request and response bodies of x under a header line, for `acurl tap --bodies`.

func WriteEnv

func WriteEnv(w io.Writer, shell, base string, clis []CLI) error

WriteEnv writes shell functions that run each CLI through the proxy at base (e.g. "http://127.0.0.1:8899") while `acurl tap` is running, and straight to the provider when it is not, so the CLI never breaks.

func WriteLine

func WriteLine(w io.Writer, x *Exchange) error

WriteLine writes a one-line summary of x, like tcpdump:

12:01:03 #4 anthropic POST /v1/messages claude-opus-5-5 200 1.2s 41.0KB > 3.1KB stream

Types

type Binding

type Binding struct {
	Env    string // e.g. "ANTHROPIC_BASE_URL"
	Route  string // Route.Name
	Suffix string // appended to the route URL, e.g. "/v1" for OpenAI-style bases
}

Binding is one environment variable that points a CLI at a route.

type Body

type Body struct {
	Text      string `json:"text,omitempty"`
	Size      int64  `json:"size"`
	Encoding  string `json:"encoding,omitempty"`
	Truncated bool   `json:"truncated,omitempty"`
}

Body is a captured message body. Text holds at most the proxy's body limit, decoded when the body had a Content-Encoding; Size is the length that passed through on the wire.

type CLI

type CLI struct {
	Name     string // the command, e.g. "claude"
	Bindings []Binding
	Note     string // limits worth knowing, shown by `acurl tap env --list`
}

CLI is an AI command line tool acurl knows how to tap.

func FindCLI

func FindCLI(name string) (CLI, bool)

FindCLI returns the CLI with name, ignoring case.

type Event

type Event struct {
	Exchange *Exchange `json:"exchange,omitempty"`
	// Dropped counts exchanges this viewer missed because it read too slowly.
	Dropped int64 `json:"dropped,omitempty"`
}

Event is one line of the events stream.

type Exchange

type Exchange struct {
	ID         int64         `json:"id"`
	Start      time.Time     `json:"start"`
	Duration   time.Duration `json:"duration_ns"`
	Route      string        `json:"route"`
	Method     string        `json:"method"`
	URL        string        `json:"url"`
	Model      string        `json:"model,omitempty"`
	ReqHeader  http.Header   `json:"request_headers"`
	ReqBody    Body          `json:"request_body"`
	Status     int           `json:"status,omitempty"`
	RespHeader http.Header   `json:"response_headers,omitempty"`
	RespBody   Body          `json:"response_body"`
	Streaming  bool          `json:"streaming,omitempty"`
	Upgrade    string        `json:"upgrade,omitempty"` // e.g. "websocket"; frames are not recorded
	Err        string        `json:"error,omitempty"`
	Canceled   bool          `json:"canceled,omitempty"` // the CLI hung up before the response ended
	Redactions []Redaction   `json:"redactions,omitempty"`
}

Exchange is one request a CLI made through the proxy and the response it got.

type Info

type Info struct {
	Name            string `json:"name"` // always "acurl tap"
	Version         string `json:"version"`
	PID             int    `json:"pid"`
	MaxBody         int    `json:"max_body"`
	KeepCredentials bool   `json:"keep_credentials"`
	Viewers         int    `json:"viewers"`
}

Info describes a running proxy.

type Options

type Options struct {
	Routes  []Route // DefaultRoutes when empty
	MaxBody int     // DefaultMaxBody when zero
	// KeepCredentials leaves API keys, tokens and cookies in captures.
	KeepCredentials bool
	// Secrets are extra strings removed from headers, URLs and bodies.
	Secrets []string
	// OnExchange receives every finished exchange, as do viewers connected
	// to EventsPath. It is called from many goroutines at once.
	OnExchange func(*Exchange)
	// OnStop is called when a client asks the proxy to stop through
	// StopPath. Without it, StopPath is refused.
	OnStop func()
	// Version is reported by PingPath.
	Version string
	// Transport sends requests upstream; a clone of http.DefaultTransport
	// when nil.
	Transport http.RoundTripper
}

Options configures a Proxy.

type Proxy

type Proxy struct {
	// contains filtered or unexported fields
}

Proxy forwards CLI requests to their providers and records each exchange.

func New

func New(opts Options) (*Proxy, error)

New returns a Proxy.

func (*Proxy) Close

func (p *Proxy) Close()

Close ends every events stream. Call it before shutting the server down.

func (*Proxy) ServeHTTP

func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements http.Handler.

type Redaction

type Redaction struct {
	Where  string `json:"where"`  // e.g. "request.header.x-api-key"
	Reason string `json:"reason"` // "credential" or "user secret"
}

Redaction records where a value was removed from a capture and why, so a reader can tell a redacted field from an empty one.

type Route

type Route struct {
	Name     string // shown in captures, e.g. "anthropic"
	Prefix   string // path prefix on the proxy, e.g. "/anthropic"
	Upstream string // scheme and host, e.g. "https://api.anthropic.com"
}

Route forwards requests under Prefix to Upstream with the prefix removed.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL