surfcode 0.1.0 → 0.1.2

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.
package/README.md CHANGED
@@ -1,326 +1,802 @@
1
- <div align="center">
1
+ # SurfCode
2
2
 
3
- <img src="assets/surfcode-logo.gif" alt="SurfCode" width="480">
3
+ **Ride the code.**
4
4
 
5
- ### Ride the code.
5
+ <p align="center">
6
+ <img src="assets/surfcode-logo.gif" alt="Animated blue SurfCode wordmark" width="480">
7
+ </p>
6
8
 
7
- **A beautiful, provider-agnostic AI coding agent for your terminal.**
8
- One keyboard-driven TUI. Any model. Your keys, your machine, no telemetry.
9
+ SurfCode is a provider-agnostic AI coding agent for the terminal. It pairs a full-screen, keyboard-driven TUI with a complete model catalog, streaming tool use, sessions, permissions, and a clean headless mode for scripts and CI.
9
10
 
10
- <br>
11
+ Full command and configuration reference: [docs/REFERENCE.md](docs/REFERENCE.md). Contributing: [CONTRIBUTING.md](CONTRIBUTING.md).
11
12
 
12
- [![License: MIT](https://img.shields.io/badge/license-MIT-2ea043?style=for-the-badge)](LICENSE)
13
- [![Node](https://img.shields.io/badge/node-%E2%89%A522-3c873a?style=for-the-badge&logo=node.js&logoColor=white)](https://nodejs.org)
14
- [![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6?style=for-the-badge&logo=typescript&logoColor=white)](tsconfig.json)
15
- [![PRs welcome](https://img.shields.io/badge/PRs-welcome-ff69b4?style=for-the-badge)](CONTRIBUTING.md)
16
-
17
- [**Install**](#-install) · [**Quick start**](#-quick-start) · [**Features**](#-features) · [**Providers**](#-any-model-any-provider) · [**Headless**](#-headless--ci) · [**Full docs**](docs/REFERENCE.md)
18
-
19
- </div>
13
+ ```text
14
+ ▀▀▀▓▄▄ ▄▄▄ ▄▄ ▀▀▀▓▄▄ ▀▀▀▓▄▄ ▀▀▀▓▄▄ ▀▀▀▓▄▄ ▀▀▀▓▄▄ ▀▀▀▓▄▄
15
+ ▄██▄░▀██▓▄ ▄███ ▐██▄ ▄██▄░▀██▓▄ ▄██▄░▀██▓▄ ▄██▄░▀██▓▄ ░░▀██▓▄ ▄██▄░▀██▓▄ ▄██▄░▀██▓▄
16
+ ▀ ▀░░░░░▀██▓ ▐███▌ ███▌▒▀ ▀░░░░░███▓▒▀ ▀░░░░░███▓▒▀ ▀░░░░░███▓ ▄▄█▀ ░▒███▓▄ ▒▀ ▀░░░░░███▓▄ ▒▀ ▀░░░░░███▓
17
+ ░░▐▒▒▄▄▄ █░░▌ ░░░█ ░░▐▒▒▄░ ░░▌ ░░▐▒▒ ░ ▀▀ ░░▐▒▒ ░ ▀░▌ █░░ ░░░░░▌ ░░▐▒▒ ░░░░░░▌ ░░▐▒▒ ░ ▀▀
18
+ ░ ▀▀░░▒▒ ▒▒█ ░▐▒▒▒▒ ░▐▓▓▐▒▌▒▀ ░▐▓▓ ▄▓▀ ░▐▓▓ ▐▒▒▌ ░▐▒▒▒▒ ░▐▓▓ ░▐▒▒▒▒ ░▐▓▓ ▄▓▀
19
+ ▄▓ ▀▓▓█ ▐▓▓▄ ░▒▓▓▓▓▌ █▓▀ ▀▓▓▄ ▀▓█ ▄ ▀▓▀ ▄█▓▄ ▓▓▓▄ ░▒▓▓▓▓▌ ▄ ▀▓▀ ▓▓▓▓▌ ▄ ▀▓▀ ▄▄▄
20
+ ▀▓█▓▄▄ ▓███ ▀███▄▓███▀ ▀███▄ ▓███ ▐█ ▀█▄▀█▄▄▓▓▒▀ ▀███▄▄▄▓███▀ ▀█▄▄▄▄▄▓███▀ ▀█▄▄▄▄▄▓███
21
+ ▀▀ ▀▀▀▀ ▀▀▀▀▀ ▀▀ ▀▀▀▀ ▀▌ ▀▀▀▀▀ ▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀
22
+ ```
20
23
 
21
- <br>
24
+ - **230+ providers, 9,000+ models** from the [models.dev](https://models.dev) catalog, with cached refresh and a bundled offline snapshot. Providers with a permanent no-cost API tier are marked `[FREE API]`. The model picker only lists models from APIs you have connected.
25
+ - **Real tool use** — read, write, edit, search, shell, fetch, todos — with inline diffs and a permission model that ranges from read-only to full computer access.
26
+ - **Fast by default** — every dialect streams over SSE for reliable tool use, but visual token painting is off unless you ask for it. Replies land complete, the TUI repaints only what changed, and `/stream on` restores live typing when you want it.
27
+ - **Reasoning effort on tap** — `/effort low|medium|high|auto` or `--effort`, mapped to native reasoning controls on OpenAI, Anthropic, Google, and OpenRouter. Gemini 3 uses `thinkingLevel`; Gemini 2.5 uses thinking budgets. Low effort on Flash-Lite really means minimal thinking. `auto` keeps the provider default.
28
+ - **Multi-model orchestration** — `/agent add provider/model` keeps the current model as the head and runs up to eight worker models in parallel through a read-only `delegate` tool. Workers never touch your files; the head verifies and synthesizes.
29
+ - **Side questions without the baggage** — `/btw <question>` answers with the current conversation as context, has no tools, and leaves no trace in the session or usage totals.
30
+ - **Accurate usage and cost** — provider-reported input, output, reasoning, cache-read, and cache-write tokens, including DeepSeek and Google cache semantics.
31
+ - **Beautiful full-screen TUI** built for long sessions: alternate screen, smooth scrolling, markdown, syntax highlighting, six themes including a pure-black `dark` palette, an always-visible resizable file dock, a single-pane options overlay, a simplified prompt, Unicode icons, a command palette, and a tiny rainbow equalizer that loops in the status bar.
32
+ - **GitHub publishing when you ask** — log in with a personal access token, then let the agent create a private or public repository, upload the whole project or selected files, and commit or push later. Nothing is published until you explicitly ask.
33
+ - **Headless when you need it** — clean stdout for pipes, NDJSON events for automation, explicit approvals, and deterministic exit codes.
34
+ - **Local-first storage** — keys, sessions, and config stay on your machine. No telemetry.
35
+ - **Optional BYOK-friendly session sync** — run the tiny self-hosted sync server or point SurfCode at your own HTTPS endpoint. Only session records are uploaded; provider API keys never leave the machine.
22
36
 
23
37
  ---
24
38
 
25
- ## ✨ Why SurfCode?
26
-
27
- Most coding agents lock you to one vendor, one model, and one pricing page. SurfCode doesn't.
39
+ ## Requirements
28
40
 
29
- | | |
30
- | :-- | :-- |
31
- | 🌊 **Provider-agnostic** | OpenAI, Anthropic, Google, OpenRouter, Groq, DeepSeek, Ollama… **232 providers and 9,000+ models** from the [models.dev](https://models.dev) catalog, with an offline snapshot bundled. |
32
- | 🛡️ **You stay in control** | Reads are free, writes and shell commands ask first. A read-only **plan mode** lets the agent look but never touch. |
33
- | 🔒 **Local-first** | Keys, sessions and config never leave your machine. **No telemetry. No background uploads.** |
34
- | ⚡ **Fast and calm** | Alternate-screen TUI that only repaints what changed. Replies land complete; turn on live typing with `/stream on`. |
35
- | 🤖 **Scriptable** | `surf run` gives clean stdout, NDJSON events and deterministic exit codes, so it drops straight into pipes and CI. |
41
+ - Node.js **22 or newer**
42
+ - A modern terminal (xterm, iTerm2, Ghostty, Kitty, WezTerm, Windows Terminal, …)
43
+ - An API key for at least one provider, or a local OpenAI-compatible server
36
44
 
37
- <br>
38
-
39
- ## 📦 Install
40
-
41
- > Requires **Node.js 22+** and a modern terminal (iTerm2, Ghostty, Kitty, WezTerm, Windows Terminal, …).
42
-
43
- **From npm**
45
+ ## Install
44
46
 
45
47
  ```bash
46
48
  npm install -g surfcode
47
49
  surf --version
48
50
  ```
49
51
 
50
- **Straight from GitHub**
51
-
52
- ```bash
53
- npm install -g github:Official-SurfCode/surfcode
54
- ```
52
+ The package installs two equivalent commands: **`surf`** and **`surfcode`**.
55
53
 
56
- **From source**
54
+ From source:
57
55
 
58
56
  ```bash
59
57
  git clone https://github.com/Official-SurfCode/surfcode.git
60
58
  cd surfcode
61
- npm install # also builds dist/
62
- npm link # puts `surf` and `surfcode` on your PATH
59
+ npm install
60
+ npm run build
61
+ npm link
63
62
  ```
64
63
 
65
- Both commands, **`surf`** and **`surfcode`**, do the same thing.
66
-
67
- <br>
68
-
69
- ## 🚀 Quick start
64
+ ## Quick start
70
65
 
71
66
  ```bash
72
67
  surf
73
68
  ```
74
69
 
75
- The first launch opens the full-screen UI. Then:
70
+ The first launch opens the interactive UI. Press `/` for commands, or connect a provider:
76
71
 
77
72
  ```text
78
- /connect pick a provider and paste a key (ctrl+v)
79
- /model browse models from the providers you've connected
80
- /help every command and shortcut
73
+ /connect pick a provider and paste a key with ctrl+v
74
+ /model browse models from your connected providers
75
+ /help commands, shortcuts, and tips
81
76
  ```
82
77
 
83
- Prefer the shell?
78
+ Prefer the shell? Authenticate directly:
84
79
 
85
80
  ```bash
86
- surf auth anthropic # hidden key prompt
87
- surf models --provider openai # browse the catalog
88
- surf run "explain src/core/agent.ts" # one-shot, no UI
81
+ surf auth anthropic # hidden key prompt, saved to auth.json
82
+ surf models --provider openai
83
+ surf run "explain src/core/agent.ts"
89
84
  ```
90
85
 
91
- Already have `ANTHROPIC_API_KEY`, `OPENAI_API_KEY` or `GEMINI_API_KEY` in your environment? SurfCode finds it and picks a sensible default model on its own.
92
-
93
- <br>
86
+ If a provider key already lives in your environment (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, …), SurfCode picks it up automatically. The next start auto-selects a sensible provider and recommended model.
94
87
 
95
- ## 🧰 Features
88
+ ---
96
89
 
97
- <table>
98
- <tr>
99
- <td width="50%" valign="top">
90
+ ## The TUI
100
91
 
101
- ### 🖥️ A TUI worth living in
102
- - Alternate screen, smooth scrolling, markdown and syntax highlighting
103
- - **Six themes**: `aurora` · `dark` · `ember` · `forest` · `violet` · `mono`
104
- - A resizable **file explorer dock** with live previews
105
- - `@file` attachments and `!command` shell passthrough
106
- - Command palette, input history, tab completion
92
+ SurfCode renders in the terminal's alternate screen, the same way vim and htop do. Your shell scrollback is restored when you exit.
107
93
 
108
- </td>
109
- <td width="50%" valign="top">
94
+ The welcome wordmark uses a native ANSI reveal and shimmer, so it animates instantly with no video runtime. The animated README banner is a blue motion lockup generated deterministically with SVG + ImageMagick: `npm run logo:render`. An optional Manim path is available for higher-resolution renders with the same layout and motion. A tiny rainbow equalizer sits in the bottom-right corner of the status bar in every chat and keeps looping while idle, even in the `mono` theme.
110
95
 
111
- ### 🔧 Real tool use
112
- - `read` · `write` · `edit` · `ls` · `glob` · `grep` · `bash` · `webfetch` · `todo_write`
113
- - Inline diffs for every edit
114
- - Non-interactive shell with timeouts and capped output
115
- - Project instructions from `SURFCODE.md`, `AGENTS.md` or `CLAUDE.md`
96
+ ### Commands
116
97
 
117
- </td>
118
- </tr>
119
- <tr>
120
- <td width="50%" valign="top">
98
+ | Command | What it does |
99
+ | --- | --- |
100
+ | `/model [query]` | Switch to a model from a connected provider |
101
+ | `/connect` | Connect a provider and save its API key |
102
+ | `/provider` | Browse every provider |
103
+ | `/theme [name]` | Change the theme (`aurora`, `dark`, `ember`, `forest`, `violet`, `mono`) |
104
+ | `/explorer [width]` | Focus the file dock (also `ctrl+o`); optionally set its width |
105
+ | `/options` | Open settings and toggles (alias: `/settings`) |
106
+ | `/effort [level]` | Set reasoning effort (`low`, `medium`, `high`, `auto`) |
107
+ | `/stream [on\|off]` | Toggle live token painting (off by default) |
108
+ | `/access [full\|safe]` | Toggle full computer access |
109
+ | `/agent [status\|on\|off\|add\|remove\|clear\|head]` | Run worker models in parallel under the head model |
110
+ | `/thinking` | Show or hide reasoning text |
111
+ | `/verbose` | Expand or collapse tool output |
112
+ | `/plan` / `/build` | Toggle read-only plan mode |
113
+ | `/clear` | Clear the visible conversation |
114
+ | `/new` | Start a fresh session |
115
+ | `/sessions` | Resume a previous session |
116
+ | `/sync [push\|pull]` | Sync sessions with your self-hosted endpoint |
117
+ | `/stop` | Stop the AI reply in flight |
118
+ | `/compact` | Summarize history to free context |
119
+ | `/btw <question>` | Ask a side question without saving it |
120
+ | `/github [status\|login\|logout]` | Connect GitHub and publish code when asked |
121
+ | `/context` | Show context-window usage |
122
+ | `/cost` | Show session tokens and cost |
123
+ | `/export [path]` | Export the conversation as Markdown |
124
+ | `/init` | Ask the agent to create a `SURFCODE.md` project guide |
125
+ | `/help` | Commands and keyboard shortcuts |
126
+ | `/quit` | Save and exit |
127
+
128
+ `/effort` stores the level in your config so it survives restarts. `auto` leaves the provider's own behavior untouched. Effort maps to `reasoning_effort` on OpenAI-compatible endpoints that support it, OpenRouter's `reasoning.effort`, Anthropic extended-thinking budgets, and Google's native controls: Gemini 3 and newer use `thinkingLevel` (`minimal` for Flash-Lite on low), while Gemini 2.5 uses `thinkingBudget` and can disable thinking entirely on Flash and Flash-Lite. Other providers also get matching instructions in the system prompt. Thought summaries are only requested when reasoning is visible (`/thinking`).
129
+
130
+ `/btw` opens an ephemeral panel. It sends the committed conversation as context, runs with no tools, and never adds the question or answer to the session, timeline, or usage. It is safe to use while the main agent is working, and closing the panel cancels an in-flight side answer.
131
+
132
+ ### Live rendering
133
+
134
+ SSE transport is always on so tool calls, usage, and retries stay reliable. **Visual** streaming is a separate setting and is off by default: the reply appears as one finished block instead of repainting every token. Turn it on with `/stream on` (or `"streaming": true`), and off again with `/stream off`. While a turn is active the TUI uses a slow animation tick; `/stream on` raises the repaint rate only for the live message.
135
+
136
+ Provider errors fail fast. Quota and rate-limit (`429`) responses are not retried behind your back, and long provider paragraphs are shortened to one line in the timeline, so a missing quota is visible immediately instead of after a minute of silence.
137
+
138
+ ### File explorer
139
+
140
+ The VS Code-like two-pane file dock is always visible in the chat view on terminals at least 57 columns wide. The tree loads lazily — only folders you expand are read — and skips build/vendor directories such as `.git`, `node_modules`, `dist`, and framework caches. The lower pane previews the highlighted file automatically with line numbers, detects binary files, and caps very large files.
141
+
142
+ Resize the dock with `ctrl+←` / `ctrl+→` from anywhere, or `[` / `]` while the dock has focus. The width is saved as `explorerWidth` in config. `ctrl+o` or `/explorer` moves keyboard focus into the dock; `esc` returns to the prompt. `/explorer 40` sets an explicit width.
121
143
 
122
- ### 🧠 Reasoning on tap
123
- - `/effort low|medium|high|auto`, mapped to each provider's native controls
124
- - Gemini 3 `thinkingLevel`, Gemini 2.5 budgets, Anthropic extended thinking, OpenAI `reasoning_effort`
125
- - `/thinking` shows or hides reasoning text
144
+ | Key | Action |
145
+ | --- | --- |
146
+ | `ctrl+o` | Focus or leave the file dock |
147
+ | `↑` / `↓` | Move through entries |
148
+ | `→` or `enter` | Open or collapse a folder |
149
+ | `←` | Collapse the folder, or jump to its parent |
150
+ | `enter` on a file | Attach `@path` to the prompt |
151
+ | `a` | Attach `@path` to the prompt |
152
+ | `r` | Refresh the tree |
153
+ | `[` / `]` | Resize the dock while focused |
154
+ | `esc` | Return to the prompt |
126
155
 
127
- </td>
128
- <td width="50%" valign="top">
156
+ ### Options
129
157
 
130
- ### 🎛️ Multi-model orchestration
131
- - `/agent add provider/model` keeps your model as the **head**
132
- - Up to **8 worker models** run in parallel via a read-only `delegate` tool
133
- - Workers never touch your files; the head verifies and synthesizes
158
+ `/options` (or `/settings`) opens one settings list: theme, model, provider, mode, reasoning effort, live streaming, explorer width, thinking text, tool output, full computer access, agent mode, and GitHub. Use `↑`/`↓` to move, `←`/`→` or `enter` to change a value, and `g` to connect GitHub. Changes save immediately and the overlay stays open; model, provider, and theme rows open their pickers.
134
159
 
135
- </td>
136
- </tr>
137
- <tr>
138
- <td width="50%" valign="top">
160
+ The `dark` theme fills the entire frame with true black (`#000000`) and keeps each element on its own vivid accent: blue for chrome, teal for your prompt, violet for SurfCode, pink for code, green for success, amber for warnings, and rose for errors.
139
161
 
140
- ### 💬 Side questions, no baggage
141
- - `/btw <question>` answers with the conversation as context
142
- - No tools, and it leaves nothing in the session or usage totals
162
+ ### Full computer access
143
163
 
144
- </td>
145
- <td width="50%" valign="top">
164
+ `/access full` is an explicit opt-in for unattended or local-machine work. It:
146
165
 
147
- ### 📊 Honest usage and cost
148
- - Provider-reported input, output, reasoning and cache tokens
149
- - `/cost` and `/context` show where your budget is going
150
- - `/compact` summarizes history to free context
166
+ - changes every permission action to `allow`
167
+ - allows file tools to read and write outside the workspace
168
+ - keeps plan mode read-only
151
169
 
152
- </td>
153
- </tr>
154
- <tr>
155
- <td width="50%" valign="top">
170
+ `/access safe` restores your normal permission policy. The status bar shows a red `full access` badge while it is active. The same setting is available as `"fullAccess": true` and applies to headless runs. In headless mode, `--yes` on its own only upgrades `ask` actions and still honors explicit `deny` rules; full access is a separate, stronger opt-in.
156
171
 
157
- ### 🐙 GitHub publishing, on request
158
- - Log in with a token, then ask in plain language
159
- - Create a repo, upload a project, commit and push later
160
- - Never force-pushes, never rewrites history, never deletes a repo
161
- - Nothing is published unless you ask
172
+ ### Multi-model agent mode
162
173
 
163
- </td>
164
- <td width="50%" valign="top">
174
+ The current model is the **head**. Add one or more workers with a `provider/model` reference:
165
175
 
166
- ### ☁️ Optional BYOK session sync
167
- - Run the tiny self-hosted sync server, or bring your own HTTPS endpoint
168
- - **Only session records sync.** API keys never leave your machine
176
+ ```text
177
+ /agent add google/gemini-3.5-flash-lite
178
+ /agent add anthropic/claude-haiku-4-5
179
+ /agent status
180
+ ```
169
181
 
170
- </td>
171
- </tr>
172
- </table>
182
+ Adding the first worker turns agent mode on. The head receives a read-only `delegate` tool and can send up to eight independent tasks at once; all selected workers start in parallel, and their answers and usage return to the head for synthesis. Workers have no tools and cannot edit files, so all real tool work stays with the head. Use `/agent remove <n|provider/model>`, `/agent clear`, or `/agent off` to change the lineup, and `/agent head provider/model` (or `/model`) to choose the head. Workers are stored in `agents.workers` in config. If a worker's key is missing, it is skipped with a warning instead of blocking the turn.
173
183
 
174
- <br>
184
+ ### GitHub publishing
175
185
 
176
- ## 🌊 Any model, any provider
186
+ Connect once, then ask the agent in plain language. In the TUI:
177
187
 
178
188
  ```text
179
- OpenAI · Anthropic · Google · Azure OpenAI · Vertex · OpenRouter · DeepSeek
180
- xAI · Mistral · Groq · Cerebras · Together · Fireworks · DeepInfra · Cohere
181
- Perplexity · Moonshot · Zhipu · GitHub Copilot · Cloudflare Workers AI …and 200+ more
189
+ /github login
190
+ /github status
191
+ /github logout
182
192
  ```
183
193
 
184
- - Providers with a permanent free API tier are tagged **`[FREE API]`** in the picker.
185
- - **Local models just work.** Ollama, llama.cpp, vLLM and LM Studio are auto-detected when running.
186
- - Bring anything OpenAI-compatible with a custom `baseURL`.
187
- - `surf update-models` refreshes the catalog; the bundled snapshot is the offline fallback.
194
+ `/github login` opens a masked prompt; paste with `ctrl+v` and press enter. The token is validated against GitHub before it is saved under the `github` key in `auth.json`. `GITHUB_TOKEN` or `GH_TOKEN` also work without saving anything. `surf auth github` provides the same flow from the shell, with `--key`, `--env`, `--remove`, and `--json` options.
188
195
 
189
- <br>
196
+ Create a token at [github.com/settings/tokens](https://github.com/settings/tokens). A classic token needs the `repo` scope. For a fine-grained token, grant **Contents** read/write and **Administration** read/write so the agent can create repositories and push code; organization repositories also need the organization's approval. A Copilot-only token may not have publishing scopes.
190
197
 
191
- ## ⌨️ Commands you'll use daily
198
+ The agent has four publishing tools:
192
199
 
193
- | Command | What it does |
194
- | :-- | :-- |
195
- | `/model` · `/connect` · `/provider` | Switch model, add a key, browse providers |
196
- | `/effort` · `/thinking` · `/stream` | Reasoning depth, visible thoughts, live typing |
197
- | `/plan` · `/build` | Toggle read-only plan mode (also `shift+tab`) |
198
- | `/agent` | Run worker models in parallel |
199
- | `/btw <q>` | Ask a side question without saving it |
200
- | `/explorer` · `/theme` · `/options` | File dock, themes, settings |
201
- | `/sessions` · `/new` · `/compact` · `/export` | Resume, restart, shrink, save as Markdown |
202
- | `/github` | Connect GitHub and publish code |
203
- | `/cost` · `/context` | Tokens, spend and context usage |
204
-
205
- <details>
206
- <summary><b>Keyboard shortcuts</b></summary>
207
-
208
- <br>
200
+ | Tool | Action |
201
+ | --- | --- |
202
+ | `github_status` | Check the token source, branch, origin, dirty state, and commit count |
203
+ | `github_create_repo` | Create a private or public repository and point `origin` at it (an existing origin is left alone unless you ask to replace it) |
204
+ | `github_upload` | Stage, commit, and push the whole workspace or selected paths |
205
+ | `github_commit` | Commit and push later changes |
206
+
207
+ `github_create_repo` requires an explicit `private: true` or `private: false`; if you did not say which, the agent asks first. `github_upload` with no file list stages every workspace change except common build/vendor directories (`node_modules`, `dist`, `build`, caches, virtualenvs, and similar); pass files or directories for a selective upload. These are publish actions, so the agent uses them only when you explicitly ask. They never force-push, rewrite history, or delete a repository.
208
+
209
+ The token is never included in the model's tool output, config, sessions, remote URLs, or command-line arguments. Git authenticates through a temporary `GIT_ASKPASS` helper, and errors are redacted before they reach the transcript.
210
+
211
+ ### Keys
209
212
 
210
213
  | Key | Action |
211
- | :-- | :-- |
214
+ | --- | --- |
212
215
  | `enter` | Send prompt |
213
- | `ctrl+j` | New line |
214
- | `esc` | Stop generating / leave dock / close overlay |
215
- | `ctrl+c` | Interrupt (press twice to exit) |
216
+ | `ctrl+j` | Insert newline |
217
+ | `esc` | Stop generating / leave explorer / close overlay |
218
+ | `/stop` | Stop the current reply and keep the partial text |
219
+ | `ctrl+c` | Interrupt; press twice to exit |
220
+ | `pgup` / `pgdn` | Scroll the transcript |
221
+ | `ctrl+u` / `ctrl+d` | Half-page scroll |
216
222
  | `tab` | Complete a command or file |
217
- | `shift+tab` | Toggle plan / build mode |
223
+ | `up` / `down` | Input history |
224
+ | `shift+tab` | Toggle plan/build mode |
218
225
  | `ctrl+t` | Cycle themes |
219
- | `ctrl+o` | Focus the file dock |
220
- | `ctrl+←` / `ctrl+→` | Resize the dock |
221
- | `ctrl+v` | Paste from the clipboard |
222
- | `pgup` / `pgdn` | Scroll the transcript |
223
- | `?` | Help (when the prompt is empty) |
226
+ | `ctrl+o` | Focus or leave the file explorer dock |
227
+ | `ctrl+←` / `ctrl+→` | Resize the file explorer dock |
228
+ | `[` / `]` | Resize the dock while it is focused |
229
+ | `ctrl+v` | Paste from the system clipboard |
230
+ | `?` | Help when the input is empty |
231
+
232
+ ### Input tricks
233
+
234
+ - `@path/to/file` attaches a file to the prompt with completion.
235
+ - `!command` runs a shell command and adds the output to the session.
236
+ - `ctrl+v` pastes clipboard text, code blocks, or API keys. It works in the main prompt, the API-key overlay, and the GitHub token prompt, so `/connect` and `/github login` never require exposing a secret in shell history.
237
+ - Paste multiline text directly; `ctrl+j` inserts a newline.
238
+ - `SURFCODE.md`, `AGENTS.md`, `CLAUDE.md`, and `.surfcode/instructions.md` in the workspace become project instructions automatically.
224
239
 
225
- </details>
240
+ ### Plan mode
226
241
 
227
- <br>
242
+ Plan mode is read-only. The agent can inspect files, search, and fetch, but write/shell tools are blocked. Use it to scope a change before switching to build mode with `shift+tab` or `/build`.
228
243
 
229
- ## 🛡️ Safe by default
244
+ ### Permissions
230
245
 
231
- Every tool action is classified, and you decide what is allowed.
246
+ Each tool action is classified as `read`, `write`, `shell`, or `fetch`:
232
247
 
233
- | Action | Default |
234
- | :-- | :-- |
235
- | `read` · `fetch` | ✅ allow |
236
- | `write` · `shell` | ❓ ask first |
237
- | Outside the workspace | 🚫 off |
238
- | **Plan mode** | 🔒 read-only, nothing can mutate |
248
+ - `read`/`fetch` default to **allow**
249
+ - `write`/`shell` default to **ask**
250
+ - Choose **yes**, **always** (allow for the rest of the run), or **no** when prompted. In the TUI, **always** also saves the action to config.
251
+ - Deny an action entirely in config with `"permissions": { "shell": "deny" }`
239
252
 
240
- - Choose **yes**, **always** or **no** at each prompt. Deny an action for good with `"permissions": { "shell": "deny" }`.
241
- - Destructive shell commands are flagged as high-risk in the prompt.
242
- - `/access full` is an explicit opt-in for trusted machines. A red badge shows while it's on.
243
- - `surf config` redacts keys, tokens and auth headers.
253
+ Plan mode is a second layer of protection on top of permissions. `/access full` bypasses all approval prompts and allows paths outside the workspace; plan mode still blocks mutations while it is active. Use `/access safe` to restore the policy above.
254
+
255
+ ---
244
256
 
245
- <br>
257
+ ## Providers
246
258
 
247
- ## 🤖 Headless & CI
259
+ SurfCode ships with a compact snapshot of models.dev. `surf update-models` (or `surf models --refresh`) downloads the latest catalog and caches it locally; the bundled snapshot remains the offline fallback. `surf doctor` warns when the cache is more than 24 hours old.
260
+
261
+ ### Built-in provider support
262
+
263
+ The catalog covers OpenAI, Anthropic, Google, Azure OpenAI, Google Vertex, Amazon Bedrock gateways, OpenRouter, DeepSeek, xAI, Mistral, Groq, Cerebras, Together, Fireworks, DeepInfra, Vercel AI Gateway, GitHub Copilot, Moonshot, Zhipu, Alibaba, MiniMax, Cohere, Perplexity, Ollama Cloud, and many more.
264
+
265
+ HTTP dialects are selected automatically:
266
+
267
+ | Dialect | Used by |
268
+ | --- | --- |
269
+ | `openai` | OpenAI-compatible APIs (most providers) |
270
+ | `anthropic` | Anthropic and Anthropic-compatible gateways |
271
+ | `google` | Google Generative Language API |
272
+ | `azure` | Azure OpenAI deployments |
273
+ | `google-vertex` | Vertex AI Gemini / Anthropic |
274
+
275
+ `surf providers` shows what is connected. `surf providers --all` shows the full catalog.
276
+
277
+ Providers with a permanent, rate-limited no-cost API tier are marked `[FREE API]` in `surf providers`, `surf auth`, and the interactive provider picker: Google, Groq, Mistral, OpenRouter, and Cloudflare Workers AI. The badge means the provider offers free inference, not that every model is free; model availability and limits change. Trial credits and local runtimes do not get the badge.
278
+
279
+ ### Local servers
280
+
281
+ Ollama, llama.cpp, vLLM, and LM Studio are supported out of the box and discovered automatically when running:
282
+
283
+ ```bash
284
+ ollama serve
285
+ surf
286
+ ```
287
+
288
+ Local providers are keyless by default. `surf providers --connected` shows servers that responded.
289
+
290
+ ### API keys
291
+
292
+ ```bash
293
+ surf auth # status for configured + popular providers
294
+ surf auth anthropic # hidden prompt
295
+ surf auth openai --key sk-... # non-interactive (mind your shell history)
296
+ surf auth google --env # show supported environment variables
297
+ surf auth openrouter --remove # delete the saved key
298
+ surf auth github # hidden prompt for a publisher token
299
+ surf auth github --env # show GITHUB_TOKEN / GH_TOKEN status
300
+ surf auth github --remove # forget the saved GitHub token
301
+ ```
302
+
303
+ Keys are stored in `auth.json` with mode `600`. They are never written into sessions or printed in full. Environment variables are read first-class and are never copied to disk. `surf config` redacts API keys and headers. In the TUI, press `ctrl+v` while the key prompt is open to paste from the system clipboard; the key stays masked and never enters shell history.
304
+
305
+ GitHub publishing uses a separate `github` entry in `auth.json`, with `GITHUB_TOKEN` and `GH_TOKEN` as fallbacks. It is never used as a model provider key. See [GitHub publishing](#github-publishing) for scopes and the approval flow.
306
+
307
+ ### Session sync (BYOK)
308
+
309
+ Session sync is optional, self-hostable, and deliberately **session-only**. The payload contains `SessionRecord` objects (messages, model choice, cwd, usage). Provider API keys, `auth.json`, provider settings, and environment variables are never uploaded.
310
+
311
+ Run the built-in tiny sync server, or point SurfCode at an HTTPS endpoint that implements the same protocol:
312
+
313
+ ```bash
314
+ # Option A: self-hosted store on this machine
315
+ surf sync serve --port 8787
316
+ # prints a generated token; pass --token to use your own
317
+
318
+ # Configure the client
319
+ surf sync setup --url http://127.0.0.1:8787 --token <token> --auto
320
+ surf sync status
321
+ surf sync # push and pull
322
+ surf sync push
323
+ surf sync pull
324
+ surf sync disable
325
+ ```
326
+
327
+ The server stores sessions under `~/.local/share/surfcode/sync-server` by default; pass `--data-dir` to change it. It speaks plain HTTP, so keep it on localhost or put it behind a TLS reverse proxy before exposing it. Remote endpoints must use HTTPS; HTTP is accepted only for localhost. The sync bearer token is stored in config and redacted by `surf config`.
328
+
329
+ Conflicts use last-write-wins by each session's `updatedAt`, with a monotonic server cursor for pulls. `--auto` pushes and pulls after each completed turn; without it, use `/sync` in the TUI or `surf sync`.
330
+
331
+ The HTTP contract is intentionally small and easy to reimplement:
332
+
333
+ ```http
334
+ POST /v1/sync
335
+ Authorization: Bearer <token>
336
+ Content-Type: application/json
337
+
338
+ { "since": 12, "pull": true, "sessions": [/* SessionRecord[] */] }
339
+ ```
340
+
341
+ The response is `{ "accepted": 1, "cursor": 13, "sessions": [/* changed SessionRecord[] */] }`. `GET /v1/health` reports `{ "ok": true }`.
342
+
343
+ Set both `SURFCODE_SYNC_URL` and `SURFCODE_SYNC_TOKEN` to inject the endpoint and token and opt in automatically, without writing either value to config. Cursor updates only write `sync.cursor` and `sync.lastSyncAt`.
344
+
345
+ ### Custom OpenAI-compatible endpoints
346
+
347
+ Any gateway that speaks the OpenAI chat-completions API can be added as a custom provider:
348
+
349
+ ```jsonc
350
+ {
351
+ "customProviders": {
352
+ "my-gateway": {
353
+ "name": "My Gateway",
354
+ "baseURL": "https://gateway.example.com/v1",
355
+ "dialect": "openai",
356
+ "env": ["MY_GATEWAY_API_KEY"],
357
+ "models": [
358
+ {
359
+ "id": "my-coder",
360
+ "name": "My Coder",
361
+ "context": 128000,
362
+ "output": 16384,
363
+ "tool_call": true,
364
+ "reasoning": true
365
+ }
366
+ ]
367
+ }
368
+ }
369
+ }
370
+ ```
371
+
372
+ Then:
373
+
374
+ ```bash
375
+ surf auth my-gateway --key ...
376
+ surf models --provider my-gateway
377
+ surf -p my-gateway -m my-coder
378
+ ```
379
+
380
+ Useful provider settings:
381
+
382
+ | Setting | Purpose |
383
+ | --- | --- |
384
+ | `baseURL` | API base URL (required for custom providers) |
385
+ | `dialect` | `openai`, `anthropic`, `google`, `azure`, `google-vertex` |
386
+ | `apiKey` | Key in config (prefer `surf auth` or env vars) |
387
+ | `headers` | Extra HTTP headers |
388
+ | `extraBody` | Extra JSON merged into each request body |
389
+ | `timeout` | Request timeout in milliseconds |
390
+ | `retries` | Extra attempts after the first request (default `2`) |
391
+ | `retryDelay` | Base exponential backoff in milliseconds (default `600`) |
392
+ | `keyless` | No API key required |
393
+ | `deployment` | Azure deployment name |
394
+ | `project` / `location` | Vertex AI project and region |
395
+ | `apiVersion` | Azure API version |
396
+
397
+ You can also override a built-in provider's endpoint under `providers.<id>` without defining a new provider.
398
+
399
+ ---
400
+
401
+ ## Headless mode
402
+
403
+ `surf run` streams the answer to **stdout** and tool progress to **stderr**, so pipes stay clean.
248
404
 
249
405
  ```bash
250
406
  surf run "summarize this repository"
407
+ surf run -m openai/gpt-5 "refactor the parser"
251
408
  cat error.log | surf run "explain this failure"
252
- surf run --plan "map the auth flow before changing it"
253
409
  surf run --yes "run the tests and fix failures"
410
+ surf run --plan "map the auth flow before changing it"
411
+ surf run --json --yes "run tests and fix failures"
412
+ ```
413
+
414
+ - Pass a prompt, use `-` to force stdin, or pipe text with no prompt.
415
+ - `--yes` auto-approves actions that would otherwise ask. Explicit `deny` rules still apply.
416
+ - `"fullAccess": true` (or `/access full` in the TUI) allows every action, including paths outside the workspace, and is honored by headless runs. Use it deliberately.
417
+ - If `agents.enabled` is true with workers configured, headless runs resolve the same workers and give the head model the parallel `delegate` tool.
418
+ - Without a TTY, permission prompts are denied rather than guessed. Use `--yes` for unattended runs.
419
+ - `--no-save` avoids writing a session; otherwise the run is saved and can be resumed.
420
+ - `--quiet` suppresses tool progress and the final summary.
421
+ - `--reasoning` streams reasoning to stderr (or as JSON events).
422
+ - `--effort low|medium|high|auto` overrides the saved effort for one run; `auto` keeps the provider default.
423
+ - When sync is enabled with `auto`, completed headless runs push their saved session. Disable auto-sync if CI should have no network side effects.
424
+
425
+ ### Sessions
426
+
427
+ ```bash
428
+ surf run --continue "keep going" # most recent session in this directory
429
+ surf run --session ses_abc123 "next" # a specific session id or prefix
430
+ ```
431
+
432
+ ### JSON events
433
+
434
+ `--json` emits one JSON object per line (NDJSON). Text deltas are streamed, and the final `result` event contains the complete answer and usage.
435
+
436
+ | Event | Fields |
437
+ | --- | --- |
438
+ | `session` | `sessionId`, `provider`, `model`, `resumed` |
439
+ | `notice` | `message` |
440
+ | `text` | `delta` |
441
+ | `reasoning` | `delta` (only with `--reasoning`) |
442
+ | `tool_start` | `id`, `name`, `arguments` |
443
+ | `tool_result` | `id`, `name`, `durationMs`, `isError`, `output` |
444
+ | `usage` | cumulative `usage` |
445
+ | `result` | `text`, `usage`, `aborted`, `sessionId`, `durationMs` |
446
+ | `error` | `message`, optional `hint` |
447
+
448
+ Example:
449
+
450
+ ```bash
254
451
  surf run --json --yes "list the files" | jq -c 'select(.type == "result")'
255
452
  ```
256
453
 
257
- Answers stream to **stdout**, tool progress to **stderr**, so pipes stay clean. With `--json` you get one NDJSON event per line. Exit codes are `0` success, `1` error, `130` interrupted.
454
+ ### Exit codes
455
+
456
+ | Code | Meaning |
457
+ | --- | --- |
458
+ | `0` | Success |
459
+ | `1` | Error (bad config, provider failure, no prompt, …) |
460
+ | `130` | Interrupted (ctrl+c / aborted run) |
258
461
 
259
- <br>
462
+ ---
463
+
464
+ ## CLI reference
465
+
466
+ ```text
467
+ surf [prompt...] open the TUI, optionally with a prompt
468
+ surf run [prompt...] one-shot headless run
469
+ surf auth [provider|github] connect or inspect providers and GitHub
470
+ surf models [query...] browse the model catalog
471
+ surf providers [query...] list provider status
472
+ surf config inspect or edit configuration
473
+ surf sessions list, show, resume, or delete sessions
474
+ surf sync [action] sync sessions or run the self-hosted store
475
+ surf doctor diagnose the installation
476
+ surf update-models refresh the models.dev catalog
477
+ ```
260
478
 
261
- ## 🩺 Handy CLI
479
+ Global flags:
262
480
 
263
481
  ```text
264
- surf open the TUI
265
- surf run [prompt] one-shot headless run
266
- surf auth [provider] connect a provider or GitHub
267
- surf models [query] browse the model catalog
268
- surf providers provider status (--all for the full catalog)
269
- surf sessions list and manage saved sessions
270
- surf config inspect or edit configuration
271
- surf doctor diagnose problems (--network to test connectivity)
272
- surf update-models refresh the models.dev catalog
482
+ --cwd <dir> working directory for tools and sessions
483
+ --home <dir> override config/data location (also SURFCODE_HOME)
484
+ --no-color disable ANSI color
485
+ -p, --provider provider id
486
+ -m, --model model id or provider/model
487
+ --plan read-only plan mode
488
+ --effort <level> reasoning effort: auto | low | medium | high
489
+ --theme <name> aurora | dark | ember | forest | violet | mono
490
+ -c, --continue resume the latest session in this directory
491
+ -s, --session resume a saved session
492
+ -P, --print force headless output
493
+ -y, --yes auto-approve tool actions
494
+ --json NDJSON events (headless)
495
+ --quiet suppress progress (headless)
496
+ --no-save do not persist a headless session
273
497
  ```
274
498
 
275
- <br>
499
+ ### `surf models`
276
500
 
277
- ## 🏗️ Project layout
501
+ ```bash
502
+ surf models # top recommended chat models
503
+ surf models claude # search names, ids, providers
504
+ surf models --provider anthropic
505
+ surf models --all --limit 100 # include embeddings, audio, image models
506
+ surf models --refresh # pull models.dev first
507
+ surf models --json
508
+ ```
509
+
510
+ ### `surf providers`
511
+
512
+ ```bash
513
+ surf providers # connected + popular defaults
514
+ surf providers --all # every provider
515
+ surf providers --connected # only ready providers
516
+ surf providers ollama # search
517
+ surf providers --json # includes freeApi on every provider
518
+ ```
519
+
520
+ `--json` exposes `freeApi: true` for providers with a permanent no-cost tier.
521
+
522
+ ### `surf config`
523
+
524
+ ```bash
525
+ surf config # effective config, secrets redacted
526
+ surf config path # config file path
527
+ surf config get theme
528
+ surf config get permissions.shell
529
+ surf config set theme ember
530
+ surf config set permissions.shell allow
531
+ surf config set providers.anthropic.baseURL https://proxy.example.com
532
+ surf config edit # $VISUAL / $EDITOR
533
+ surf config validate
534
+ ```
535
+
536
+ Values are parsed as JSON when possible, so `true`, `0.7`, `["a"]`, and `{...}` work. Comments in the JSONC file are preserved.
537
+
538
+ ### `surf sessions`
539
+
540
+ ```bash
541
+ surf sessions # current workspace
542
+ surf sessions --all
543
+ surf sessions show ses_abc123 # transcript
544
+ surf sessions show ses_abc123 --full
545
+ surf sessions resume ses_abc123 # open the TUI in that session
546
+ surf sessions rm ses_abc123 # confirms first
547
+ surf sessions rm ses_abc123 --yes
548
+ ```
549
+
550
+ ### `surf sync`
551
+
552
+ ```bash
553
+ surf sync setup --url https://sync.example.com --token <token> --auto
554
+ surf sync # push and pull
555
+ surf sync push
556
+ surf sync pull
557
+ surf sync status
558
+ surf sync enable
559
+ surf sync disable
560
+ surf sync serve --port 8787 # self-hosted store
561
+ surf sync serve --host 0.0.0.0 --data-dir /srv/surfcode # behind a TLS reverse proxy
562
+ ```
563
+
564
+ `setup` enables sync; `--auto` additionally syncs after every completed turn. Use an HTTPS URL for remote hosts. The built-in server binds to `127.0.0.1` by default and should only be exposed remotely through a TLS reverse proxy.
565
+
566
+ ### `surf doctor`
567
+
568
+ ```bash
569
+ surf doctor
570
+ surf doctor --network
571
+ surf doctor --json
572
+ ```
573
+
574
+ Checks Node version, config parsing, directory permissions, credential-file mode, catalog freshness, selected model resolution, workspace writability, and optionally models.dev connectivity.
575
+
576
+ ### `surf update-models`
577
+
578
+ ```bash
579
+ surf update-models
580
+ surf update-models --url https://internal.example.com/models.json
581
+ ```
582
+
583
+ ---
584
+
585
+ ## Configuration
586
+
587
+ Config lives at the platform config directory (use `surf config path`):
588
+
589
+ | Platform | Default location |
590
+ | --- | --- |
591
+ | Linux | `~/.config/surfcode/config.json` |
592
+ | macOS | `~/Library/Preferences/surfcode/config.json` |
593
+ | Windows | `%APPDATA%\surfcode\Config\config.json` |
594
+
595
+ Run `surf config path` for the exact path on your machine. Sessions live separately in the platform data directory (Linux `~/.local/share/surfcode/sessions`, macOS `~/Library/Application Support/surfcode/sessions`, Windows `%LOCALAPPDATA%\surfcode\Data\sessions`); set `SURFCODE_HOME` to relocate both config and data.
596
+
597
+ ```jsonc
598
+ {
599
+ "version": 1,
600
+
601
+ "provider": "anthropic",
602
+ "model": "claude-sonnet-4-5",
603
+ "theme": "aurora",
604
+ "mode": "build",
605
+
606
+ "permissions": {
607
+ "read": "allow",
608
+ "write": "ask",
609
+ "shell": "ask",
610
+ "fetch": "allow"
611
+ },
612
+
613
+ "maxSteps": 40,
614
+ "maxTokens": 8192,
615
+ "temperature": 0.2,
616
+ "effort": "auto",
617
+ "showReasoning": false,
618
+ "verboseTools": false,
619
+ "streaming": false,
620
+ "explorerWidth": 34,
621
+ "fullAccess": false,
622
+ "agents": {
623
+ "enabled": false,
624
+ "workers": []
625
+ },
626
+ "allowOutsideWorkspace": false,
627
+
628
+ "sync": {
629
+ "enabled": false,
630
+ "auto": false,
631
+ "url": "https://sync.example.com",
632
+ "token": "<sync bearer token>",
633
+ "cursor": 0
634
+ },
635
+
636
+ "providers": {
637
+ "openai": {
638
+ "baseURL": "https://api.openai.com/v1"
639
+ }
640
+ },
641
+
642
+ "customProviders": {
643
+ "my-gateway": {
644
+ "name": "My Gateway",
645
+ "baseURL": "https://gateway.example.com/v1",
646
+ "dialect": "openai",
647
+ "env": ["MY_GATEWAY_API_KEY"],
648
+ "models": [
649
+ { "id": "my-coder", "name": "My Coder", "context": 128000, "output": 16384 }
650
+ ]
651
+ }
652
+ }
653
+ }
654
+ ```
655
+
656
+ `streaming` only controls visual token painting in the TUI; provider traffic always streams. `explorerWidth` is the preferred dock width in columns (clamped so the chat keeps room); resize it live with `ctrl+←/→` or `[`/`]`. `fullAccess` bypasses approvals and the workspace boundary, while `allowOutsideWorkspace` allows outside paths but still asks for `write`/`shell` according to `permissions`. `agents.workers` is a list of `{ "provider": "...", "model": "..." }` objects used by `/agent` and headless runs.
657
+
658
+ Environment overrides:
659
+
660
+ | Variable | Effect |
661
+ | --- | --- |
662
+ | `SURFCODE_PROVIDER` | Override provider |
663
+ | `SURFCODE_MODEL` | Override model |
664
+ | `SURFCODE_THEME` | Override theme |
665
+ | `SURFCODE_HOME` | Config + data directory |
666
+ | `SURFCODE_SYNC_URL` | Session sync endpoint |
667
+ | `SURFCODE_SYNC_TOKEN` | Session sync bearer token |
668
+ | `SURFCODE_MODELS_URL` | Alternate models.dev-compatible catalog URL |
669
+ | `SURFCODE_DEBUG` | Print stack traces on unexpected errors |
670
+
671
+ ### Provider credentials in the environment
672
+
673
+ SurfCode uses each provider's documented environment variables from models.dev, including:
278
674
 
279
675
  ```text
280
- src/
281
- cli.tsx command-line entry
282
- cli/ auth, config, sessions, inspect, headless, interactive
283
- core/ catalog, providers, chat dialects, agent loop, sessions
284
- tools/ read, write, edit, grep, bash, webfetch, GitHub…
285
- tui/ Ink UI, rendering, themes, overlays, file explorer
286
- sync/ self-hosted session sync server
287
- data/ bundled models.dev snapshot
288
- test/ unit and CLI integration tests (mock SSE servers, no keys needed)
289
- scripts/ catalog updates, logo rendering, asset copying
290
- docs/ full reference
676
+ ANTHROPIC_API_KEY OPENAI_API_KEY GOOGLE_API_KEY
677
+ OPENROUTER_API_KEY DEEPSEEK_API_KEY XAI_API_KEY
678
+ MISTRAL_API_KEY GROQ_API_KEY CEREBRAS_API_KEY
679
+ TOGETHER_API_KEY FIREWORKS_API_KEY DEEPINFRA_API_KEY
680
+ AZURE_API_KEY GITHUB_TOKEN OLLAMA_API_KEY
291
681
  ```
292
682
 
293
- <br>
683
+ Run `surf auth <provider> --env` to see the exact variables for any provider.
684
+
685
+ ---
686
+
687
+ ## Tools
688
+
689
+ | Tool | Action | Notes |
690
+ | --- | --- | --- |
691
+ | `read` | read | Numbered lines, binary detection, output limits |
692
+ | `write` | write | Creates directories, full-file writes |
693
+ | `edit` | write | Exact string replacement with diff output |
694
+ | `ls` | read | Directory listing |
695
+ | `glob` | read | Fast file matching |
696
+ | `grep` | read | Content search with context |
697
+ | `bash` | shell | Non-interactive shell, timeouts, output caps |
698
+ | `webfetch` | fetch | Fetch URLs and convert HTML to text |
699
+ | `github_status` | read | Token source, branch, origin remote, and dirty state |
700
+ | `github_create_repo` | shell | Create a private or public repository; asks first |
701
+ | `github_upload` | shell | Commit and push the workspace or selected files |
702
+ | `github_commit` | shell | Commit and push later changes |
703
+ | `todo_write` | read | Maintain the visible task list |
704
+
705
+ Shell commands run in the workspace in a non-interactive environment and are killed on timeout or abort. Output is truncated deterministically so it cannot blow up the context window.
294
706
 
295
- ## 🤝 Contributing
707
+ ---
296
708
 
297
- Contributions are welcome. The short version:
709
+ ## Development
298
710
 
299
711
  ```bash
300
712
  npm install
301
- npm run dev # run from source
713
+ npm run dev # tsx src/cli.tsx
302
714
  npm run typecheck
303
- npm test # no API keys required
715
+ npm test
716
+ npm run build # dist/cli.js + bundled catalog
717
+ npm run models:update # regenerate src/data/models.json
718
+ npm run logo:render # deterministic SVG GIF for the README (~50KB)
719
+ npm run logo:render -- manim # optional: render the Manim scene instead
304
720
  ```
305
721
 
306
- See [CONTRIBUTING.md](CONTRIBUTING.md) for how to add a tool or a new provider dialect.
722
+ Project layout:
307
723
 
308
- <br>
724
+ ```text
725
+ src/
726
+ cli.tsx command-line entry and command wiring
727
+ cli/ auth, config, sessions, inspect, headless, interactive
728
+ core/ catalog, providers, chat dialects, agent loop, sessions, sync client
729
+ sync/ self-hosted session sync server
730
+ tools/ tool implementations
731
+ tui/ full-screen Ink UI, rendering, themes, overlays, icons
732
+ data/models.json compact models.dev snapshot
733
+ test/ unit + CLI integration tests with a mock SSE server
734
+ manim/ optional Manim version of the README wordmark
735
+ scripts/ catalog updates, logo rendering, asset copying
736
+ assets/ rendered README animation
737
+ ```
309
738
 
310
- ## 📚 Documentation
739
+ The test suite starts local mock SSE servers and exercises all streaming dialects, retries, fail-fast `429` handling, stream cancellation, usage variants, the full headless path, tool calls, session persistence, JSON events, session sync auth/push/pull conflict handling, the simplified prompt, the resizable file dock, parallel worker delegation, GitHub token handling and local commit/push, and inspection commands. No live provider keys are required.
311
740
 
312
- The **[full reference](docs/REFERENCE.md)** covers every setting and flag: configuration, custom endpoints, session sync, multi-model agents, GitHub publishing, JSON event schema, troubleshooting and more.
741
+ The TUI is designed for a monospace font with box-drawing and Unicode icon coverage. [JetBrains Mono](https://www.jetbrains.com/lp/mono/) is a good default; on Konsole, choose it in **Settings → Edit Current Profile → Appearance**.
313
742
 
314
- <br>
743
+ ### Adding a tool
315
744
 
316
- ## 📄 License
745
+ 1. Implement a `ToolDefinition` in `src/tools/`.
746
+ 2. Export it in `createTools()`.
747
+ 3. Set `readOnly` and the permission `action`.
748
+ 4. Add a renderer branch in `src/tui/render.ts` if the output needs a custom card.
317
749
 
318
- MIT © 2026 Geetank. See [LICENSE](LICENSE).
750
+ ### Adding a dialect
319
751
 
320
- <div align="center">
752
+ 1. Add the dialect to `Dialect` in `src/core/types.ts`.
753
+ 2. Implement the streaming adapter in `src/core/chat/`.
754
+ 3. Dispatch it from `src/core/chat/index.ts`.
755
+ 4. Map it in `inferDialect()`.
756
+
757
+ ---
758
+
759
+ ## Security
760
+
761
+ - API keys live in `auth.json` (mode `600`) or the environment. SurfCode never writes API keys into sessions.
762
+ - Config directories and session files are created with private permissions where the platform supports it.
763
+ - `surf config` and `surf config get` redact API keys, authorization headers, tokens, and passwords.
764
+ - Destructive shell commands are marked high-risk in approval prompts.
765
+ - Plan mode blocks all mutating tools.
766
+ - `allowOutsideWorkspace` is off by default; file tools stay inside the workspace unless enabled.
767
+ - Full computer access (`fullAccess` / `/access full`) is off by default. When enabled, it allows every tool action and paths outside the workspace, so only use it on machines and repositories you trust. Plan mode remains read-only.
768
+ - `/agent` workers receive conversation context but no tools. Their requests use the same saved credentials and are never shown with keys in the transcript.
769
+ - GitHub publishing uses a personal access token stored under `github` in `auth.json` (mode `600`) or `GITHUB_TOKEN`/`GH_TOKEN`. It is never written to config or sessions, never printed in full, and never added to a remote URL or command-line argument. Pushes use a temporary `GIT_ASKPASS` helper, and git errors are redacted.
770
+ - Repository creation, uploads, commits, and pushes are approval-gated tools. SurfCode uses them only when you explicitly ask, never force-pushes or rewrites history, and has no repository-deletion tool.
771
+ - Permission prompts are never auto-approved in headless mode. Without a TTY, an `ask` action is denied; use `--yes` deliberately.
772
+ - Session sync is opt-in. If enabled, only `SessionRecord` data is uploaded to your endpoint; provider keys, auth files, provider settings, and environment variables are excluded. Remote sync endpoints must use HTTPS.
773
+ - `surf config` redacts the sync bearer token just like provider keys.
774
+ - No telemetry, no background uploads. Network access is limited to the provider you choose, URLs fetched by the `webfetch` tool, models.dev when you refresh the catalog, and your configured sync endpoint when sync is enabled.
775
+
776
+ ---
321
777
 
322
- <br>
778
+ ## Troubleshooting
323
779
 
324
- **If SurfCode saves you time, a ⭐ on the repo means a lot.**
780
+ **`No API key for …`**<br>
781
+ Run `surf auth <provider>`, export the provider's environment variable, or add a `baseURL`/`keyless` custom provider.
325
782
 
326
- </div>
783
+ **`interactive UI needs a terminal`**<br>
784
+ Use `surf run "…"` in scripts, CI, or when piping output.
785
+
786
+ **A provider is connected but not listed as ready**<br>
787
+ Its endpoint may be missing. Azure needs `AZURE_RESOURCE_NAME`, Vertex needs `GOOGLE_VERTEX_PROJECT`, and proprietary providers may need a custom gateway URL.
788
+
789
+ **Local model server not detected**<br>
790
+ Start the server first, then run `surf providers --connected`. If it uses a non-standard port, set `providers.ollama.baseURL` (or the matching local provider) to its `/v1` endpoint.
791
+
792
+ **Models changed upstream**<br>
793
+ Run `surf update-models`, or `surf models --refresh`.
794
+
795
+ **Something is misconfigured**<br>
796
+ Run `surf doctor --network` and `surf config validate`.
797
+
798
+ ---
799
+
800
+ ## License
801
+
802
+ MIT © 2026 Geetank. See [LICENSE](LICENSE).