Claudes Code, Codex, shells, and editors in joyful harmony. Speak with the dead, jump to your phone, and more.
CLIs in tabs and panes | Forever coding agent history | What if tmux and Claude fell in love?
- Tabs and panes — Organize projects with multiple coding agents, shells, browsers, editors, and more on a tab - and as many tabs as you want.
- Desktop, laptop, phone — Run on your main machine, then work on your project anywhere via VPN or Tailscale.
- Speak with the dead — Resume any Claude, Codex, or OpenCode session from any device (even if you weren't using freshell to run it)
- Fancy tabs — Auto-name from terminal content, drag-and-drop reorder, and per-pane type icons so you know what's in each tab
- Freshclaude — An interactive alternative to Claude CLI that works with your Anthropic subscription. Rich chat UI with collapsible tool strips, token budget display, and full session persistence.
- Extension system — Add new pane types, CLI integrations, and server-side services via manifest-based extensions. Enable and disable from the Extensions management page.
- Self-configuring workspace — Just ask Claude or Codex to open a browser in a pane, or create a tab with four subagents. Built-in tmux-like API and skill makes it simple.
- Live pane headers — See your active directory, git branch, and context usage in every pane title bar, updating live as you work. Fresh-agent panes carry their context meter in their status strip instead of the header.
- Host pressure dashboard pane — CPU, memory, pressure, and I/O at a glance with near-zero overhead (metrics stream only while you're watching). Linux, WSL, and macOS only — not shown on Windows.
- Activity notifications — Configurable attention indicators (highlight, pulse, darken) on tabs and pane headers when a coding CLI finishes its turn, with click or type dismiss modes
- AI-powered session titles — Right-click any session and generate a Gemini-powered title based on conversation content
- Progressive sidebar search — Two-phase search with instant local results followed by deep server-side content search
- Mobile responsive — Auto-collapsing sidebar and overlay navigation for phones and tablets
- Stream Deck — Drive freshell from an Elgato Stream Deck: tabs on keys with repo icons and status backgrounds (or classic live previews and status rings), press to focus, long-press to approve or stop agents. See Stream Deck.
# Clone the repository at the latest stable release
git clone --branch v0.7.5 https://github.com/danshapiro/freshell.git
cd freshell
# Install dependencies
npm install
# Build and run
npm run serveOn first run, freshell auto-generates a .env file with a secure random AUTH_TOKEN. The token is printed to the console at startup — open the URL shown to connect.
Node.js 18+ (20+ recommended) and platform build tools for native modules (windows-build-tools on Windows, Xcode CLI Tools on macOS, build-essential python3 on Linux).
Note: On native Windows, terminals default to WSL. Set
WINDOWS_SHELL=cmdorWINDOWS_SHELL=powershellto use a native Windows shell instead.
The desktop app normally runs one instance with one configuration. Profiles let you run multiple independent desktop clients on the same machine at the same time — for example one connected to your work server and one to a personal server.
Each named profile gets its own:
- settings, window state, and logs (
~/.freshell-<id>/; the default profile keeps using~/.freshell/) - Electron storage dir (
…/Freshell-<id>in packaged builds,freshell-<id>in dev/unpackaged runs), so cookies and localStorage never mix - single-instance lock: launching the same profile twice focuses the running window; different profiles run side by side
Create ~/.freshell/profiles.json:
{
"profiles": [
{ "id": "work", "label": "Work" },
{ "id": "home" }
]
}Rules: id is lowercase letters/digits/dashes starting with a letter or digit
(max 32 chars); default and profile-picker are reserved (the first means
the original un-namespaced environment; the second is the picker launcher's
own storage dir); label is optional display text.
When at least one named profile is defined, launching the app without a
profile shows a picker (the default profile is always listed first; the built-
in default counts, so one named profile in the file already means "more than
one configured"). The picker is a small launcher: whichever profile you pick,
the app relaunches itself pinned to it — you'll see a quick restart, then the
app continues in the chosen profile. Pin a launch to a profile with
--profile=<id> or FRESHELL_PROFILE=<id>; named ids do not have to be
listed in profiles.json — an unlisted id simply starts with a fresh
configuration.
- Global hotkey: the first instance to register an accelerator keeps it;
later instances log a warning (
global_hotkey_registration_failed) and have no hotkey. Give each profile a distinct hotkey in its own settings. - App-bound servers: each profile spawns its own server pinned to that
profile's config dir (
FRESHELL_CONFIG_DIR) and port — a named profile never adopts another profile's already-running local server; choose a distinct port per profile. Once named profiles exist (listed inprofiles.json, used from the command line, or previously run — including a stray~/.freshell-<id>backup dir, which shape-checks by name), the same applies to the Default profile: it no longer auto-attaches to a discovered local server, and if its configured port is held by a neighbor, Freshell bumps to the next free port and saves that port into the profile's settings (visible in the setup summary). An app-bound profile that finds its OWN config dir's server already resident attaches to it instead of double-spawning. - Daemon services (
freshell.service,com.freshell.server, "Freshell Server" task) are machine-global single instances — daemon mode is available only on the Default profile; named profiles fall back to the chooser instead. - Silent-install provisioning (
desktop.provision) applies to the default profile only. - Auto-update relaunches the app without
--profile: after an update, the picker shows again (pick your profile back). - Installing/upgrading on Windows terminates all running Freshell instances.
- Relaunching while a profile is running: on Linux/Windows, a launch without a
flag shows the picker again and choosing the running profile focuses its
window; launching with the same
--profileas a running instance focuses that window (the new process quits). On macOS, relaunching from Finder or the Dock while ANY Freshell instance is running just activates the running instance (the OS enforces this) and never shows the picker — use--profile=flags orFRESHELL_PROFILEfrom a terminal, or Quit before relaunching to get the picker. Two simultaneous flag-less launches race for the picker's launcher slot: the first shows the picker; the second quietly exits and brings the existing picker forward. - Daemon-service caveat for the Node server: the shipped daemon templates have
always contained an (until now inert)
FRESHELL_CONFIG_DIRenvironment line; starting with this release the Node server honors it. If you hand-generated a daemon unit from those templates with a non-default config directory, the value now takes effect at next start (state relocates to that directory): remove the line from your unit, or move your existing~/.freshellcontents into the directory it names. Units using the default~/.freshellpath are unaffected — and if your service's working directory is not the config dir (systemd user units default to$HOME), the server copies an existing.envfrom the old location into the config dir rather than rotating your token. Rust-server installs never read this variable.
npm run dev # Development with hot reload
npm run serve # Production build and runnpm run serve is intended for main. If you run it from another branch, Freshell asks for confirmation in an interactive terminal and refuses in non-interactive shells unless FRESHELL_ALLOW_NON_MAIN_SERVE=1 is set.
Freshclaude, Freshcodex, and Freshopencode share a chat interface with attachments, tool output, questions, and approval controls. Use /model or click the model name to choose a model and thinking level. Changes apply to your next message; the picker remembers recent choices for each project.
You can queue follow-up messages while an agent works. They run one at a time, and the queue stays available if the session disconnects or ends. Expand the queue to read or cancel individual messages. Codex permission settings control when it asks for approval; “Never ask” does not change the session’s file or network access limits.
Freshell can drive an Elgato Stream Deck straight from the browser. Each key shows a tab — by default the Status icons style: title on top, centered repo icons, and a status background (green for tabs that want attention), with keys sorted so attention-seeking tabs come first. Press a key to focus that tab; long-press (500 ms) to open an action layer with BACK / APPROVE / STOP keys (it closes itself after 10 s). When you have more tabs than keys, the last key pages through them (wrapping around). On a Stream Deck +, the dials cycle tabs and flip pages and the touch strip shows the active tab plus busy/waiting counts (waiting = tabs that finished a turn or are waiting for approval). The deck dims after a configurable idle timeout and wakes on activity.
Requirements
- Chrome or Edge (WebHID). Not supported in the freshell desktop app — use Chrome or Edge instead.
- An Elgato Stream Deck. The Stream Deck Mini is the primary target; other models (including the Stream Deck + dials and touch strip) are driven by their reported capabilities.
Connecting: Settings → Stream Deck → turn on Enable Stream Deck, click Connect Stream Deck, and pick the device in the browser prompt. After that first grant, freshell reconnects automatically — including after unplug/replug and page reloads. Deck settings are stored in the browser (localStorage), so they are per browser profile, not per freshell server.
Virtual deck: Settings → Stream Deck → Show virtual deck opens an on-screen deck panel that mirrors the keys. It works without any hardware — and in browsers without WebHID.
Tile style: Settings → Stream Deck → Tile style switches between Status icons (the default, described above) and Terminal previews — the classic look with a title banner, a live mini terminal preview on each key, and colored status rings (blue busy, green needs-attention, amber waiting for approval), with keys in plain tab-bar order. Switching takes effect immediately, on the hardware deck and the virtual deck alike.
Linux device permissions (udev): hidraw device nodes default to root-only, so the browser cannot open the deck until you grant access to the Elgato vendor id (0fd9):
sudo tee /etc/udev/rules.d/50-elgato-stream-deck.rules >/dev/null <<'EOF'
SUBSYSTEM=="usb", ATTRS{idVendor}=="0fd9", TAG+="uaccess"
KERNEL=="hidraw*", ATTRS{idVendor}=="0fd9", TAG+="uaccess"
EOF
sudo udevadm control --reload-rules && sudo udevadm triggerThen unplug and replug the deck. Without the rule, the connection status shows "In use by another window or app — or missing device permissions (Linux udev)" — the browser cannot distinguish the two failure causes.
Chrome Memory Saver: Chrome's Memory Saver can discard a long-hidden freshell tab even while the deck is connected — the deck goes dark until you revisit the tab. To avoid this, add your freshell URL to Memory Saver's "Always keep this site active" list (chrome://settings/performance).
| Shortcut | Action |
|---|---|
Alt+T |
New tab |
Alt+W |
Close tab |
Alt+H / Alt+Shift+T |
Reopen closed tab |
Ctrl+Shift+[ / Alt+[ |
Previous tab |
Ctrl+Shift+] / Alt+] |
Next tab |
Ctrl+Shift+ArrowLeft |
Move tab left |
Ctrl+Shift+ArrowRight |
Move tab right |
Ctrl+Shift+C |
Copy selection (in terminal) |
Ctrl+V / Ctrl+Shift+V |
Paste (in terminal) |
Ctrl+F |
Search (in terminal) |
Shift+Enter |
Newline (in terminal) |
Cmd/Ctrl+End |
Scroll to bottom (in terminal) |
Right-click / Shift+F10 |
Context menu |
| Variable | Required | Description |
|---|---|---|
AUTH_TOKEN |
Auto | Authentication token (auto-generated on first run, min 16 chars) |
PORT |
No | Server port (default: 3001) |
ALLOWED_ORIGINS |
No | Auto-managed CORS origins for the active server bind host and LAN IPs |
EXTRA_ALLOWED_ORIGINS |
No | Comma-separated custom CORS origins preserved across runtime origin rebuilds |
CLAUDE_HOME |
No | Path to Claude config directory (default: ~/.claude) |
CODEX_HOME |
No | Path to Codex config directory (default: ~/.codex) |
WINDOWS_SHELL |
No | Windows shell: wsl (default), cmd, or powershell |
WSL_DISTRO |
No | WSL distribution name (Windows only) |
CLAUDE_CMD |
No | Claude CLI command override |
CODEX_CMD |
No | Codex CLI command override |
OPENCODE_CMD |
No | OpenCode CLI command override |
GEMINI_CMD |
No | Gemini CLI command override |
KIMI_CMD |
No | Kimi CLI command override |
AMPLIFIER_CMD |
No | Amplifier CLI command override |
FRESHELL_AUTO_RESUME_IDENTITY_GRACE_MS |
No | Comma-separated identity-grace recheck delays before a crashed agent pane settles (default: 2500,2500 — 5s total); set to empty to disable |
GOOGLE_GENERATIVE_AI_API_KEY |
No | Gemini API key for AI-powered terminal summaries |
Freshell indexes local session history and can launch terminals for these coding CLIs:
Enable/disable providers and set defaults in the Settings UI or via ~/.freshell/config.json.
OpenCode sessions are discovered directly from OpenCode's local session database, so existing OpenCode work can be resumed from freshell without importing anything manually.
OpenCode permissions are controlled by the OpenCode configuration for the OS user running freshell. Freshell does not set OPENCODE_PERMISSION or pass --dangerously-skip-permissions for OpenCode sessions; OS filesystem permissions remain the hard boundary.
Amplifier loads the freshell MCP only if its bundle mounts tool-mcp (the default anchors bundle does not). Add tool-mcp to your Amplifier bundle to enable orchestration.
- Frontend: React 18, Redux Toolkit, Tailwind CSS, xterm.js, Monaco Editor, Zod, lucide-react
- Backend: Express, WebSocket (ws), node-pty, Pino, Chokidar, Zod
- Build: Vite, TypeScript
- Testing: Vitest, Testing Library, supertest, superwstest
- AI: Vercel AI SDK with Google Gemini
Freshell supports custom pane types via extensions. Three categories are available:
- Client — Static HTML/JS served by freshell (no server needed)
- Server — Your own HTTP server, managed by freshell with automatic port allocation
- CLI — Any terminal tool wrapped as a pane
Drop a directory with a freshell.json manifest into ~/.freshell/extensions/ and restart freshell. See examples/extensions/ for working examples of each type.
Contributions are welcome. Start from origin/main in a worktree, submit a Pull Request against main, and keep behavior changes on PR branches. After a PR merges, update local main from origin/main. See docs/development/branch-model.md.
Projects built by the community around freshell.
- freshell-container — Docker container packaging freshell with all supported coding CLI providers for self-hosted, multi-device access
MIT License — see LICENSE for details.
Made with terminals and caffeine

