# Technical Documentation: mountain-loop/yaak

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/mountain-loop/yaak)

### Overview (`README.md`)

<p align="center">
  <a href="https://github.com/JamesIves/github-sponsors-readme-action">
    <img width="200px" src="https://github.com/mountain-loop/yaak/raw/main/crates-tauri/yaak-app-client/icons/icon.png">
  </a>
</p>

<h1 align="center">
  💫 Yaak ➟ Desktop API Client 💫
</h1>

<p align="center">
    A fast, privacy-first API client for REST, GraphQL, SSE, WebSocket, and gRPC – built with Tauri, Rust, and React.
</p>
<p align="center">
 Development is funded by community-purchased <a href="https://yaak.app/pricing">licenses</a>. You can also <a href="https://github.com/sponsors/gschier">become a sponsor</a> to have your logo appear below. 💖
</p>
<br>

<p align="center">
  <a href="https://github.com/MVST-Solutions"><img src="https:&#x2F;&#x2F;github.com&#x2F;MVST-Solutions.png" width="80px" alt="User avatar: MVST-Solutions" /></a>&nbsp;&nbsp;<a href="https://github.com/dharsanb"><img src="https:&#x2F;&#x2F;github.com&#x2F;dharsanb.png" width="80px" alt="User avatar: dharsanb" /></a>&nbsp;&nbsp;<a href="https://github.com/railwayapp"><img src="https:&#x2F;&#x2F;github.com&#x2F;railwayapp.png" width="80px" alt="User avatar: railwayapp" /></a>&nbsp;&nbsp;<a href="https://github.com/caseyamcl"><img src="https:&#x2F;&#x2F;github.com&#x2F;caseyamcl.png" width="80px" alt="User avatar: caseyamcl" /></a>&nbsp;&nbsp;<a href="https://github.com/bytebase"><img src="https:&#x2F;&#x2F;github.com&#x2F;bytebase.png" width="80px" alt="User avatar: bytebase" /></a>&nbsp;&nbsp;<a href="https://github.com/rauchg"><img src="https:&#x2F;&#x2F;github.com&#x2F;rauchg.png" width="80px" alt="User avatar: rauchg" /></a>&nbsp;&nbsp;<a href="https://github.com/"><img src="https:&#x2F;&#x2F;raw.githubusercontent.com&#x2F;JamesIves&#x2F;github-sponsors-readme-action&#x2F;dev&#x2F;.github&#x2F;assets&#x2F;placeholder.png" width="80px" alt="User avatar: " /></a>&nbsp;&nbsp;
</p>
<p align="center">
  <a href="https://github.com/seanwash"><img src="https:&#x2F;&#x2F;github.com&#x2F;seanwash.png" width="50px" alt="User avatar: seanwash" /></a>&nbsp;&nbsp;<a href="https://github.com/jerath"><img src="https:&#x2F;&#x2F;github.com&#x2F;jerath.png" width="50px" alt="User avatar: jerath" /></a>&nbsp;&nbsp;<a href="https://github.com/itsa-sh"><img src="https:&#x2F;&#x2F;github.com&#x2F;itsa-sh.png" width="50px" alt="User avatar: itsa-sh" /></a>&nbsp;&nbsp;<a href="https://github.com/dmmulroy"><img src="https:&#x2F;&#x2F;github.com&#x2F;dmmulroy.png" width="50px" alt="User avatar: dmmulroy" /></a>&nbsp;&nbsp;<a href="https://github.com/timcole"><img src="https:&#x2F;&#x2F;github.com&#x2F;timcole.png" width="50px" alt="User avatar: timcole" /></a>&nbsp;&nbsp;<a href="https://github.com/VLZH"><img src="https:&#x2F;&#x2F;github.com&#x2F;VLZH.png" width="50px" alt="User avatar: VLZH" /></a>&nbsp;&nbsp;<a href="https://github.com/terasaka2k"><img src="https:&#x2F;&#x2F;github.com&#x2F;terasaka2k.png" width="50px" alt="User avatar: terasaka2k" /></a>&nbsp;&nbsp;<a href="https://github.com/andriyor"><img src="https:&#x2F;&#x2F;github.com&#x2F;andriyor.png" width="50px" alt="User avatar: andriyor" /></a>&nbsp;&nbsp;<a href="https://github.com/majudhu"><img src="https:&#x2F;&#x2F;github.com&#x2F;majudhu.png" width="50px" alt="User avatar: majudhu" /></a>&nbsp;&nbsp;<a href="https://github.com/axelrindle"><img src="https:&#x2F;&#x2F;github.com&#x2F;axelrindle.png" width="50px" alt="User avatar: axelrindle" /></a>&nbsp;&nbsp;<a href="https://github.com/jirizverina"><img src="https:&#x2F;&#x2F;github.com&#x2F;jirizverina.png" width="50px" alt="User avatar: jirizverina" /></a>&nbsp;&nbsp;<a href="https://github.com/chip-well"><img src="https:&#x2F;&#x2F;github.com&#x2F;chip-well.png" width="50px" alt="User avatar: chip-well" /></a>&nbsp;&nbsp;<a href="https://github.com/GRAYAH"><img src="https:&#x2F;&#x2F;github.com&#x2F;GRAYAH.png" width="50px" alt="User avatar: GRAYAH" /></a>&nbsp;&nbsp;<a href="https://github.com/flashblaze"><img src="https:&#x2F;&#x2F;github.com&#x2F;flashblaze.png" width="50px" alt="User avatar: flashblaze" /></a>&nbsp;&nbsp;<a href="https://github.com/Frostist"><img src="https:&#x2F;&#x2F;github.com&#x2F;Frostist.png" width="50px" alt="User avatar: Frostist" /></a>&nbsp;&nbsp;<a href="https://github.com/PurplProto"><img src="https:&#x2F;&#x2F;github.com&#x2F;PurplProto.png" width="50px" alt="User avatar: PurplProto" /></a>&nbsp;&nbsp;
</p>



## Install

Download Yaak for macOS, Windows, or Linux from [yaak.app/download](https://yaak.app/download). Package managers and other install methods are in the [installation docs](https://yaak.app/docs/getting-started/installation).

## Features

Yaak is an offline-first API client designed to stay out of your way while giving you everything you need when you need it.
Built with [Tauri](https://tauri.app), Rust, and React, it’s fast, lightweight, and private. No telemetry, no VC funding, and no cloud lock-in.

### 🌐 Work with any API

- Import collections from Postman, Insomnia, OpenAPI, Swagger, or Curl.
- Send requests via REST, GraphQL, gRPC, WebSocket, or Server-Sent Events.
- Filter and inspect responses with JSONPath or XPath.

### 🔐 Stay secure

- Use OAuth 2.0, JWT, Basic Auth, or custom plugins for authentication.
- Secure sensitive values with encrypted secrets.
- Store secrets in your OS keychain.

### ☁️ Organize & collaborate

- Group requests into workspaces and nested folders.
- Use environment variables to switch between dev, staging, and prod.
- Mirror workspaces to your filesystem for versioning in Git or syncing with Dropbox.

### 🧩 Extend & customize

- Insert dynamic values like UUIDs or timestamps with template tags.
- Pick from built-in themes or build your own.
- Create plugins to extend authentication, template tags, or the UI.

## Contribution Policy

> [!IMPORTANT]
> Community PRs are currently limited to bug fixes.
> If your PR is not a bug fix, link the [feedback item](https://yaak.app/feedback) where @gschier explicitly gave you permission to work on it.
> See [`CONTRIBUTING.md`](CONTRIBUTING.md) for policy details and [`DEVELOPMENT.md`](DEVELOPMENT.md) for local setup.

## Useful Resources

- [Feedback and Bug Reports](https://feedback.yaak.app)
- [Documentation](https://yaak.app/docs)
- [Yaak vs Postman](https://yaak.app/alternatives/postman)
- [Yaak vs Bruno](https://yaak.app/alternatives/bruno)
- [Yaak vs Insomnia](https://yaak.app/alternatives/insomnia)

## License

The source is [MIT licensed](LICENSE) and can be built and run for free, for both personal and commercial use.

The prebuilt binaries follow Yaak's [pricing terms](https://yaak.app/pricing): free for personal use, license required for commercial use.

---

### Overview (`crates-cli/yaak-cli/README.md`)

# Yaak CLI

The `yaak` CLI for publishing plugins and creating/updating/sending requests.

## Installation

```sh
npm install @yaakapp/cli
```

## Agentic Workflows

The `yaak` CLI is primarily meant to be used by AI agents, and has the following features:

- `schema` subcommands to get the JSON Schema for any model (eg. `yaak request schema http`)
- `--json '{...}'` input format to create and update data
- `--verbose` mode for extracting debug info while sending requests
- The ability to send entire workspaces and folders (Supports `--parallel` and `--fail-fast`)

### Agent Setup

Install the Yaak skill so your coding agent knows how to drive the CLI:

```sh
yaak agent install                                  # every agent found on this machine
npx skills add mountain-loop/yaak --skill use-yaak  # or with the skills CLI
```

Claude Code and Codex can install it as a plugin instead. In Claude Code, run
`/plugin install yaak --marketplace mountain-loop/yaak`; the plugin also registers the
`yaak mcp` server. In Codex, run `codex plugin marketplace add mountain-loop/yaak`,
then `codex plugin add yaak@yaak`.

Any MCP client can run the server over stdio:

```json
{
  "mcpServers": {
    "yaak": { "command": "npx", "args": ["-y", "@yaakapp/cli", "mcp"] }
  }
}
```

### Example Prompts

Use the `yaak` CLI with agents like Claude or Codex to do useful things for you.

Here are some example prompts:

```text
Scan my API routes and create a workspace (using yaak cli) with
all the requests needed for me to do manual testing?
```

```text
Send all the GraphQL requests in my workspace
```

## Description

Here's the current print of `yaak --help`

```text
Yaak CLI - API client from the command line

Usage: yaak [OPTIONS] <COMMAND>

Commands:
  auth         Authentication commands
  plugin       Plugin development and publishing commands
  send         Send a request, folder, or workspace by ID
  workspace    Workspace commands
  request      Request commands
  folder       Folder commands
  environment  Environment commands

Options:
      --data-dir <DATA_DIR>        Use a custom data directory
  -e, --environment <ENVIRONMENT>  Environment ID to use for variable substitution
  -v, --verbose                    Enable verbose send output (events and streamed response body)
      --log [<LEVEL>]              Enable CLI logging; optionally set level (error|warn|info|debug|trace) [possible values: error, warn, info, debug, trace]
  -h, --help                       Print help
  -V, --version                    Print version

Agent Hints:
  - Template variable syntax is ${[ my_var ]}, not {{ ... }}
  - Template function syntax is ${[ namespace.my_func(a='aaa',b='bbb') ]}
  - View JSONSchema for models before creating or updating (eg. `yaak request schema http`)
  - Deletion requires confirmation (--yes for non-interactive environments)
```

---

### Overview (`crates-server/yaak-playground/README.md`)

# yaak-playground

The API behind [yaak.run](https://yaak.run), for trying Yaak without bringing your own.

- Users and todos are fixed sample data.
- Posts can be created, changed, and deleted. Each client IP gets its own copy, which goes back
  to the sample data `YAAK_PLAYGROUND_RESET_AFTER_SECS` after its first write.
- `POST /auth/token` returns a bearer token for `GET /auth/me`. `POST /auth/session` sets a
  session cookie for `GET /auth/session`. Any username with the password `yaak` logs in.
- `GET /` lists every endpoint and `GET /openapi.json` describes them.

Nothing is written to disk, so a restart resets everything.

```sh
cargo run -p yaak-playground   # http://127.0.0.1:9228
```

Run `yaak-playground --help` for the limits. Behind a load balancer that sets
`X-Forwarded-For`, set `YAAK_PLAYGROUND_TRUST_FORWARDED_FOR=true`.

The image is `Dockerfile.playground` at the repo root.

---

### Overview (`crates-server/yaak-web/README.md`)

# yaak-web

The network half of Yaak in a browser — and, with `--serve`, the half that
hands the browser the app in the first place.

A tab can't see an HTTP response the way a desktop app can: CORS hides most
headers (2 of 8 in a typical response), redirects are followed silently, and
there is no timeline. So the tab renders the request and posts it here, and this
process puts it on the network with the desktop's own engine (`yaak-http`) and
streams back everything that happened — every header, every redirect hop, DNS
timing, the body — for the tab to store.

It is a **stateless executor**. It keeps nothing: no database, no files, no
sessions, no cookies between calls. Every byte it sees comes from the tab in the
request, and every byte it returns is stored by the tab. Restart it any time.

## Self-hosting it

One container, no configuration, nothing behind it:

```shell
docker run -p 8080:8080 ghcr.io/mountain-loop/yaak-web
```

Open <http://localhost:8080>. The image carries the built web client and this
binary, which serves it — so the app and its sends are on one origin, and the
tab's send URL is a path (`/v1/http/send`) rather than an address anyone has to
configure. The image is `linux/amd64` and `linux/arm64`, built from
`Dockerfile.web` at the repo root.

Your data lives in your browser (SQLite compiled to wasm, in IndexedDB), not in
the container. The container is stateless: nothing is written to disk, so
upgrading is `docker pull` and nothing else.

Two settings are worth knowing about:

```shell
docker run -p 8080:8080 \
  -e YAAK_WEB_ALLOW_PRIVATE_NETWORKS=true \
  -e YAAK_WEB_RATE_LIMIT_PER_MINUTE=0 \
  ghcr.io/mountain-loop/yaak-web
```

- **`YAAK_WEB_ALLOW_PRIVATE_NETWORKS=true`** lets sends reach loopback,
  private and link-local addresses. Off by default, and it should stay off on
  anything strangers can reach — see [What it refuses](#what-it-refuses-and-why).
  Turn it on for an instance on your own network, where calling the API on the
  next machine is the whole point. Note that "private" is relative to the
  _container_: `127.0.0.1` is the container itself, and reaching the Docker
  host means `host.docker.internal` (or `--network host`).
- **`YAAK_WEB_RATE_LIMIT_PER_MINUTE`** defaults to 120 sends per client IP,
  which suits a public instance and not a team of your own; `0` disables it.

Behind a reverse proxy, add `YAAK_WEB_TRUST_FORWARDED_FOR=true` so the rate
limit sees real client addresses instead of its own — and only then, since
otherwise anyone can spoof the header. If the reverse proxy buffers responses,
tell it not to: sends are streamed, and the `X-Accel-Buffering: no` header this
binary sets is honoured by nginx-shaped ones.

## Running it from source

```shell
cargo run -p yaak-web -- --serve dist/apps/yaak-client
```

after a `YAAK_TARGET=web SKIP_WASM_BUILD=1 npx vp -C apps/yaak-client build`.
Without `--serve` it is the send executor alone, which is what the frontend
dev server wants:

```shell
cargo run -p yaak-web
YAAK_TARGET=web npm run dev --workspace @yaakapp/yaak-client
```

The dev server passes `/v1` through to this binary, so a dev build sends to its
own origin exactly like a production build does — one address to open, and no
CORS in the loop. `PORT` moves this server and the dev server follows it. A
production build also sends to its own origin, unless `VITE_YAAK_WEB_URL` was
set when it was built.

Reaching the dev server from another machine is `HOST=0.0.0.0`. Vite allows
addresses but not names, so opening it as a hostname also needs
`ALLOWED_HOSTS=that-name`.

## Configuration

Every flag has an environment variable, so a container needs no arguments;
`--help` lists them all. The listen address uses the platform conventions —
`HOST` and `PORT` — so the published image runs unchanged on a
platform-as-a-service that assigns a port. Everything else is `YAAK_WEB_*`.

| Flag                       | Default     | What                                                                                          |
| -------------------------- | ----------- | --------------------------------------------------------------------------------------------- |
| `--serve`                  | off         | Also serve a built web client from this directory, on the same origin.                        |
| `--host`                   | `127.0.0.1` | Interface to listen on. The image sets `0.0.0.0`. May be a name; `localhost` resolves.        |
| `--port`                   | `9227`      | Port to listen on. The image sets `8080`.                                                     |
| `--allow-private-networks` | off         | Allow sends to loopback, private and link-local addresses.                                    |
| `--allowed-origins`        | `*`         | CORS origins, comma-separated. Unused when the app is served from here: same origin, no CORS. |
| `--max-request-bytes`      | 16 MiB      | Largest rendered request accepted from the tab.                                               |
| `--max-response-bytes`     | 64 MiB      | Largest upstream body relayed before the send is cut off.                                     |
| `--max-timeout-secs`       | 60          | Ceiling on a send's timeout; a request asking for more (or none) gets this.                   |
| `--rate-limit-per-minute`  | 120         | Sends per client IP per minute; 0 disables.                                                   |
| `--max-concurrent`         | 256         | Sends in flight at once.                                                                      |
| `--trust-forwarded-for`    | off         | Take the client IP from `X-Forwarded-For`. Only behind a load balancer that sets it.          |

## Logging

Default logs contain a startup message and operational warnings and errors, not request URLs, client IPs,
DNS lookups, or per-request timing. To diagnose a self-hosted instance, opt in with
`RUST_LOG=warn,yaak_http=error,yaak_web=debug`. This includes request URLs and IPs,
so only enable it while debugging. Hosting infrastructure may maintain its own
access logs independently.

## Serving the app

`--serve DIR` puts a file server behind the API routes: `/v1/*` is matched
first, everything else comes from `DIR`, and a path with no file behind it gets
`index.html` so the app's own routes survive a refresh. Responses are compressed
(gzip or zstd) on the fly. `/assets/*` is cached forever — Vite content-hashes
those names — and everything else is `no-cache`, so a new deploy arrives on the
next reload.

Serving files changes nothing about sending: the same rendered request, the same
destination policy, the same stateless executor. It exists so that a
self-hosted Yaak is one thing to run rather than two.

## Split deployments

The app and the sender can still be separate services — one CDN-hosted bundle and
one server elsewhere, or one server shared by several fronts. Then the bundle has
to be told where to send, at build time:

```shell
docker build -f Dockerfile.web \
  --build-arg VITE_YAAK_WEB_URL=https://send.example.com .
```

and the server needs the CORS origins its callers use, since the requests are no
longer same-origin:

```shell
docker run -p 8080:8080 \
  -e YAAK_WEB_ALLOWED_ORIGINS=https://yaak.example.com \
  ghcr.io/mountain-loop/yaak-web \
  yaak-web
```

The trailing `yaak-web` is a command override: the same image run without
`--serve`, so it executes sends and serves no app.

## What it refuses, and why

A hosted sender is, by construction, a machine that makes HTTP requests on
behalf of strangers. Left alone that is an open relay into whatever network it
sits on. So by default it refuses to connect to:

- loopback (`127/8`, `::1`), private (`10/8`, `172.16/12`, `192.168/16`,
  `fc00::/7`), link-local (`169.254/16` — where cloud metadata lives — and
  `fe80::/10`), carrier-grade NAT, multicast, reserved and unspecified ranges,
  IPv4 addresses carried inside IPv6 forms (`::ffff:a.b.c.d`, the well-known
  NAT64 prefix, 6to4), and the whole NAT64 local-use range;
- anything not `http://` or `https://`.

The check runs **on the resolved addresses, after DNS**, for every hop of a
redirect chain, so a public hostname that points at an internal address is
caught, and so is a `Location:` header that points at one. It also refuses body
types that would read files on its own disk (`binary`, multipart file
fields), since no browser tab could legitimately mean those.

Refusal reasons are included in debug logs when enabled. On a public instance
(`web.yaak.app`, or anything else strangers can reach), private-network protection
must stay on: the machine's private
network is the host's, not the user's, so a `localhost` or LAN API is not the
user's to reach through it — the desktop app is what reaches those. On an
instance you run for yourself, that reasoning is inverted, and
`--allow-private-networks` inverts the policy with it. It allows every range
above, including `169.254.169.254`, so use it only where the network on the
other side is one the users are entitled to.

There is no authentication either way: an instance is anonymous, protected by
the per-client rate limit and the destination policy. Anything more (a shared
token, per-user quotas) is a later slice and would sit in front of `send_http`
in `lib.rs`, or in middleware on the exported router. Put TLS in front of a public instance.

## The wire

`POST /v1/http/send` with a JSON body:

```json
{
  "request":  { "url": "https://…", "method": "GET", "headers": […], "body": {…}, "bodyType": null, "urlParameters": […] },
  "settings": { "validateCertificates": true, "followRedirects": true, "timeoutMs": 0, "sendCookies": true, "storeCookies": true },
  "cookies":  [ … ]
}
```

`request` is a Yaak `HttpRequest` in the desktop's own model shape with every
template already rendered by the tab; the server builds the URL, headers and
body from it exactly the way the desktop does after rendering. `cookies` is the
jar's contents (or `null` for no jar).

The reply is `application/x-ndjson`, one JSON frame per line, in the order things
happened:

| `type`     | When                                      | Carries                                                                            |
| ---------- | ----------------------------------------- | ---------------------------------------------------------------------------------- |
| `event`    | as the engine produces them               | one timeline event, in the desktop's `http_response_event.event` shape             |
| `response` | once, when the final hop's headers arrive | status, all headers, request headers as sent, remote address, HTTP version, timing |
| `body`     | as the body is read                       | a decompressed chunk, base64                                                       |
| `done`     | last, on success                          | elapsed, byte counts, and the cookie jar as the send left it                       |
| `error`    | last, on failure                          | the reason, and any cookies collected before the failure                           |

Refusals that happen before anything is sent (a blocked destination, a bad body,
rate limit, capacity) are plain HTTP errors (`403`, `400`, `429`, `503`) with
`{"error": "…"}`, not streams.

Why a streamed HTTP response and not a WebSocket: one `POST` is stateless by
construction, cancellable by closing the connection, readable with `curl`, and
needs no upgrade handling on either side. A WebSocket only earns its keep when
traffic is bidirectional, which a single send is not.

The TypeScript side of this contract is generated from `src/wire.rs` by ts-rs
into `bindings/` (run `cargo test -p yaak-web` after changing a frame)
and published to the tab as `@yaakapp-internal/web`, so a change to the
wire on one side is a type error on the other.

`GET /v1/health` reports the version and the effective limits.

## What comes later

Not built, by design, but the router is shaped for it: a WebSocket relay
(`/v1/ws/relay`) and a gRPC relay (`/v1/grpc/relay`) would be long-lived,
bidirectional endpoints on the same binary, behind the same destination policy
and limits. They differ from this endpoint in holding per-connection
in-memory state while a connection is open (never persisted), which brings
connection limits and a larger abuse surface — the reason they are separate
work.

---

### Overview (`packages/plugin-runtime-types/README.md`)

# Yaak Plugin API

Yaak is a desktop [API client](https://yaak.app/blog/yet-another-api-client) for
interacting with REST, GraphQL, Server Sent Events (SSE), WebSocket, and gRPC APIs. It's
built using Tauri, Rust, and ReactJS.

Plugins can be created in TypeScript, which are executed alongside Yaak in a NodeJS
runtime. This package contains the TypeScript type definitions required to make building
Yaak plugins a breeze.

## Quick Start

The easiest way to get started is by generating a plugin with the Yaak CLI:

```shell
npx @yaakapp/cli generate
```

For more details on creating plugins, check out
the [Quick Start Guide](https://yaak.app/docs/plugin-development/plugins-quick-start)

## Installation

If you prefer starting from scratch, manually install the types package:

```shell
npm install -D @yaakapp/api
```

---

### Overview (`plugins-external/faker/README.md`)

# Yaak Faker Plugin

This is a template function that generates realistic fake data
for testing and development using [FakerJS](https://fakerjs.dev).



## Example JSON Body

Here's an example JSON body that uses fake data:

```json
{
  "id": "${[ faker.string.uuid() ]}",
  "name": "${[ faker.person.fullName() ]}",
  "email": "${[ faker.internet.email() ]}",
  "phone": "${[ faker.phone.number() ]}",
  "address": {
    "street": "${[ faker.location.streetAddress() ]}",
    "city": "${[ faker.location.city() ]}",
    "country": "${[ faker.location.country() ]}",
    "zipCode": "${[ faker.location.zipCode() ]}"
  },
  "company": "${[ faker.company.name() ]}",
  "website": "${[ faker.internet.url() ]}"
}
```

This will generate a random JSON body on every request:

```json
{
  "id": "589f0aec-7310-4bf2-81c4-0b1bb7f1c3c1",
  "name": "Lucy Gottlieb-Weissnat",
  "email": "Destiny_Herzog@gmail.com",
  "phone": "411.805.2871 x699",
  "address": {
    "street": "846 Christ Mills",
    "city": "Spencerfurt",
    "country": "United Kingdom",
    "zipCode": "20354"
  },
  "company": "Emard, Kohler and Rutherford",
  "website": "https://watery-detective.org"
}
```

## Available Categories

The plugin provides access to all FakerJS modules and their methods:

| Category   | Description               | Example Methods                            |
| ---------- | ------------------------- | ------------------------------------------ |
| `airline`  | Airline-related data      | `aircraftType`, `airline`, `airplane`      |
| `animal`   | Animal names and types    | `bear`, `bird`, `cat`, `dog`, `fish`       |
| `color`    | Colors in various formats | `human`, `rgb`, `hex`, `hsl`               |
| `commerce` | E-commerce data           | `department`, `product`, `price`           |
| `company`  | Company information       | `name`, `catchPhrase`, `bs`                |
| `database` | Database-related data     | `column`, `type`, `collation`              |
| `date`     | Date and time values      | `recent`, `future`, `past`, `between`      |
| `finance`  | Financial data            | `account`, `amount`, `currency`            |
| `git`      | Git-related data          | `branch`, `commitEntry`, `commitSha`       |
| `hacker`   | Tech/hacker terminology   | `abbreviation`, `noun`, `phrase`           |
| `image`    | Image URLs and data       | `avatar`, `url`, `dataUri`                 |
| `internet` | Internet-related data     | `email`, `url`, `ip`, `userAgent`          |
| `location` | Geographic data           | `city`, `country`, `latitude`, `longitude` |
| `lorem`    | Lorem ipsum text          | `word`, `sentence`, `paragraph`            |
| `person`   | Personal information      | `firstName`, `lastName`, `fullName`        |
| `music`    | Music-related data        | `genre`, `songName`, `artist`              |
| `number`   | Numeric data              | `int`, `float`, `binary`, `hex`            |
| `phone`    | Phone numbers             | `number`, `imei`                           |
| `science`  | Scientific data           | `chemicalElement`, `unit`                  |
| `string`   | String utilities          | `uuid`, `alpha`, `alphanumeric`            |
| `system`   | System-related data       | `fileName`, `mimeType`, `fileExt`          |
| `vehicle`  | Vehicle information       | `vehicle`, `manufacturer`, `model`         |
| `word`     | Word generation           | `adjective`, `adverb`, `conjunction`       |

---

### Overview (`plugins-external/httpsnippet/README.md`)

# Yaak HTTP Snippet Plugin

Generate code snippets for HTTP requests in various languages and frameworks,
powered by [@readme/httpsnippet](https://github.com/readmeio/httpsnippet).



## How It Works

Right-click any HTTP request (or use the `...` menu) and select **Generate Code Snippet**.
A dialog lets you pick a language and library, with a live preview of the generated code.
Click **Copy to Clipboard** to copy the snippet. Your language and library selections are
remembered for next time.

## Supported Languages

Each language supports one or more libraries, listed in the order they appear in the
dialog:

| Language    | Libraries                                 |
| ----------- | ----------------------------------------- |
| Agent       | Agent Prompt                              |
| C           | Libcurl                                   |
| Clojure     | clj-http                                  |
| Crystal     | http::client                              |
| C#          | HttpClient, RestSharp                     |
| Go          | NewRequest                                |
| HTTP        | HTTP/1.1                                  |
| Java        | AsyncHttp, java.net.http, OkHttp, Unirest |
| JavaScript  | XMLHttpRequest, Axios, fetch, jQuery      |
| Kotlin      | OkHttp                                    |
| Node.js     | HTTP, Axios, fetch                        |
| Objective-C | NSURLSession                              |
| OCaml       | CoHTTP                                    |
| PHP         | cURL, Guzzle, HTTP v1, HTTP v2            |
| Powershell  | Invoke-WebRequest, Invoke-RestMethod      |
| Python      | Requests                                  |
| R           | httr                                      |
| Ruby        | net::http, faraday                        |
| Rust        | reqwest                                   |
| Shell       | cURL, HTTPie, Wget                        |
| Swift       | URLSession                                |

The **Agent** target produces a prompt describing the request rather than code, for handing
to an AI coding agent.

## Features

- Renders template variables before generating snippets, so the output reflects real values
- Supports all body types: JSON, form-urlencoded, multipart, GraphQL, and raw text
- Includes authentication headers (Basic, Bearer, and API Key)
- Includes query parameters and custom headers

---

### Overview (`plugins-external/mcp-server/README.md`)

# Yaak MCP Server Plugin

Exposes Yaak's functionality via the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/).

**Deprecated.** Yaak now has a built-in MCP server with the same tools. Uninstall this plugin,
then turn the server on in Settings > Agents, or run `yaak mcp` from the Yaak CLI.

## Setup

Add this to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):

```json
{
  "mcpServers": {
    "yaak": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "http://127.0.0.1:64343/mcp"]
    }
  }
}
```

Restart Claude Desktop and make sure Yaak is running.

## Available Tools

- `list_http_requests` - List all HTTP requests in a workspace
- `get_http_request` - Get details of a specific HTTP request
- `send_http_request` - Send an HTTP request and get the response
- `create_http_request` - Create a new HTTP request
- `update_http_request` - Update an existing HTTP request
- `delete_http_request` - Delete an HTTP request
- `list_folders` - List all folders in a workspace
- `list_workspaces` - List all open workspaces
- `get_workspace_id` - Get the current workspace ID
- `get_environment_id` - Get the current environment ID
- `copy_to_clipboard` - Copy text to the system clipboard
- `show_toast` - Show a toast notification in Yaak

## Environment Selection

`send_http_request` accepts an optional `environmentId` for that execution. It overrides the
active environment without changing the selection in Yaak. Omitting it uses the host's
active environment, as before.

The ID must identify a base or sub-environment in the request's workspace. An empty,
unknown, foreign-workspace, or folder environment ID returns an error before sending.
Use the base environment ID to run with global variables only. Folder variables still
apply according to the request's folder hierarchy.

```json
{
  "id": "rq_example",
  "workspaceId": "wk_example",
  "environmentId": "ev_staging"
}
```

This requires a Yaak host with support for the `environmentId` plugin API field;
older hosts ignore it. `get_environment_id` reports the active selection, not the
override used by a previous send.

---

### Overview (`plugins/action-copy-curl/README.md`)

# Copy as cUrl

A request action plugin for Yaak that converts HTTP requests into [curl](https://curl.se)
commands, making it easy to share, debug, and execute requests outside Yaak.



## Overview

This plugin adds a 'Copy as Curl' action to HTTP requests, converting any request into its
equivalent curl command. This is useful for debugging, sharing requests with team members,
and executing requests in terminal environments where `curl` is available.

## How It Works

The plugin analyzes the given HTTP request and generates a properly formatted curl command
that includes:

- HTTP method (GET, POST, PUT, DELETE, etc.)
- Request URL with query parameters
- Headers (including authentication headers)
- Request body (for POST, PUT, PATCH requests)
- Authentication credentials

## Usage

1. Configure an HTTP request as usual in Yaak
2. Right-click on the request in the sidebar
3. Select 'Copy as Curl'
4. The command is copied to your clipboard
5. Share or execute the command

## Generated Curl Examples

### Simple GET Request

```bash
curl -X GET 'https://api.example.com/users' \
  --header 'Accept: application/json'
```

### POST Request with JSON Data

```bash
curl -X POST 'https://api.example.com/users' \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json' \
  --data '{
    "name": "John Doe",
    "email": "john@example.com"
  }'
```

### Request with Multi-part Form Data

```bash
curl -X POST 'yaak.app' \
  --header 'Content-Type: multipart/form-data' \
  --form 'hello=world' \
  --form file=@/path/to/file.json
```

### Request with Authentication

```bash
curl -X GET 'https://api.example.com/protected' \
  --user 'username:password'
```

---

### Overview (`plugins/action-copy-grpcurl/README.md`)

# Copy as gRPCurl

An HTTP request action plugin that converts gRPC requests
into [gRPCurl](https://github.com/fullstorydev/grpcurl) commands, enabling easy sharing,
debugging, and execution of gRPC calls outside Yaak.



## Overview

This plugin adds a "Copy as gRPCurl" action to gRPC requests, converting any gRPC request
into its equivalent executable command. This is useful for debugging gRPC services,
sharing requests with team members, or executing gRPC calls in terminal environments where
`grpcurl` is available.

## How It Works

The plugin analyzes your gRPC request configuration and generates a properly formatted
`grpcurl` command that includes:

- gRPC service and method names
- Server address and port
- Request message data (JSON format)
- Metadata (headers)
- Authentication credentials
- Protocol buffer definitions

## Usage

1. Configure a gRPC request as usual in Yaak
2. Right-click on the request sidebar item
3. Select "Copy as gRPCurl" from the available actions
4. The command is copied to your clipboard
5. Share or execute the command

## Generated gRPCurl Examples

### Simple Unary Call

```bash
grpcurl -plaintext \
  -d '{"name": "John Doe"}' \
  localhost:9090 \
  user.UserService/GetUser
```

### Call with Metadata

```bash
grpcurl -plaintext \
  -H "authorization: Bearer my-token" \
  -H "x-api-version: v1" \
  -d '{"user_id": "12345"}' \
  api.example.com:443 \
  user.UserService/GetUserProfile
```

### Call with TLS

```bash
grpcurl \
  -d '{"query": "search term"}' \
  secure-api.example.com:443 \
  search.SearchService/Search
```

### Call with Proto Files

```bash
grpcurl -import-path /path/to/protos \
  -proto /other/path/to/user.proto \
  -d '{"email": "user@example.com"}' \
  localhost:9090 \
  user.UserService/CreateUser
```

---

### Overview (`plugins/auth-aws/README.md`)

# AWS Signature Version 4 Auth

A plugin for authenticating AWS-compatible requests using the
[AWS Signature Version 4 signing process](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html).  
This enables secure, signed requests to AWS services (or any S3-compatible APIs like
Cloudflare R2).



## Overview

This plugin provides AWS Signature authentication for API requests in Yaak. SigV4 is used
by nearly all AWS APIs to verify the authenticity and integrity of requests using
cryptographic signatures.

With this plugin, you can securely sign requests to AWS services such as S3, STS, Lambda,
API Gateway, DynamoDB, and more. You can also authenticate against S3-compatible services
like **Cloudflare R2**, **MinIO**, or **Wasabi**.

## How AWS Signature Version 4 Works

SigV4 signs requests by creating a hash of key request components (method, URL, headers,
and optionally the payload) using your AWS credentials. The resulting HMAC signature is
added in the `Authorization` header along with credential scope metadata.

Example header:

```
Authorization: AWS4-HMAC-SHA256 Credential=AKIA…/20251011/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-date, Signature=abcdef123456…
```

Each request must include a timestamp (`X-Amz-Date`) and may include a session token if
using temporary credentials.

## Configuration

The plugin presents the following fields:

- **Access Key ID** – Your AWS access key identifier
- **Secret Access Key** – The secret associated with the access key
- **Session Token** _(optional)_ – Used for temporary or assumed-role credentials (treated as secret)
- **Region** – AWS region (e.g., `us-east-1`)
- **Service** – AWS service identifier (e.g., `sts`, `s3`, `execute-api`)

## Usage

1. Configure a request, folder, or workspace to use **AWS SigV4 Authentication**
2. Enter your AWS credentials and target service/region
3. The plugin will automatically sign outgoing requests with valid SigV4 headers

---

### Overview (`plugins/auth-basic/README.md`)

# Basic Authentication

A simple Basic Authentication plugin that implements HTTP Basic Auth according
to [RFC 7617](https://datatracker.ietf.org/doc/html/rfc7617), enabling secure
authentication with username and password credentials.



## Overview

This plugin provides HTTP Basic Authentication support for API requests in Yaak. Basic
Auth is one of the most widely supported authentication methods, making it ideal for APIs
that require simple username/password authentication without the complexity of OAuth
flows.

## How Basic Authentication Works

Basic Authentication encodes your username and password credentials using Base64 encoding
and sends them in the `Authorization` header with each request. The format is:

```
Authorization: Basic <base64-encoded-credentials>
```

Where `<base64-encoded-credentials>` is the Base64 encoding of `username:password`.

## Configuration

The plugin presents two fields:

- **Username**: Username or user identifier
- **Password**: Password or authentication token

## Usage

1. Configure the request, folder, or workspace to use Basic Authentication
2. Enter your username and password in the authentication configuration
3. The plugin will automatically add the proper `Authorization` header to your requests

## Troubleshooting

- **401 Unauthorized**: Verify your username and password are correct
- **403 Forbidden**: Check if your account has the necessary permissions
- **Connection Issues**: Ensure you're using HTTPS for secure transmission

---

### Overview (`plugins/auth-bearer/README.md`)

# Bearer Token Authentication Plugin

A Bearer Token authentication plugin for Yaak that
implements [RFC 6750](https://datatracker.ietf.org/doc/html/rfc6750), enabling secure API
access using tokens, API keys, and other bearer credentials.



## Overview

This plugin provides Bearer Token authentication support for your API requests in Yaak.
Bearer Token authentication is widely used in modern APIs, especially those following REST
principles and OAuth 2.0 standards. It's the preferred method for APIs that issue access
tokens, API keys, or other bearer credentials.

## How Bearer Token Authentication Works

Bearer Token authentication sends your token in the `Authorization` header with each
request using the Bearer scheme:

```
Authorization: Bearer <your-token>
```

The token is transmitted as-is without any additional encoding, making it simple and
efficient for API authentication.

## Configuration

The plugin requires only one field:

- **Token**: Your bearer token, access token, API key, or other credential
- **Prefix**: The prefix to use for the Authorization header, which will be of the
  format "<PREFIX> <TOKEN>"

## Usage

1. Configure the request, folder, or workspace to use Bearer Authentication
2. Enter the token and optional prefix in the authentication configuration
3. The plugin will automatically add the proper `Authorization` header to your requests

## Troubleshooting

- **401 Unauthorized**: Verify your token is valid and not expired
- **403 Forbidden**: Check if your token has the necessary permissions/scopes
- **Invalid Token Format**: Ensure you're using the complete token without truncation
- **Token Expiration**: Refresh or regenerate expired tokens

---

### Overview (`plugins/auth-digest/README.md`)

# Digest Authentication

An HTTP Digest Authentication plugin that implements
[RFC 7616](https://datatracker.ietf.org/doc/html/rfc7616), with fallback to the
older [RFC 2617](https://datatracker.ietf.org/doc/html/rfc2617) and
[RFC 2069](https://datatracker.ietf.org/doc/html/rfc2069) behaviour that many
servers still speak.

## Overview

Digest Authentication proves you know a password without ever putting it on the
wire. The server issues a one-time `nonce`, and the client answers with a hash
over the credentials, the nonce and the request itself.

## How it works

Because the digest is computed over a server-issued nonce, the challenge has to
be fetched before the real request can be signed. On each send, the plugin:

1. Sends an unauthenticated probe of the same method and URL, carrying no
   headers of its own so it cannot authorize anything
2. Reads the `WWW-Authenticate: Digest …` challenge from the `401` response
3. Computes the response hash and returns the `Authorization` header

## Configuration

- **Username**: Username or user identifier
- **Password**: Password or authentication token
- **Realm** (advanced): Only needed when the server offers more than one realm.
  Leave it empty and the first challenge the server prefers is used.

## Supported challenges

| Feature    | Supported                                                     |
| ---------- | ------------------------------------------------------------- |
| Algorithm  | `MD5`, `MD5-sess`, `SHA-256`, `SHA-256-sess`                  |
| `qop`      | `auth`, `auth-int`, and challenges that omit `qop` (RFC 2069) |
| `opaque`   | Echoed back when the server sends one                         |
| `userhash` | Declined with `userhash=false`                                |

`auth-int` is used when the server offers it and Yaak has the request body in
hand. Bodies that are streamed from disk or above the size Yaak passes to auth
plugins aren't available to hash, so those requests use `auth` instead.

Credentials are normalized to Unicode NFC before hashing, per RFC 7616 §4.
Usernames outside ASCII are sent as an RFC 5987 extended value (`username*`).

## Troubleshooting

- **Server did not offer Digest authentication**: The endpoint answered the probe
  with a different scheme, or with no `WWW-Authenticate` header at all. Check the
  URL, and whether the endpoint requires auth in the first place.
- **Unsupported Digest algorithm**: The server asked for an algorithm this plugin
  doesn't implement, such as `SHA-512-256`.
- **401 Unauthorized**: Verify the username and password. If the server offers
  several realms, set the Realm field to the one your account belongs to.

---

### Overview (`plugins/auth-jwt/README.md`)

# JSON Web Token (JWT) Authentication

A [JSON Web Token](https://datatracker.ietf.org/doc/html/rfc7519) (JWT) authentication
plugin that supports token generation, signing, and automatic header management.



## Overview

This plugin provides JWT authentication support for API requests. JWT is a compact,
URL-safe means of representing claims between two parties, commonly used for
authentication and information exchange in modern web applications and APIs.

## How JWT Authentication Works

JWT authentication involves creating a signed token containing claims about the user or
application. The token is sent in the `Authorization` header:

```
Authorization: Bearer <jwt-token>
```

A JWT consists of three parts separated by dots:

- **Header**: Contains the token type and signing algorithm
- **Payload**: Contains the claims (user data, permissions, expiration, etc.)
- **Signature**: Ensures the token hasn't been tampered with

## Usage

1. Configure the request, folder, or workspace to use JWT Authentication
2. Set up your signing algorithm and secret/key
3. Add custom JWT header fields if needed
4. Configure the required claims for your JWT payload
5. The plugin will generate, sign, and include the JWT in your requests

## Common Use Cases

JWT authentication is commonly used for:

- **Microservices Authentication**: Service-to-service communication
- **API Gateway Integration**: Authenticating with API gateways
- **Single Sign-On (SSO)**: Sharing authentication across applications
- **Stateless Authentication**: No server-side session storage required
- **Mobile App APIs**: Secure authentication for mobile applications
- **Third-party Integrations**: Authenticating with external services

## Troubleshooting

- **Invalid Signature**: Check your secret/key and algorithm configuration
- **Token Expired**: Verify expiration time settings
- **Invalid Claims**: Ensure required claims are properly configured
- **Algorithm Mismatch**: Verify the algorithm matches what the API expects
- **Key Format Issues**: Ensure RSA keys are in the correct PEM format

---

### Overview (`plugins/auth-oauth2/README.md`)

# OAuth 2.0 Authentication

An [OAuth 2.0](https://datatracker.ietf.org/doc/html/rfc6749) authentication plugin that
supports multiple grant types and flows, enabling secure API authentication with OAuth 2.0
providers.



## Overview

This plugin implements OAuth 2.0 authentication for requests, supporting the most common
OAuth 2.0 grant types used in modern API integrations. It handles token management,
automatic refresh, and [PKCE](https://datatracker.ietf.org/doc/html/rfc7636) (Proof Key
for Code Exchange) for enhanced security.

## Supported Grant Types

### Authorization Code Flow

The most secure and commonly used OAuth 2.0 flow for web applications.

- Standard Authorization Code flow
- Optional PKCE (Proof Key for Code Exchange) for enhanced security
- Supports automatic token refresh

### Client Credentials Flow

Ideal for server-to-server authentication where no user interaction is required.

### Implicit Flow

Legacy flow for single-page applications (deprecated but still supported):

- Direct access token retrieval
- No refresh token support
- Suitable for legacy integrations

### Resource Owner Password Credentials Flow

Direct username/password authentication.

- User credentials are exchanged directly for tokens
- Should only be used with trusted applications
- Supports automatic token refresh

## Features

- **Automatic Token Management**: Handles token storage, expiration, and refresh
  automatically
- **PKCE Support**: Enhanced security for Authorization Code flow
- **Token Persistence**: Stores tokens between sessions
- **Flexible Configuration**: Supports custom authorization and token endpoints
- **Scope Management**: Configure required OAuth scopes for your API
- **Custom Parameters**: Add headers and params to the authorization, token, and refresh
  requests, replacing generated entries of the same name
- **Error Handling**: Comprehensive error handling and user feedback

## Usage

1. Configure the request, folder, or workspace to use OAuth 2.0 Authentication
2. Select the appropriate grant type for your use case
3. Fill in the required OAuth 2.0 parameters from your API provider
4. The plugin will handle the authentication flow and token management automatically

## Compatibility

This plugin is compatible with OAuth 2.0 providers including:

- Google APIs
- Microsoft Graph
- GitHub API
- Auth0
- Okta
- And many other OAuth 2.0 compliant services

---

### Overview (`plugins/importer-bruno/README.md`)

# Bruno importer

Import Bruno's **Single File (YAML)** or **Bruno Collection (ZIP)** exports, or select a collection directory. Both OpenCollection `1.0.0` YAML collections and legacy `.bru` collections are supported. Individual `.bru` request files also work. A directory/ZIP must contain one collection root (`opencollection.yml`, `opencollection.yaml`, or `bruno.json`); a wrapper directory in the ZIP is allowed. Use **Choose folder** for an on-disk collection, rather than selecting its configuration file alone.

The importer consumes the shared [file-tree API](../../packages/plugin-runtime-types/IMPORTERS.md). It skips `.git`, `node_modules`, `__MACOSX`, and hidden entries. It does not install dependencies, load dotenv files, or run collection scripts.

Supported:

- Nested folders, ordering, descriptions, collection/folder headers, and variables.
- Environments, disabled variables, typed values, and secret placeholders. Environment inheritance is flattened into each selectable environment because Yaak does not have nested selectable environments.
- HTTP URLs, query/path parameters, headers, raw bodies, URL-encoded forms, multipart files, and selected binary files.
- GraphQL query and variables in Yaak's sendable body shape.
- gRPC URL, service/method, metadata, and selected message; WebSocket URL, headers, and selected message.
- Explicit auth inheritance, no auth, basic, bearer, digest, NTLM, API key, AWS credentials, common OAuth 1.0 header signing, and OAuth 2.0 flows with header tokens.
- Plain `{{variable}}` references converted to Yaak templates. Expressions, dynamic variables, and `process.env` references stay unchanged.

Scripts, assertions, actions, request-scoped variables, examples, advanced settings, and unsupported authentication configurations are retained in resource descriptions for manual migration, not executed. Alternative bodies/messages use the selected variant (or the first when unspecified); retained source variants remain in descriptions. File paths are preserved without reading files; relative paths and gRPC proto files may need to be configured after import. External secret providers, certificates, proxies, OAuth token caches, and WebSocket binary messages are not activated by the importer.

OpenCollection does not export persistent resource IDs, so Yaak derives the import source keys using its existing fallback matching.

Implementation references:

- [OpenCollection types and schema](https://github.com/opencollection-dev/opencollection)
- [Bruno's bundled export converter](https://github.com/usebruno/bruno/blob/main/packages/bruno-converters/src/opencollection/bruno-to-opencollection.ts)
- [Bruno's Share exporter](https://github.com/usebruno/bruno/blob/main/packages/bruno-app/src/utils/exporters/opencollection.js)

Legacy `.bru` parsing uses Bruno's official `@usebruno/filestore` (backed by `@usebruno/lang`). Its YAML serializers normalize parsed collections to OpenCollection before they enter the same Yaak conversion path. YAML collections are assembled directly to preserve their fields.

---

### Overview (`crates/common/yaak-rpc-schema/README.md`)

# yaak-rpc-schema

The wire schema for the app's RPC surface: every command name, its request
payload, and its response type, declared once.

Every host that serves the Yaak UI — the desktop app today, the browser bridge
and anything after it — imports these types and implements the commands against
them. That is what keeps a request's shape from drifting between hosts, and it
is why the TypeScript bindings (`bindings/gen_rpc.ts`, exposed to the frontend
as `@yaakapp-internal/rpc-schema`) are generated from one place.

Nothing here depends on Tauri or on any host. Request structs are plain data,
and so are the few response types declared here rather than in an engine crate.
Command *bodies* live with the host that runs them.

## Adding a command

1. Add its request struct and an entry in `with_commands!` in `src/lib.rs`.
2. Write the adapter in each host — the desktop's live in
   `crates-tauri/yaak-app-client/src/rpc_ext.rs`. A host that does not support
   the command still has to say so; a missing adapter fails to compile.
3. Regenerate the bindings: `cargo test -p yaak-rpc-schema` writes
   `bindings/gen_rpc.ts`, which is committed.

## How hosts consume the list

`with_commands!` takes the name of a `macro_rules!` macro and calls it with the
full `name(Req) -> Res` list. Each host writes a small macro that receives that
list and builds its router:

```rust
macro_rules! register_commands {
    ( $( $name:ident ( $req:ty ) -> $res:ty ),* $(,)? ) => {
        pub fn build_router() -> RpcRouter<MyCtx> {
            let mut router = RpcRouter::new();
            $( router.register(stringify!($name), rpc_handler_async!($name)); )*
            router
        }
    };
}
yaak_rpc_schema::with_commands!(register_commands);
```

The schema decides *what* commands exist; the host decides *how* each one runs.

---

### Overview (`packages/platform/src/web/README.md`)

# The browser host

Yaak running in a plain tab: no install, no local process. The desktop's own
model layer — `yaak-models`, SQLite included — runs compiled to wasm inside a
worker the tab talks to, so a browser stores exactly what a desktop install
stores, migrations and all.

Select it at build time. `vp run web:dev` does that and starts the send server
alongside the frontend:

```shell
vp run web:dev
```

The flag resolves `@yaakapp-internal/platform` to `../index.web.ts`, which
installs this host instead of the Tauri one. It is a separate entry rather than
a branch inside `index.ts` so that a web build never pulls `@tauri-apps/*` into
the module graph at all — a folded branch would drop the code but keep the
imports it guarded.

Desktop builds are untouched: without the flag, `packages/platform/src/index.ts`
installs the Tauri host exactly as before.

## How it fits together

```
tab (index.ts, commands.ts) ──MessagePort──▶ worker.ts ──▶ @yaakapp-internal/web (wasm)
        │                   ◀── model_writes ──          crates/yaak-wasm → yaak-models → SQLite
        │                                                               └─ pages in IndexedDB
        └── send.ts ──POST rendered request──▶ yaak-web (crates-server) ──▶ the internet
                     ◀── NDJSON: events, response, body, cookies ──
```

| File            | What it is                                                                                                                            |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `index.ts`      | The `Platform` implementation.                                                                                                        |
| `commands.ts`   | The command table: model commands forward to the worker; the rest is fixed answers and refusals-with-a-reason.                        |
| `connection.ts` | A tab's end of the wire: request/response over a `MessagePort`, event delivery, and the tab's identity (`label`).                     |
| `send.ts`       | Sending: the worker renders (`prepare_http_send`), the server executes, this file stores what comes back where the desktop stores it. |
| `server.ts`     | Where the Yaak server is, and the wire shapes it speaks (generated from `crates-server/yaak-web/src/wire.rs`).                        |
| `worker.ts`     | The process that owns the database. Loads the wasm, opens the DB once, answers each port, fans `model_writes` out to every port.      |
| `protocol.ts`   | The message shapes both sides import.                                                                                                 |
| `errors.ts`     | `UnsupportedCommandError`, the structured refusal.                                                                                    |
| `storage.ts`    | `navigator.storage.persist()`.                                                                                                        |

The Rust side is `crates/yaak-wasm` (`@yaakapp-internal/wasm`): `boot()`,
`rpc(cmd, payload, label)` returning `{ result, events }`, blob get/put, and
`prepare_http_send(payload)` — the database half of a send (environment chain,
inherited headers and auth, request settings, cookie jar, rendering), which is
`yaak_models::render::render_http_request`, the same function the desktop
renders with.
Its `pkg/` is committed; rebuilding needs a clang with a WebAssembly backend
(`brew install llvm`), and `build-wasm.cjs` skips with a notice when there
isn't one, so a desktop `npm run bootstrap` never depends on it.

Behaviours worth knowing before changing anything:

- **The worker is a `SharedWorker`, and only that.** The browser hands every
  tab on the origin the same one, which is what makes "one database owner"
  true without anyone coordinating — and makes the browser look like the
  desktop: one process holds the data, every window talks to it, it pushes
  writes to all of them. It still takes a Web Lock before opening, for the one
  overlap the browser doesn't rule out (a reloading tab's dying predecessor).
  There is deliberately no fallback to a per-tab worker: two kinds of worker
  that can both come up is a race. Every current browser, desktop and mobile,
  has `SharedWorker` and Web Locks (Chrome for Android since 148, April 2026);
  older ones get a clear "unsupported browser" message rather than a second
  SQLite over the same pages.
- **Every write is stamped with the calling tab's `label`** as
  `UpdateSource::Window`, exactly like a desktop window label, so the frontend
  store's echo handling is unchanged.
- **Cascade rules, duplicate naming, id generation, serde defaults, and the
  lazy first-run bootstrap are all the Rust code's.** Nothing about what a
  model _is_ is decided in TypeScript.
- **Persistence is `relaxed-idb`**: SQLite pages live in IndexedDB, writes land
  in memory and flush shortly after. A tab closing mid-flush loses at most the
  last few writes.

## Commands

109 commands are declared in `@yaakapp-internal/rpc-schema`. This host answers
32, declines 43 by name with a reason, and refuses the remaining 34 generically.

### Implemented (32)

| Group                           | Commands                                                                                                                                                                                                                                                 |
| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Models                          | `models_workspace_models`, `models_upsert`, `models_delete`, `models_duplicate`, `models_get_settings`, `models_get_graphql_introspection`, `models_upsert_graphql_introspection`, `models_grpc_events`, `models_websocket_events`                       |
| Sending                         | `cmd_send_http_request` (through the Yaak server; see below)                                                                                                                                                                                             |
| App                             | `cmd_metadata`, `cmd_get_workspace_meta`, `cmd_default_headers`, `cmd_get_themes`, `cmd_check_for_updates`, `cmd_dismiss_notification`, `cmd_plugin_init_errors`                                                                                         |
| Bodies                          | `cmd_http_response_body`, `cmd_http_response_body_path`, `cmd_http_request_body`, `cmd_get_http_response_events`, `cmd_get_sse_events`                                                                                                                   |
| Plugin surfaces (empty results) | `cmd_http_request_actions`, `cmd_websocket_request_actions`, `cmd_grpc_request_actions`, `cmd_workspace_actions`, `cmd_folder_actions`, `cmd_template_function_summaries`, `cmd_get_http_authentication_summaries`, `cmd_get_http_authentication_config` |
| Text                            | `cmd_format_json`, `cmd_render_template`                                                                                                                                                                                                                 |

Some of these answer honestly rather than fully, and the difference matters:

- `cmd_render_template` returns the template **unrendered**. Resolving variables
  and calling template functions is plugin work. The preview shows the raw
  `${[…]}` rather than a wrong value.
- `cmd_get_http_authentication_summaries` returns the auth methods Yaak ships as
  plugins, so the picker is truthful about the product — but
  `cmd_get_http_authentication_config` returns an empty form, because the plugin
  that defines the form isn't running.
- `cmd_template_function_summaries` returns one provider contributing no
  functions. Both summary commands are polled every second until they return
  something, so an empty list is a poll that never stops rather than a quiet no.
- `cmd_metadata` reports empty strings for the data, log, plugin and project
  directories. There is no filesystem behind this host.

### Declined by name (43)

Each returns an `UnsupportedCommandError` carrying `cmd`, a user-facing
`message`, and the `capability` a caller should have checked. The UI turns it
into a toast.

| Reason                           | Commands                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Sending, the parts not wired yet | `cmd_send_ephemeral_request`, `cmd_delete_send_history`, `cmd_delete_all_http_responses`, `cmd_import_url`                                                                                                                                                                                                                                                                                                                                                                                                            |
| No plugin runtime                | `cmd_reload_plugins`, `cmd_plugin_info`, `cmd_plugins_search`, `cmd_plugins_install`, `cmd_plugins_install_from_directory`, `cmd_plugins_uninstall`, `cmd_plugins_updates`, `cmd_plugins_update_all`, `cmd_template_function_config`, `cmd_template_tokens_to_string`, `cmd_call_http_request_action`, `cmd_call_websocket_request_action`, `cmd_call_grpc_request_action`, `cmd_call_workspace_action`, `cmd_call_folder_action`, `cmd_call_http_authentication_action`, `cmd_curl_to_request`, `cmd_format_graphql` |
| No filesystem                    | `cmd_import_data`, `cmd_export_data`, `cmd_save_response`, `cmd_save_base64_to_binary`                                                                                                                                                                                                                                                                                                                                                                                                                                |
| Needs a real socket              | `cmd_grpc_reflect`, `cmd_grpc_go`, `cmd_delete_all_grpc_connections`, `cmd_ws_connect`, `cmd_ws_send`, `cmd_ws_close`, `cmd_ws_delete_connections`                                                                                                                                                                                                                                                                                                                                                                    |
| Workspace encryption             | `cmd_enable_encryption`, `cmd_disable_encryption`, `cmd_reveal_workspace_key`, `cmd_set_workspace_key`, `cmd_secure_template`, `cmd_decrypt_template`                                                                                                                                                                                                                                                                                                                                                                 |
| One tab, no windows              | `cmd_new_child_window`, `cmd_new_main_window`, `cmd_restart`                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| Other                            | `cmd_send_feedback`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |

### Refused generically (34)

The 30 `cmd_git_*` commands and `cmd_sync_calculate`, `cmd_sync_calculate_fs`,
`cmd_sync_apply`, `cmd_sync_watch`. Nothing in the app reaches them unless a
workspace has a sync directory, which a browser tab cannot set.

Anything added to the schema later also lands here, and the error names the
command — an unlisted command is a gap in `commands.ts`, and whoever hits it
should be able to see which.

## Capabilities

Reported honestly, so callers gate on the question rather than on the host:

| True                                   | False                                                                                                                                                                                         |
| -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `httpSending`, `timeline`, `cookieJar` | `grpc`, `websocket`, `git`, `sync`, `tlsOptions`, `localFiles`, `multiWindow`, `windowChrome`, `interfaceZoom`, `plugins`, `encryption`, `updater`, `clipboardRead`, `systemFonts`, `license` |

`interfaceZoom: false` leaves Cmd/Ctrl `+`, `-` and `0` to the browser instead
of swallowing them, and drops those three rows from the hotkeys screen.

`multiWindow: false` means the host cannot open a _second window_ on demand —
what `cmd_new_child_window` does for Settings and workspace switching. It is not
a claim that nothing else is looking: other tabs may well be open on the same
worker, and it pushes every write to all of them regardless.

`windowChrome: false` means the browser owns the frame around the page, so the
header draws no window controls and reserves no room for macOS traffic lights.

## Multiple tabs

Each tab mints a label at load (`tab_xxxxxxxx`) and sends it with every command;
the worker stamps writes with it as `UpdateSource::Window { label }`, standing in
for the desktop's window label. The worker fans each write out to every
connected tab, and the receiving tab's store applies or ignores it exactly as a
desktop window would.

The label is deliberately _not_ kept in `sessionStorage`: duplicating a tab
copies session storage, and two tabs sharing one identity would each mistake the
other's writes for an echo of their own and drop them.

## Known gaps

- **Storage persistence is requested, not guaranteed.** `navigator.storage.persist()`
  runs at boot; browsers grant it on their own heuristics and often decline on
  `localhost`.
- **`pkg/yaak_web_bg.wasm` is 3.8 MB and committed** (no `wasm-opt`, matching
  `yaak-templates`). It will churn on every model-layer change; a CI-built
  artifact is the real answer.
- **Settings opens in the same tab** and is left with the browser's Back button.
- **Settings shows Data Directory / Logs Directory rows** with empty values; the
  Create Workspace dialog offers directory sync and encryption. Should be gated
  on `localFiles` / `sync` / `encryption`.
- **`cmd_render_template` returns the template unrendered.** Resolving variables
  and calling template functions is plugin work.
- **A declined command logs an unhandled rejection** next to its toast — the
  app's own `createFastMutation.mutate`, same on desktop.
- **`yaak-rpc-schema` does not come to wasm** (it pulls the git/gRPC/plugin
  crates for their types), so the crate declares the handful of request shapes
  it needs locally, and `commands.ts` stays typed against `RpcSchema`.

## Sending

A page cannot see a response the way a desktop app can — CORS exposes a handful
of headers, redirects are followed silently, there is no timeline — so the
network half of a send runs on a small stateless server,
`crates-server/yaak-web`. This layer stays the only place data lives:

1. `send.ts` creates the `http_response` row (state `initialized`), as the
   desktop does, so anything that goes wrong lands in the response pane.
2. The worker resolves and renders the request (`prepare_http_send`): the
   environment chain, inherited headers and auth, request settings, the cookie
   jar. This is the desktop's `HttpSendInputs`, in Rust, on the same model layer,
   with `yaak_models::render::render_http_request`. Variables (`${[ name ]}`)
   render here with no plugins involved.
3. The rendered request, the settings and the jar's cookies are POSTed to the
   server. It streams back timeline events, the response head, body chunks and a
   terminal frame carrying the jar as the send left it.
4. Each frame is written where the desktop writes it: the response row as it
   progresses, `http_response_event` rows for the timeline (which is why
   `timeline` is true), the body under the response id via `blob_put`, and the
   cookie jar through `models_upsert`. Every write fans out to every tab.

**What sends today:** any saved request whose templates are variables and whose
authentication is none, or an inline header. Sending a request that needs a
template _function_ (`${[ timestamp() ]}`) or an authentication plugin (bearer,
basic, OAuth, …) is refused before anything leaves the tab, with a message naming
what it needs; those light up when plugins run in the browser. Requests with a
file body or multipart file fields are refused by the server (it has no access to
your files, and must not read its own). And on a public instance a request to
`localhost` or a LAN address can't work: the server runs elsewhere and refuses
private ranges outright — that is what the desktop app is for. A self-hosted
server on your own network can be started with `--allow-private-networks`, which
is the one case where those addresses are the user's to reach.

**Where the tab sends** (`server.ts`): its own origin, in development as well as
production, so there is no address to learn and no CORS in the loop. A
production build is served by the sender itself (`yaak-web --serve
dist/apps/yaak-client`, which is what the `ghcr.io/mountain-loop/yaak-web`
image runs). In development the Vite server passes `/v1` through to `yaak-web`
instead, following `PORT` to find it — `vp run web:dev` starts both.
`VITE_YAAK_WEB_URL` overrides this, for a deployment that keeps the app and the
server apart.

---

--- METRICS ---
- Files Extracted: 19
- Estimated Token Budget: ~18339 tokens
- Canonical Reference: https://codewiki.google/github.com/mountain-loop/yaak
