Skip to content

gausin3/postmaster

Repository files navigation

Postmaster

A local-first, Rust-native API client and load-testing platform — the Postman alternative for developers who want a fast tool that treats their API collections as reviewable code.

Full design: docs/ and the build plan. Decisions: docs/decisions/.

Why

  • Native speed, no Electron. Tauri v2 + a Rust core: tiny installer, low RAM, native HTTP/2/3 + gRPC with no browser CORS.
  • Collections are reviewable code. Plain-file YAML, one request per file, stable IDs, git-native diffs; optional sync through your own Google Drive (no backend).
  • Drop-in + modern. Run unmodified Postman pm.* scripts, or use a typed TypeScript-first API — in one sandbox.
  • Load testing is first-class. A native, coordinated-omission-correct engine that reuses your collections, plus one-click export to k6.

Install

Prebuilt installers for macOS / Windows / Linux are attached to each GitHub Release (built by CI — see .github/workflows/release.yml). Grab the one for your OS.

  • macOS (.dmg, universal — Apple Silicon + Intel). The build is ad-hoc signed but not notarized (no paid Apple account), so the first launch is blocked by Gatekeeper. Drag Postmaster to Applications, then right-click it → Open (then Open again) once, or run xattr -dr com.apple.quarantine /Applications/Postmaster.app.
  • Windows (.msi or -setup.exe, unsigned). SmartScreen shows "Windows protected your PC" → More info → Run anyway.
  • Linux.AppImage (chmod +x then run) or .deb. No extra steps.

Once installed, Postmaster keeps itself up to date — on launch it checks the Releases page and installs newer signed builds in place (verified against an embedded key; see auto-update). You do the manual install only once.

One-line installers (once the tap/bucket are published — see packaging/; Homebrew also clears the macOS Gatekeeper step for you):

brew install --cask gausin3/tap/postmaster                                   # macOS / Linux
scoop bucket add postmaster https://github.com/gausin3/scoop-bucket; scoop install postmaster  # Windows

Build from source (needs Rust, Node 22, pnpm):

pnpm install && pnpm --filter @postmaster/desktop tauri build

No paid code-signing certificates are used, so macOS and Windows show a one-time "unverified developer" prompt. Removing it entirely needs Apple notarization ($99/yr) / a Windows cert.

Status

Phase 0 — contracts frozen (done) and Phase 1 — local API client (MVP) underway. A request now travels formatirnet-core and onto the wire; apirun send proves it end-to-end.

Component Where What
Canonical data model crates/format The single source of truth (plan §3.3). Serde + canonical YAML for requests, collections, environments, workflows, examples.
Round-trip golden tests crates/format/tests Byte-stable serialization, secrets-never-on-disk, kind detection.
JSON-Schema → TS codegen tools/codegen, schemas/ Rust schema is the only source; apps/desktop/src/model/format.ts is generated.
QuickJS isolation spike experiments/quickjs-spike Risk-gate benchmark; resolved by ADR 0001.
Runtime model + lowering crates/ir RequestIr/ResponseIr; lower() resolves {{vars}} (including Postman-style dynamic variables{{$guid}}/{{$timestamp}}/{{$isoTimestamp}}/{{$randomInt}}/{{$randomEmail}}/… , a curated ~54-name faker-lite subset generated fresh at send time; an explicit variable of the same name always wins, and an unknown {{$name}} is left verbatim — never silently wrong), builds the URL, converts bodies, and applies auth — the simple schemes inline (Basic/Bearer/API key) plus AWS Signature V4 request signing (canonical request → HMAC-SHA256 signing key → Authorization: AWS4-HMAC-SHA256, with x-amz-date/x-amz-content-sha256/optional session token; path + query AWS-canonicalized, x-amz-* headers signed; validated against the AWS SigV4 test-suite vector) and OAuth 1.0a request signing (RFC 5849; HMAC-SHA1 / PLAINTEXT, the query + form-body params folded into the signature base string, Authorization: OAuth …; validated against the canonical X/Twitter vector; PLAINTEXT refused over http) and JWT bearer (sign a compact JWS — HS256/384/512 — over a claims payload and apply it as Authorization: Bearer … or a ?token= query param; validated against the canonical jwt.io vector). They sign uniformly across every run path (single/collection/workflow/load); redirect-following is disabled for a signed request since the signature is URL-bound.
HTTP engine crates/net-core Native reqwest/rustls transport (no CORS); integration-tested against a localhost server (plan §13). Engine features (plan §24 / "Yaak-#4"): gzip/deflate/brotli/zstd response decompression; mTLS / client certificates (a PEM cert+key presented for mutual TLS on https/grpcs/wss — HTTP via reqwest::Identity, gRPC/WS via a shared rustls config with with_client_auth_cert; PEM bytes zeroized after parse; proven by a gold test against a rustls server that requires client auth); proxy support (http(s)/socks5; system/disabled/custom, env-aware); stream-large-responses-to-disk (a body over the per-request threshold spills to a private 0o700 cache file — create_new/0o600, a 2 GiB cap, partial-file cleanup on error, bounded retention — so a huge download never sits in RAM); plus a per-workspace cookie jar (see below) and a :name path-parameter editor. HTTP Digest auth (RFC 7616) runs the challenge-response 401 round-trip here in the send path (parse WWW-Authenticate, compute the response — MD5 / SHA-256 / SHA-512-256, qop=auth/auth-int — and re-send), so it works uniformly for single / collection / workflow / load; validated against the RFC 7616/2617 test vectors + a gold integration test (independent server recompute).
Cookie jar crates/net-core + crates/app-core A per-workspace cookie jar (plan §24): auto-captures Set-Cookie and replays a matching Cookie: header (RFC 6265 scoping + the public-suffix list, including across redirect hops) using reqwest's own cookie_provider; persists to a workspace-private .postmaster/cookies.json (gitignored — cookies are bearer-equivalent secrets, unlike the encrypted vault). A management UI lists/deletes cookies (values masked), clears the jar, and toggles capture; proven by an integration test (capture + replay + cross-host isolation + disable + delete).
Streaming transports crates/net-core The engine→GUI streaming seam + Server-Sent Events + WebSocket (plan §19): two long-lived, bidirectional-capable transports on one seam. SSE — a spec-correct field parser (multi-line data, CRLF, comments, partial-UTF8, BOM, event/id) over reqwest's chunked body. WebSocket — a tokio-tungstenite/rustls driver (text + binary, send + receive, a manual + 30s keepalive ping, the close handshake, wss:// honoring verify-TLS, 8 MiB/1 MiB size caps). Both reuse ir::lower() for url/headers/auth/{{var}} and a typed wire model (StreamEvent/Control, per-connection seq) serialized identically over the Tauri Channel (native) and the apirun WebSocket bridge (browser). Bounded coalescing backpressure (a chatty server can't OOM or stall the socket — drops surface as a Lagged; binary frames forward only a bounded hex preview); a self-cleaning connection registry; the per-request timeout becomes a connect (+ SSE idle) deadline so a half-open upstream can't pin a task.
gRPC (dynamic) crates/net-core Codegen-free dynamic gRPC (plan §5/§21): discover the schema at runtime via server reflection (grpc.reflection.v1, v1alpha fallback) or local .proto files (compiled with protox — no protoc binary), encode/decode JSON↔protobuf with prost-reflect over a custom tonic codec, so any service is callable without compile-time stubs. All four call patterns — unary, server-/client-/bidirectional-streaming — ride the same StreamEvent/Control seam + connection registry as SSE/WebSocket (unary is a degenerate stream). The tonic channel is built over the in-tree hyper-rustls (one rustls/ring in the whole tree; grpcs:// honors verify-TLS; grpc:// is plaintext h2c); auth/metadata flow as request headers (gRPC-reserved keys filtered); 64-bit ints round-trip as JSON strings (proto3 mapping). Proven end-to-end by an integration test against a real tonic server (all 4 patterns + reflection + protox).
GraphQL crates/net-core First-class GraphQL (plan §5/§22): schema introspection (the canonical __schema query, parsed with serde_json — no GraphQL crate) feeding a browsable doc explorer; query/mutation over HTTP (the operation is a Body::GraphQl POST the engine already lowers to JSON); and subscriptions over WebSocket — graphql-transport-ws with a legacy graphql-ws fallback (negotiated via Sec-WebSocket-Protocol), on the same streaming seam. The run auto-routes by operation kind (a subscription opens the WS stream — connection_init/acksubscribenextcomplete; query/mutation go over HTTP). Proven by integration tests against real modern + legacy subscription servers + an introspection endpoint.
CLI bins/apirun apirun send — the Newman-equivalent stub; loads a request, sends it, prints the response.
Script sandbox crates/sandbox One QuickJS realm per worker (realm-reuse, ADR 0001) with the pm.* shim, mini-chai assertions, variable scopes, console capture, setNextRequest, and a runaway-loop deadline.
Execution engine crates/runner run_request — the full lifecycle (resolve vars → pre-request → send → test), composing ir + net-core + sandbox. apirun run exposes it; exits non-zero on assertion failure.
Importers crates/importer Postman Collection v2.1 → the canonical model: folders, requests, the auth matrix, all body modes, pm.* scripts preserved verbatim. OpenAPI 3.x / Swagger 2.0 (JSON or YAML, plan §9) → a collection: paths→folders-by-tag/requests, servers/host+basePath→a {{baseUrl}} variable (server variables filled from defaults), parameters→path ({id}:id)/query/header, requestBody/in:body/formData→Body (a JSON schema-example skeleton, urlencoded, or form-data), securitySchemes→Auth (http bearer/basic, apiKey, oauth2 with the real grant/URLs/scopes). Both share one tolerant serde model + a fidelity report (every approximation/skip is a degraded/unsupported note — never a silent drop). The import path auto-detects the format. apirun import exposes it.
Collection runner crates/runner (run_collection / run_collection_load) Runs a whole imported collection: effective auth inheritance, variable state threaded across requests, setNextRequest sequencing, aggregated report. Functional mode runs a chosen subset in a chosen order (run_ids), a set iteration count, and an inter-request delay; Performance mode drives the same sequence under N virtual users (each its own thread + sandbox) for RPS + per-request latency percentiles + SLO gates. apirun run-collection.
Secrets crates/secrets Two complementary tiers. (1) Keychain secrets — OS keychain (keyring) + a domain-allow-list-gated resolver: a secret is released into a request only if the destination host is in its allow-list (plan §4.4, §6.4). apirun secret set/get/rm. (2) Sharable workspace encryption (plan §23) — a per-workspace data key (DEK) encrypts secret environment values with XChaCha20-Poly1305 (AAD-bound to the variable key) into the committable .env.yaml as ciphertext, so the whole YAML workspace is git-diffable + safe to share with no plaintext on disk. The DEK lives in process memory only while unlocked (zeroized on drop, never persisted) and is wrapped in a committable vault (.postmaster/vault.json) two ways: by a per-workspace OS-keychain master key (transparent local unlock; isolated per workspace so one keychain leak can't unwrap every vault) and/or by an Argon2id passphrase (64 MiB / t=4 — portable unlock + recovery on another machine). AEAD rejects any tampering; a corrupt vault is never overwritten; the vault is written atomically.
OAuth 2.0 crates/auth A TokenManager that obtains OAuth 2.0 access tokens and applies them as a Bearer just before lowering (plan §5): Client Credentials + Password auto-fetch on send; Authorization Code + PKCE (S256) runs an interactive browser flow — open the consent page (the open crate), catch the redirect on an ephemeral 127.0.0.1 loopback (RFC 8252), validate state, exchange the code, and cache the token (auto-refreshed via the refresh token). In-memory cache keyed by endpoint+client+scope+grant with per-key single-flight refresh; tokens never touch disk/UI.
Workflow engine crates/runner (run_workflow) The flagship (plan §7): an explicit DAG over .flow.yaml. Nodes: start/request/setVariable/script/condition/capture/forEach/while/delay/fork/join/complete. condition routes along true/false edges (vs Postman's setNextRequest goto); loops use ports + a back-edge with a frame stack and a maxIterations guard; capture pulls a response value into a variable; fork/join run branches concurrently (real I/O overlap, no threads) with copy-on-fanout variable scopes merged back at the join (only a branch's changed keys apply; last branch in edge order wins a conflict). apirun workflow.
Filesystem store crates/store Loads/saves the on-disk workspace tree (collections, requests, environments, workflows, examples) as plain YAML; load_dir builds the request registry the workflow engine resolves refs against, skipping .git/.runs/target.
Data-driven runs crates/datafile + run_collection_iterations Parse CSV / JSON (Postman parity) and XLSX (a Postman-beating extra) into rows; run the collection once per row with columns in the data scope ({{col}} / pm.iterationData). apirun run-collection --data <file>.
Load / perf engine crates/loadgen Native virtual users (tokio) sharing one connection pool + a data work-queue: N VUs drain all M rows, each row exactly once — fixing Postman's one-row-per-VU cap. Modes: exhaust-data (default), iterations, duration, --per-vu (Postman-compatible). Runs pm.test assertions per iteration (thread-local sandbox) and gates on SLO thresholds (p95/p99/error-rate) — non-zero exit for CI. hdrhistogram p50–p99 + RPS. apirun load --vus N.
Capture to disk crates/runner (RunRecord) --out <dir> writes one JSON record per request (resolved request + full response + timing + assertions; body UTF-8 or base64), per data-iteration too. Postman needs workarounds for this.
Python bridge crates/sandbox (pm.python) Scripts can call functions in a .py file on disk — pm.python("helpers.py", "sign", msg, key) — out-of-process via JSON. Opt-in (--allow-python) since it deliberately escapes the sandbox; throws otherwise.
Code generation crates/codegen Copy as cURL + client-code snippets in 8 targets (cURL, raw HTTP, Python requests, JavaScript fetch, Node axios, Go net/http, Rust reqwest, and a k6 load-test scriptexport const options + http.request + a 2xx check). Generated from the lowered RequestIr, so the snippet is the exact wire request — absolute URL, materialized auth headers (Basic/Bearer/API-key/SigV4/OAuth1 already applied), resolved body — with per-language string escaping, content-type/redirect/timeout/verify-TLS handling, and --digest/multipart/file-body support. Host / Content-Length are left to each client.
Workspace sync crates/sync Provider-agnostic cloud sync for the whole workspace. A SyncProvider trait abstracts the remote; a three-way merge (local content-hash vs. a stored manifest base vs. the remote revision) classifies every file into push / pull / delete / conflict — a file changed on both sides is surfaced for one-click resolution (Keep local / Take remote), never silently overwritten. v1 ships a local-synced-folder provider: point it at a folder your Google Drive / OneDrive / Dropbox desktop client already mirrors and you get real cloud sync with zero OAuth. Secrets are encrypted on disk (see the Secrets row), so only the committable YAML travels; machine-local state (.postmaster/, dotfiles, node_modules, target, symlinks) is skipped. The manifest + config live under <workspace>/.postmaster/sync/ and never sync themselves. Two more providers ship behind the same trait — direct-API Google Drive (the hidden per-app appDataFolder) and OneDrive (the per-app folder, Microsoft Graph) — BYOC: you bring your own OAuth client id + a refresh token; the refresh token and client secret are kept in your OS keychain (no plaintext on disk — a config-file fallback only where no keychain is available, e.g. headless/CI), an access token is minted per sync and never stored, and HTTP is injected so the REST mapping is unit-tested with a mock.
App service (UI seam) crates/app-core Transport-agnostic operations over serializable DTOs (send, import_collection) — the clean boundary the UI calls. Tested.
GUI apps/desktop React + TS + Vite: a persistent workspace sidebar — collections / requests / environments live on disk as plain YAML and survive restarts (plan §4.1): create a collection, import a Postman v2.1 collection or an OpenAPI 3.x / Swagger 2.0 spec (JSON or YAML, auto-detected) from a file / drag-drop / paste, rename, delete-to-.trash (recoverable — see the Trash view), ▶ run a saved collection straight from the tree (no re-import), ⧉ duplicate a collection or request, ↑/↓ reorder requests and folders within a collection (persisted as a diffable seq), ⤓ export a collection to Postman v2.1 JSON, ⚙ edit a collection's settings (name, description, default auth, variables, pre-request/test scripts), ⏱ schedule a collection to run on a timer (local-first — fires while the app is open; with an optional wall-clock start time ("start at 11 PM") and stop conditions — after N runs and/or by a date, whichever comes first (then it auto-finishes); edit an existing schedule, Run now for an immediate run with a ■ Stop to cancel it mid-flight (cooperative — halts at the next request boundary); point OS cron at apirun run-collection for unattended runs), and click a request to open it; the workspace defaults to an app-data folder with a Choose folder… option, ☁ Sync the whole workspace to the cloud — a local folder your Drive / OneDrive / Dropbox client mirrors, or direct-API Google Drive / OneDrive (pick a provider; folders are zero-OAuth, the direct APIs are BYOC — Sign in runs the OAuth consent in your browser and captures a refresh token, or paste one you already have; the refresh token + client secret are stored in your OS keychain, never plaintext on disk, shown by a 🔒 Credentials in OS keychain badge) — then Sync now → a push/pull/delete report; conflicts that changed on both sides are surfaced with Keep-local / Take-remote buttons, never silently overwritten, a { } Dynamic variables reference drawer (browse the supported {{$guid}}/{{$timestamp}}/{{$randomInt}}/… tokens grouped, each with a live example and one-click copy — they resolve fresh on every send anywhere a {{…}} is interpolated), an environment/variables manager (edit vars; {{name}} placeholders resolve at send time with a live resolved-URL preview; save named environments — the active one is remembered across restarts; mark any variable 🔒 secret to store it encrypted in the committable .env.yaml — an enc-bar enables encryption, locks/unlocks the workspace, and sets a portable passphrase; secret values are masked, can't be edited while locked, and a tampered/corrupted secret is flagged rather than silently shown empty), the request editor with Params / Body / Auth / Pre-request / Tests / Settings sub-tabs (Params is a query-parameter grid kept two-way-synced to the URL — edits in either place flow to the other, with {{vars}} preserved; Body offers the full Postman-style type picker — none / form-data / x-www-form-urlencoded / raw + language (Text/JSON/XML/HTML/JS) / binary / GraphQL — each with its own editor, file parts via a native picker or typed path; Auth offers the schemes the engine actually applies on the wire — No Auth / Inherit / Basic / Digest (RFC 7616 challenge-response) / Bearer / API Key (header or query) / OAuth 2.0 (Client Credentials + Password grants — token auto-fetched, cached with single-flight refresh, applied as a Bearer on send; and Authorization Code + PKCE — a "Get token" button opens your browser to consent, catches the loopback redirect, and caches the token, refreshing it automatically) / AWS Signature V4 (access key / secret key / region / service / optional session token — the request is signed on every send) / OAuth 1.0a (consumer key/secret + token/token-secret, HMAC-SHA1 or PLAINTEXT — signed on every send) / JWT Bearer (HS256/384/512 over a claims payload, applied as a Bearer header or a ?token= query param) — with {{var}} resolution in every field and no dead toggles (a Send before you authorize fails loud rather than going out unauthenticated); an imported request using a scheme we can't author yet (OAuth 2.0 Implicit/Device-Code, NTLM, Hawk, EdgeGrid, …) shows a read-only notice and is preserved verbatim on save, never silently dropped; Settings exposes the engine-honored follow-redirects / verify-TLS / timeout / HTTP-version / mTLS client-certificate / proxy (system/disabled/custom) / stream-big-responses-to-disk controls; a Path sub-tab edits the :name path parameters in the URL; a 🍪 Cookies drawer manages the per-workspace cookie jar; the Headers tab has a read-only "auto-generated headers" reveal showing exactly what the engine adds for you — Host / Content-Type / Content-Length / Accept-Encoding — computed honestly from the real send path), a response viewer that runs the full lifecycle (pm.test assertions render as pass/fail rows with failure messages, plus captured console), and a collection runner panel (▶ Run collection → run the whole collection, optionally once per CSV/JSON data rowchoose a data file from disk or paste it — with a per-request/per-iteration results table), a load-test dashboard (⚡ Load → drive the current request with N virtual users; modes iterations / duration / per-VU / exhaust-data; live RPS, p50–p99 latency percentiles, status distribution, and pass/fail SLO gates), and a workflow canvas (⊟ Workflow → paste a .flow.yaml, run the DAG, and see a node graph with per-node pass/fail status rings, branch labels, and click-to-inspect console/assertions), request tabs (several requests open at once, each its own editor + run result; double-click a tab to rename), Save (⌘/Ctrl+S → persist edits back to the workspace .req.yaml; a dirty-dot marks unsaved changes, new requests Save-As into a chosen collection), save-to-YAML (⤓ YAML → serialize the request to canonical, git-diffable .req.yaml to copy or download), code generation (</> Code → the active request as a runnable snippet in cURL / raw HTTP / Python / JavaScript / Node / Go / Rust / k6 (a load-test script) — the exact wire request with auth headers materialized — pick a language and copy), and a history view (🕘 History → records every run kind, data-consciously: each Send keeps a full request/response/tests record, while collection / workflow runs leave a lightweight row (name + pass/fail + iteration/node count) — their per-request detail is never bulk-stored — and load runs are recorded only when you opt in; browse newest-first with a kind badge, search by method/URL/name/kind, click a Send for its full detail and double-click (or ↗ Open in editor) to reopen it to tweak and re-run, while aggregate rows show a summary-only panel; each run surface also offers an opt-in "Save full results" that exports the complete breakdown to a folder you choose — failure is surfaced without ever losing the run), and a Trash recovery view (🗑 Trash → every delete is recorded with its original location; restore an item back to where it came from (refused, never clobbering, if something already lives there), purge one, or empty the trash), and a streaming message timeline (a protocol selector by the URL bar switches the request between HTTP, SSE, WebSocket, gRPC, and GraphQL; for a streaming protocol the Send button becomes Connect, opening a long-lived connection whose events stream into a live timeline — status chip, per-message direction/event:/timestamp, binary frames as a hex dump, a "dropped" marker if the server outpaces the UI, and Disconnect — ring-buffered so a chatty stream stays responsive. WebSocket adds a send box (compose + send messages, plus Ping) for true bidirectional traffic; the connection round-trips a clean close handshake on Disconnect. gRPC adds a service/method picker (populated by ↻ Reflect for reflection servers, or by loading local .proto files — no protoc), a JSON message editor seeded from the method's input type, and a metadata table; the timeline shows the request/response message(s) with the gRPC status, and client-/bidirectional-streaming calls reuse the send box plus a Done sending half-close. GraphQL adds a query editor, a variables editor, and a schema doc explorer (browse types/fields/args after ↻ Schema introspects the endpoint); Run auto-detects the operation kind — a subscription opens the WebSocket timeline (graphql-transport-ws), query/mutation return in the response viewer. A streaming request saves/reloads as protocol: sse/webSocket/grpc (with its service/method/metadata) like any other). Runs today via apirun serve (a local axum bridge → app-core → engine); the identical frontend embeds in the Tauri desktop shell next (swapping the HTTP bridge for native invoke).
Desktop shell apps/desktop/src-tauri The native Tauri v2 app (postmaster-desktop): thin #[tauri::command] handlers (send_request / run_request / run_collection / run_load / import_collection) wrap app-core, so the same React frontend runs as a real desktop window via native invoke (no HTTP, no CORS). pnpm --filter @postmaster/desktop tauri dev.
GUI bridge apirun serve Serves the built SPA + POST /api/send / /api/run / /api/run-collection / /api/load / /api/run-workflow / /api/import, same-origin (no CORS). /api/run and /api/run-collection execute scripts through the runner; /api/load drives the native load engine on a multi-thread runtime (the QuickJS sandbox is only touched synchronously, so handler futures stay Send); /api/run-workflow parses a .flow.yaml and runs the DAG, returning the graph + per-node results; /api/request-yaml serializes a request to canonical .req.yaml; /api/sync-status / /api/set-sync-folder / /api/set-cloud-provider / /api/sync-now / /api/resolve-sync-conflict (+ disconnect / auto-sync) drive workspace cloud sync; /api/code-targets + /api/generate-code produce client-code snippets; run-collection and load accept an optional pasted CSV/JSON data file. apirun serve --dir apps/desktop/dist.

Phase 1 complete; Phase 2 underway. An unmodified Postman v2.1 collection imports, runs, and passes its pm.* tests with cross-request chaining and keychain-backed secrets. The workflow engine runs branching, forEach/while loops, fork/join parallel branches, and response→variable capture (all tested).

Next — the Tauri GUI (everything so far is CLI-first; the Rust core is UI-agnostic). See the plan's roadmap (§12).

Develop

Prerequisites: Rust (stable, via rustup), Node ≥ 20, pnpm.

cargo test --workspace          # Rust tests (round-trip golden suite)
cargo clippy --workspace        # lints
pnpm install                    # JS deps
pnpm codegen                    # regenerate schema + TS types from crates/format
cargo run -p quickjs-spike --release   # reproduce the sandbox benchmark

# send a request end-to-end through the Rust core:
cargo run -p apirun -- send examples/demo/get.req.yaml --env examples/demo/local.env.yaml

# run a request's full lifecycle (pre-request -> send -> test assertions):
cargo run -p apirun -- run examples/demo/run-with-tests.req.yaml --env examples/demo/local.env.yaml

# import an existing Postman v2.1 collection (with a fidelity report):
cargo run -p apirun -- import examples/demo/postman-collection.json

# import AND run a whole collection (variables thread across requests):
cargo run -p apirun -- run-collection examples/demo/collection-run.json --env examples/demo/local.env.yaml

# run a workflow graph (loads requests from the .flow.yaml's directory):
cargo run -p apirun -- workflow examples/workflow/smoke.flow.yaml --var baseUrl=http://127.0.0.1:8731

# data-driven: run a collection once per CSV/JSON/XLSX row:
cargo run -p apirun -- run-collection examples/demo/collection-data.json --data examples/demo/users.csv --var baseUrl=http://127.0.0.1:8731

# load test: 20 virtual users, 1000 iterations (or --data file.csv to run every row once):
cargo run -p apirun -- load examples/workflow/probe.req.yaml --vus 20 --iterations 1000 --var baseUrl=http://127.0.0.1:8731

# load test as a CI gate: fail (non-zero exit) if p95 > 200ms or error rate > 1%:
cargo run -p apirun -- load examples/workflow/probe.req.yaml --vus 20 --iterations 1000 --var baseUrl=http://127.0.0.1:8731 --slo-p95 200 --slo-error-rate 0.01

# write every request+response to disk while running a collection:
cargo run -p apirun -- run-collection examples/demo/collection-data.json --data examples/demo/users.csv --var baseUrl=http://127.0.0.1:8731 --out ./run-output

# call a Python function from a script (opt-in; escapes the sandbox):
cargo run -p apirun -- run examples/python/python-demo.req.yaml --var baseUrl=http://127.0.0.1:8731 --allow-python

# run the GUI in a browser (build the frontend, then serve it + the engine):
pnpm --filter @postmaster/desktop build
cargo run -p apirun -- serve --dir apps/desktop/dist --port 1456   # open http://127.0.0.1:1456

# …or run it as a native desktop window (Tauri v2; Vite + the Rust shell):
pnpm --filter @postmaster/desktop tauri dev
# regenerate the app iconset from a source PNG before shipping a release build:
# pnpm --filter @postmaster/desktop exec tauri icon path/to/icon.png

pnpm codegen must be run after any change to crates/format; CI runs pnpm codegen:check and fails on drift, keeping the Rust core and the UI in sync.

License

Postmaster is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0). See LICENSE for the full text.

Contributing

Contributions are welcome — see CONTRIBUTING.md to get started.

By contributing to Postmaster, you agree to the Contributor License Agreement. You retain ownership of your work and grant the Maintainer a broad, irrevocable license — including the right to offer the project (and your contribution) under separate commercial or proprietary terms (dual licensing) alongside AGPL-3.0.

About

No description, website, or topics provided.

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors