From zero to a running program in five minutes
Capa is a Python 3.10+ package that transpiles .capa source into Python and executes it with a small runtime. Four ways to install; pick one.
Four paths
pip install if you already have Python 3.10+; the one-line installer if you want capa on your PATH without it; the manual binary if you prefer to verify the asset yourself; from source if you intend to contribute.
Option A: pip install recommended
If you already have Python 3.10+, this is the shortest path. Capa is on PyPI as the distribution capa-language; the import package and the capa command are unchanged. Open a new shell and capa --version should answer.
$ pip install capa-language
$ capa --run hello.capaOption B: one-line installer
Downloads the latest pre-built binary into ~/.local/bin/capa (Linux / macOS) or %LOCALAPPDATA%\capa\capa.exe (Windows). Open a new shell and capa --version should answer.
# Linux / macOS
$ curl -fsSL https://github.com/nelsonduarte/capa-language/releases/latest/download/install.sh | bashPS> irm https://github.com/nelsonduarte/capa-language/releases/latest/download/install.ps1 | iexOption C: manual binary download
Each tagged release publishes a standalone PyInstaller binary that bundles the compiler and its own Python interpreter into one file, so it runs on a machine with no Python installed. The language server is bundled too, so capa lsp works straight from it.
$ curl -L -o capa \
https://github.com/nelsonduarte/capa-language/releases/latest/download/capa-linux-x86_64
$ chmod +x capa
$ ./capa --run hello.capa$ curl -L -o capa \
https://github.com/nelsonduarte/capa-language/releases/latest/download/capa-macos-arm64
$ chmod +x capa
$ xattr -d com.apple.quarantine capa # bypass Gatekeeper
$ ./capa --run hello.capaPS> Invoke-WebRequest `
-Uri "https://github.com/nelsonduarte/capa-language/releases/latest/download/capa-windows-x86_64.exe" `
-OutFile capa.exe
PS> .\capa.exe --run hello.capaOption D: from source
Requirements: Python 3.10+ and git. Tested on 3.10, 3.12 and 3.14 across all three platforms. Zero runtime dependencies outside the standard library.
$ git clone https://github.com/nelsonduarte/capa-language
$ cd capa
$ pip install -e .
$ python -m unittest discover tests # over 4,000 tests, a couple of minutesThe editable install registers the package and adds a capa command on your PATH, so both capa <args> and python -m capa <args> work from any directory. The Wasm backend is optional: pip install -e '.[wasm]' unlocks capa --wasm --run.
Dependencies in capa.toml
Projects declare dependencies in a capa.toml at the root. The same file pins the source: a git URL with an exact tag, or a relative path. A signed registry index maps short names to git sources and is GPG-verified before any name is trusted.
[package]
name = "my-project"
version = "0.1.0"
capa = ">=0.8.4"
[dependencies]
capa_log = { git = "https://github.com/nelsonduarte/capa_log", tag = "v0.1.2" }
[dev-dependencies]
capa_test = { git = "https://github.com/nelsonduarte/capa_test", tag = "v0.1.1" }$ capa install # resolve, fetch deps into ./vendor/, write capa.lock
$ capa --run main.capa
$ capa install # idempotent: rerun to refresh against capa.lockThe capa.lock pins the resolved git SHA plus a SHA-256 of the fetched tarball, so installs are bit-reproducible. When a dependency carries a verify_key, capa install verifies the GPG tag signature or Sigstore SLSA attestation before unpacking. Since 1.18.1, a missing gh on your PATH is an error rather than a warning for such a dependency: the verifier being absent no longer downgrades to "install it anyway". CAPA_ALLOW_MISSING_GH=1 overrides, and names on stderr every dependency it let through. Full guide in docs/packages.md.
The compiler floor, and a manifest that will not parse
The capa key above is the oldest compiler the project says it can be built with. Since 1.19.0 it is enforced. Before that it was parsed and ignored, so a floor had never been tested against a running compiler; if your project declares a floor above the compiler you are running, you now get a refusal where you used to get a build:
$ capa --check main.capa
capa: /path/capa.toml: this project declares capa = '>=99.0.0', but the
compiler running the build is 1.27.0.
$ echo $?
1The policy splits by root versus dependency. The root manifest's floor is a hard error (exit 1). A dependency's floor is a warning naming the package, because you cannot satisfy it by editing a manifest you own; that warning is raised by the commands that compose the dependency graph (--check-capabilities, --compose-sbom, --check-policies), not by a plain --check. A missing capa key constrains nothing. CAPA_IGNORE_CAPA_FLOOR=1 downgrades the refusal to a warning that reprints, in full, the refusal it overrode. Before reaching for it, check whether the floor or the compiler is the thing that is out of date.
The same release stopped the compiler ignoring a capa.toml it could not parse. Any parse error, including one lowercase letter in a capability name, is now a refusal:
$ capa --check main.capa
capa: broken capa.toml: /path/capa.toml: [capabilities].max names unknown
capability(ies): ['stdio']; allowed: ['Clock', 'Db', 'Env', 'Fs', 'Net',
'Proc', 'Random', 'Serve', 'Stdio']
$ echo $?
2There is deliberately no escape hatch for this half. Ignoring the manifest discards the name-to-directory mapping that makes a declared dependency path authoritative, and on 1.18.1 that was measured compiling an unaudited directory that merely shared the dependency's name, printing ok and exiting 0. Details in the 2026-07-20 advisory.
Seed libraries
The signed registry lists eighteen libraries; the original seed set of eight is below, and the packages page has the full list. Each declares its own capability surface, visible in the SBOM before you read its code. A library marked none declares no capability, so no function in it can perform an effect: the discipline is checked per function, over the code the compiler reads.
| Package | What it does | Capability |
|---|---|---|
| capa_cli | Command-line argument parsing: flags, positionals, subcommands | none |
| capa_csv | RFC 4180 CSV parser, header view, and writer | none |
| capa_datetime | Date and time values plus formatting | none |
| capa_hash | SHA-256, SHA-224 and HMAC-SHA256, constant-time compare | none |
| capa_http | HTTP client: request and response handling | Net |
| capa_log | Structured logging with levels and formatters | Stdio |
| capa_sbom | CycloneDX / SPDX JSON parsing with capability queries | none |
| capa_test | A tiny assertion library for the capa test runner | Stdio |
Hello, Capa
The fastest path is to let the compiler scaffold one for you.
$ capa init my-project
$ cd my-project
$ capa --run main.capa
Hello from Capa!Or type it yourself; create hello.capa:
// hello.capa
fun main(stdio: Stdio)
stdio.println("Hello, Capa!")The stdio: Stdio parameter is the language asking explicitly for the right to write to standard output. The runtime hands one to main, and main can hand it further down, or not. This is the whole language in one line.
Pipeline modes and tooling flags
Each pipeline mode subsumes the previous: --run implies --transpile, which implies --check.
| Flag | What it does |
|---|---|
| init [name] | Scaffold a new project: main.capa, README.md, .gitignore, .capa-version. |
| (none) | Tokenize: print the lexer token stream. |
| --parse | Parse to AST and print a structured dump. |
| --check | Full analyzer: name resolution, types, capability discipline. Prints errors or OK. |
| --transpile | Emit equivalent Python 3.10+ source to stdout. |
| --run | Transpile and execute. The everyday flag. |
| --fmt / --fmt-check | Rewrite in canonical style, or verify only. |
| --doc | Emit a self-contained HTML page from /// doc comments. |
| --manifest … --provenance | Emit the authority graph in five formats: Capa JSON, CycloneDX 1.5, SPDX 2.3, VEX, SLSA L1 provenance. |
| --watch | Re-run on every change. Implies --run. |
| lsp | Start the language server on stdio. |
$ capa --check examples/io.capa
OK
$ capa --run examples/grades.capa
=== Roster ===
Ana: 17.5 (Excellent)
Bruno: 13.0 (Pass)Every invocation also works as python -m capa <args> if capa is not on your PATH.
An LSP server in the box
Any LSP-capable editor (Helix, Neovim, Zed, VSCode, JetBrains, Emacs…) gets diagnostics, hover, go-to-definition, find-references, outline, completion, semantic highlighting, rename and Quick Fixes: the server runs the same lexer / parser / analyzer the CLI runs.
[[language]]
name = "capa"
language-servers = ["capa"]
file-types = ["capa"]
[language-server.capa]
command = "capa"
args = ["lsp"]The VSCode extension is on the Marketplace, with syntax highlighting, snippets, and a bundled LSP client that auto-connects to capa lsp:
$ code --install-extension nelsonduarte.capa-languageWhere to go next
Read the case →
Why Capa exists, the problem it addresses, and what it does not claim to solve.
Migrate from Python →
Move an existing program into Capa one function at a time, shrinking the Unsafe surface.
Tour the language →
A guided pass through the syntax: types, control flow, generics, capabilities, attenuation.
Read the examples →
Over fifty runnable programs, from hello.capa to SBOM parsers and the CVE case studies.