Metadata-Version: 2.4
Name: kopya
Version: 0.1.1
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Rust
Classifier: Topic :: Software Development :: Testing
License-File: LICENSE
Summary: Record your app's real API calls once, replay them offline forever.
Keywords: mock,proxy,testing,record-replay,sse,llm,vcr
Home-Page: https://github.com/thinkgrid-labs/kopya
License-Expression: Apache-2.0
Requires-Python: >=3.8
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Homepage, https://github.com/thinkgrid-labs/kopya
Project-URL: Issues, https://github.com/thinkgrid-labs/kopya/issues

# kopya

**Record your app's real API calls once. Replay them offline, forever.**

`kopya` (Filipino for *"copy"*) is a single static binary that sits between your app and
the internet. Run your test suite once against the real API, and every call after that is
served from a fixture file — no network, no API bill, no flakes.

```console
$ kopya record -- pytest tests/
...
kopya recorded 47 calls → .kopya/fixtures.json
  Secrets were redacted. Review the file before committing it.

$ kopya replay -- pytest tests/
kopya loaded 47 recorded calls from .kopya/fixtures.json
...
kopya replayed 47 calls · 0 live
  Saved ~128,400 tokens and 94.2s.
```

Your application code does not change. No mock library, no dependency injection, no
monkeypatching.

## Why

Tests that hit OpenAI, Anthropic, Stripe or any other third-party API are slow,
nondeterministic, and cost real money. The existing answers all have a catch: language
-bound mock libraries only cover one runtime, and the proxy-based tools need Python or a
JVM in your CI image.

kopya is one ~7MB binary with no runtime. It boots instantly in a container and works the
same for Python, Node, Go, Rust, Ruby, Java or `curl`.

## Install

Whichever ecosystem you already have. All three install the same prebuilt binary —
there is no Node or Python runtime involved once it is on disk.

```sh
npx @kopya/cli --help     # Node
uvx kopya --help          # Python
cargo install kopya       # Rust
```

(The npm package is scoped `@kopya/cli`; the installed command is still `kopya`.)

Or grab a binary from the [releases page](https://github.com/thinkgrid-labs/kopya/releases).
Prebuilt for macOS (Intel and Apple Silicon), Linux (x64 and arm64, statically linked
against musl) and Windows x64.

## How it works

kopya runs an HTTP/HTTPS proxy on `127.0.0.1` and launches your command with the proxy and
CA environment variables already set:

| Variable | Why |
| --- | --- |
| `HTTP_PROXY` / `HTTPS_PROXY` | Routes traffic through kopya |
| `NODE_USE_ENV_PROXY=1` | Node's built-in `fetch` ignores `HTTP_PROXY` without it |
| `NODE_EXTRA_CA_CERTS` | Node uses its own CA bundle, not the system keychain |
| `REQUESTS_CA_BUNDLE` | Python `requests` / `httpx` use `certifi` |
| `SSL_CERT_FILE`, `CURL_CA_BUNDLE` | OpenSSL and curl |

This matters: **trusting a root CA in the system keychain is not enough** for Node, Python
or Java. Setting these per-process is what makes "zero code changes" actually true. If you
point an already-running process at kopya instead, run `kopya ca trust` and export the
variables it prints.

## Commands

```sh
kopya record -- <command>     # run <command>, save its outbound calls
kopya replay -- <command>     # run <command> against the saved calls
kopya ca path                 # print the root CA path (creates it on first run)
kopya ca trust                # install the root CA into the system trust store
kopya ca untrust              # remove it
```

Useful flags:

- `--out` / `--in` — fixture file path (default `.kopya/fixtures.json`)
- `--port` — proxy port (default `8888`)
- `--allow-live` — let unmatched requests reach the network instead of failing
- `--realtime` — reproduce the original timing of recorded streams
- `--no-redact` — keep credentials in the fixture. Don't.

Running `kopya record` or `kopya replay` with no `--` command starts the proxy in the
foreground so you can point an existing process at it.

## Streaming responses

`text/event-stream` responses — every streaming LLM API — are recorded frame by
frame, with the offset each frame arrived at, and replayed with those boundaries
intact. A recording is passed through to your app live as it happens, so the run
you record behaves exactly like an unproxied one.

By default replay emits the frames as fast as the client will take them, because
the point of replay is to make a slow test fast:

```
recorded   7 frames over 1015ms
replayed   7 frames over 7ms          # 145x faster, same frames
replayed   7 frames over 1012ms       # with --realtime
```

Use `--realtime` when the thing under test is the streaming behaviour itself —
time-to-first-token, incremental rendering, or a client-side timeout.

Token usage is parsed out of the final frame, so the summary can report what a
replayed run would have cost.

## Strict by default

In replay, a request with no recorded match returns `502` **and fails the run**, even if
your test swallowed the error. A green replay run therefore proves the suite is genuinely
hermetic. Pass `--allow-live` when you want the softer behaviour.

## Fixtures are meant to be committed

The fixture file is the artifact your team shares, so it is designed to survive code
review:

- Headers are stored in sorted maps and bodies as plain text, so diffs stay readable
- Credentials are redacted before anything touches disk — `Authorization`, `Cookie`,
  `x-api-key` and friends, plus bare `sk-`/`ghp_`/`AKIA`-style tokens found in bodies
- Binary payloads are dropped rather than base64-inflating the file

Always read a fixture before committing it the first time. Redaction is conservative by
design, not a guarantee.

## Status

Early. `0.1` covers HTTP/1.1 record and replay, including server-sent event streams,
with redaction, response decompression and strict matching.

**Roadmap:** HTTP/2, a GitHub Action, Homebrew, request-count assertions, and latency +
error injection.

Not planned for `0.1`: gRPC, WebSocket, a GUI, declarative YAML rule files.

## License

Licensed under the [Apache License, Version 2.0](LICENSE).

Contributions are accepted under the same license, per section 5 of the Apache 2.0 terms —
no separate CLA.

