CLI guide

A private AI coding agent
for your terminal

Provider-agnostic. Bring your own model, keep your own privacy. It reads, writes and runs code — and every action, built-in or not, is classified and gated behind your approval before it happens.

Install npm i -g privateer-agent macOS · Linux · Windows Open source MIT Built on the Pi agent toolkit

Install

Four ways in. The bundle is the least fussy — it ships its own pinned Node runtime, so you don't need Node installed at all.

macOS / Linux — bundle
curl -fsSL https://privateer.pro/install.sh | sh
Windows — bundle (PowerShell)
irm https://privateer.pro/install.ps1 | iex
npm — needs Node 22.19 or newer
npm install -g privateer-agent --ignore-scripts
No install at all
npx privateer-agent

--ignore-scripts is the tested form and costs you nothing: the package deliberately declares no install scripts, and the patches it needs are applied at first launch instead. See verifying the install.

Platform notes

PlatformNotes
macOSPrebuilt bundles for Apple silicon and Intel.
Linuxx64 bundle. On arm64, use the npm or npx route.
Windowsx64 bundle; arm64 runs it under emulation. A bash is required — Git for Windows or WSL. The launcher checks at startup and tells you if it can't find one.

If your bash lives somewhere unusual, point at it with shellPath in ~/.privateer/agent/settings.json.


First run

Start it in the directory you want to work in. The agent's world is that directory.

cd ~/code/my-project
privateer

Getting a model

You need one of three things. Pick whichever suits you — the agent behaves the same either way.

OptionHowWhat it costs
Your Privateer account/loginBilled to your plan & credit. Unlocks the confidential enclave models.
Your own provider key/login keys, or set an env var before launchingYou pay the provider directly. Privateer is not in the billing path.
A local modelRun Ollama and set OLLAMA_BASE_URLNothing. Inference stays on the machine.

Provider keys are read from the environment: OPENROUTER_API_KEY, ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, XAI_API_KEY, GROQ_API_KEY, DEEPSEEK_API_KEY, NEAR_AI_API_KEY, TINFOIL_API_KEY, VENICE_API_KEY, OLLAMA_BASE_URL.

Signing in

/login uses the device-code flow. Privateer opens an Authorize this terminal? page on privateer.pro showing a short code, and you confirm it matches the one in your terminal.

⚠️

Only ever approve a code you generated yourself. That prompt authorizes a terminal to act on your account. If a code arrives any other way — pasted to you, in an email, read out over a call — it is not yours, and approving it hands someone else a session. Nobody at Privateer will ever ask you to approve one.

On a machine with no browser, PRIVATEER_NO_BROWSER=1 prints the URL and code instead. /logout signs out this machine and revokes every terminal on it.

Teach it the project

/init writes a starter PRIVATEER.md in the current directory — project context the agent reads on every turn. Put the things a new colleague would need: what the project is, how to run it, the conventions you actually keep. AGENTS.md and CLAUDE.md are read too, so an existing file works as-is.


Models & privacy shields

/models opens a searchable picker. Beside every model is a shield saying what kind of privacy you're getting — and, crucially, how strong the evidence for it is. Those are two different things, and the picker refuses to blur them.

ShieldEvidenceWhat it means
Verified TEEcryptographicRemote attestation ran, proved genuine enclave hardware, and the live TLS key matched the attestation report.
On-deviceobservableA loopback endpoint — inference is local; no prompt leaves the machine.
ZDR (enforced)observableRouting is constrained to providers that contractually don't retain data, and the router refuses to serve when it can't meet the policy. Enforcement of a policy, not hardware attestation.
TEE (unconfirmed)noneThe provider claims an enclave, but attestation was incomplete or the live key couldn't be matched here. Treat as unverified until it goes green.
ZDR (by policy)policyThe provider states it doesn't retain data. A promise we can't verify — not hardware, not attested.
StandardnoneNo special privacy guarantee.

A TEE claim starts yellow and only turns green once attestation actually succeeds, which happens in the background after you pick. /verify re-runs it on demand; /surface shows the tool surface and provenance for the current session.

🛡️

The distinction is the point. "Verified" is reserved for routing this machine checked for itself — something we merely assert never earns it, no matter how confident we are. If a rung looks unflattering, that is the ladder working.


Terminal commands

What you can type at your shell, as opposed to inside a session.

CommandWhat it does
privateerStart an interactive session in the current directory.
privateer --versionPrint the Privateer version and the Pi version underneath it.
privateer --no-quarterStart with the permission gate off. Prints a red banner. See unattended runs.
privateer updateUpdate Privateer itself, using whichever installer you used.
privateer update --extensionsUpdate every installed tool pack.
privateer update <pack>Update one pack, by npm name or git URL.
privateer update --allTool packs first, then Privateer.
privateer harborRun the Local Harbor in the foreground.
privateer harbor installInstall it as a login service so it auto-starts and stays reachable with no terminal open.
privateer harbor uninstallRemove the login service.
privateer harbor statusShow whether the service is installed and the Local Harbor is live.
privateer verifyCheck this installation: shape, provenance, dependency drift, patch state, where credentials live. --offline skips the registry checks.
privateer acpSpeak the Agent Client Protocol over stdio, for editors. See editors & ACP.

Slash commands

Typed inside a session. Tab-completion lists them; this is the whole set.

Account & session

CommandDescription
/loginLog in to your Privateer account · /login keys for a provider API key
/logoutLog out of Privateer on this machine (revokes all its terminals)
/privateerPrivateer account: /privateer [status | login | logout]
/modelsPick a model with its privacy posture (TEE / ZDR / standard) — searchable
/verifyRe-check or fetch the attestation for the current model
/surfaceShow the session's tool surface and provenance
/updateUpdate Privateer to the latest release, or /update [<pack> | check] for tool packs

/signin and /signout still work as undocumented aliases — they were the original names and cost nothing to keep.

Permissions

CommandDescription
/modeShow or set the permission mode: default | acceptEdits | bypass | plan
/no-quarter [on|off]Lower the moat for this session — run unattended with no approval prompts

Project & tools

CommandDescription
/initCreate a starter PRIVATEER.md project-context file in this directory
/connectAdd, enable, or remove MCP connectors (GitHub, Notion, Linear, …)
/mcpShow MCP server status — what actually connected
/mcp-auth <server>Authenticate with an MCP server over OAuth
/extensionsManage installed Pi extensions: /extensions [add <npm:pkg> | remove <npm:pkg>]
/web-toolsConfigure web search and fetch. Signed in, web_search and web_fetch already work on your account — no key to obtain. Use this to point them at a provider of your own instead (Brave, Tavily, a self-hosted SearXNG), which always takes precedence over the account route.
/hints [on|off]Rotating tips in the working line

Voice

CommandDescription
/speakSpoken responses: [on | off | stop | test | provider [id] | voice [name] | rate [n] | length [n|full] | stream on|off | announce on|off]
/talkVoice input — records and transcribes into the composer: [provider [id] | send on|off | lang [code] | loop on|off | silence [ms] | vocab [words] | key [binding]]

Sub-agents

Bounded parallel helpers, for work that splits cleanly: /run, /chain, /run-chain, /parallel, /prompt-workflow, /chain-prompts, plus the fleet controls /subagents-fleet, /subagents-models, /subagents-profiles, /subagent-cost and /subagents-doctor. A sub-agent inherits the parent's exact permission setup — it cannot quietly hold more than you do.

Remote

CommandDescription
/remote-access on|offDrive this terminal from the Privateer app. Off by default.

Inherited from Pi

Privateer is a distribution of the open Pi agent, so Pi's own commands are all present: /settings, /export, /import, /share, /copy, /name, /session, /new, /resume, /fork, /clone, /tree, /compact, /reload, /trust, /hotkeys, /changelog, /scoped-models.

ℹ️

/workflow is deliberately not in the terminal. Multi-step declarative workflows are authored in the app and executed by the Local Harbor, because a workflow that outlives your terminal session is the only kind worth having. /model redirects to /models so there is one picker, not two.


Keyboard

KeyDoes
shift+tabToggle no quarter — the permission gate off and on.
alt+tPush-to-talk (with voice set up).
ctrl+tHide thinking text. Make it stick in /settings.
alt+enterQueue a follow-up without interrupting the current turn.
ctrl+oExpand collapsed tool output.
ctrl+gDraft a long prompt in $EDITOR.
@Reference a file in your prompt. Tab completes the path.

Rebind anything in ~/.privateer/agent/keybindings.json; /hotkeys shows the live map.

Mentioning files with @

Typing @src/server.ts inlines that file for the turn — text is appended to your message, images are attached for vision. Two limits worth knowing: a text file is read up to 256 KB and an image up to 5 MB, and mentions are confined to the working directory. @../secrets.env is skipped rather than prompted for, because a mention bypasses the approval gate by design and a bypass must not be able to reach outside the project.


Files & settings

Everything Privateer keeps lives under one directory, so there is a single tree to inspect, back up, or delete.

~/.privateer/
├── credentials.json          # account tokens (0600)
├── config.json               # Privateer prefs: remote, webhooks, posture, hints, channels, acp
├── account-sessions.json     # per-terminal inference sessions (0600)
├── speak.json                # voice settings
├── routines/                 # routine output, dated, plus latest.md
├── workflows/                # one file per saved workflow (0600)
├── bin/privateer             # the command shim
└── agent/                    # the Pi agent dir
    ├── auth.json             # provider keys
    ├── models.json  settings.json  trust.json  keybindings.json
    ├── mcp-desktop.json      # connectors — the source of truth
    ├── mcp.json              # generated projection: enabled only. Don't hand-edit.
    ├── mcp-oauth/            # connector OAuth tokens
    ├── sessions/             # your history, as files on your own disk
    └── extensions/           # drop your own Pi extensions here

Your conversation history stays here, on your machine. Nothing about a session is uploaded unless you turn on remote access or use an account model, which sends that turn's prompt for inference the same way any provider call does.

Per-project configuration

A project directory can carry its own settings, skills, prompts and extensions. Privateer reads .privateer/ and .pi/, in that order — and it only ever writes to .privateer/, so a repo that already uses Pi keeps working untouched.

my-project/
├── PRIVATEER.md              # project context (or AGENTS.md / CLAUDE.md)
└── .privateer/
    ├── settings.json         # deep-merged over the global one; arrays replace
    ├── skills/               # project-specific skills
    ├── prompts/  themes/  extensions/
    ├── SYSTEM.md             # replaces the system prompt
    └── APPEND_SYSTEM.md      # adds to it

Where both exist and disagree, .privateer/ wins. Launch with --no-context-files (or -nc) to ignore context files entirely for one run.


Environment variables

VariableEffect
PRIVATEER_HOMEMove the whole config tree somewhere else.
PRIVATEER_MODELStart on a specific model.
PRIVATEER_MODEStart in a specific permission mode.
PRIVATEER_NO_QUARTERStart with the gate off. Same as --no-quarter.
PRIVATEER_NO_BROWSERPrint the sign-in code instead of opening a browser.
PRIVATEER_NO_SPLASHSkip the startup splash.
PRIVATEER_API_KEYUse a Privateer API key instead of an interactive login.
PRIVATEER_SERVER_URLPoint at a different Privateer server.
PRIVATEER_DEBUGVerbose diagnostics. PRIVATEER_RELAY_DEBUG for the relay specifically.
PRIVATEER_FFMPEG, PRIVATEER_FFPROBEPoint the media tools at specific binaries.
HARBOR_WEB, HARBOR_MEDIATurn web access and media generation on or off for the Local Harbor.
PRIVATEER_WEB_SEARCHprivateer to search on your account even when you have a provider key configured; own to insist on your own provider.

The moat — how permissions work

Every action the agent can take is classified before it happens: shell commands, file writes, tool calls, anything an MCP connector offers. An action that clears the current mode runs; anything else stops and asks you.

ModeBehaviour
defaultAsk before anything that writes, runs, or reaches outside the project.
acceptEditsFile edits inside the project go through without asking. Shell and everything else still ask.
bypassFar fewer prompts, for when you're watching closely.
planRead and reason only. Nothing is changed or run.

Switch with /mode.

🏰

Two filters sit above every mode, and no mode relaxes them: a danger classifier that refuses destructive shell commands, and a protected-path list that guards files you would not want rewritten by accident. Relaxing the mode widens what runs without asking — it does not widen what is allowed to run.

Tools that come from a connector are gated identically to built-ins. A tool is not trusted because you configured the server it came from.

Unattended runs — “no quarter”

Sometimes you want the agent to just go: a long migration, a batch job, something you'll review at the end. shift+tab, /no-quarter on, or launching with --no-quarter turns the approval prompts off for the session.

While it's on, the banner is red and a footer indicator stays lit — there is no way to be in this state and not know it. Sub-agents inherit it.

Shell commands, edits, out-of-directory access and protected files all run without asking. The danger filter still holds — destructive shell commands stay blocked even here — but treat the working directory as fully writable and think about what's reachable from it before you start. No quarter is also deliberately unreachable from the phone: it is a decision you make at the machine.


Connectors (MCP)

A connector is a Model Context Protocol server whose tools become the agent's tools. /connect opens a picker over a curated catalog — GitHub, Slack, Notion, Linear, Jira & Confluence, Sentry, Stripe, Asana, Supabase, Figma, Gmail, Google Calendar, Drive and Docs, PostgreSQL, Playwright, Brave Search, Google Maps, Filesystem and more — plus a Custom connector entry that takes any stdio command line or https:// URL.

New tools are live in the session you're already in; the adapter reloads in place. /mcp shows what actually connected, which is the thing to check first when a tool doesn't appear.

TransportWhat it means
stdioLocal. The server runs as a child process on your machine. Nothing leaves the box except what that server itself chooses to send.
httpRemote. An endpoint someone else hosts, authenticating with OAuth (/mcp-auth <server>), a bearer token, or nothing. Whatever the agent hands it leaves your machine.

Configuration lives in two files under ~/.privateer/agent/: mcp-desktop.json is the source of truth and holds every connector with an enabled flag; mcp.json is generated from it and holds only the enabled ones. Edit the first, never the second. Because both sit in the shared home, the terminal, the desktop app and the phone all see one coherent list.

⚠️

Connector secrets are stored in plaintext — environment values, bearer tokens, an Authorization header — in mcp-desktop.json on that machine. This is unavoidable: the adapter has to hand the real token to the server. Protect the file the way you protect ~/.aws/credentials. The masked input in /connect is screen-share hygiene, not a storage claim.

Editing connectors from the phone works differently, because the relay in between is untrusted: secrets are write-only in both directions. A listing returns names and which are set, never values; a value typed on the phone is sealed to that terminal's key before it leaves the device.


The Local Harbor

The Local Harbor is a resident agent on your own computer. It runs scheduled routines and chat bridges, comes back at login, and outlives the terminal that started it — which is what makes scheduled work actually work.

privateer harbor install     # auto-start at login
privateer harbor status      # installed? live?
privateer harbor             # or just run it in the foreground
PlatformHow it installs
macOSA launchd user agent. No root, no admin prompt.
LinuxA systemd --user unit. No root.
WindowsNo login service. Run it in a terminal, or let the desktop app supervise it.

"Local Harbor" and "Harbor" are different things. The Local Harbor is this — a process on your own machine, under your own account, with your files. Harbor on its own means Privateer's hosted agents, which run on our infrastructure inside a hardware enclave we can't read into. Different machine, different promise. This guide only covers the local one.

A Local Harbor is headless by design. Drive it from the app by attaching to it — it has no terminal to take over, and it never speaks aloud.

Routines — scheduled work

A routine is a prompt on a schedule, run by the Local Harbor. Ask the agent to create one in plain language, or write it directly. Schedules are either a 5-field cron expression or a one-off ISO timestamp.

Results are delivered wherever you say:

ChannelWhere it lands
fileDefault. A dated directory under ~/.privateer/routines/, plus latest.md.
noticeA notification on the machine.
relayStraight to an attached app session.
cloudThe encrypted outbox — shows up in the app's Inbox on any of your devices.
emailEmailed to you.
webhook:<name>POSTed to a webhook you configured in ~/.privateer/config.json.

A routine runs with a safe read and web toolset by default; anything beyond that is an explicit grant, and extra grants are flagged when you approve the routine. A routine can also carry a per-connector allow-list, so an unattended job can hold exactly GitHub's create_issue without holding all of MCP.


Voice

Two independent halves. /speak on reads answers aloud; /talk types what you say into the composer, with alt+t as push-to-talk.

By default speech uses your operating system's own voice — say on macOS, espeak-ng on Linux, System.Speech on Windows — so nothing leaves the machine. Signed in, it upgrades to the account's confidential speech models. Voice input needs a capture tool on PATH: sox, or arecord, parecord or ffmpeg.

Settings persist in ~/.privateer/speak.json. Voice is off by default and interactive-only: the Local Harbor, ACP sessions and chat bridges never speak.

🎙️

Two known gaps on the account voice provider: /speak rate and /talk vocab aren't supported there. Both work on the OS voices.


Editors & the Agent Client Protocol

Privateer speaks ACP, the interface Zed and other editors use to talk to coding agents. The host launches privateer acp and speaks JSON-RPC over stdio: the host owns the transport and the interface, Privateer owns the agent.

The moat comes with it. The host renders approvals; it does not grant them. A cancelled dialog, an unparseable answer, or a host that can't be reached all resolve to deny. Set the ceiling yourself in ~/.privateer/config.json — a host cannot widen it:

{
  "acp": {
    "tools": ["read", "grep", "find", "ls"],
    "posture": "approve"
  }
}

The default is that read-only ceiling. posture is readonly, approve (each risky action asks) or auto. The working directory comes from the host per session, and anything outside it is refused rather than prompted for.

⚠️

Some hosts auto-approve. Buzz, for instance, answers every permission request itself by choosing "allow once" — there is no path to a human and no setting that changes it. Privateer still classifies every action and still refuses anything outside the working directory, but under such a host approve behaves like auto. Your real controls are acp.tools and acp.posture: choose the ceiling assuming everything in it will run unattended.

Setup for Zed, and the full Buzz walkthrough, are in the API reference's integration section.


Drive it from the app

/remote-access on lets the Privateer app on your phone, tablet or another computer drive this terminal. It is off by default and you approve the terminal in the app before anything connects.

From there the app can watch the stream, send prompts, answer approval prompts, switch the model, and manage connectors, skills, routines and extensions on that terminal. What it cannot do is turn no quarter on — that decision stays at the machine.

Multiple terminals on one computer group under that machine, so a phone shows you which box each agent is on.


Updating

privateer update knows how you installed it and does the right thing — re-running the bundle installer, or npm i -g privateer-agent@latest. There's a passive daily check that tells you when a release is out; it never installs anything on its own.

Tool packs update separately, so you can take a connector fix without moving the agent: /update check to see what's available, privateer update --extensions for all of them, privateer update --all for packs and then the CLI.

Uninstalling

One command, and it undoes what the installer did:

curl -fsSL https://privateer.pro/uninstall.sh | sh

It removes the bundle in ~/.privateer/app, the privateer launcher, the PATH line the installer added to your shell profile, and — first, before any of the files it runs on — the Local Harbor login service, so nothing is left running after the files it needs are gone.

Your sign-in stays unless you ask for it. ~/.privateer holds your device login, model list and settings, and the desktop app shares it, so the script keeps it and tells you how to remove it. Pass --all to take it in the same run, or --dry-run to see exactly what would go without changing anything. Remember there is no password recovery: once that sign-in is gone, anything still encrypted with it is gone with it.

Installed through npm instead? npm uninstall -g privateer-agent removes the command; the uninstaller above still handles the login service and ~/.privateer.

The desktop app is separate software and removes itself: Settings → Uninstall Privateer inside the app sweeps its own bundle, its app data, its copy of the privateer shim and the login service, and asks before touching ~/.privateer. On Windows, Add/Remove Programs does the same job. Dragging the app to the Trash leaves its data — and, if you turned one on, its login service — behind.

Verifying the install

An agent with shell access is worth being careful about, so the install is built to be checkable.

privateer verify              # install shape, provenance, dependency drift, patch state
privateer verify --offline    # skip the registry checks
npm audit signatures
npm view privateer-agent dist.attestations

The package declares no install scripts at all — nothing runs at npm install time. The patches Privateer needs over its Pi base are applied at first launch instead, where you can see them. Publishing happens from CI with provenance attestations, never from a laptop.

The CLI is open source under MIT: github.com/privateer-agent/privateer-agent. The Privateer app server is a separate, closed codebase — what we publish for it is a transparency layer you can inspect, not the whole server.


Troubleshooting

Windows: it exits saying it needs a shell

Privateer needs a bash. Install Git for Windows or use WSL. If yours is installed somewhere unusual, set shellPath in ~/.privateer/agent/settings.json.

privateer isn't found after installing

The bundle installers put the shim in ~/.privateer/bin. Make sure that's on your PATH, then open a new terminal. If you installed through npm, check npm bin -g is on the path. The desktop app can install the shim for you from its Agents screen.

Sign-in doesn't open a browser

Set PRIVATEER_NO_BROWSER=1 and Privateer prints the URL and code for you to open elsewhere. Over SSH this is usually what you want.

“Your Privateer session expired”

Run /login again. If it keeps happening, /logout then /login clears the machine's credentials and starts fresh — note that this revokes every terminal on the machine.

A connector's tools don't show up

/mcp first — it shows what connected and what failed. For an http connector needing OAuth, run /mcp-auth <server>. For a stdio one, check the command actually runs in your shell; a version-managed node that isn't on the minimal PATH is the usual culprit.

Something worked yesterday and doesn't today

privateer verify reports dependency drift and patch state, which catches most of these. PRIVATEER_DEBUG=1 for detail.

A scheduled routine never ran

privateer harbor status — a routine only runs while the Local Harbor is up. On Windows there is no login service, so it runs only while a terminal or the desktop app is supervising it.

Still stuck

privateer.pro/support, or open an issue on GitHub.