A self-hosted AI chat with a sleek web UI and a rich terminal CLI.
Connect local or cloud models, search the web, automate a browser, and keep lightweight persistent memory.
A self-hosted AI chat with a polished web UI and CLI. SURF aims to be a practical local-first AI surface for trying models, web search, browser automation, and lightweight memory in one place.
- Multi-provider chat via Ollama, Anthropic, OpenAI, OpenRouter, and compatible endpoints
- Local web UI and terminal CLI
- DuckDuckGo-based search and page fetching
- Basic browser-agent and memory features
- Test coverage and CI depth
- Runtime state/config separation
- Reliability around advanced features like browser automation and research flows
|
🦙 Any AI Provider |
🔍 Free Web Search |
|
🤖 Browser Agent |
🧠 Persistent Memory |
|
📄 Conversation Summariser |
📖 Skills System |
|
📊 Analytics Dashboard |
👁 Vision & Images |
core/surf.py— main CLI runtime, provider selection, token/context helperscore/web_ui.py— Flask web interface and local state handlingcore/ai_search.py— DuckDuckGo search + page fetching helperscore/ai_tools.py— wrappers for use with tool-calling frameworkscore/browser_agent.py— browser automation pathskills/— optional markdown-driven extensions
SURF now keeps local runtime state under .surf/ by default instead of cluttering the project root.
You can override the location with SURF_DATA_DIR=/path/to/data.
This makes first-run and cleanup more predictable, especially when switching machines or testing multiple setups.
If you want the shortest possible path from clone to working app, see docs/FIRST_RUN.md.
.\setup.ps1chmod +x setup.sh && ./setup.shpython -m venv venv
source venv/bin/activate # Windows: .\venv\Scripts\Activate.ps1
pip install -r requirements.txt
playwright install chromiumSee .env.example for the supported key names and runtime data override.
python chat.py # Ollama (default, auto-starts)
python chat.py --search # Start with web search on
python chat.py -p anthropic # Anthropic Claude
python chat.py -p openai # OpenAI GPT
python chat.py -p openrouter # OpenRouter
python chat.py -p custom -u http://localhost:1234/v1 -m my-modelpython chat.py --web # opens http://localhost:7777
# or from inside the CLI:
/webType / to open the interactive slash-command menu (with tab completion).
| Command | Description |
|---|---|
/search |
Toggle web search on / off |
/think |
Toggle thinking-block display |
/stream |
Toggle live streaming |
/model <name> |
Switch model |
/models |
List available Ollama models |
/vision <name> |
Set a dedicated vision model (e.g. llama3.2-vision) |
/image <path> |
Attach a local image to the next message |
/provider <name> |
Switch provider (ollama / anthropic / openai / openrouter / custom) |
/key <provider> <key> |
Set an API key |
/url <base_url> |
Set a custom API base URL |
/summarize |
AI-generated summary of the current conversation |
/research <topic> |
Deep research mode — searches, reads pages, synthesises |
/new |
Start a new conversation |
/clear |
Clear conversation history |
/status |
Show current settings |
/web [port] |
Launch the web UI (default port 7777) |
/help |
List all commands |
/quit |
Exit |
Open http://localhost:7777 after running with --web.
Sidebar
- Provider selector, chat model and vision model dropdowns
- Feature toggles: web search, thinking, streaming, agent mode, stats overlay
- Code syntax-highlight theme picker
- Global and session memory panels
Topbar
- 📊 Stats — analytics dashboard (overview, model breakdown, speed, conversation history)
- 📖 Skills — enable / disable skill modules
- 🧠 Memory — view and edit remembered facts
- 📄 Summarize — generate a structured AI summary of the current chat
Chat
- Markdown rendering with syntax-highlighted code blocks
- Image upload (camera icon) — auto-routed through the vision model if set
- Slash-command menu (type
/in the input) - Conversation branching — fork from any assistant message
Gives SURF eyes and hands. When agent mode is enabled, SURF can:
- Open a real Chromium browser (headless)
- Take screenshots at each step
- Send the screenshot to your vision model (or extract structured elements for non-vision models)
- Decide and execute actions:
navigate,click,type,scroll,wait,done
Enable in the web UI sidebar or with /agent in the CLI.
Screenshots of each step are saved to agent_screenshots/.
SURF maintains two memory scopes:
- Global — persisted in
.surf/surf_memory.jsonby default, available in every conversation - Session — auto-extracted during the chat, cleared when the conversation ends
Manage memories from the web sidebar, the Memory topbar button, or the /clear command. Facts are deduplicated automatically.
Skills are Markdown files in skills/<name>/SKILL.md with a YAML front-matter header:
---
name: Web Researcher
description: Search the web and synthesize information
icon: 🌐
enabled: true
---Built-in skills:
| Skill | Description |
|---|---|
| Web Researcher | Real-time search + multi-source synthesis with citations |
| File Reader | Read and analyse workspace files (disabled by default) |
Add a new skill by dropping a folder + SKILL.md into skills/.
Expose SURF's search capabilities as tools for Claude Desktop or any MCP client:
python core/mcp_server.pyTools: web_search, fetch_webpage, web_research
from core.ai_search import search, fetch, research, news_search
results = search("Python async patterns") # DuckDuckGo results
page = fetch("https://docs.python.org") # Fetch + extract page text
info = research("what is retrieval-augmented generation") # Search + read top pages
news = news_search("AI updates today") # Latest newsPass via environment variable or the /key command:
export ANTHROPIC_API_KEY=sk-ant-...
export OPENAI_API_KEY=sk-...
export OPENROUTER_API_KEY=sk-or-...Keys are stored locally in .surf/surf_keys.json when set with /key.
surf/
├── chat.py ← Entry point
├── core/
│ ├── surf.py ← CLI — providers, commands, Rich UI
│ ├── web_ui.py ← Flask web server + REST/SSE API
│ ├── browser_agent.py ← Playwright autonomous browser agent
│ ├── ai_search.py ← DuckDuckGo search + page fetching
│ ├── ai_tools.py ← Chat function factories (Ollama, Anthropic, OpenAI…)
│ ├── mcp_server.py ← MCP server for Claude Desktop
│ └── quick_chat.py ← Lightweight single-shot CLI
├── skills/
│ ├── web_researcher/ ← Web search + synthesis skill
│ └── file_reader/ ← File reading skill
├── static/
│ ├── index.html ← Web UI (single-page app)
│ ├── css/style.css
│ └── js/app.js
├── assets/ ← README graphics
├── agent_screenshots/ ← Browser agent step captures
├── .surf/
│ ├── surf_chats.json ← Saved conversations
│ ├── surf_memory.json ← Persistent memory facts
│ ├── surf_stats.json ← Usage analytics
│ └── surf_keys.json ← Stored API keys
- Python 3.10+
- Ollama for local models — ollama.ai
- Chromium (installed by
playwright install chromium) for web search and browser agent
See requirements.txt for the full Python dependency list.
MIT