fp CLI reference
The fp binary runs Fetchpier workspaces without Electron. It supports interactive terminal output,
machine-readable reports, stable exit codes, and a programmatic Node.js API.
A workspace is a folder containing .fetchpier/settings.json. Requests are the .http files under its
requests/ folder; environments are the folders under environments/.
Commands
Section titled “Commands”| Command | Description | Needs a workspace |
|---|---|---|
fp run [paths...] | Alias for fp request run. | Yes |
fp request run [paths...] | Run .http files and/or folders. With no path, runs the workspace’s requests/. | Yes |
fp env list | Print each environment’s folder name, one per line — the names --env accepts. | Yes |
fp env current | Print the environment a run would select (--env > FETCHPIER_ENV > default). | Yes |
fp init [name] [--template rest|graphql] | Create a workspace in ./<name>, or in the current folder when name is omitted, with a starter request (rest by default). | No |
fp --help, fp <command> --help | Help for the program or a command. | No |
fp -V, fp --version | Print the CLI identity, version and copyright notice. | No |
When a command needs a workspace and none is found, it exits with code 4.
fp --version prints the product name, the CLI version, and the project’s copyright line to stdout.
Invoking fp with no arguments prints the same banner above the usage text on stderr and keeps the
usage exit code. No other invocation prints a banner, so a JSON report piped from stdout is never
preceded by one. The CLI’s version number is independent of the desktop application’s.
Which files run
Section titled “Which files run”-
No path: every
.httpfile under the workspace’srequests/folder, recursively — the same set from any directory, inside or outside the workspace. -
Explicit paths (files or folders, as many as you like) are relative to the current directory, like any other command-line tool, so shell completion always produces a valid argument. If a path is missing from the current directory but exists inside the workspace, the error suggests the path to type:
$ fp run requests/auth/login.http --workspace my-apiError: Path not found: requests/auth/login.httpDid you mean my-api/requests/auth/login.http? Paths are relative to the current directory. -
Folders are walked in name order;
.runs/,.fetchpier/and.git/are skipped. -
A
.httpfile may hold several requests (###separators); only the first is executed, with a notice naming the file. -
A target folder (or the workspace’s
requests/) with no.httpfile stops the run with exit code19: a run that sends nothing is not a success.
fp init
Section titled “fp init”fp init creates .fetchpier/, a default environment with a baseUrl variable,
requests/example.http, and a .gitignore (only if none exists). Existing files are never
overwritten, and it fails with exit code 4 if the target already is a workspace. It ends by printing
the command to run next (fp run --workspace my-api, or fp run when initializing the current
folder), which works as printed from the same directory.
Run options
Section titled “Run options”| Option | Effect |
|---|---|
-w, --workspace <path> | Select the workspace explicitly. |
-e, --env <name> | Select an environment by folder name (case-insensitive). |
--env-var KEY=VALUE | Override a variable for this run; repeatable. A script that later sets the same variable wins from then on. |
--persist-env | Save script variable changes (fp.environment.set / remove) to the local values, exactly as the desktop does. Without it they apply to the rest of the run only. |
--bail | Stop after the first failed request — failed test, script error, file with no request, or a request that got no response. |
--delay <ms> | Wait between requests (whole number, 0 or more). |
--timeout <ms> | Per-request timeout. Default 30000 (30 s, like the desktop); 0 disables it — the only way to. |
--proxy <url> | Send every request through this proxy (http://user:pass@host:port). Wins over HTTPS_PROXY / HTTP_PROXY. |
--no-proxy | Ignore HTTPS_PROXY / HTTP_PROXY / NO_PROXY for this run. Cannot be combined with --proxy. |
--insecure | Disable TLS certificate verification for this run. |
--reporter-json <file> | Write JSON; use - for stdout, which silences the human reporter. |
--reporter-json-no-body | Omit response bodies from JSON. |
--reporter-junit <file> | Write JUnit XML. |
--reporter-html <file> | Write a standalone HTML report. |
--reporter-html-only-failures | Open the HTML report with its failures filter already applied. |
--save-history | Record each request that got a response in the workspace history (requests/.runs/), like the desktop activity log. |
--suppress-exit-code | Return 0 for run outcomes; fatal setup errors remain nonzero. |
--show-secrets | Do not redact credentials and cookies in the output. Use with care. |
--ascii | Use ASCII instead of Unicode glyphs. |
--no-color | Disable ANSI colors; NO_COLOR and FORCE_COLOR are also honored. |
--silent | Suppress stdout while retaining stderr. |
-v, -vv, -vvv | Increase verbosity (see below). |
--timeout and --delay accept whole numbers from 0 to 2147483647 (the runtime’s timer limit,
about 24.8 days); anything else is a usage error (exit 3). A single-value option given twice keeps
its last value, the usual command-line convention — two --reporter-json write only the last one.
Verbosity
Section titled “Verbosity”Levels add up, and are the same for one or many requests:
| Level | Adds |
|---|---|
| default | The request and response lines, and every script’s fp.log / warn / error output. |
-v | Each request’s test list, the full URL instead of its path, and whether it went via proxy or direct. |
-vv | Request and response headers. |
-vvv | Request and response bodies, each cut at 64 KB (the JSON report has the full body). |
Redaction and secret masking apply at every level. A binary body is never dumped to the terminal:
-vvv prints one line with its size and type instead (a compressed response that was decoded is judged
by its decoded content). Other text is printed as received.
Context resolution
Section titled “Context resolution”- Workspace:
--workspace>FETCHPIER_WORKSPACE> upward discovery of a folder containing.fetchpier/settings.json, starting at the current directory and stopping above your home directory. - Environment:
--env>FETCHPIER_ENV>default, matched case-insensitively against the environment’s folder name. The CLI never reads the desktop’s active-environment preference, which keeps CI predictable. - Variables: environment values, then per-machine local values, then
--env-varoverrides, then whatever earlier scripts of the run set.{{$env.NAME}}references resolve from a.envfile at the workspace root, then from the process environment.
fp env list prints a warning for any environment folder whose config.json cannot be read.
fp env current prints the name a run would select without checking that it exists; a run with an
unknown environment exits with code 6 and lists the available ones, and one whose config.json
cannot be read exits with code 4 naming the file. Like the desktop, fp run and fp env list keep an
environment’s display name in step with its folder: if the name in config.json does not match the
folder (for example after renaming the folder by hand), they rewrite that one field.
Environment variables
Section titled “Environment variables”| Variable | Purpose |
|---|---|
FETCHPIER_WORKSPACE | Workspace folder, when --workspace is not given. |
FETCHPIER_ENV | Environment folder name, when --env is not given. |
FETCHPIER_WORKSPACE_KEY | Workspace key (FPK1-…) used to decrypt secret variables. |
HTTPS_PROXY / HTTP_PROXY | Proxy for https / http targets. |
NO_PROXY | Targets that connect directly. |
NO_COLOR | Disable colors (same as --no-color). |
FORCE_COLOR | Force colors on when stdout is not a terminal (0 does not force). |
Variables and secrets during a run
Section titled “Variables and secrets during a run”Variables between requests. A value a script sets with fp.environment.set (or removes with
fp.environment.remove) applies to every later request of the same run — a login’s token reaches the
next call. A value set under a secret variable’s name stays masked as {secret:<name>} everywhere.
Without --persist-env nothing is written to the workspace.
Missing variables. A {{var}} with no value in the chosen environment is sent literally, and a
{{$env.NAME}} that neither .env nor the process environment defines is sent empty; each prints a
warning (also in the JSON report, with a stable code) for the request that uses it. Neither fails the
run — check the warnings, or assert on the response, to make a missing credential fail a pipeline. A
name defined with an empty value is intended and produces no warning. Malformed percent-encoding also
produces a warning and is sent literally.
Secrets in scripts. Only the secrets a request references (in its URL, headers, body or auth) are
decrypted for it, so a script that reads a secret its own request does not use gets undefined.
Reference it in the request, or pass it through a plain variable.
Secrets in CI. Provide the workspace key through FETCHPIER_WORKSPACE_KEY, typically injected from
your CI provider’s secret store. Resolution is lazy: a run that touches no secret needs no key. A run
that needs a secret it cannot resolve stops before sending, with a dedicated exit code (14–17).
--persist-env after a key rotation. If a teammate rotated the workspace key and you pulled it,
your local values stay encrypted with the previous key until you open the workspace in the desktop app
with the new key (it re-encrypts them). Until then, fp run --persist-env does not save script writes
to secret variables, prints a warning naming the cause and the fix, keeps using the value in memory
for the rest of the run, and saves writes to plain variables normally. A CI checkout has no local
values, so this only happens on a developer machine.
Network behavior
Section titled “Network behavior”Proxy. HTTPS_PROXY applies to https targets and HTTP_PROXY to http targets (lowercase forms
too); NO_PROXY excludes hosts, domain suffixes, host:port entries, or everything (*). --proxy
overrides both variables; --no-proxy ignores all three. The active proxy is shown in the header
without credentials, followed by the NO_PROXY list when there is one. An unusable proxy URL stops the
run before anything is sent (exit code 3); a proxy that does not answer fails the request in the
report.
Default headers and compression. When a request does not set them, the CLI sends
User-Agent: Fetchpier-CLI/<version> and Accept-Encoding: gzip, deflate, br. Compressed responses
(gzip, deflate, br) are decoded; the JSON report carries compressedBytes, decompressedBytes
and decodingStatus. A body that cannot be decoded is kept as received, with a warning, and
fp.response.json() fails with the real cause.
Binary bodies. A request whose body is a file attached in the desktop (staged under
requests/.files/) is sent with exactly that file’s bytes, and with the Content-Type of its file
type unless the request sets one. A missing file, or an invalid reference, fails that request as
“not run” instead of sending it empty.
Request outcomes
Section titled “Request outcomes”The runner executes pre-request scripts, applies auth and variables, sends the request, captures
cookies, executes post-response scripts, and evaluates tests. It continues after a failed request
unless --bail is set. A request that received any response — including a 4xx or 5xx — passes unless
one of its tests fails.
Network failures, TLS failures, timeouts, an interrupt while a request is in flight, script errors,
and failed assertions are request outcomes, not internal crashes. The run continues to the next
request (or stops, with --bail), the summary is produced, and every configured report is still
written — the run that most needs inspecting is the one whose service was down. Conditions that
prevent a run from starting at all — a missing workspace, environment, or path, an unreadable
configuration, an unusable proxy, a missing workspace key — remain fatal and keep their own exit codes.
A request that got no response reads no response: <reason> (unreachable, TLS, timeout, proxy); one
that was never attempted reads not run: <reason> (a file with no request, a pre-request script that
failed, a binary file that is missing or whose reference is invalid). An expired timeout is reported
as timed out after <limit>.
Reports
Section titled “Reports”A single run can write JSON, JUnit XML, and standalone HTML simultaneously. JSON is versioned for consumers; JUnit works with common CI test dashboards; HTML works offline and includes theme and failure filters. Report targets must point to an existing parent directory.
- Only JSON supports stdout.
--reporter-json -writes to stdout; passing-to any other reporter is rejected with a clear message (exit code2) rather than quietly creating a file literally named-. - The JSON schema version moves only on breaking changes. It changes only when a consumer would have to alter its code to keep parsing the report; new optional fields do not move it.
- A request that never got a response is always a visible failure. Every format shows it as a failure carrying its reason — never as an entry that is merely missing, and never as an empty group that a dashboard would read as passing.
Redaction. Reports and terminal output redact Authorization / Proxy-Authorization, the API-key
auth credential (query or header), and Cookie / Set-Cookie values. --show-secrets reveals them,
but secret variables still print as {secret:<name>} — including in what a script logs, a test’s name,
or a failed assertion’s message. Avoid publishing reports that contain production request or response
data.
Masking limits. Secrets are masked by exact match, like CI log masking: a server that echoes a secret back escaped (inside JSON, URL-encoded) or a script that transforms it (hashes, encodes, slices it) is not recognized, and a very short secret can mask unrelated text that contains it. Prefer secrets of a realistic length.
TLS and history
Section titled “TLS and history”The runner applies the workspace’s TLS configuration exactly like the desktop: the CA certificates and
client certificates listed in the tls block of .fetchpier/settings.json, whose files live under
.fetchpier/certs/ (both are normally managed from the desktop’s workspace settings and committed with
the workspace). A client certificate is presented to the hosts it is configured for; a passphrase that
references a secret variable is decrypted with FETCHPIER_WORKSPACE_KEY. Certificates configured only
in the desktop’s own app settings are not visible to the runner. --insecure turns off certificate
verification for a run.
History is off by default in automation. --save-history records each request that got a response in
requests/.runs/ — a local debugging record, git-ignored by default. Like the desktop activity log it
hides Authorization but keeps API keys and cookie values as sent: do not upload it as a CI artifact.
The reports are the CI artifact, and they redact those values.
Exit codes
Section titled “Exit codes”| Code | Meaning |
|---|---|
0 | Success. |
1 | A request failed (failed test, script error, no response, or a file with no request), or a usage error (unknown command or option, bare fp). A run with failed requests never reports success, even when it declared no assertions. |
2 | A reporter output target is invalid (missing directory, or - for a reporter other than JSON). |
3 | Invalid flag value (--timeout, --delay), an unusable proxy URL (flag or variable), or --proxy together with --no-proxy. |
4 | Workspace not found or invalid — including a .fetchpier/settings.json or a selected environment’s config.json that cannot be parsed; or fp init on a folder that already is a workspace. |
5 | An input path does not exist or is not a .http file/folder; or the workspace has no requests/ folder. |
6 | Environment not found. |
7 | Malformed --env-var; expected KEY=VALUE. |
8 | Reserved for fatal parse errors. A file that cannot be parsed into a request is reported as a failed request (exit 1). |
9 | Unknown reporter (reachable only through the programmatic API). |
10–13 | Reserved for multipart errors. Today a multipart problem is reported as a failed request with its reason (exit 1). |
14 | A referenced secret needs decryption but FETCHPIER_WORKSPACE_KEY is not set. |
15 | The workspace key is malformed or does not match this workspace. |
16 | A secret value cannot be decrypted. |
17 | A referenced secret has no value set (“needs value”). |
18 | The Node.js runtime is older than the CLI’s minimum (22.19.0). |
19 | Nothing to run: a target folder (or the workspace’s requests/) holds no .http file. Not masked by --suppress-exit-code. |
130 | Interrupted by Ctrl-C or SIGTERM after partial reports are flushed. |
255 | Internal error; report it as a bug. |
Interrupting a run cancels the in-flight request and flushes any partial reports before exiting with
130 — no stack trace, because an interrupt is an intended outcome. Requests already completed are
reported normally.
Programmatic API
Section titled “Programmatic API”The package supports ESM and CommonJS consumers:
import { run, init, listEnvironments, currentEnvironment } from '@fetchpier/cli'
// No paths: the workspace's requests/ tree, from any working directory.const summary = await run({ paths: [], workspace: '/path/to/ws', silent: true })console.log(summary.summary.requests) // { total, passed, failed }console.log(summary.summary.tests) // { total, passed, failed }run returns the structured RunSummary (the JSON reporter’s schema) and never calls process.exit
— your code decides what to do with the result. Explicit paths are relative to the process’s current
directory. By default run also prints the terminal report to stdout; pass silent: true or your own
reporters to avoid it. Fatal conditions (workspace, path, environment, secrets) reject the promise
instead of returning a summary; a CliError carries its exit code in exitCode.
CI examples
Section titled “CI examples”GitHub Actions — workspace in the api/ folder of the repository:
- uses: actions/checkout@v4- uses: actions/setup-node@v4 with: node-version: 22- run: npm install -g @fetchpier/cli- run: mkdir -p reports && fp run --env ci --reporter-junit reports/fp.xml env: FETCHPIER_WORKSPACE: ${{ github.workspace }}/api FETCHPIER_WORKSPACE_KEY: ${{ secrets.FETCHPIER_WORKSPACE_KEY }}- if: always() uses: actions/upload-artifact@v4 with: { name: fetchpier-report, path: reports/fp.xml }GitLab CI — workspace at the repository root:
api-tests: image: node:22 script: - npm install -g @fetchpier/cli - fp run --reporter-junit report.xml artifacts: when: always reports: junit: report.xmlTo run a subset, pass a path relative to the job’s working directory, e.g. fp run api/requests/smoke.
Sandbox threat model
Section titled “Sandbox threat model”Pre- and post-request scripts run in the same sandbox, with the same fp.* API, as in the desktop.
The sandbox prevents accidental mistakes in scripts; it is not a defense against an adversary who has
placed malicious code in your workspace. If you run untrusted collections in CI, run fp inside a
container or another isolation boundary.
Unsupported operations
Section titled “Unsupported operations”The current CLI does not switch or mutate desktop environment selection (fp env switch is not part
of this release: the desktop keeps its active environment in its own user-data store, outside the
workspace), rename or duplicate environments, import schemas, send ad-hoc URLs, install shell
completions, run requests in parallel, or accept a per-run CA file. Configure shared TLS material in
the workspace and use the desktop for schema import and environment maintenance.