perch¶
Pull requests will be closed unread. Feature ideas β GitHub Discussions. Bug reports for shipped behavior β issues (welcome). The code is Apache-2.0 β fork freely. Full policy in CONTRIBUTING.md. This stance is "for now" and will change once the grammar / op catalog stabilises.
One file. Every surface. Define your commands once in a
.perchfile β run them as a CLI, a web UI πͺ, a REPL, an AI-agent tool π€, or a portable binary. macOS Β· Linux Β· Windows.
--server is the dashboard--build ships a portable executable, no Go neededSame redis.perch file, five rendering modes β animation cycles through each.
The three things perch does that nothing else does¶
π― One file β five surfaces
One commands.perch β CLI Β· web UI Β· REPL Β· MCP tool Β· portable binary. Not five integrations β one abstraction, five renderings.
Nothing in bash / Make / Click / Cobra / Just / Task does this.
π wasm_run β sandbox by construction
Load a WebAssembly module. It sees ONLY the argv, env vars, and mounts you declared β anything else doesn't exist. Not policy. Construction.
π― Killer demo: zero-trust AI plugin runtime Β· reference Β· 5 walkthroughs
π§ͺ perch simulate β "what would happen on THAT host?" v2
Walk the program against a hypothetical env. Per-op verdicts: WILL_RUN β Β· WILL_FAIL β Β· MIGHT_FAIL ?. v2: state threading, oracles, multi-scenario.
Also recently shipped β everything else that landed
- π
requiresmanifest β declare every external resource (binΒ·envΒ·hostΒ·read/writeΒ· OS Β· arch). Every external op verifies it before running; undeclared access errors. Gated, every time β - π SHA-256 bin pinning β
hash "sha256:β¦"/hash_file "bundle:β¦"compares bytes on disk; never executes the binary. - π₯οΈ OS + arch context blocks β
os "β¦" β¦ endΒ·arch "β¦" β¦ enddeclare cross-platform/arch branches structurally; compose for matrix builds.simulateprunes mismatched leaves as dead code. - πͺ’ Keyword-free dispatchbreaking β no
run/call: a bare name invokes a command, expands a template, or runs a declared bin (deploy -target=linux). Names are globally unique, so it's unambiguous;--checkerrors on any collision. - π·οΈ Bare top-level bindings +
bin β¦ asbreaking β declare shared values bare (noglobalsblock); reference them inrequiresby name (write BUILD_DIR); give a path-bin a handle (bin "./bins/x" as x). - π Catch needs
proxy_argsbreaking β the catchβshell forwarding--scanflagged HIGH can't happen implicitly;${proxy_args}is unbound without the modifier. - π’ Version checks like math β
assert_version "${v}" >= "1.28.0"(infix) +version_extractto pull a version from any tool's output. Semver-aware. - π― Risk score in
--scanβ one glance: π’ SAFE / π‘ LOW / π MED / π΄ HIGH with concrete reasons; surfaced as a colored pill in the web UI. - π
try/rescue/finally+matchβ 30-kind error enum (shell_exit_nonzero,http_ssrf_blocked, β¦) for structured recovery + cleanup. - πͺ Web UI for non-devs β the same
.perchfile served as a tabbed localhost app (Run / Simulate / Scan / Check / About). - π‘ MCP live streaming β per-line stdout/stderr as
notifications/progressevents; no silent waits on long verbs. - π¦ 22 ready-made recipes β Redis Β· Postgres Β· devstack Β· aistack Β· kafka Β· modern-unix Β· gh-flow Β· docker-mgr Β· mkcert Β· backup Β· scan-secrets.
- π§ͺ 3 runnable
wasm_rundemos β schema validator, K8s policy check, agent-safe diff summarizer. - π¦ Declarative
bundle+ aliases βinclude "./mod.wasm" as modβwasm_run mod(bare ident, zero disk reads).
The philosophy β what perch is trying to do¶
Every team has an operational layer: the scripts that build the project, spin up the dev stack, deploy the service, wrap the clunky CLI, run the runbook. It's the least-loved code you own β and you usually write it five times in five incompatible languages. A bash script for the terminal. A Makefile for CI. A Cobra/Click/Typer program when the bash gets too hairy. A little Flask dashboard so non-devs can click a button. An MCP/JSON-tool backend now that an agent needs to run it too. Five descriptions of the same handful of verbs, drifting apart the moment they're written.
perch's bet is that this is one program, not five β and that you should describe it once.
β Describe once, render many
You declare typed verbs in one .perch file. Being a CLI, a web form, a REPL command, an MCP tool, and a binary entry point is the tool's job β not yours. No schema written five times, nothing to keep in sync.
β‘ The manifest is the contract
Capabilities are declared and gated, not bolted on after. A file states what it touches (requires); the operator narrows it (--no-*); neither side can exceed the other. You can know what a file intends before you run it. Honest scope: perch enforces its own ops and scrubs the subprocess env to the manifest β a declared bin's filesystem/network still needs an OS sandbox underneath. What we can and can't enforce β
β’ Cross-platform is the runtime's problem
Common operations β copy, mkdir, hash, http, gzip β are first-class ops, not per-OS shell incantations. exec runs declared binaries without a shell. The same file behaves the same on macOS, Linux, and Windows.
β£ Legible by construction
Typed args, --check, --scan, simulate, a structured error enum, and an audit log come standard. Operational code is the code most often run by someone who didn't write it β so it has to be readable and inspectable, not clever.
β€ Ship one artifact
Hand someone a file, or --build a single portable binary that needs no Go, no perch, no clone. The install tax on the recipient should be one download β and embedding can carry an entire Python/Node project along.
β₯ Stay small on purpose
perch is a control plane, not a programming language. It orchestrates tools and glues steps together; it is deliberately not where you write your application's business logic. A small, closed vocabulary is what makes the other five promises keepable.
The throughline: the operational layer should be something you write once, read easily, hand to a teammate β or an AI agent, or CI β with confidence, and run anywhere. Everything else perch ships is in service of that one goal.
Want the longer argument? sandboxed-by-design and trust-by-manifest lay out the security worldview; os-in-a-program covers the cross-platform stance.
What perch enforces today β and what it doesn't (be skeptical of the word 'sandbox')
perch is controlled scripting, not a kernel sandbox. Here is the honest line between what the manifest actually enforces and what it can't:
| Concern | Status today |
|---|---|
perch's own ops (http_get, read_file, write_file, exec bin check) |
β Enforced β undeclared access errors, every time |
| Which binaries run | β
Enforced β only declared bins spawn (bin_not_declared) |
| Subprocess environment | β
Scrubbed β a spawned tool sees only declared env + a default operational set (PATH/HOME/β¦); undeclared secrets are dropped |
| A subprocess's own filesystem & network | β οΈ Not enforced β once git/docker runs, perch can't parse its args; requires read/write/host bound perch's ops, not the tool's. Needs OS confinement (sandbox-exec, Landlock, firejail) β on the roadmap |
Genuinely adversarial .perch files |
β οΈ Run perch itself inside a container/VM; the manifest describes intent, the OS enforces isolation |
With --no-shell --no-subprocess the boundary is airtight (perch spawns nothing). With subprocesses allowed, treat the manifest as an honest declaration of intent + an env scrub, not a jail. Full discussion: sandboxed-by-design Β§ the subprocess trust boundary.
πͺ The web UI β two products in one binary¶
perch --server is a single feature with two distinct value props depending on who you are:
π€ "I'm using AI agents and want to see what they're doing"
As more teams give AI agents access to their infra ("deploy my app", "restart that pod"), non-devs are increasingly downstream of automated work they can't see. perch --server is the "shows your work" companion to perch-mcp: same .perch file, agent uses the MCP surface, you watch every op stream live in the Run tab. Open π§ͺ Simulate to ask "what would happen if the agent retried?" or π Scan to see what a verb actually does before granting it.
ποΈ "I just want to control my system from a UI"
You operate stuff β Docker, K8s, your home server, a small fleet β and you'd rather click than type the same kubectl β¦ | jq β¦ | grep β¦ for the 400th time. You don't want to write a frontend, stand up Retool, or maintain Backstage. Declare your verbs in commands.perch; run perch --server; that's the UI. Add a command restart_pod, refresh the page, the form is there. No app to deploy. No CSS to write. The file is the dashboard.
Same engine for both audiences. Same .perch file, same interpreter, same capability gates. Add a verb β it's a CLI command, a web UI form, an MCP tool, a REPL command, and a binary entry point. Four consumers, zero duplicate schemas.
Here's what they see¶
πͺΆ deploy
πdeploy_canary proxy_args
rollback_release test-allowed
βΆ Run tab β type-aware form per command, live NDJSON output, Copy-as-CLI hand-off
πͺΆ deploy
ππ§ͺ Simulate tab β "what would happen on the prod host if I ran this?" answered without a terminal
Five tabs, one file, zero config:
βΆ Run
Searchable list of every command. Type-aware form inputs β checkbox for bools, number spinner for ints, multi-line textarea for rest args. Click Run β output streams live. Copy as CLI button hands the form back as a shell command. Globals panel + mod badges (test Β· detached Β· proxy_args).
π§ͺ Simulate
Every --sim-* flag becomes a form field. Paste a v2 fixture JSON (capabilities + oracles + scenarios) and Simulate β per-op outcomes for each scenario side by side. *"What would this do on the prod host?"* answered without a terminal.
π Scan
One click β the full capability + risk audit. Leads with a one-glance risk score (π’ SAFE / π‘ LOW / π MED / π΄ HIGH) and concrete reasons (uses sudo, executes shell, catch forwards proxy_args, β¦). Severity pills, the recommended hardened invocation, every host / write root / env var the program touches. Run this before executing anything you didn't write yourself.
β Check
One click β syntactic validation (same engine as perch --check in pre-commit). Issue list with severity counts.
βΉ About + Theme
Program metadata + doc links. Dark mode auto-respects prefers-color-scheme and persists per browser. Hash-routed tabs (#run, #simulate, β¦) so any tab is bookmarkable.
π JSON API
Every panel is a JSON endpoint you can drive from another internal tool: GET /api/program, POST /api/check / /api/scan / /api/simulate, NDJSON-streaming /api/exec. Embed perch in your dashboard, Slack bot, or Backstage plugin.
Security: single-tenant + localhost-bound by default; pair with your reverse proxy + SSO for shared access. Capability restrictions inherit from launch β perch --no-shell --no-network --server produces a UI where shell ops error and HTTP is denied. Commands marked private are hidden + rejected.
See them in action¶
What perch replaces¶
| Today | Tomorrow with perch |
|---|---|
π bin/ of bash scripts + a Makefile + a CI YAML duplicating both |
πͺΆ One commands.perch file that local dev, CI, and on-call all execute |
| π οΈ A bespoke Cobra / Click / Typer CLI with hand-rolled arg parsing | πͺΆ Typed args in declared verbs with per-command --help for free |
| π€ A FastAPI service exposing safe ops to an LLM agent | πͺΆ perch-mcp reads the same file β typed tools, capability gates, audit |
| π A wiki page telling new hires which scripts to run | πͺΆ perch --help + an optional perch --server web UI |
| π¦ "First install Python 3.11, then a venv, then pip install β¦" | πͺΆ perch --build ships one binary with the project embedded |
πͺ΅ ad-hoc echo-style logging + manual screenshot of CI output |
πͺΆ --audit FILE.ndjson structured trace + --report span tree |
| π§ͺ "Run it and see" as the only test strategy | πͺΆ perch test sandboxed behavior tests with assert_* ops |
Adoption is incremental. Wrap your existing .sh files in a shell op; gain typed args + --help + audit + MCP in minutes. Promote to native ops over time. Migration guide β
π¦ Ready-made recipes β install in one curl¶
22 curated .perch files that solve real problems. Local Redis, the
whole AI/observability/Kafka stack, cross-platform tool installers, daily
Docker/kubectl wrappers. Download one, audit with perch --scan, run it.
# Pick one. Run it.
curl -fsSL https://raw.githubusercontent.com/olivierdevelops/perch/main/recipes/redis.perch -o redis.perch
curl -fsSL https://raw.githubusercontent.com/olivierdevelops/perch/main/recipes/_lib.perch -o _lib.perch
perch --scan -f redis.perch # audit before running
perch -f redis.perch up # 8 verbs ready: up / down / cli / flush / monitor / logs / backup / status
| Pain | Recipe | One command |
|---|---|---|
| "I need Postgres + Redis + S3 locally for my web app" | devstack | perch -f devstack.perch up (Postgres + Redis + MinIO in parallel) |
| "I want to play with local LLMs" | aistack | perch -f aistack.perch up (Ollama + ChromaDB + Open WebUI) |
| "I need local metrics + logs + dashboards" | observe | perch -f observe.perch up (Prometheus + Grafana + Loki) |
| "I keep typing 12 docker flags wrong" | docker-mgr | perch -f docker-mgr.perch prune_safe |
"Three teammates wrote three different git pr aliases" |
gh-flow | perch -f gh-flow.perch pr / land / sync / cleanup |
| "I want every modern CLI tool installed cross-platform" | modern-unix | perch -f modern-unix.perch install (ripgrep, fd, bat, fzf, jq, yq, eza, zoxide) |
| "Set up local HTTPS for dev" | mkcert-local | perch -f mkcert-local.perch install_ca && perch -f mkcert-local.perch cert localhost dev.local |
| "Encrypted backups, no SaaS" | backup | perch -f backup.perch snapshot ~/Documents (restic wrapper) |
The full catalog: single services (redis, postgres, mongodb, mysql, mailpit, minio, rabbitmq, localstack), stacks (devstack, aistack, observe, kafka-stack), tool installers (modern-unix, clouds, node-stack, python-stack), CLI wrappers (gh-flow, docker-mgr, kube-helpers), ops/security (mkcert-local, backup, scan-secrets).
Why teams adopt perch¶
π‘οΈ Safe by composition
Restriction flags compose β --no-shell --no-network --env HOME,PATH. Default-on SSRF and redirect guards. --scan audits a file before you run it. perch gates its own ops and scrubs the subprocess env β controlled scripting, not a kernel sandbox (honest scope).
π€ Agent-native, no backend
Replace your LLM-tool backend with a .perch file. perch-mcp --no-shell --no-network -f ops.perch is the whole stack. Typed verbs, declared schemas, capability gates, audit log. Live streaming over MCP progress notifications β long-running verbs emit per-line stdout/stderr events as they run instead of returning a silent blob. Streaming β
π Zero-install recipients
perch --build -o myapp produces a single executable. Recipients run one file β no Go, no perch, no source clone. --include ./src embeds an entire Python / Node project alongside.
π§ͺ Behavior tests built in
Mark a command test. perch test runs it in a sandboxed temp cwd with --no-shell / --no-network / --no-subprocess on by default. Seven assert_* ops. Drop into pre-commit + CI. Details β
π Full visibility β --trace Β· --audit Β· --report
--trace streams every op to stderr as it fires (live, indented for block nesting). --audit FILE.ndjson writes the same events as JSON for downstream ingest. --report renders the span tree after the run with full error context. Same hook order, three audiences.
Who this is for¶
Platform & DevEx teams
You maintain the bin/ folder of bash scripts, the Makefile no one trusts on Windows, and the README that tells new hires which steps to skip. Replace all three with one shippable binary.
SREs & on-call
Wrap kubectl / docker / rsync / openssl behind named verbs with typed args. Give the support team a web UI for safe runbooks. Stream every action as NDJSON straight to your audit pipeline.
Tool authors shipping internal CLIs
Embed a Python or Node project inside a single binary that installs itself into a hash-addressed cache, sets up its own venv, and drops a launcher in $PATH. Recipients need no Go, no pip, no clone.
Teams building with AI agents
Replace your LLM-tool backend with a .perch file. Typed args, declared verbs, composable restrictions β perch-mcp --no-shell --no-network --env KUBECONFIG -f ops.perch is the whole backend. See how β
Sound familiar?¶
perch is one DSL, one runtime, and one binary that solves all of these together.
Three commands to start¶
# 1) install
go install github.com/olivierdevelops/perch@latest
# 2) scaffold
perch --init
# 3) explore
perch --help # list commands
perch <cmd> --help # per-command help (args, defaults, examples)
perch --check # static validation
perch test # run every command marked `test` (sandboxed)
perch simulate cmd --sim-os=linux --sim-have-bin=kubectl # what would happen on THAT host?
perch simulate cmd --sim-file fixture.json # multi-scenario: happy / github-down / kubectl-missing
perch --report cmd # execute + render the span tree
Cross-platform without thinking about it¶
~30 variables auto-bound at every command start. No declaration, no let, no if uname. Hover any row below β that's what perch sees on the running machine.
What's in the box¶
~140 cross-platform ops
cp, mkdir, gzip, tar_create, http_get, download, sha256_file, regex_replace, json_get, bundle_extract, β¦ all implemented in Go, identical on macOS / Linux / Windows. Skip the bash tax. Catalog β
Static --check validator
Catches typo'd arg types, mismatched defaults, duplicate args, colliding positional indexes, missing run TARGET, unknown ops, unresolved ${name} placeholders β before any command runs. Wire it into pre-commit.
Templates & execution contexts
Wrap any body in parallel, timeout, retry, with_env, with_cwd, sandbox, or cache. Lift repetition into template NAME ... end parameter-substitution stamps. Details β
Unified if EXPR ... end
Comparisons (if os == "linux"), truthy/falsy (if has_bin, if not has_bin), predicate calls (if exists "./bin"), numeric (if size > 1000000). One block, every shape.
Preview before running
--dry-run prints every op with interpolated args and skips execution. --ask prompts y/n/a/q per op. --scan walks the program statically and reports needed capabilities + risk findings. No surprises in CI.
Editor integration
perch-lsp provides diagnostics, completion, hover, document outline. perch --install-vscode bundles the VS Code extension; --install-lsp alone for Neovim / Helix / Zed. Tree-sitter grammar for syntax beyond LSP. Setup β
Catch passthrough & fuzzy suggestions
Levenshtein-based "Did you meanβ¦?" for typo'd command names. Inside catch, ${proxy_args} holds the full unknown invocation β shell "git ${proxy_args}" makes perch a drop-in superset of any tool.
Block-shaped args + per-command --help
arg NAME ... end with labelled inner fields (type, default, description, optional, index, rest). perch <cmd> --help renders usage + table from the spec. No manual doc-strings.
Four use cases enterprise teams recognise¶
1. Replace the Makefile that everyone is afraid of¶
Before: a 400-line Makefile with shell variations behind every target; Windows users on WSL; the CI YAML reimplements half of it; nobody touches test-integration because the last person who did is now in a different company.
After: one commands.perch shared by local dev and CI. perch --check runs in pre-commit. perch --help is the README.
command test
description "Run unit + integration tests"
do
go test -race ./...
if exists "./integration"
go test -tags=integration ./integration/...
end
end
end
β Walkthrough: tutorials/01-replace-your-makefile.md
2. Ship a Python / Node / monorepo project as one self-installing binary¶
Before: "first install pyenv, then python 3.11, then a venv, then pip install -r requirements.txt, thenβ¦" Three pages of README and a Slack channel for install help.
After: you hand them stt_bin. They run ./stt_bin install. The binary extracts an embedded archive into ~/.cache/perch/<hash>/, creates a venv, runs pip install, drops a launcher in ~/.local/bin/stt. Done.
perch --build -f commands.perch --include ./src -o stt_bin
scp stt_bin user@server:/usr/local/bin/
ssh user@server 'stt_bin install && stt example.wav'
The recipient needs only what your install command requires (here: python3). No package manager. No registry. No internet at install time.
β Worked example: demos/05-python-installer
3. Give AI agents a safe operations surface β without standing up a backend¶
Deep dive: LLM control plane β why a
.perchfile +perch-mcp+ a few CLI flags replaces 2,000 lines of FastAPI scaffolding.
Before: the agent gets a shell. You hope. You write a long system prompt about what it should and shouldn't do. You audit logs after the fact.
After: the agent gets perch-mcp pointed at ops.perch. It can call exactly the verbs you declared, with exactly the arg types you declared. Anything else returns a typed error. No shell escape ever.
requires
bin "ssh" # the file declares the one tool it shells out to
end
command restart_service
description "Restart a service on a host"
arg host
type string
description "Hostname (must match /^[a-z0-9.-]+$/)"
end
arg service
type string
description "Service name (one of: web, worker, scheduler)"
end
do
if not regex_match "${host}" "^[a-z0-9.-]+$"
fail "invalid hostname"
end
ssh "${host}" systemctl restart "${service}"
end
end
The agent never sees ssh. It sees restart_service(host, service) with typed args. The schema is the security boundary.
β Details: mcp.md
4. Wrap a clunky CLI behind sane verbs¶
Before: every team member memorises 12 docker flags. Mistakes cost an afternoon. The Slack channel has the same three questions every week.
After: ship dev (a perch binary). The team types dev up, dev logs, dev shell, dev reset. Unknown verbs fall through to docker via catch passthrough, so power users lose nothing.
requires
bin "docker" # everything this file runs is declared
end
command up
description "Start the dev stack"
do
docker compose up -d # shell-free: structured argv, no metachar surface
docker compose exec api migrate up
print "β Stack running at http://localhost:8080"
end
end
catch passthrough
description "Forward unknown commands to docker"
proxy_args # explicit opt-in to bind ${proxy_args}
do
shell "docker ${proxy_args}" # proxy_args must word-split β shell (the escape case)
end
end
One binary. Onboarding goes from "read this 6-page doc" to "run dev up."
β More patterns: applications.md
For platform / SRE / security teams¶
The questions enterprise teams ask up-front, answered in one place:
π‘οΈ Security model
Capability gating, not kernel sandboxing. Composable --no-shell / --no-network / --no-write / --no-subprocess flags. --env A,B,C restricts host-env visibility. --allow-bin git,docker narrows shell to argv[0]. --allow-host api.github.com restricts network. Layer with firejail / sandbox-exec / AppContainer for genuinely adversarial input. sandbox.md β
π§Ύ Audit + replay
--audit FILE.ndjson records every op call with timestamp, args, duration, error, exit code, and bound-variable state. Same shape as Linux auditd but at the op level. Pipe to Loki / Datadog / CloudWatch. --report renders the same stream as a human-readable span tree after the run.
π€ AI agent safety
The MCP boundary is the file's grammar. Agents call declared verbs with typed args β anything else is a typed error. perch-mcp --no-shell --no-network --env KUBECONFIG -f ops.perch is the full policy. No FastAPI scaffolding, no manual JSON-Schema, no agent-readable shell escape. LLM control plane β
π SSRF + redirect protection (default-on)
HTTP ops refuse loopback / link-local / RFC 1918 / IPv6 ULA destinations by default β closes the AWS metadata SSRF (169.254.169.254). httpsβhttp downgrades refused. Max 5 redirect hops, each re-validated (DNS-rebinding defense via multi-A check). Layer --allow-host for a strict allowlist.
πͺ Cross-platform parity
The ~140 ops are identical Go implementations across macOS / Linux / Windows. With --no-shell the boundary is airtight (no subprocess can fire). Real "works on my machine" elimination, not aspiration.
π License + dependencies
Apache-2.0. One Go binary, no SaaS, no telemetry, no phone-home. Self-host or `go install`. Bundle into your own distribution. No license fees, no per-seat costs, no cloud account required. Source: github.com/olivierdevelops/perch.
π§ͺ Pre-commit + CI integration
perch --check for static validation, perch test for behavior. Both exit non-zero on failure β wire into any CI. Per-test sandboxes prevent state leakage. The same .perch drives local dev, CI, and production. testing.md β
π Static audit of unknown scripts
perch --scan FILE walks a program WITHOUT executing it and reports: capabilities needed, env vars referenced, risk findings (sudo, catch passthrough to shell, unvalidated ${var} in shell args), and the tightest CLI invocation that should still let it run. Review third-party .perch files before adopting them.
π Declared requirements + supply-chain pinning
A requires block makes the file declare every external resource it touches β bins (with SHA-256 hash pins), env vars, hosts, filesystem read/write scopes, OS, arch. When present, every external op verifies the manifest immediately before executing, on every call (stateless β no allow-cache): undeclared shell bins, hosts, env reads, or filesystem paths all error. A regression test fails if any external op stops refusing undeclared access. perch --check additionally flags literal undeclared use at lint time β proving a file is feasible on a target host without running it. Hash pins (inline or hash_file "bundle:..." embedded in the fat binary) defend against PATH-shadow + trojaned mirrors, read-only. capability-gating.md β Β· requires.md β. Where this is heading: zero ambient authority β programs start with NO external access at all; today the manifest is opt-in (a file without it keeps ambient access).
π Status & maturity
Pre-1.0 (v0.x). DSL surface is stable; op catalog continues to grow. SemVer applies once v1.0 is tagged. CI runs the full test suite on every commit. The repo eats its own dog food β commands.perch is what builds / tests / cleans perch itself.
How it compares¶
| perch | Make | Just | Task | bash scripts | Cobra/Click | |
|---|---|---|---|---|---|---|
Cross-platform without if uname |
β | β οΈ | β οΈ | β | β | β |
Typed args + per-command --help |
β | β | β | β οΈ | β | β |
Static validator (--check) |
β | β | β | β | β | β οΈ |
Sandboxed behavior tests (perch test) |
β | β | β | β | β | β |
parallel / retry / timeout / cache blocks |
β | β οΈ | β | β οΈ | β | β |
Span-tree execution report (--report) |
β | β | β | β | β | β |
| Built-in web UI | β | β | β | β | β | β |
| MCP server for AI agents | β | β | β | β | β | β |
| Single-binary distribution | β | β | β | β | β | β |
| Embed source/data inside binary | β | β | β | β | β | β οΈ |
| LSP + VS Code extension | β | β | β οΈ | β οΈ | β | n/a |
| ~70 portable ops (no bash) | β | β | β | β οΈ | β | n/a |
| Zero-build authoring (no Go required) | β | β | β | β | β | β |
The 30-second tour¶
Common questions¶
How do I audit a .perch file I didn't write? perch --scan FILE walks the program statically β no execution β and reports:
- Capabilities needed. Does it need shell? Which binaries? Network? Which hosts? Writes? Which paths?
- Env vars referenced. Every
${UPPERCASE_NAME}it touches. - Risk findings. Sudo use, catch-passthrough to shell, unvalidated
${var}in shell args, downloads followed bymake_executable, etc. Each ratedHIGH/MED/LOWwith a concrete fix suggestion. - Recommended invocation. The tightest CLI flag combination that should still let the script run β assembled automatically. Hands you the safe command to copy-paste.
Try it: perch --scan -f deploy.perch. See the animated demo above.
I already have bash scripts β what's the migration story? Three options, ranked by effort: (1) Wrap β write a thin .perch that calls your existing .sh files via the shell op; gain typed args + --help + MCP + web UI + audit log in minutes. (2) Translate β perch --import deploy.sh produces a .perch scaffold preserving semantics line-for-line (mostly shell ops to start), reviewable + statically checkable. (3) Rewrite β promote each shell op to native ops over time; once nothing needs shell, --no-shell becomes a real fence. Full guide: migrating-from-shell.md.
Is it a build tool or a CLI framework? Both. Same file becomes a Make-style task runner and a Cobra-style typed CLI. Pick the surface (CLI / web / REPL / MCP / binary) that fits the caller.
Are HTTP redirects and SSRF handled? Yes β four layered protections, all default-on. Plus a strict host allowlist when you want to pin which domains the script can reach.
| Default | What it stops |
|---|---|
| Block private-IP requests + redirects | AWS metadata (169.254.169.254), localhost pivot, RFC 1918 pivot |
| Block https β http redirects | scheme downgrade |
| Cap at 5 redirect hops | redirect bombing |
| DNS-rebinding defense | multi-A responses get ALL records checked |
--allow-host HOST[,HOST...] (additive, repeatable) layers a strict allowlist on top. Every initial URL AND every redirect destination must match. Patterns: exact (api.github.com), single-label wildcard (*.s3.amazonaws.com), host:port (localhost:8080), IP literal. Composes AND-wise with the SSRF guard.
# Only api.github.com and the docker registry are reachable β
# anything else returns "host not in --allow-host allowlist".
perch --allow-host api.github.com,registry.docker.io,*.docker.io deploy
Opt-out flags for the genuine cases: --allow-private-ips, --allow-scheme-downgrade, --max-redirects N, --no-redirects. Run perch help --allow-host for the full story.
Where do I look up what a flag or concept means? perch help β auto-generated reference. Three surfaces share the same catalog:
perch help # top-level index, grouped by Execution / Authoring / Security / β¦
perch help --no-shell # detail on one flag
perch help shebang # or one concept
perch help shell # fuzzy match (4 results in this case)
perch help --json # full machine-readable dump β for agents and tooling
Every error message includes a perch help <topic> hint pointing exactly at the right entry: op "shell" is disabled by --no-shell β run perch help --no-shell for details. Both humans and AI agents land on the same canonical reference.
How do I install it / how do I run a remote .perch file? Two one-liners.
Install (macOS / Linux / WSL β picks the right binary for your platform):
Run a .perch file straight from a URL (no save-to-disk step), with restrictions you choose:
curl -fsSL https://raw.githubusercontent.com/olivierdevelops/perch/main/scripts/sample.perch \
| perch --no-shell --no-network -f - hello
-f - means "read the perch source from stdin." Stdin input is treated as untrusted by default β shell, subprocess, network, write, and host env-var visibility are all disabled. Grant capabilities explicitly:
# default: nothing dangerous can fire
curl URL | perch -f - run
# I'm okay with this script using shell:
curl URL | perch -f - --allow-shell run
# I'm okay with shell + network + 2 env vars:
curl URL | perch -f - --allow-shell --allow-network --env HOME,API_KEY run
# I trust this pipe completely (it's my own .perch):
cat my.perch | perch -f - --trust-stdin run
Same model Deno uses: deny-by-default, opt-in with --allow-*. Banner shows "π stdin (untrusted): ..." with the exact flags blocking each capability. File input (-f file.perch) is unchanged β the deny-by-default only applies to stdin since that's where untrusted scripts arrive.
Can I run .perch files as scripts (shebang)? Yes. perch --init writes a #!/usr/bin/env perch line at the top and sets the file executable. Then ./commands.perch runs the main command; ./commands.perch hello runs hello; everything between just works. Conceptually a .perch file is a script and a structured CLI surface β both at once.
$ perch --init
$ chmod +x commands.perch
$ ./commands.perch # runs the `main` command
$ ./commands.perch hello # runs `hello`
$ ./commands.perch --help # lists commands
The shebang line is just a # comment to perch's parser, so it has no effect on parsing.
Is it a cross-platform shell? Yes β and that's the point. With ~140 built-in ops (cp, mkdir, gzip, tar_create, http_get, sha256_file, regex_replace, β¦) you can write a script that runs identically on macOS / Linux / Windows without falling back to bash or cmd. Disable the shell op and you have a pure portable script. See sandbox.md for the "pure" mode design.
Can I see what a command will do before running it? Yes β perch --dry-run cmd prints every op with its interpolated args and skips execution; perch --ask cmd is the same plan interactively (y = run, n = skip, a = run all remaining, q = quit). See it in the terminal below.
Can I lock down what a .perch file is allowed to do? Yes β composable flags, each naming what it disables:
perch --no-shell --no-subprocess --no-network --no-write deploy
perch --env HOME,PATH,API_KEY deploy # ${OTHER_SECRET} now errors
--no-shell blocks shell/shell_output/shell_detached/try_shell. --no-subprocess blocks pkg_install/kill_by_name/etc. --no-network blocks every http_*, download, port_*, etc. --no-write blocks every FS-mutation op. --env restricts which host env vars resolve via ${β¦}. perch --restrictions lists exactly what each flag blocks. The full capability sandbox (FS roots, network host allowlists, --untrusted with permission previews, file-side sandbox block) is designed in sandbox.md.
Who writes the sandbox β the author or the user? Both. The author writes a sandbox block in the .perch file as a manifest of intent β "this is what I need to do my job." Reviewers audit it; perch --check enforces it statically. The user layers further restrictions at run time (--no-shell, --no-network, --no-write, --env, --allow-*, --untrusted). The runtime enforces the intersection β neither side can grant more than the other allows. Same model as Android permissions: app declares, user grants, OS enforces the overlap. Details in sandbox.md Β§2.5.
Do recipients need to install perch? No. perch --build produces a standalone binary. They run that.
Does it work on Windows? Yes. The ~140 built-in ops are Go implementations, identical on macOS / Linux / Windows. Only shell invocations are inherently OS-specific.
What about secrets? perch reads env vars at runtime (${HOME}, ${API_KEY}, β¦). Don't bake secrets into the file. The static --check doesn't store anything.
Can I extend it with my own ops? Yes β perch is a Go library too. Drop a handler into infra/ops/ and register it. But the ~70 built-ins cover most needs.
How is this different from capy? capy is a small transpiler engine. perch uses capy to define its grammar (lib.perch), so the perch DSL is what capy ingests. capy isn't a language; perch is.
β More: faq.md
Where to next¶
For platform / DevEx
For tool authors
For AI / agent teams
For editor users
Full documentation¶
Grouped by what you're trying to do. Each row is one page.
π Get started in 30 minutes¶
| recipes.md | 22 ready-to-run .perch files β Redis, Postgres, devstack, aistack, observe, kubectl helpers, β¦ |
| getting-started.md | Five-minute hands-on tour |
| migrating-from-shell.md | Wrap your existing .sh files β three migration strategies |
| tutorials/01-replace-your-makefile.md | Convert a real Makefile to perch |
| faq.md | vs Make / Just / Task / Cobra; the common questions |
π οΈ Author commands (developers)¶
| language.md | Every keyword, modifier, and operator |
| op-reference.md | The built-in op catalog (~140 ops) |
| execution-contexts.md | parallel / retry / timeout / sandbox / cache blocks + templates + --report |
| testing.md | perch test β sandboxed behavior tests with assert_* ops |
| requires.md | requires β file-declared manifest (bins, env, hosts, FS read/write scopes, OS, hash pins) |
| capability-gating.md | Every external op verifies requires before executing β full per-op coverage table |
| lsp.md | VS Code / Neovim / Helix / Zed integration |
| applications.md | 22 real applications worth copying |
π¦ Ship as a product¶
| tutorials/02-ship-a-tool.md | perch --build deep-dive |
| tutorials/03-cross-platform-installer.md | One installer for three OSes |
| embedding.md | Fat-binary format spec β what's inside, how to verify it |
π‘οΈ Adopt at scale (platform / SRE / security)¶
| sandbox.md | Capability model β env / FS / net / shell scopes, --untrusted, file-side sandbox blocks |
| requires.md | File-declared manifest β bins / env / hosts / FS read+write scopes / OS / arch + SHA-256 hash pins; supply-chain provenance built in |
| capability-gating.md | The enforcement guarantee β every external op checks the manifest before it runs, every time; per-op coverage table + regression test |
| llm-control-plane.md | Replace your LLM-tool backend with a .perch file |
| mcp.md | MCP server reference (JSON-RPC over stdio) |
| applications.md | 22 patterns; many are SRE / platform-team shaped |
πͺ Background reading (design intent)¶
| os-in-a-program.md | The "operating system you can scp" framing |
| user-experience.md | UX roadmap |
| ai-assisted-authoring.md | Notes on agent-authored .perch files |
Source on GitHub: olivierdevelops/perch. Apache-2.0. One Go binary, no SaaS, no telemetry.