Freshell is a self-hosted, browser-accessible terminal multiplexer and session organizer. It provides multi-tab terminal management with support for terminals and coding CLI's like Claude Code and Codex. Key features include session history browsing, AI-powered summaries (via Google Gemini), and remote access over LAN/VPN with token-based authentication.
- We are working on an infinite schedule with infinite tokens. This is unusual! We do not have time pressure, and can do things correctly.
- We use Red-Green-Refactor TDD for all changes but the most trivial (e.g. doc changes). We never skip the tests, and never skip the refactor.
- We ensure both unit test & e2e coverage of everything.
- Before starting anything, think - what's the most idiomatic solution for the technology we're using?
- We prefer clean architecture and correctness over small patches.
- We fix the system over the symptom.
- Always work in a worktree (in .worktrees)
- Many agents may be working in the worktree at the same time. If you see activity from other agents (for example test runs or file changes), respect it.
- Specific user instructions override ALL other instructions, including the above, and including superpowers or skills
- Server uses NodeNext/ESM; relative imports must include
.jsextensions - Always consider checking logs for debugging; server logs (including client console logs) are in the server process stdout/stderr (e.g.,
npm run dev/npm start). - Debug logging toggle (UI Settings → Debugging → Debug logging) enables debug-level logs and perf logging; keep OFF outside perf investigations.
- When adding new user-facing features or making significant UI changes, update
docs/index.htmlto reflect them. It's a nonfunctional mock of the default experience, so only major changes need to be added.
- Broad repo-supported test runs wait for the shared coordinator gate; if another agent holds it, wait rather than kill a foreign holder.
- Set
FRESHELL_TEST_SUMMARYwhen you want holder/status output to show a human-meaningful reason for a broad run. - Use
npm run test:statusto inspect the current holder, recent results, and any advisory reusable baseline. - Use
npm run test:vitest -- ...for a repo-owned direct Vitest path. Rawnpx vitestis not a coordinated workflow. test:unitis the exact default-configtest/unitworkload,test:integrationis the exact server-configtest/serverworkload, andtest:serverstays watch-capable unless you pass an explicit broad--run.
You are running inside Freshell right now. This session, the terminal the user is typing in, is served by the main branch. If you break main, you kill yourself mid-operation and the user has to clean up your mess with a separate agent.
- Never run
git mergedirectly on main - merge conflicts write<<<<<<< HEADmarkers into source files, which crashes the server instantly - Always merge main INTO the feature branch in the worktree first, resolve any conflicts there
- Before fast-forwarding main, run
npm testand confirm all tests pass (not just related tests) - If you find failing tests, you must stop everything to understand them and make improvements, even if you do not think you were responsible for them.
- Then fast-forward main:
git merge --ff-only feature/branch- this is atomic (pointer move, no intermediate states) - If
--ff-onlyfails, go back to the worktree and rebase/merge until it can fast-forward
- Never use broad kill patterns (for example
pkill -f "tsx watch server/index.ts",pkill -f vite,pkill node). - Start manual worktree servers on a unique port and record their PID, then stop only that PID.
- Dev mode example (source + Vite):
PORT=3344 npm run dev:server > /tmp/freshell-3344.log 2>&1 & echo $! > /tmp/freshell-3344.pid - Production mode example (built dist):
PORT=3344 npm start > /tmp/freshell-3344.log 2>&1 & echo $! > /tmp/freshell-3344.pid- NEVER run
node dist/server/index.jsdirectly — usenpm startwhich setsNODE_ENV=production; without it the server prints the Vite port (5173) in the startup URL even though Vite isn't running
- NEVER run
- Example stop:
kill "$(cat /tmp/freshell-3344.pid)" && rm -f /tmp/freshell-3344.pid - Before stopping any process, verify it belongs to the worktree (
ps -fp <pid>and confirm cwd/path includes.worktrees/...).
Codex Agent in CMD Instructions (Codex agents only; only when running in CMD on windows; all other agents must ignore)
- Prefer bash/WSL over PowerShell; Windows paths map like
D:\...->/mnt/d/.... - Use
bash -lc "<cmd>"for non-interactive commands; avoid interactive shells so commands return control. - Apply_patch expects Windows-style paths.
- If a bash command produces no visible output, rerun with
tty: trueto force output. - PowerShell may hang for dozens of seconds before starting in this tool; stick to bash unless explicitly required.
- Don't make silly mistakes like installing Linux binaries in node_modules when we're on windows
npm run dev # Run client + server concurrently with hot reload
npm run dev:client # Vite dev server only (port 5173)
npm run dev:server # Node with tsx watch for server auto-reloadnpm run build # Full build (client + server)
npm run build:client # Vite build → dist/client
npm run build:server # TypeScript compile → dist/server
npm run serve # Build and run production server
# Note: `npm run build` is guarded — it will refuse to overwrite dist/
# if a production server is detected on the configured PORT. Use
# `npm run check` for safe verification, or build from a worktree.npm test # Coordinated full suite (default + server configs)
npm run check # Typecheck, then coordinated full suite
npm run verify # Build, then coordinated full suite
npm run test:coverage # Coordinated default-config coverage run
npm run test:status # Show active holder, latest results, and advisory baseline info
npm run test:vitest -- ... # Repo-owned direct Vitest path for focused passthrough work- Frontend: React 18, Redux Toolkit, Vite, Tailwind CSS, shadcn/ui, xterm.js, Zod
- Backend: Node.js/Express, node-pty, WebSocket (ws), Chokidar, Vercel AI SDK + Google Generative AI
- Testing: Vitest, Testing Library, supertest, superwstest
src/- React frontend applicationcomponents/- UI components (TabBar, Sidebar, TerminalView, HistoryView, etc.)store/- Redux slices (tabs, connection, sessions, settings, claude)lib/- Utilities (api.ts, claude-types.ts)
server/- Node.js/Express backendindex.ts- HTTP/REST routes and server entryws-handler.ts- WebSocket protocol handlerterminal-registry.ts- PTY lifecycle managementclaude-session.ts- Claude session discovery & indexingclaude-indexer.ts- File watcher for ~/.claude directory
test/- Test suites organized by unit/integration and client/server
WebSocket Protocol: Schema-validated messages using Zod. Handshake flow: client sends hello with token → server validates → sends ready. Message types include terminal.create/input/resize/detach/attach and broadcasts like sessions.updated.
PTY Lifecycle: Each terminal has a unique ID. Server maintains 64KB scrollback buffer. On attach, client receives buffer snapshot then streams new output. On detach, process continues running (background session). Configurable idle timeout (180 mins default).
Claude Session Discovery: Watches ~/.claude/projects/*/sessions/*.jsonl for new files. Parses JSONL streams to extract messages, groups by project path.
Redux State Management: Slices for tabs, panes, connection, sessions, settings, claude. Persist middleware saves tabs and panes to localStorage. Async thunks for API calls.
Configuration Persistence: User config stored at ~/.freshell/config.json. Atomic writes with temp file + rename. Settings changes POST to /api/settings and broadcast via WebSocket.
Pane System: Tabs contain pane layouts (tree structure of splits). Each pane owns its terminal lifecycle via createRequestId and terminalId. When splitting panes, each new pane gets its own createRequestId, ensuring independent backend terminals. Pane content types: terminal (with mode, shell, status) and browser (with URL, devtools state).
- Browser loads → fetches settings from
/api/settingsand sessions from/api/sessions - WebSocket connects → client sends
hellowith auth token → server sendsready - Terminal creation → Pane content has
createRequestId→ UI sendsterminal.createWS message with that ID → server spawns PTY → sends backterminal.createdwithterminalId→ pane content updated - Terminal I/O →
terminal.inputWS messages write to PTY stdin → stdout/stderr streams to attached clients
All components must be accessible for browser-use automation and WCAG compliance:
Semantic HTML:
- Use
<button>,<a>,<input>,<label>for interactive elements (not div with onClick) - Use semantic headers (
<h1>-<h6>), nav, main, aside - Use proper form structure with labels associated to controls
ARIA & Labels:
- Icon-only buttons:
aria-label="Description"or<span className="sr-only"> - Clickable cards/tiles:
role="button"+aria-label - Custom components: appropriate roles and ARIA props
- Complex widgets:
aria-expanded,aria-pressed,aria-selectedwhere applicable
Browser-use Requirements:
- All interactive elements must be indexable (semantic HTML or proper roles)
- All interactive elements must be identifiable (visible text or aria-label)
- Never rely on selectors for automation; fix accessibility instead
Linting:
- Run
npm run lintto check a11y violations (eslint-plugin-jsx-a11y) - Fix with
npm run lint:fixfor auto-fixable issues - A11y linting is CI requirement before merging
@/→src/@test/→test/