surfcode 0.1.1 → 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 +688 -212
- package/dist/cli/headless.js +1 -1
- package/dist/core/agent.js +25 -12
- package/dist/core/agent.js.map +1 -1
- package/dist/core/chat/common.js +55 -0
- package/dist/core/chat/common.js.map +1 -1
- package/dist/core/chat/google.js +53 -2
- package/dist/core/chat/google.js.map +1 -1
- package/dist/core/chat/index.js +19 -2
- package/dist/core/chat/index.js.map +1 -1
- package/dist/tui/App.js +27 -6
- package/dist/tui/App.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,326 +1,802 @@
|
|
|
1
|
-
|
|
1
|
+
# SurfCode
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Ride the code.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
<p align="center">
|
|
6
|
+
<img src="assets/surfcode-logo.gif" alt="Animated blue SurfCode wordmark" width="480">
|
|
7
|
+
</p>
|
|
6
8
|
|
|
7
|
-
|
|
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
|
-
|
|
11
|
+
Full command and configuration reference: [docs/REFERENCE.md](docs/REFERENCE.md). Contributing: [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
```text
|
|
14
|
+
▀▀▀▓▄▄ ▄▄▄ ▄▄ ▀▀▀▓▄▄ ▀▀▀▓▄▄ ▀▀▀▓▄▄ ▀▀▀▓▄▄ ▀▀▀▓▄▄ ▀▀▀▓▄▄
|
|
15
|
+
▄██▄░▀██▓▄ ▄███ ▐██▄ ▄██▄░▀██▓▄ ▄██▄░▀██▓▄ ▄██▄░▀██▓▄ ░░▀██▓▄ ▄██▄░▀██▓▄ ▄██▄░▀██▓▄
|
|
16
|
+
▀ ▀░░░░░▀██▓ ▐███▌ ███▌▒▀ ▀░░░░░███▓▒▀ ▀░░░░░███▓▒▀ ▀░░░░░███▓ ▄▄█▀ ░▒███▓▄ ▒▀ ▀░░░░░███▓▄ ▒▀ ▀░░░░░███▓
|
|
17
|
+
░░▐▒▒▄▄▄ █░░▌ ░░░█ ░░▐▒▒▄░ ░░▌ ░░▐▒▒ ░ ▀▀ ░░▐▒▒ ░ ▀░▌ █░░ ░░░░░▌ ░░▐▒▒ ░░░░░░▌ ░░▐▒▒ ░ ▀▀
|
|
18
|
+
░ ▀▀░░▒▒ ▒▒█ ░▐▒▒▒▒ ░▐▓▓▐▒▌▒▀ ░▐▓▓ ▄▓▀ ░▐▓▓ ▐▒▒▌ ░▐▒▒▒▒ ░▐▓▓ ░▐▒▒▒▒ ░▐▓▓ ▄▓▀
|
|
19
|
+
▄▓ ▀▓▓█ ▐▓▓▄ ░▒▓▓▓▓▌ █▓▀ ▀▓▓▄ ▀▓█ ▄ ▀▓▀ ▄█▓▄ ▓▓▓▄ ░▒▓▓▓▓▌ ▄ ▀▓▀ ▓▓▓▓▌ ▄ ▀▓▀ ▄▄▄
|
|
20
|
+
▀▓█▓▄▄ ▓███ ▀███▄▓███▀ ▀███▄ ▓███ ▐█ ▀█▄▀█▄▄▓▓▒▀ ▀███▄▄▄▓███▀ ▀█▄▄▄▄▄▓███▀ ▀█▄▄▄▄▄▓███
|
|
21
|
+
▀▀ ▀▀▀▀ ▀▀▀▀▀ ▀▀ ▀▀▀▀ ▀▌ ▀▀▀▀▀ ▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀
|
|
22
|
+
```
|
|
20
23
|
|
|
21
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
62
|
-
npm
|
|
59
|
+
npm install
|
|
60
|
+
npm run build
|
|
61
|
+
npm link
|
|
63
62
|
```
|
|
64
63
|
|
|
65
|
-
|
|
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
|
|
70
|
+
The first launch opens the interactive UI. Press `/` for commands, or connect a provider:
|
|
76
71
|
|
|
77
72
|
```text
|
|
78
|
-
/connect
|
|
79
|
-
/model
|
|
80
|
-
/help
|
|
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
|
|
87
|
-
surf models --provider openai
|
|
88
|
-
surf run "explain src/core/agent.ts"
|
|
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
|
-
|
|
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
|
-
|
|
88
|
+
---
|
|
96
89
|
|
|
97
|
-
|
|
98
|
-
<tr>
|
|
99
|
-
<td width="50%" valign="top">
|
|
90
|
+
## The TUI
|
|
100
91
|
|
|
101
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
128
|
-
<td width="50%" valign="top">
|
|
156
|
+
### Options
|
|
129
157
|
|
|
130
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
145
|
-
<td width="50%" valign="top">
|
|
164
|
+
`/access full` is an explicit opt-in for unattended or local-machine work. It:
|
|
146
165
|
|
|
147
|
-
|
|
148
|
-
-
|
|
149
|
-
-
|
|
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
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
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
|
-
|
|
184
|
+
### GitHub publishing
|
|
175
185
|
|
|
176
|
-
|
|
186
|
+
Connect once, then ask the agent in plain language. In the TUI:
|
|
177
187
|
|
|
178
188
|
```text
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
189
|
+
/github login
|
|
190
|
+
/github status
|
|
191
|
+
/github logout
|
|
182
192
|
```
|
|
183
193
|
|
|
184
|
-
|
|
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
|
-
|
|
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
|
-
|
|
198
|
+
The agent has four publishing tools:
|
|
192
199
|
|
|
193
|
-
|
|
|
194
|
-
|
|
|
195
|
-
|
|
|
196
|
-
|
|
|
197
|
-
|
|
|
198
|
-
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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` |
|
|
214
|
-
| `esc` | Stop generating / leave
|
|
215
|
-
| `
|
|
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
|
-
| `
|
|
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
|
-
| `
|
|
222
|
-
| `
|
|
223
|
-
| `?` | Help
|
|
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
|
-
|
|
240
|
+
### Plan mode
|
|
226
241
|
|
|
227
|
-
|
|
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
|
-
|
|
244
|
+
### Permissions
|
|
230
245
|
|
|
231
|
-
|
|
246
|
+
Each tool action is classified as `read`, `write`, `shell`, or `fetch`:
|
|
232
247
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
|
|
257
|
+
## Providers
|
|
246
258
|
|
|
247
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
479
|
+
Global flags:
|
|
262
480
|
|
|
263
481
|
```text
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
-
|
|
499
|
+
### `surf models`
|
|
276
500
|
|
|
277
|
-
|
|
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
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
<
|
|
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
|
-
|
|
707
|
+
---
|
|
296
708
|
|
|
297
|
-
|
|
709
|
+
## Development
|
|
298
710
|
|
|
299
711
|
```bash
|
|
300
712
|
npm install
|
|
301
|
-
npm run dev
|
|
713
|
+
npm run dev # tsx src/cli.tsx
|
|
302
714
|
npm run typecheck
|
|
303
|
-
npm test
|
|
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
|
-
|
|
722
|
+
Project layout:
|
|
307
723
|
|
|
308
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
743
|
+
### Adding a tool
|
|
315
744
|
|
|
316
|
-
|
|
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
|
-
|
|
750
|
+
### Adding a dialect
|
|
319
751
|
|
|
320
|
-
|
|
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
|
-
|
|
778
|
+
## Troubleshooting
|
|
323
779
|
|
|
324
|
-
|
|
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
|
-
|
|
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).
|