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