privateer-agent 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/README.md +135 -443
  2. package/bin/privateer-tui +86 -0
  3. package/bin/privateer.mjs +17 -6
  4. package/bin/pv +28 -0
  5. package/package.json +26 -23
  6. package/src/auth/privateer.ts +57 -0
  7. package/src/boot.ts +43 -0
  8. package/src/bridge/engineAdapter.ts +182 -0
  9. package/src/cli/chat.ts +243 -0
  10. package/src/config/paths.ts +24 -46
  11. package/src/config/permissionMode.ts +5 -0
  12. package/src/crypto/outboxSeal.ts +61 -0
  13. package/src/daemon/index.ts +204 -121
  14. package/src/daemon/ipc.ts +1 -1
  15. package/src/engine/errors.ts +85 -45
  16. package/src/engine/router.ts +11 -165
  17. package/src/ext/permissionGate.ts +216 -0
  18. package/src/main.ts +32 -0
  19. package/src/permissions/classify.ts +172 -0
  20. package/src/permissions/gate.ts +11 -14
  21. package/src/permissions/mode.ts +6 -2
  22. package/src/permissions/{uiGate.ts → modeGate.ts} +18 -3
  23. package/src/providers/account.ts +170 -0
  24. package/src/providers/catalog.ts +73 -61
  25. package/src/providers/genModelsJson.ts +97 -0
  26. package/src/remote/relayClient.ts +15 -0
  27. package/src/remote/remoteBridge.ts +152 -0
  28. package/src/routines/delivery.ts +118 -9
  29. package/src/routines/schema.ts +24 -5
  30. package/src/routines/store.ts +44 -1
  31. package/src/routines/toolSelect.ts +3 -1
  32. package/src/session.ts +81 -273
  33. package/src/tools/routine.ts +120 -101
  34. package/src/tools/saveAttachment.ts +39 -42
  35. package/src/tools/sendFile.ts +75 -0
  36. package/src/util/attachmentStore.ts +18 -35
  37. package/src/util/redact.ts +33 -3
  38. package/LICENSE +0 -21
  39. package/src/agents/loader.ts +0 -49
  40. package/src/commands/custom.ts +0 -123
  41. package/src/commands/registry.ts +0 -618
  42. package/src/components/AgentGroupView.tsx +0 -104
  43. package/src/components/App.tsx +0 -1626
  44. package/src/components/ApprovalPrompt.tsx +0 -49
  45. package/src/components/Banner.tsx +0 -78
  46. package/src/components/Markdown.tsx +0 -183
  47. package/src/components/ModeHint.tsx +0 -40
  48. package/src/components/ModelPicker.tsx +0 -302
  49. package/src/components/Onboarding.tsx +0 -203
  50. package/src/components/OptionPicker.tsx +0 -134
  51. package/src/components/PlanConfirm.tsx +0 -37
  52. package/src/components/PrivateerLogin.tsx +0 -109
  53. package/src/components/PromptInput.tsx +0 -602
  54. package/src/components/RewindPicker.tsx +0 -69
  55. package/src/components/Root.tsx +0 -116
  56. package/src/components/SessionPicker.tsx +0 -64
  57. package/src/components/StatusBar.tsx +0 -131
  58. package/src/components/TodoPanel.tsx +0 -36
  59. package/src/components/ToolCallView.tsx +0 -113
  60. package/src/components/Transcript.tsx +0 -210
  61. package/src/components/figures.ts +0 -14
  62. package/src/components/promptModel.ts +0 -73
  63. package/src/components/spinnerVerbs.ts +0 -46
  64. package/src/components/theme.ts +0 -57
  65. package/src/components/types.ts +0 -34
  66. package/src/components/useTeeShield.ts +0 -104
  67. package/src/components/useTerminalWidth.ts +0 -24
  68. package/src/components/useZdrShield.ts +0 -126
  69. package/src/config/load.ts +0 -115
  70. package/src/config/schema.ts +0 -94
  71. package/src/context/outputStyles.ts +0 -42
  72. package/src/context/projectInfo.ts +0 -59
  73. package/src/context/systemPrompt.ts +0 -176
  74. package/src/engine/QueryEngine.ts +0 -399
  75. package/src/hooks/engine.ts +0 -155
  76. package/src/main.tsx +0 -209
  77. package/src/mcp/client.ts +0 -251
  78. package/src/mcp/oauth.ts +0 -245
  79. package/src/memory/auto.ts +0 -146
  80. package/src/memory/checkpoints.ts +0 -227
  81. package/src/memory/store.ts +0 -127
  82. package/src/providers/attestation.ts +0 -149
  83. package/src/providers/capabilities.ts +0 -104
  84. package/src/providers/models.ts +0 -183
  85. package/src/providers/registry.ts +0 -71
  86. package/src/providers/resolve.ts +0 -78
  87. package/src/skills/installer.ts +0 -222
  88. package/src/skills/loader.ts +0 -88
  89. package/src/tools/askUser.ts +0 -92
  90. package/src/tools/bash.ts +0 -98
  91. package/src/tools/context.ts +0 -128
  92. package/src/tools/edit.ts +0 -67
  93. package/src/tools/exec.ts +0 -60
  94. package/src/tools/glob.ts +0 -39
  95. package/src/tools/grep.ts +0 -86
  96. package/src/tools/index.ts +0 -83
  97. package/src/tools/memory.ts +0 -53
  98. package/src/tools/processRegistry.ts +0 -77
  99. package/src/tools/read.ts +0 -42
  100. package/src/tools/sendFileToClient.ts +0 -55
  101. package/src/tools/skill.ts +0 -44
  102. package/src/tools/task.ts +0 -52
  103. package/src/tools/todo.ts +0 -36
  104. package/src/tools/todoStore.ts +0 -31
  105. package/src/tools/walk.ts +0 -44
  106. package/src/tools/web.ts +0 -145
  107. package/src/tools/worktree.ts +0 -145
  108. package/src/tools/write.ts +0 -40
  109. package/src/util/images.ts +0 -378
  110. package/src/util/limit.ts +0 -32
  111. package/src/version.ts +0 -13
package/README.md CHANGED
@@ -5,20 +5,18 @@
5
5
  <h1 align="center">⚓ Privateer</h1>
6
6
 
7
7
  <p align="center">
8
- <strong>A provider-agnostic terminal coding agent — bring your own model.</strong>
8
+ <strong>A provider-agnostic terminal coding agent — bring your own model, keep your own privacy.</strong>
9
9
  </p>
10
10
 
11
11
  <p align="center">
12
- <a href="https://github.com/privateer-agent/privateer-agent/actions/workflows/ci.yml">
13
- <img src="https://github.com/privateer-agent/privateer-agent/actions/workflows/ci.yml/badge.svg" alt="CI" />
14
- </a>
15
12
  <a href="https://www.npmjs.com/package/privateer-agent">
16
13
  <img src="https://img.shields.io/npm/v/privateer-agent" alt="npm" />
17
14
  </a>
18
- <img src="https://img.shields.io/badge/node-%E2%89%A520-brightgreen" alt="Node >= 20" />
15
+ <img src="https://img.shields.io/badge/node-%E2%89%A522.19-brightgreen" alt="Node >= 22.19" />
19
16
  <img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT License" />
20
- <img src="https://img.shields.io/badge/providers-OpenRouter%20·%20Anthropic%20·%20OpenAI%20·%20Ollama%20·%20NEAR%20AI-5b8def" alt="Providers" />
21
- <img src="https://img.shields.io/badge/built%20on-Vercel%20AI%20SDK-black" alt="Vercel AI SDK" />
17
+ <img src="https://img.shields.io/badge/permissions-safe%20by%20default-2ea44f" alt="Safe by default" />
18
+ <img src="https://img.shields.io/badge/inference-TEE%20attested%20·%20on--device%20PII%20gate-5b8def" alt="Private inference" />
19
+ <img src="https://img.shields.io/badge/providers-OpenRouter%20·%20Anthropic%20·%20OpenAI%20·%20Google%20·%20xAI%20·%20Groq%20·%20Mistral%20·%20Z.ai%20·%20DeepSeek%20·%20Qwen%20·%20Ollama%20·%20NEAR%20AI%20·%20Tinfoil%20·%20Venice-5b8def" alt="Providers" />
22
20
  </p>
23
21
 
24
22
  ```bash
@@ -26,75 +24,69 @@ curl -fsSL https://privateer.pro/install.sh | sh # installs the `privateer` c
26
24
  npx privateer-agent # or run it instantly, nothing installed
27
25
  ```
28
26
 
29
- Switch between **OpenRouter**, **Anthropic**, **OpenAI**, local **Ollama**, and **NEAR AI**
30
- (private, attestable inference) with one command. Built on the Vercel AI SDK, so tool-calling
31
- and streaming work identically across every provider no model lock-in, no separate code paths.
32
- MCP servers, Claude Code-compatible skills, scheduled routines, and approval from your phone
33
- included.
34
-
35
- <p align="center">
36
- <img src="docs/screenshot.png" alt="Privateer running in the terminal" width="820" />
37
- </p>
27
+ Point it at a frontier model today and a local Ollama model tomorrow **OpenRouter**,
28
+ **Anthropic**, **OpenAI**, **Google**, **xAI**, **Groq**, **Mistral**, **Z.ai** (GLM),
29
+ **DeepSeek**, **Qwen**, local **Ollama**, **NEAR AI** or **Tinfoil** (verifiable TEE
30
+ inference), **Venice** / **Fireworks** (no-retention inference), and any **custom
31
+ OpenAI-compatible endpoint** (LM Studio, vLLM, llama.cpp…) are interchangeable at
32
+ `/model` time, including mid-session. No model lock-in, no separate code paths. MCP
33
+ servers, sub-agents, scheduled routines, and one-tap approval from your phone are
34
+ included and every one of the agent's actions runs through a **safe-by-default
35
+ permission gate**.
38
36
 
39
37
  ## Why Privateer?
40
38
 
41
- - **No lock-in.** Point it at a frontier model today and a local Ollama model tomorrow —
42
- `/model` swaps mid-session. Your config, commands, and agents come along for the ride.
43
- - **No API key required.** Bring your own key (BYOK) from any supported provider, run
44
- keyless against a local Ollama — or `/login` to bill a Privateer account instead.
45
- - **The agent UX you already know.** Plan mode, checkpoint/rewind, a modal prompt, slash
46
- commands, sub-agents, and project memory but vendor-neutral.
47
- - **Genuinely extensible.** MCP servers, lifecycle hooks, custom commands, output styles,
48
- sub-agents, and skills are all just files under `.privateer/`. No plugins to compile.
49
- - **Zero binary deps.** The file/search/shell tools are pure Node nothing to install
50
- beyond `node`.
39
+ - **No lock-in.** One agent, every provider. `/model` swaps mid-session and your config,
40
+ commands, and agents come along for the ride. No vendor's models are privileged.
41
+ - **No API key required.** Bring your own key from any supported provider, run keyless
42
+ against a local Ollama — or `/signin` to bill a Privateer account instead.
43
+ - **Safe by default.** Every edit, shell command, and network call is classified and gated
44
+ before it runs; destructive commands are blocked even in unattended runs. You stay in
45
+ control, whether you're watching or not.
46
+ - **Privacy you can verify, not just trust.** Confidential-enclave (TEE) inference is
47
+ cryptographically **attested** not a policy promise and an **on-device PII gate**
48
+ warns before structured personal data ever leaves your machine for an unverified model.
49
+ - **Genuinely extensible.** MCP servers, sub-agents, custom commands, and skills — all just
50
+ files. Nothing to compile.
51
51
 
52
52
  ## Highlights
53
53
 
54
- - **MCP servers** (local stdio + remote HTTP/SSE, with interactive OAuth), lifecycle
55
- **hooks**, and **custom sub-agents**
56
- - **Claude Code-compatible skills** published Agent Skills drop in unchanged; install
57
- from GitHub with `/skills install owner/repo`
58
- - **Scheduled routines** a daemon runs approved tasks unattended, cron or one-off
59
- - **Approve it from your phone** link the terminal to the Privateer app with
60
- `/remote-access` (off by default) and Allow/Deny every action remotely
61
- - **Zero-Data-Retention surfacing** for OpenRouter: a status-bar shield colors the selected
62
- model's retention posture, and `/zdr` pins routing to zero-retention endpoints
63
- - **Private, verifiable inference** via NEAR AI: every model runs in a TEE, a `⛉ TEE` status
64
- shield reflects the live attestation, and `/verify` fetches the attestation report (validate
65
- the raw quote chains with the NEAR Cloud Verifier for full cryptographic proof)
66
- - **Plan mode** (read-only present a plan approve), **checkpoint/rewind** of
67
- conversation and files
68
- - A modal prompt with `/` command and `@` file autocomplete, `!` shell passthrough,
69
- `#` memory append, input history, optional **vim** mode, and **ctrl-r** history search
70
- - Layered `settings.json` (user project → local → managed), **custom slash commands**
71
- and **output styles** as markdown files
72
- - Background shells, bounded parallel sub-agents, thinking display, structured compaction,
73
- and image attachment for vision-capable models
54
+ - **Private, verifiable inference** via **NEAR AI** and **Tinfoil**: every model runs inside
55
+ a Trusted Execution Environment, a live status shield reflects the attestation, and
56
+ `/verify` fetches and checks the cryptographic report on demand genuine proof the
57
+ inference ran on real confidential hardware, not a terms-of-service page.
58
+ - **On-device PII gate.** Before a prompt goes to an *unverified* channel, Privateer scans it
59
+ locally for structured personal data (emails, phone numbers, SSNs, cards, IBANs, IPs…) and
60
+ offers to redact or hold it — detection never leaves your machine, and an attested TEE
61
+ channel skips the check because it provably can't read your data anyway.
62
+ - **Honest privacy posture, graded.** A verified TEE and a "we promise not to retain"
63
+ policy are **never rendered the same** the badge tells you exactly how strong the
64
+ guarantee is (cryptographically verified observable policy none).
65
+ - **Approve it from your phone.** Link the terminal to the Privateer app with
66
+ `/remote-access` (off by default) and Allow/Deny every action remotely while execution
67
+ stays on your machine — supervise long agent runs from anywhere.
68
+ - **Scheduled routines.** A background daemon runs approved tasks unattended cron or
69
+ one-off and the agent can schedule its own follow-up work. Results deliver to a file,
70
+ the next session, your phone, email, or a webhook.
71
+ - **MCP servers, sub-agents & skills.** Connect Model Context Protocol servers (local stdio
72
+ or remote HTTP with OAuth), delegate work to bounded parallel sub-agents, and drop in
73
+ skills all gated like everything else.
74
+ - **Zero-Data-Retention surfacing** for OpenRouter — see the selected model's retention
75
+ posture before you send, and pin routing to zero-retention endpoints.
76
+ - **Plan mode**, checkpoint/rewind, session branching, a modal prompt with `/` command and
77
+ `@` file autocomplete, `!` shell passthrough, background shells, and image attachment for
78
+ vision-capable models.
74
79
 
75
80
  ## Quickstart
76
81
 
77
82
  ```bash
78
83
  curl -fsSL https://privateer.pro/install.sh | sh # or: npm install -g privateer-agent
79
- export OPENROUTER_API_KEY=sk-or-... # one provider is enough — or skip and run /login
80
- privateer # launches the interactive TUI
84
+ export OPENROUTER_API_KEY=sk-or-... # one provider is enough — or skip and /signin
85
+ privateer # launches the interactive agent
81
86
  ```
82
87
 
83
88
  First run walks you through picking a provider and default model. From there, just type.
84
- (No install at all: `npx privateer-agent`.)
85
-
86
- ## Contents
87
-
88
- - [Requirements](#requirements) · [Install](#install) · [Configure a provider](#configure-a-provider) · [Model routing](#model-routing) · [Data retention (ZDR)](#data-retention-zdr) · [Private inference (NEAR AI)](#private-inference-near-ai) · [Privateer account](#privateer-account-billed-inference--what-it-sees) · [Usage](#usage)
89
- - [The prompt](#the-prompt) · [Slash commands](#slash-commands) · [Tools](#tools)
90
- - [Customize & extend](#customize--extend) · [Permission modes](#permission-modes) · [Project context](#project-context)
91
- - [How it compares](#how-it-compares) · [Develop](#develop) · [Caveats](#caveats) · [Docs](#docs) · [License](#license)
92
-
93
- ## Requirements
94
-
95
- - macOS or Linux
96
- - Node.js ≥ 20 (pure Node, zero binary dependencies)
97
- - An API key for at least one provider — or a local Ollama install, or a Privateer account (`/login`)
89
+ No install at all: `npx privateer-agent`.
98
90
 
99
91
  ## Install
100
92
 
@@ -102,348 +94,95 @@ First run walks you through picking a provider and default model. From there, ju
102
94
  npm install -g privateer-agent # installs the `privateer` command
103
95
  # or run it without installing:
104
96
  npx privateer-agent
105
- # or the one-liner installer (verifies Node >= 20, then installs globally):
97
+ # or the one-liner installer (verifies Node, then installs globally):
106
98
  curl -fsSL https://privateer.pro/install.sh | sh
107
99
  ```
108
100
 
109
- **From source** (for hacking on Privateer):
101
+ **Requirements:** macOS or Linux, Node.js ≥ 22.19.0.
102
+
103
+ **From source:**
110
104
 
111
105
  ```bash
112
106
  git clone https://github.com/privateer-agent/privateer-agent.git
113
107
  cd privateer-agent
114
- npm install # install dependencies
115
- npm start # launches the interactive TUI (or: node bin/privateer.mjs)
116
- npm link # optional: put your local `privateer` on PATH
108
+ npm install
109
+ npm start # launches the interactive agent
117
110
  ```
118
111
 
119
112
  ## Configure a provider
120
113
 
121
- Privateer reads credentials from environment variables or a config file.
122
-
123
- **Env vars (quickest):**
114
+ Privateer reads credentials from environment variables (or sign in to an account and skip
115
+ keys entirely). One provider is enough to start:
124
116
 
125
117
  ```bash
126
118
  export OPENROUTER_API_KEY=sk-or-... # gateway to ~everything
127
119
  export ANTHROPIC_API_KEY=sk-ant-...
128
120
  export OPENAI_API_KEY=sk-...
129
- export OLLAMA_BASE_URL=http://localhost:11434/api # optional; defaults to this
130
- export NEAR_AI_API_KEY=... # private TEE inference (cloud.near.ai)
121
+ export GEMINI_API_KEY=AIza... # Google
122
+ export XAI_API_KEY=xai-... # xAI (Grok)
123
+ export GROQ_API_KEY=gsk_... # Groq
124
+ export DEEPSEEK_API_KEY=sk-... # DeepSeek
125
+ export OLLAMA_BASE_URL=http://localhost:11434/v1 # local, keyless
126
+ export NEAR_AI_API_KEY=... # verifiable TEE inference (cloud.near.ai)
127
+ export TINFOIL_API_KEY=... # verifiable TEE inference (tinfoil.sh)
128
+ export VENICE_API_KEY=vapi_... # no-retention inference
131
129
  ```
132
130
 
133
- **Config file** `~/.privateer/config.json` (global) and/or `./.privateer/config.json` (per project):
134
-
135
- ```json
136
- {
137
- "defaultModel": "openrouter:anthropic/claude-opus-4.8",
138
- "permissionMode": "default",
139
- "providers": {
140
- "openrouter": { "apiKey": "sk-or-..." },
141
- "anthropic": { "apiKey": "sk-ant-..." }
142
- }
143
- }
144
- ```
131
+ Pick a model with **`/model`** (browse each configured provider's live catalog) or pass one
132
+ directly as `provider/model` — e.g. `openrouter/anthropic/claude-opus-4.8`,
133
+ `ollama/qwen3-coder`, `nearai/zai-org/GLM-5.1-FP8`. Any OpenAI-compatible server (LM Studio,
134
+ vLLM, llama.cpp) works as a custom provider — just give it a base URL.
145
135
 
146
136
  Override the config location with `PRIVATEER_HOME`.
147
137
 
148
- ## Model routing
149
-
150
- `defaultModel` handles most turns, but it's often the wrong tool for a particular one
151
- it may not accept the file you dropped in, or you'd rather spend a cheaper model on a
152
- trivial question. The optional **`router`** block lets Privateer switch models per turn
153
- based on the turn's **data type** and shape:
154
-
155
- ```json
156
- {
157
- "defaultModel": "openrouter:minimax/minimax-m3",
158
- "router": {
159
- "vision": "openrouter:google/gemini-2.5-flash",
160
- "document": "openrouter:anthropic/claude-opus-4.8",
161
- "audio": "openrouter:google/gemini-2.5-flash",
162
- "video": "openrouter:google/gemini-2.5-flash",
163
- "long": "openrouter:anthropic/claude-opus-4.8",
164
- "fast": "openrouter:openai/gpt-4o-mini",
165
- "longThreshold": 60000,
166
- "fastMaxChars": 280,
167
- "inlineTextMaxBytes": 65536,
168
- "auto": true
169
- }
170
- }
171
- ```
172
-
173
- Reference a file in the prompt drag-drop, paste a path, or `@`-mention and Privateer
174
- classifies it by **modality** and routes accordingly:
175
-
176
- | Route | Chosen when the turn (or conversation) includes… |
177
- |---|---|
178
- | **vision** | an image (`.png .jpg .jpeg .gif .webp`) |
179
- | **document** | a PDF (`.pdf`) |
180
- | **audio** | audio (`.mp3 .wav .m4a .ogg .flac`) |
181
- | **video** | video (`.mp4 .mov .webm .mkv`) |
182
- | **long** | the estimated context exceeds `longThreshold` tokens (default: half of `contextBudget`) |
183
- | **fast** | the prompt is `fastMaxChars` characters (and needs no attachment) |
184
- | **default** | everything else (`defaultModel`) |
185
-
186
- Each attached file collapses to a chip `[Image #1]`, `[PDF #2]`, `[Audio #3]`,
187
- `[Video #4]` while the file itself rides along to the model. **Code/CSV/markdown and
188
- other text files aren't routed**: they're read and inlined into the prompt (up to
189
- `inlineTextMaxBytes`; larger ones are left as a path for the agent's read tool).
190
-
191
- **Capability requirements outrank `long`/`fast`.** A turn that needs a modality is
192
- routed to a model that can actually accept it and routing is **sticky**: once a file
193
- is in the conversation, later turns stay on a capable model so the attachment is never
194
- replayed to one that can't read it. A turn that needs **several** modalities at once
195
- (say an image *and* a PDF) is routed to a model whose support covers all of them. When
196
- a turn is routed, the transcript shows a line like `↪ routed to gemini-2.5-flash ·
197
- image input`.
198
-
199
- **Hybrid auto-detect** (`"auto": true`, the default): if you reference, say, a PDF but
200
- haven't set `router.document`, and your `defaultModel` can't read PDFs, Privateer
201
- auto-selects a capable model from a configured provider. Set the route explicitly to
202
- control exactly which model is used, or `"auto": false` to disable it (you'll get a
203
- one-line warning when nothing can handle the modality).
204
-
205
- ## Data retention (ZDR)
206
-
207
- When you route through **OpenRouter**, where your prompts end up depends on which upstream
208
- endpoint serves the request — some retain data, some don't. Privateer surfaces that for the
209
- **selected model** so you can see the posture before you send, and optionally enforce it.
210
-
211
- **The status-bar shield.** A `⛉ ZDR` badge sits in the status line, colored against the
212
- model you have selected:
213
-
214
- | Badge | Meaning |
215
- |---|---|
216
- | 🟢 `⛉ ZDR` | The model has a zero-retention endpoint **and** enforcement is on — the request is pinned to it, so prompts can't be retained. |
217
- | 🟡 `⛉ ZDR` | A zero-retention endpoint exists, but enforcement is off — a request *may* still land on an endpoint that retains prompts. |
218
- | 🔴 `⛉ ZDR` | No zero-retention endpoint for this model, or it's blocked by your account's privacy settings — under enforcement the request is rejected outright. |
219
- | `⛉ ZDR?` (dim) | Posture unknown — no OpenRouter key yet, still loading, or the lookup failed. |
220
-
221
- The badge only appears for OpenRouter models; other providers show nothing. The posture is
222
- derived from two authenticated OpenRouter endpoints — `/endpoints/zdr` (models with at least
223
- one zero-retention endpoint) and `/models/user` (models your account's privacy settings
224
- actually permit) — fetched once per account and re-evaluated synchronously as you switch
225
- models. The same colors annotate every row in the `/model` picker, with a legend explaining
226
- them.
227
-
228
- **Enforcement.** Run **`/zdr`** to toggle enforcement (persisted as
229
- `providers.openrouter.enforceZdr`). With it on, Privateer pins routing to zero-retention
230
- endpoints (`provider.zdr` on every request), so yellow models go green — and any model
231
- *without* a zero-retention endpoint is rejected rather than silently retained. Toggle it off
232
- to let OpenRouter route freely. Enforcement applies to OpenRouter only; add an OpenRouter key
233
- with `/login` first.
234
-
235
- ## Private inference (NEAR AI)
236
-
237
- **NEAR AI Cloud** runs every model inside a **Trusted Execution Environment** — an Intel TDX
238
- confidential VM paired with an NVIDIA confidential-computing GPU. Your prompts are encrypted
239
- all the way into the enclave (TLS terminates *inside* the TEE, not at a load balancer), so
240
- the model's inputs, weights, and outputs are invisible to the infrastructure provider, the
241
- model provider, and NEAR itself. And it's not "trust us": each request can produce a
242
- **cryptographic attestation** attesting that the inference happened on genuine TEE hardware,
243
- signed by a key that never leaves the enclave and bound to a nonce you supply. (Privateer's
244
- `/verify` does a pragmatic check of that report; full validation of the quote chains is done
245
- with the NEAR Cloud Verifier — see `/verify` below.)
246
-
247
- It's a drop-in OpenAI-compatible provider — pick a `nearai:*` model with `/model` (e.g.
248
- `nearai:zai-org/GLM-5.1-FP8`) and everything else works as usual.
249
-
250
- **The status-bar shield.** A `⛉ TEE` badge appears whenever a NEAR AI model is selected,
251
- colored by the live attestation for that model:
252
-
253
- | Badge | Meaning |
254
- |---|---|
255
- | 🟢 `⛉ TEE` | A fresh attestation came back bound to our nonce, with a TEE signing key and NVIDIA + Intel hardware evidence — confidential **and** verifiable. |
256
- | 🟡 `⛉ TEE` | A report returned but couldn't be fully confirmed here (missing signing key, hardware marker, or nonce echo). |
257
- | 🔴 `⛉ TEE` | No attestation material returned. |
258
- | `⛉ TEE?` (dim) | Unknown — no NEAR AI key yet, still loading, or the lookup failed. |
259
-
260
- **`/verify`.** Run it on a NEAR AI model to fetch the attestation on demand and print the
261
- verdict, detected hardware, the enclave's signing address, and the nonce. Privateer does a
262
- pragmatic freshness + presence check suited to a terminal; for full validation of the raw
263
- NVIDIA/Intel quote chains, take the printed report to the
264
- [NEAR AI Cloud Verifier](https://github.com/nearai/cloud-verifier).
265
-
266
- ## Privateer account (billed inference) — what it sees
267
-
268
- Instead of bringing your own provider key, run **`/login`** to sign into a Privateer account
269
- (an app-brokered device flow — you approve a short code in the Privateer app/web, so wallet
270
- and email accounts work identically and no password or wallet key ever touches the terminal).
271
- Inference then runs on Privateer's server and is billed to your subscription.
272
-
273
- What that means for your data, precisely:
274
-
275
- - **The server proxies your prompts; it does not store them.** Like the Privateer apps, the
276
- account path sends your prompt to the server, which forwards it to the model provider and
277
- streams the reply back. The only thing written server-side is **billing metadata** — model
278
- id, token counts, cost — never prompt or response text, and nothing is logged in plaintext.
279
- - **Same privacy guarantee as the apps: ZDR / TEE, not in-transit E2EE.** The server has to
280
- read your prompt to run inference (true of every product, every path). The guarantee isn't
281
- "nobody sees it" — it's "nobody *retains* it": OpenRouter routes are pinned to zero-retention
282
- endpoints, and the account default is a **NEAR TEE** model where even the provider can't read
283
- the prompt. Run **`/verify`** to check the live attestation — a pragmatic freshness + presence
284
- check (signing key + hardware markers + your nonce echoed); for full cryptographic validation
285
- of the raw quote chains, take the printed report to the NEAR AI Cloud Verifier (see above).
286
- - **Your local transcript is plaintext on your machine.** Privateer's end-to-end encryption
287
- protects data **at rest in Privateer's storage** — it does not (and cannot) encrypt the
288
- conversation files this CLI keeps on your own disk under `~/.privateer/`. Treat them like any
289
- local shell history.
290
- - **Your session token is stored unencrypted on disk** at `~/.privateer/credentials.json`,
291
- protected only by file permissions (`0600` — readable just by your user). It's a scoped
292
- session — not your password or wallet/encryption keys — and it's long-lived: anyone who can
293
- read that file (root, a backup, a compromised account) has a usable billed session until it's
294
- revoked. It rotates on refresh with server-side reuse detection; revoke it any time from the
295
- app (**Settings → Linked terminals**, which now lists individual terminals) or with **`/logout`**.
296
- - **`/remote-access` streams this terminal's activity to your phone.** When you turn it on, the
297
- app can drive this terminal: prompts come down, and the agent's replies **and tool input/output**
298
- go up through the Privateer server relay so you can watch and approve actions remotely. Output is
299
- size-truncated and run through a best-effort secret redactor before it leaves, but that's a safety
300
- net, not a guarantee — terminal output can contain whatever a command prints. The relay is
301
- live-only (nothing is archived) and carries no keys; the terminal label sent is a non-PII random
302
- tag (no username/host/path). It's **off** until you run `/remote-access on`.
303
-
304
- > **Only approve a code you generated yourself.** The login code authorizes *this* terminal to
305
- > spend on your account. If someone sends you a code and asks you to approve it ("paste this to
306
- > activate…"), **don't** — approving it hands *them* a billed session on *your* account. A code
307
- > you didn't just create in your own terminal is an attack, not a convenience.
308
-
309
- ## Usage
310
-
311
- ```bash
312
- privateer # interactive TUI with the default model
313
- privateer -m openrouter:anthropic/claude-opus-4.8
314
- privateer -c # resume the last session in this dir
315
- privateer -p "summarize src/" # headless one-shot, prints to stdout
316
- ```
317
-
318
- Run `/model` (no argument) to browse the models each configured provider actually
319
- offers — the list is fetched live using the API key you entered, then filtered as you
320
- type. Onboarding ends on the same picker so you choose your default model up front.
321
-
322
- You can also pass a model string directly as `provider:model`:
323
-
324
- | Example | |
325
- |---|---|
326
- | `openrouter:anthropic/claude-opus-4.8` | any model on OpenRouter |
327
- | `anthropic:claude-opus-4-8` | direct Anthropic |
328
- | `openai:gpt-5.5` | direct OpenAI |
329
- | `ollama:qwen3-coder` | local model |
330
-
331
- ## The prompt
332
-
333
- The input is modal — the first character chooses what happens:
334
-
335
- | Prefix | Mode |
336
- |---|---|
337
- | _(text)_ | a normal prompt to the model |
338
- | `/` | a slash command — opens an autocomplete menu |
339
- | `@` | a file mention — fuzzy-completes paths from the cwd |
340
- | `!` | run a shell command locally and show its output (no model turn) |
341
- | `#` | append the rest of the line to `PRIVATEER.md` |
342
-
343
- Also: **↑/↓** history, **ctrl-r** reverse history search, emacs line editing
344
- (`ctrl-a/e/u/w`), `ctrl-l` to clear the screen, and **`\`+Enter** for a newline. Messages
345
- typed while the agent is busy are queued and run in order. `/vim` toggles modal (vim)
346
- editing. Reference an image file to attach it for vision-capable models — by `@`-mention
347
- (`@screenshot.png`), or by pasting a path anywhere in the prompt. Absolute paths and paths
348
- with spaces work too, quoted (`"/Users/me/My Shot.png"`) or backslash-escaped
349
- (`/Users/me/My\ Shot.png`); a leading `/path/...` is treated as a file, not a command. Each
350
- referenced image collapses to a short `[Image #1]` chip in the transcript (numbered across
351
- the session) while the picture itself rides along to the model.
352
-
353
- While the agent is working, press **Esc** to interrupt the turn (partial output is kept);
354
- **Ctrl-C** quits.
355
-
356
- ## Slash commands
357
-
358
- Built-ins (plus any custom commands you add):
359
-
360
- | Command | |
361
- |---|---|
362
- | `/help` `/doctor` `/config` | help, diagnostics, resolved settings layers |
363
- | `/model [spec]` `/provider` `/keys` | choose a model, list providers, manage API keys |
364
- | `/login` `/logout` | sign a Privateer account in/out (see [Privateer account](#privateer-account-billed-inference--what-it-sees)) |
365
- | `/remote-access [on\|off\|status]` | link this terminal to the Privateer app for phone approval (off by default) |
366
- | `/permissions [mode]` `/cost` `/context` | permission mode, token usage, context window |
367
- | `/init` `/memory` `/todo` | write/show `PRIVATEER.md`; show the task list |
368
- | `/agents` `/mcp [logout]` `/hooks` | inspect sub-agents; MCP status / clear OAuth; hooks |
369
- | `/skills [list\|info\|install\|remove]` | manage skills (see [Customize & extend](#customize--extend)) |
370
- | `/routine [list\|pause\|resume\|rm\|run]` | manage scheduled routines |
371
- | `/output-style [name]` `/vim` `/verbose` | persona, modal editing, full tool output |
372
- | `/zdr` | toggle OpenRouter zero-data-retention enforcement (see [Data retention](#data-retention-zdr)) |
373
- | `/verify` | fetch the NEAR AI TEE attestation for the current model (see [Private inference](#private-inference-near-ai)) |
374
- | `/rewind` `/compact` `/clear` `/export` | restore a checkpoint, compact, clear, save transcript |
375
- | `/resume` `/sessions` | pick up an earlier session in this directory |
376
- | `/exit` | quit |
377
-
378
- - `/model` — open a picker of each provider's live models (or `/model provider:id` to set one directly).
379
- - `/init` — the agent explores the repo and writes a `PRIVATEER.md` for you
380
- (`/init --stub` just drops an empty template, no model call).
381
- - `/rewind` — pick an earlier checkpoint and restore the conversation, the files, or both.
382
- - `/compact` — summarize older history to reclaim context (also happens automatically).
383
-
384
- ## Tools
385
-
386
- `read` · `write` · `edit` · `glob` · `grep` · `bash` · `bash_output` · `kill_shell` ·
387
- `todo` · `task` · `web_fetch` · `web_search` — plus any tools exposed by connected MCP servers.
388
-
389
- The file/search/shell tools are pure-Node (no external binaries required). Mutating tools
390
- (write/edit/bash) and network tools (web_fetch/web_search, MCP) go through the permission gate.
391
- `todo` maintains the live task list; `task` delegates an investigation to a sub-agent that
392
- returns a summary. `bash` can run detached with `run_in_background`; `bash_output` polls a
393
- background shell's new output and `kill_shell` stops it.
394
-
395
- ## Customize & extend
396
-
397
- Everything below is optional and lives under `.privateer/` (project) or `~/.privateer/`
398
- (user); project files win. Settings merge across `config.json` → `settings.json` →
399
- `settings.local.json` (run `/config` to see the resolved chain).
400
-
401
- - **Custom commands** — `.privateer/commands/<name>.md`. The body is a prompt template
402
- (`$ARGUMENTS`, `$1`…`$9`); optional frontmatter sets `description`/`argument-hint`. They
403
- appear in `/help` and `/` autocomplete; subfolders namespace as `dir:name`.
404
- - **Output styles** — `.privateer/output-styles/<name>.md` swap the agent's persona.
405
- Switch with `/output-style <name>` (or `default`).
406
- - **Sub-agents** — `.privateer/agents/<name>.md` with frontmatter (`description`, `tools`,
407
- `model`). Invoke via the `task` tool's `subagent_type`; `/agents` lists them.
408
- - **Skills** — `.privateer/skills/<name>/SKILL.md` (frontmatter `name`/`description` +
409
- instruction body, plus any bundled `scripts/`/`references/` files). The format is
410
- Claude Code-compatible, so published Agent Skills drop in unchanged. The agent sees a
411
- catalog of names and descriptions and loads a skill's full instructions on demand via
412
- the `skill` tool; `/skill-name` invokes one explicitly. Manage with `/skills`
413
- (`list`, `info <name>`, `install <owner/repo[/path]> [--project] [--all] [--force]`,
414
- `remove <name>`) — install fetches from GitHub with a shallow clone and never executes
415
- anything it downloads.
416
- - **Hooks** — a `hooks` section in `settings.json` runs shell commands on `PreToolUse`,
417
- `PostToolUse`, `UserPromptSubmit`, and `Stop`. A hook blocks by exiting `2` or printing
418
- `{"decision":"block"}`; `UserPromptSubmit` can inject `additionalContext`. `/hooks` lists them.
419
- - **MCP servers** — declare them in `.privateer/mcp.json` (`{ "mcpServers": { … } }`).
420
- Both **local stdio** servers (`{ "command", "args", "env" }`) and **remote HTTP** servers
421
- (`{ "url", "headers?", "transport?" }`) are supported; remote defaults to Streamable HTTP
422
- with a fallback to legacy SSE. Their tools are namespaced `server__tool` and gated like the
423
- rest. Remote servers authenticate by a static `headers` bearer token, or — when none is set —
424
- via **interactive OAuth** (PKCE + dynamic client registration): on a `401` Privateer opens your
425
- browser, catches the redirect on a loopback port, and caches the tokens (owner-only) under
426
- `~/.privateer/mcp-auth/`. `/mcp` shows each server's connection and auth state;
427
- `/mcp logout [server]` clears saved OAuth.
428
-
429
- ```json
430
- {
431
- "mcpServers": {
432
- "fs": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "."] },
433
- "github": { "url": "https://api.githubcopilot.com/mcp/" },
434
- "internal": { "url": "https://mcp.example.com/mcp", "headers": { "Authorization": "Bearer $TOKEN" } }
435
- }
436
- }
437
- ```
438
- - **Routines** — saved tasks the scheduler daemon (`privateer daemon --detach`) runs
439
- unattended, recurring (cron) or one-off. Ask the agent ("summarize world news every
440
- morning") and approve; manage with `/routine` (list/pause/resume/rm/run). Runs use a safe
441
- read/web toolset by default; a routine's `tools` list can also grant specific MCP tools
442
- (`server__tool` or `server__*`) — flagged at approval, since they then run with no one
443
- watching. See the [Sheet → WhatsApp recipe](docs/recipes/sheet-to-whatsapp.md) for a
444
- full business automation built this way.
445
- - **Status line** — set `statusLine` to a shell command; it receives session JSON on stdin
446
- and its stdout becomes the status line.
138
+ ## Private & verifiable inference
139
+
140
+ **NEAR AI Cloud** and **Tinfoil** run every model inside a **Trusted Execution Environment**
141
+ a confidential VM where TLS terminates *inside* the enclave, so your prompt's inputs, weights,
142
+ and outputs are invisible to the infrastructure provider, the model provider, and the host
143
+ itself. It isn't "trust us": each request can produce a **cryptographic attestation** proving
144
+ the inference ran on genuine TEE hardware.
145
+
146
+ - A **status shield** colors the selected model's live posture — 🟢 verified, 🟡 returned but
147
+ unconfirmed, 🔴 no attestation material.
148
+ - **`/verify`** fetches the attestation on demand and prints the evidence. Privateer does a
149
+ pragmatic terminal-suited check; take the printed report to the
150
+ [NEAR AI Cloud Verifier](https://github.com/nearai/cloud-verifier) or the
151
+ [Tinfoil verifier](https://github.com/tinfoilsh/tinfoil-cli) for full quote-chain validation.
152
+ - **The posture is graded honestly.** A verified enclave (`cryptographic`), a pinned
153
+ zero-retention route (`observable`), and a provider's retention *promise* (`policy`) are
154
+ labeled distinctly — a claim never gets to read like a proof.
155
+
156
+ ## The PII gate
157
+
158
+ Before any prompt is sent to a channel that *isn't* verified-private, Privateer scans it
159
+ **locally** for structured personal data — emails, phone numbers, SSNs, credit-card numbers
160
+ (Luhn-checked), IBANs (mod-97), IP and MAC addresses. If it finds any, it warns and offers to
161
+ **redact** or **send as-is** (or remember your choice for the session). Detection is
162
+ deterministic and on-device — no model ever sees the data in order to find it — and it's
163
+ skipped entirely on an attested TEE or on-device channel, which provably can't read your
164
+ prompt anyway. It's best-effort structured-PII detection, labeled as such — a safety net, not
165
+ a guarantee.
166
+
167
+ ## Privateer account (billed inference)
168
+
169
+ Instead of bringing your own key, run **`/signin`** to sign into a Privateer account — an
170
+ app-brokered device flow where you approve a short code in the Privateer app, so wallet and
171
+ email accounts work identically and no password or key ever touches the terminal. Inference
172
+ is then billed to your subscription and defaults to a **NEAR TEE** model. Sign out any time
173
+ with `/signout`; manage linked terminals from the app.
174
+
175
+ > **Only approve a sign-in code you generated yourself.** The code authorizes *this* terminal
176
+ > to spend on your account. If someone sends you a code and asks you to approve it, don't —
177
+ > that hands *them* a billed session on *your* account.
178
+
179
+ ## Approve from your phone
180
+
181
+ Turn on **`/remote-access`** (off by default) to link this terminal to the Privateer app. The
182
+ app can then drive the terminal prompts come down, and each proposed action goes up for
183
+ **Allow/Deny** while execution stays on your machine. The relay is live-only (nothing is
184
+ archived), carries no keys, and output is size-truncated and run through a best-effort secret
185
+ redactor before it leaves.
447
186
 
448
187
  ## Permission modes
449
188
 
@@ -451,40 +190,22 @@ Everything below is optional and lives under `.privateer/` (project) or `~/.priv
451
190
  |---|---|
452
191
  | `default` | prompt before edits and shell commands |
453
192
  | `acceptEdits` | auto-approve file edits; still prompt for shell commands |
454
- | `bypass` | no prompts (also `--dangerously-skip-permissions` or `--no-quarter`) |
455
- | `plan` | read-only; the agent presents a plan, then you approve to leave plan mode |
456
-
457
- Out of the box the mode is **`acceptEdits`** (naming is a nod to convention: the mode
458
- called `default` prompts on everything, but isn't the shipped default). Edits are still
459
- checkpointed, so `/rewind` undoes them; prefer prompting on every edit? Run
460
- `/permissions default` once — it persists. At an approval prompt: **y** allow once ·
461
- **a** always · **n** deny. In plan mode, after the agent presents its plan: **a** approve
462
- and exit plan mode · **k** keep planning.
193
+ | `bypass` | no prompts (destructive commands are *still* blocked) |
194
+ | `plan` | read-only; the agent presents a plan, then you approve to proceed |
463
195
 
464
- ## Project context
196
+ Switch with **`/mode`**. Even in `bypass`, a danger filter blocks destructive shell commands,
197
+ and protected files (`.env`, shell rc files…) are guarded — the gate is never fully off.
465
198
 
466
- Create a `PRIVATEER.md` in your repo (via `/init`) to give the agent standing
467
- context — conventions, architecture notes, anything it should always know.
199
+ ## Extend it
468
200
 
469
- ## How it compares
470
-
471
- There are excellent terminal coding agents already. What this one does differently:
472
-
473
- - **Provider-agnostic by construction, not adaptation.** One agent loop over the Vercel AI
474
- SDK; OpenRouter, Anthropic, OpenAI, local Ollama, and NEAR AI are interchangeable at
475
- `/model` time, including mid-session. No vendor's models are privileged.
476
- - **Retention posture is a UI element.** ZDR status is visible before you send and
477
- enforceable per request (`/zdr`); TEE inference is attestable (`/verify`). Most tools
478
- leave this to the provider's terms-of-service page.
479
- - **Phone approval.** `/remote-access` relays each proposed action to the Privateer app
480
- for Allow/Deny while execution stays on your machine — useful for long agent runs you
481
- want to supervise from anywhere.
482
- - **Interop over ecosystem.** Skills use the Agent Skills format, so Claude Code skills
483
- drop in unchanged; MCP covers tools. The goal is to reuse what exists, not grow a
484
- parallel plugin world.
485
-
486
- And, honestly, what it doesn't have: the maturity of the incumbents. It's a young
487
- codebase — see [Caveats](#caveats) for the sharp edges we know about.
201
+ - **MCP servers** — declare them and their tools become first-class, gated like the rest
202
+ (local stdio, or remote HTTP with interactive OAuth).
203
+ - **Sub-agents** delegate investigations to bounded parallel agents that run under the same
204
+ permission gate.
205
+ - **Routines** saved tasks the daemon runs unattended; ask the agent to schedule work and
206
+ approve it once.
207
+ - **Web tools** — private-by-default web search/fetch with pluggable backends (self-hosted
208
+ SearXNG for fully private search).
488
209
 
489
210
  ## Develop
490
211
 
@@ -493,35 +214,6 @@ npm run typecheck
493
214
  npm test
494
215
  ```
495
216
 
496
- ## Caveats
497
-
498
- Privateer's agent core is built provider-agnostic from the ground up; a few areas are
499
- deliberately simplified for now:
500
-
501
- - **Prompt caching and extended thinking are Anthropic-only.** Ephemeral cache breakpoints
502
- and the `thinkingBudget` setting apply to direct Anthropic models and OpenRouter routes to
503
- `anthropic/*`. Other providers ignore them (a harmless no-op).
504
- - **Checkpoints are in-memory.** `/rewind` is a within-session undo; snapshots live in memory
505
- and aren't persisted across restarts.
506
- - **Remote MCP OAuth uses a fixed loopback port** (`7777` by default; override with
507
- `PRIVATEER_OAUTH_PORT`) so the redirect URI stays stable across runs. Set `PRIVATEER_NO_BROWSER=1`
508
- in headless environments to skip the auto-launch and use the printed URL.
509
- - **Image attachment assumes vision support.** Referenced images are sent as content parts;
510
- non-vision models will return an error.
511
- - **Compaction estimates context size** (~4 chars/token). The summary itself is
512
- schema-guided (goals / decisions / files / open threads), with a plain-text fallback, and
513
- the most recent messages are always kept verbatim.
514
- - **`web_search` scrapes DuckDuckGo's keyless HTML endpoint.** It needs no API key but is
515
- best-effort and can break if their markup changes. `web_fetch` is robust for known URLs.
516
- - **Protected files** (`.env`, `.npmrc`, shell rc files, …) always prompt before edit — except
517
- in `bypass` mode, which by definition skips all prompts.
518
-
519
- ## Docs
520
-
521
- - [Architecture](docs/ARCHITECTURE.md) — how the provider layer, agent loop, tools, and permissions fit together
522
- - [Recipe: Sheet → WhatsApp](docs/recipes/sheet-to-whatsapp.md) — an unattended routine + MCP servers messaging new spreadsheet rows
523
- - [Brand assets](brand/README.md) — the logo and icon set
524
-
525
217
  ## License
526
218
 
527
219
  [MIT](LICENSE) © Patrick