CLI Reference
The zpm command-line interface provides a structured way to interact with the Prolog inference engine via the Model Context Protocol (MCP).
Usage
zpm [COMMAND] [FLAGS] [OPTIONS]Commands
init
Initializes a new zpm project directory in the current working directory.
zpm initCreates the .zpm/ directory structure:
.zpm/— Project root for configuration and persistence.zpm/kb/— Knowledge base directory for Prolog files and snapshots.zpm/data/— Ephemeral data directory for write-ahead journal and locks.zpm/.gitignore— Git ignore rules (excludesdata/)
This command is idempotent. Running it on an already-initialized project prints a success message and exits without modifying existing content.
Example:
# Initialize a new project
zpm init
# Verify the directory structure
ls -la .zpm/
# Output:
# drwxr-xr-x kb/
# drwxr-xr-x data/
# -rw-r--r-- .gitignoreExit Codes:
0— Success (directory created or already initialized)1— Error (permission denied, filesystem error)
serve
Starts the zpm MCP server, listening on stdin/stdout for JSON-RPC 2.0 requests.
zpm serveOn startup, the server:
- Discovers the nearest
.zpm/directory by walking up from the current working directory - Loads all
.plfiles from.zpm/kb/into the Prolog engine - Initializes persistence (WAL journal in
.zpm/data/, snapshots in.zpm/kb/) - Begins accepting MCP messages on STDIO
If no .zpm/ directory is found in the directory ancestry, the server exits with an error suggesting zpm init. If .zpm/ exists but is not writable, the server enters degraded mode (in-memory only).
The server implements the full MCP protocol, including:
- Tool discovery (
tools/list) - Tool execution (
tools/call) - Request/response routing
This is the primary command for integrating zpm with MCP-compatible clients (Claude Code, Claude Desktop, Cursor, Zed, Gemini CLI, Codex CLI, or custom applications). See Configure in Your MCP Client for per-client configuration.
Example:
# Start the server
zig-out/bin/zpm serve
# In another terminal, send MCP requests
echo '{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {...}}' | socat - EXEC:'zig-out/bin/zpm serve'upgrade
Downloads the latest release binary from GitHub, verifies its SHA256 checksum, and atomically replaces the currently running executable.
zpm upgrade [--channel stable|dev] [--dry-run]On invocation, the command:
- Resolves the target release from the GitHub releases API for
awf-project/zpm:stable(default): the latest release withprerelease: falsedev: the most recently published release, regardless of theprereleaseflag
- Short-circuits with an “already up to date” message if the resolved tag matches the running binary’s embedded version
- Detects the host OS/architecture and selects the matching release asset:
- Linux x86_64 →
zpm-linux-x86_64 - Linux arm64 →
zpm-linux-arm64 - macOS (x86_64 or arm64) →
zpm-darwin-universal(single fat binary)
- Linux x86_64 →
- Downloads the asset and its
SHA256SUMSfile to a temporary location with explicit HTTP timeouts - Computes the SHA256 of the downloaded asset and compares it against the published entry (matched by full basename)
- Writes the verified bytes to
<install-path>.new, preserves the original file mode, and renames the temp file over the running binary
If any step fails (network error, checksum mismatch, permission denied, unsupported platform, unknown channel), the command exits non-zero and leaves the originally installed binary byte-identical.
Flags:
| Flag | Description |
|---|---|
--channel stable|dev | Release channel to pull from (default: stable). Unknown values are rejected with an error listing the supported channels. |
--dry-run | Report the target version, asset URL, expected checksum, and install path without modifying anything. |
Example:
# Upgrade to the latest stable release
zpm upgrade
# Preview the upgrade plan without touching the binary
zpm upgrade --dry-run
# Pull the latest prerelease (dev channel)
zpm upgrade --channel devExit Codes:
0— Success (binary replaced, or already up to date)1— Error (network failure, checksum mismatch, permission denied, unsupported platform, unknown channel)
Supported Platforms: Linux (x86_64, arm64) and macOS (x86_64, arm64). On other platforms, the command exits with an error listing the supported targets.
Read-Only Filesystems: If the running binary lives on a path without write permission (e.g., /usr/local/bin without sudo), the command surfaces the permission error and suggests re-running with elevated privileges.
memory
Manage named memory segments for domain-isolated knowledge bases.
zpm memory <subcommand> [OPTIONS]Memory segments are independent Prolog modules with their own WAL and snapshot persistence. Use them to isolate knowledge by domain (e.g., one memory per feature branch, per agent, or per concern).
memory create
Create a new named memory segment.
zpm memory create --name <name> [--scope project|global]Creates the on-disk directory structure (.zpm/kb/<name>/) with an empty knowledge.pl module header. The memory is automatically mounted on the next command invocation (see Auto-mount below).
The name must be a valid Prolog atom: lowercase letter followed by alphanumeric characters and underscores.
| Flag | Description |
|---|---|
--name | (required) Memory name |
--scope | project (default) or global |
memory mount
Mount an existing memory segment with a specific mode, updating the persistent manifest.
zpm memory mount --name <name> [--mode rw|ro]Loads the knowledge base, replays the WAL, and updates .zpm/mounts.json so the memory is mounted with the specified mode on all subsequent CLI invocations. Default mode is rw (read-write); use --mode ro to prevent mutations.
This command is useful for:
- Mounting an unmounted memory (previously removed via
memory unmount) - Changing a memory’s mode from
rwtoroor vice versa - Ensuring a memory is mounted in a subsequent invocation after being explicitly unmounted
| Flag | Description |
|---|---|
--name | (required) Memory name to mount |
--mode | rw (default) or ro |
memory unmount
Unmount a mounted memory, flushing its WAL and freeing resources.
zpm memory unmount --name <name>The default memory cannot be unmounted. Unmounting removes the memory from the persistent manifest (.zpm/mounts.json), so it will not be mounted on the next CLI invocation unless explicitly remounted with memory mount.
| Flag | Description |
|---|---|
--name | (required) Memory name to unmount |
memory list
List all currently mounted memories with their scope and mode.
zpm memory listAuto-mount
Each CLI invocation is a separate process. At startup, initBootstrap reads the persistent mount manifest (.zpm/mounts.json) and mounts every memory listed in it. On first boot, if the manifest does not exist, zpm scans .zpm/kb/ for subdirectories and generates the manifest with all discovered memories (as read-write, project scope).
This means:
- After
memory create, the memory is automatically added to the manifest and usable on all subsequent invocations memory mount --mode roupdates the manifest so the memory is mounted read-only on subsequent invocations (no need to repeat the command)memory unmountremoves the memory from the manifest, so it will not be mounted on the next invocation- All mount decisions persist across CLI invocations without manual intervention
Examples:
# Create and use an isolated memory for a feature
zpm memory create --name feature_auth
zpm remember-fact --fact "task_done(login)" --memory feature_auth
zpm query-logic --goal "task_done(X)" --memory feature_auth
# Mount in read-only mode for safe consultation
zpm memory mount --name project_kb --mode roTool Subcommands
Every MCP tool is available as a CLI subcommand using kebab-case (e.g. remember_fact → remember-fact). Tool invocations share the same bootstrap as zpm serve — they discover the nearest .zpm/, load the knowledge base, run the handler, and exit.
zpm <tool-name> [<positional>] [--flag value ...] [--format json|text]Tool fields are passed as --kebab-case flags by default — remember_fact.fact becomes --fact, explain_why.max_depth becomes --max-depth, and so on. Two tools take a positional first argument as a historical exception: define-rule accepts the rule head positionally, and assume-fact accepts the fact positionally. Every other tool’s required and optional fields use --<flag> <value> syntax. The full tool roster lives in
MCP Tools Reference; each entry documents its fields.
Every tool subcommand also accepts --format json|text. The default (text) prints the tool’s native output (queries already emit JSON, writes emit human-readable confirmations). --format json produces a JSON array whose elements are the raw text field of each result block, e.g. ["Asserted: parent(tom, bob)"] for a write or ["[{\"X\":\"tom\"}]"] for a query. Note that query output is doubly encoded — the inner string is itself JSON; pipe through jq -r '.[]' and parse the unwrapped string if you need structured access.
All knowledge and reasoning tool subcommands also accept --memory <name> to target a specific mounted memory segment instead of the default. When omitted, operations target the default memory. Mutation commands (remember-fact, define-rule, forget-fact, etc.) return an error if the target memory is mounted read-only.
Examples:
# Insert a fact (US1)
zpm remember-fact --fact "decision(backend, trealla, performance)"
# Upsert: replace by functor + first argument
zpm upsert-fact --fact "task_status(f017, done)"
# Two-arg tool: head positional, body flag
zpm define-rule "ancestor(X, Z)" --body "parent(X, Y), ancestor(Y, Z)"
# Query and pipe JSON output to jq (US2)
zpm query-logic --goal "task_status(X, done)" | jq '.[].X'
# Snapshot management (US3)
zpm save-snapshot --name "before-upgrade"
zpm list-snapshots
zpm restore-snapshot --name "before-upgrade"
# Truth maintenance: fact is positional, assumption is a flag
zpm assume-fact "requires_reboot(host)" --assumption "deploy_plan_v2"
zpm list-assumptions
# Target a specific memory segment
zpm remember-fact --fact "auth_done(oauth)" --memory feature_auth
zpm query-logic --goal "auth_done(X)" --memory feature_authDiscovering Commands:
zpm --help # Lists init, serve, upgrade, memory, version, and every tool subcommand
zpm query-logic --help # Shows the tool's flags (and any positional argument)
zpm memory --help # Lists memory subcommands (create, mount, unmount, list)Help is generated from each tool’s registry entry; adding a new MCP tool automatically produces a matching CLI entry with no manual documentation regeneration (NFR-004).
Concurrency Warning: Running CLI tool commands against a .zpm/ that is actively being served by zpm serve is undefined behaviour for writes; the persistence layer does not yet coordinate locks between processes. Stop the server before issuing write commands, or restrict CLI use to read-only queries.
Flags
-h, --help
Displays help text listing available commands, subcommands, and options.
zpm --help
zpm -hRunning zpm without arguments also displays the help banner, but exits with status 1 because no subcommand was selected.
Exit Code: 0 for --help/-h; 1 when invoked with no arguments at all.
version subcommand
Use the version subcommand to print the current zpm version:
zpm versionExample Output:
zpm 0.2.3Exit Code: 0
The --version and -v flags are auto-generated by the underlying CLI parser, but currently fall through to the help banner with exit 1 (a known limitation in how zig-cli handles the version short-circuit). Prefer the version subcommand for scripts and installation checks.
Exit Codes
| Code | Meaning |
|---|---|
| 0 | Success (--help/-h, version subcommand, serve running normally, tool result with is_error=false) |
| 1 | Error (zpm with no arguments, unknown subcommand, invalid or missing required flags, parse error, no .zpm/ found, serve crashed, tool result with is_error=true) |
Tool subcommands write results to stdout; tool handler diagnostics (including the tool name and the offending field) are written to stderr, per FR-007. Argv parser errors emitted by zig-cli (unknown options, malformed flag values, missing required arguments) may be silently dropped before reaching the terminal in some environments — the exit code remains the authoritative signal.
Common Usage Patterns
Integrate with MCP Client
Configure your MCP client to spawn zpm:
- Command:
zpm serve(or absolute path tozig-out/bin/zpmif built from source) - Transport: stdio
- Working directory: project root containing
.zpm/
Per-client configuration examples (Claude Code, Claude Desktop, Cursor, Zed, Gemini CLI, Codex CLI) live in the Getting Started guide.
Debug Server Startup
Test that the server starts and responds:
# Send initialize and immediate EOF
echo '{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2025-11-25", "capabilities": {}, "clientInfo": {"name": "test"}}}' | zig-out/bin/zpm serveCheck Installation
Verify zpm is installed and working:
zpm version # Should print version (e.g. 'zpm 0.2.3')
zpm --help # Should display help with exit 0
zpm serve & # Should start without blocking terminalArchitecture Notes
The CLI layer is split across several modules for clear separation of concerns:
- Registry (
src/cli/registry.zig) — All 29 MCP tools (including 4 memory management tools), each with aParamSpecarray describing its CLI shape (kind, required, positional, kebab name) - Tool Command Generator (
src/cli/tool_command.zig) — Comptime genericToolCommand(comptime def)that synthesizes acli.Commandper registry entry, including its options, positional args, and exec thunk - App Assembler (
src/cli/app.zig) — Builds the top-levelcli.Appwith theinit,serve,upgrade, andversionsubcommands plus every generated tool command - Bootstrap (
src/cli/bootstrap.zig) — Shared initialization: discover.zpm/, load knowledge base, start Prolog engine - Output (
src/cli/output.zig) — Format tool results for stdout per the--formatflag, and write diagnostics to stderr per FR-007 - Subcommand Handlers (
src/cli/{init,serve,upgrade}.zig) — Entry-point logic for three of the four built-in subcommands; the fourth (version) is inlined inapp.zigbecause it has no dependencies and amounts to a one-line print
This architecture ensures:
- Automatic CLI generation for all MCP tools (NFR-004: adding a tool generates a matching CLI entry with no manual work)
- Help text is rendered by the underlying CLI parser straight from the registry, so help drift between docs and reality is structurally impossible
- Deterministic argument ordering (positional first, then flags) across all tools
- Fast
--helpandversionresponses (no knowledge base load) - Type-validated flags:
--max-depth abcis rejected at parse time instead of being silently dropped
Troubleshooting
Server doesn’t respond to commands
- Ensure the server is running:
zpm serve & - Verify stdin/stdout are connected properly
- Check that you’re sending valid JSON-RPC 2.0 requests
Unrecognized subcommand falls through to help
- An unknown subcommand (e.g.
zpm bogus) prints the help banner on stdout and exits 1, with no diagnostic identifying the offending name (the parser’s error message is dropped before reaching the terminal — see Exit Codes above). - Valid subcommands are
init,serve,upgrade,version, and every MCP tool in kebab-case (e.g.remember-fact,query-logic,save-snapshot). - Run
zpm --helpto list every available subcommand. - The tool list is derived from the registry, so any tool accessible via MCP is also accessible on the CLI.
Unexpected hang
- If
zpm serveappears to hang, it’s likely waiting for MCP requests on stdin - This is expected behavior for an MCP server
- Send a valid request or press Ctrl+C to terminate
Future Extensions
The following CLI features are deferred and not currently implemented:
- Interactive REPL mode (
zpm repl) for successive queries - Alternative output formats (YAML, TOML, CSV)
- Shell completion scripts (bash/zsh/fish)
- Batch piping (stdin → multiple tool calls)
.zpm/config.toml— Project-level configuration file--transportflag — TCP/HTTP server modes (currently STDIO only)--log-levelflag — Debug logging control- Cross-process locking for concurrent CLI +
zpm serveaccess to the same.zpm/
See the Roadmap for more details.