Skip to main content

CLI Commands

7 min read Last updated
On this page

This page lists every phel command with its main flags and a working example. Run vendor/bin/phel list for the list and vendor/bin/phel help <command> for every flag of one command.

Overview

CommandAliasWhat it does
initScaffold a new project
runrRun a file or namespace
testtRun tests
evaleEvaluate an expression and print the result
replInteractive prompt
buildbCompile the project to PHP
exportGenerate PHP classes for exported functions
formatfmtFormat .phel files
lintStatic analysis
balanceFind and fix unbalanced brackets
compilePrint the PHP a snippet compiles to
docShow docs for core functions
explainExplain an error code
watchReload namespaces on file change
nreplnREPL server for editors
lspLanguage server for editors
profilePer-function timings
benchRun benchmarks
mutateMutation testing
doctorCheck PHP, extensions and config
configShow the effective config
cache:clearClear compiled and namespace caches
cache:warmWarm module caches for production
nsloaded-nsList or inspect loaded namespaces
analyze, index, api-daemonJSON output for tools
agent-installInstall AI agent skill files
completionPrint a shell completion script

Flags that work on every command:

FlagEffect
--warn-deprecationsPrint deprecation notices to stderr. Same as PHEL_WARN_DEPRECATIONS=1
-q, --quietShow errors only
-v, -vv, -vvvMore output
--no-ansiNo colors (or set NO_COLOR)

--stack-trace on run, eval, repl and test shows every stack frame, including the internal ones that are collapsed by default.

Initialize a project

vendor/bin/phel init my-app                          # Flat layout: src/, tests/
vendor/bin/phel init my-app --nested                 # src/phel/, tests/phel/
vendor/bin/phel init my-app --minimal                # single main.phel at the root
vendor/bin/phel init my-api --template=http-json-api # start from a bundled example

Writes phel-config.php, a main namespace, a test, and a .gitignore in the current directory. The project name defaults to app.

FlagEffect
--nestedNested layout
-m, --minimalRoot layout
-t, --template[=NAME]Scaffold from a bundled template. Without a value, lists them
--list-templatesList templates (http-json-api, todo-app, cli-wordcount)
--dry-runShow what would be created
--forceOverwrite existing files
--no-gitignore, --no-testsSkip those files

A template's namespaces, composer.json and entry points are renamed to your project name.

Run a script

vendor/bin/phel run                          # auto-detects main.phel or core.phel
vendor/bin/phel run src/cli.phel --name Alice

Arguments after the path reach your code as *argv*, here ["--name" "Alice"]. The path can also be a namespace.

FlagEffect
-t, --with-timePrint time and memory used after the run
--clear-opcacheClear OPcache before running
--debug[=FILTER]Line-by-line trace to ./phel-debug.log. Optional file filter, e.g. --debug="core"
--stack-traceShow every stack frame

See Getting Started.

Test

vendor/bin/phel test                                  # everything in the test dirs
vendor/bin/phel test tests/app/math-test.phel
vendor/bin/phel test --filter=greet --parallel=auto
FlagEffect
-f, --filter=REGEXMatch test names. Repeatable
--include=TAG, --exclude=TAGRun or skip tagged tests. Repeatable. --exclude wins
--ns=GLOBMatch namespaces. * is one segment, ** any. Repeatable
--listList the selected tests without running them
--fail-fastStop at the first failure
--reporter=NAMEdefault, testdox, dot, tap, junit-xml, github. Repeatable. github is added on GitHub Actions
--testdoxSame as --reporter=testdox
-o, --output=PATHWrite the junit-xml report to a file
--parallel=NRun namespaces in workers: a number, auto (CPU count, max 8), or max
--watchRe-run on every .phel or phel-config.php change
--last-failedRe-run only the tests that failed last time
--changed[=REF]Run only tests affected by changed files (uncommitted, or git diff REF)
--repeat=NRun each test N times, to find flaky tests
--random-order, --seed=INTShuffle test order, reproducibly with a seed
--fail-on-focusExit non-zero when a ^:focus test narrowed the run. Always on when CI is set
--slowest=NPrint the N slowest tests
--coverage[=FORMAT]Line coverage via pcov or xdebug: text, clover, html, per-test
--coverage-output=PATHWrite the coverage report to a file (a dir for html)

See Testing for how to write tests and use these flags.

Evaluate an expression

vendor/bin/phel eval '(+ 1 2 3)'
# 6

echo '(map inc [1 2 3])' | vendor/bin/phel eval -
# (2 3 4)

For multi-line code, pass a quoted heredoc on stdin. Nothing inside it needs escaping:

vendor/bin/phel eval - <<'PHEL'
(ns app)
(println (+ 40 2))
PHEL

REPL

vendor/bin/phel repl

Interactive prompt with history, completion, and *1, *2, *3, *e. Leave with (exit), (quit) or Ctrl+D. See REPL.

Build the project

vendor/bin/phel build                  # incremental, uses the cache
vendor/bin/phel build --no-cache -O 2  # clean, fully optimized

Compiles every namespace to PHP in the build dir (out/ by default). When withMainPhelNamespace is set, it also writes an entry file (out/index.php) that you run with plain PHP, so production requests skip compiling. See Deployment.

FlagEffect
--cache, --no-cacheUse or skip the build cache
--source-map, --no-source-mapEmit source maps
-O, --optimization-level=NOverride the configured level. 2 inlines ^:pure calls and rewrites self tail calls. See Performance
--reportPrint namespace count, compiled size per namespace, total size and build time
--timingPrint compile time per phase (lex, parse, read, analyze, emit). Pair with --no-cache

Set the entry namespace and output paths in Configuration.

Export definitions

vendor/bin/phel export

Writes one PHP class per namespace, with one method per function marked {:export true}, so PHP code can call Phel. Set the dirs and namespace prefix in Configuration. See PHP Interop.

Format

vendor/bin/phel format                              # the configured format dirs
vendor/bin/phel format src/main.phel
vendor/bin/phel format --dry-run                    # exit non-zero if a file would change
vendor/bin/phel format --exclude='src/generated/*'  # repeatable

Rewrites files in place. Indents definition and body forms cljfmt-style and collapses repeated blank lines. --exclude is combined with the format-exclude config key.

Lint

vendor/bin/phel lint                     # the configured source dirs
vendor/bin/phel lint src --format=json   # human (default), json, github

Reports issues without changing files. Exits 1 on errors, including a file it cannot parse. --no-cache skips the lint cache.

Default severityRules
Errorunresolved-symbol, arity-mismatch, invalid-destructuring, duplicate-key, duplicate-def
Warningunused-binding, unused-require, unused-import, shadowed-binding, redundant-do, discouraged-var, comment-style

Change severities (:error, :warning, :info, :hint, :off) or exclude files in phel-lint.phel at the project root, or pass --config=PATH:

{:rules {:phel/unused-binding :off
         :phel/arity-mismatch :error}
 :exclude {:phel/unused-binding ["src/local.phel" "app.experimental.*"]}}

Balance

vendor/bin/phel balance src/broken.phel
# Unbalanced (1):
#   src/broken.phel:2:0: unclosed '(', needs ')'
# Run again with --fix to append the missing delimiters.

Brackets inside strings, comments, regexes and char literals are ignored. --fix only appends missing closers. A surplus or mismatched closer is reported and left alone.

Compile to PHP

vendor/bin/phel compile '(php/strlen "hello")'
# strlen("hello");

vendor/bin/phel compile src/main.phel
echo '(map inc [1 2 3])' | vendor/bin/phel compile -

Prints the PHP without running it. A form that folds to a constant emits no PHP; the folded value goes to stderr.

Look up docs

vendor/bin/phel doc map                  # search: also matches map?, mapcat, ...
vendor/bin/phel doc --format=json > docs.json

With no search term it lists every function. --ns=NS loads more namespaces. A search with no match prints No function matches "..." and still exits 0.

Explain an error code

vendor/bin/phel explain PHEL001
# Undefined symbol [PHEL001]
#
# The analyzer reached a symbol that is bound nowhere: ...

vendor/bin/phel explain 1   # same code, short form
vendor/bin/phel explain     # list every code

Prints the meaning, the smallest program that raises it, and the fix. The same text is on the Error Reference.

Watch

vendor/bin/phel watch                 # the configured source dirs
vendor/bin/phel watch src -b polling

Re-evaluates changed namespaces in dependency order. -b, --backend is auto (default), inotify, fswatch or polling. --poll=MS (default 500) and --debounce=MS (default 100) tune it. From Phel code, use phel.watch:

(ns my-app
  (:require phel.watch :refer [watch!]))

(watch! ["src/"])

nREPL

vendor/bin/phel nrepl                # 127.0.0.1:7888
vendor/bin/phel nrepl --port=0       # random free port

Bencode-over-TCP server for Calva, CIDER, Conjure and Cursive. -p, --port and --host set the address. The bound port is written to .nrepl-port and removed when the server stops. See Editor support.

LSP

vendor/bin/phel lsp

Language server (LSP 3.17) over stdio. Your editor starts it. See Editor support.

Profile

vendor/bin/phel profile src/main.phel
vendor/bin/phel profile src/main.phel --sort=total --format=json -o profile.json

Runs a script and reports per-function call counts and timings, plus compile-phase costs.

FlagEffect
--top=NRows in the table (default 20)
-s, --sort=KEYself (default), total, calls, avg
-f, --format=FORMATtable (default), json, both
-o, --output=PATHWrite the JSON report to a file
--no-compile-phasesSkip the compile-phase report

Bench

vendor/bin/phel bench                                     # every defbench under the test dirs
vendor/bin/phel bench --store=.phel/bench-baseline.json   # save a baseline
vendor/bin/phel bench --ref=.phel/bench-baseline.json --tolerance=10
vendor/bin/phel bench --ab=main --pairs=5 --filter=step   # compare with a git ref

Runs functions defined with phel.bench/defbench. With --tolerance, it exits non-zero when a benchmark is slower than the baseline by more than that percentage. --ab runs the ref in a temporary worktree and your working tree in alternating pairs. Other flags: -f, --filter, --revs, --iterations, --warmup.

Mutate

vendor/bin/phel mutate                        # project sources against project tests
vendor/bin/phel mutate src/app/calc.phel --only=arith,compare
vendor/bin/phel mutate --min-msi=80 --parallel=auto

Mutates every defn and reports the mutants your tests do not catch. The unmutated suite must pass first.

FlagEffect
--tests=PATHTests to run. Repeatable. Default: test dirs
--only=IDSComma-separated mutator ids
--min-msi=N, --min-covered-msi=NExit 1 below this score (percent)
--reporter=FORMAT, -o PATHtext or json, optionally to a file
--parallel=NWorkers: a number or auto
--changed[=REF]Mutate only changed files
--timeout-factor=NA mutant slower than N times the baseline counts as killed (default 3)

Check your environment

vendor/bin/phel doctor
# Checking requirements:
#  - json extension: OK
#  - mbstring extension: OK
#  - readline extension: OK
# ...

Checks the PHP version and extensions, module health, your config, and OPcache, and prints a fix for anything missing. Run it first when Phel fails before your code loads.

Inspect configuration

vendor/bin/phel config
# Sources:
#  - project root: /path/to/project
#  - phel-config.php: found (/path/to/project/phel-config.php)
#  - phel-config-local.php: not present
#  - PHEL_DIR env: (unset)
#
# Effective config:
# { "src-dirs": ["src"], "test-dirs": ["tests"], ... }

vendor/bin/phel config --format=json

Shows the merged config and where each part came from, then validation warnings. Use it when phel-config.php, phel-config-local.php or PHEL_DIR does not take effect. See Configuration.

Caches

vendor/bin/phel cache:clear
vendor/bin/phel cache:warm --clear --attributes

cache:clear removes the compiled-code, namespace and temp caches and empties the OPcache file cache. Run it after every upgrade. cache:warm pre-resolves module classes for production; -c, --clear starts fresh and -a, --attributes also caches #[ServiceMap] attributes.

Runtime state (cache, REPL history, error log) lives under .phel/. Move it with withPhelDir('...') or PHEL_DIR.

Tooling commands

For editors and scripts. You rarely run these by hand.

vendor/bin/phel analyze src/main.phel                 # JSON diagnostics for one file
vendor/bin/phel index src tests --output=index.json   # project symbol index
vendor/bin/phel api-daemon                            # JSON-RPC over stdio
vendor/bin/phel ns                                    # list loaded namespaces
vendor/bin/phel ns phel.core --simple                 # inspect one, names only

The Gacela framework also adds debug:container, debug:dependencies, debug:modules, list:modules, profile:report and validate:config. They inspect Phel's internal modules.

Agent install

vendor/bin/phel agent-install --auto     # agents detected in this project
vendor/bin/phel agent-install claude     # one platform
vendor/bin/phel agent-install --all      # every platform
vendor/bin/phel agent-install --check    # exit 1 if installed docs are stale

Writes a skill file for Claude, Cursor, Codex, Gemini, Copilot or Aider, plus a shared .agents/ docs tree. Re-running updates only what changed upstream and keeps files you edited. An existing skill file is backed up to .pre-phel.bak.

FlagEffect
--no-docsSkip the .agents/ docs tree
--with-examplesAlso copy example projects into .agents/examples/
--dry-runShow what would be written
--forceOverwrite without the skill backup, and overwrite edited docs (backing them up)
--uninstallRemove skill files and restore .pre-phel.bak

See AI agents.

Shell completion

vendor/bin/phel completion zsh > completion.sh && source completion.sh
eval "$(vendor/bin/phel completion zsh)"   # or in ~/.zshrc

Supports bash, zsh and fish. Without an argument it reads $SHELL.