Architecture
Overview
Penates is a Node.js application using Express, express-ws, node-pty, and tmux. It is written as ES Modules and runs on the default port 3333. There is no build step and no frontend framework.
The backend serves a REST API for all CRUD operations and a WebSocket API for terminal I/O and live file events. The frontend is a single-page application delivered from public/index.html.
Backend
Each feature area lives in a dedicated lib/ module. These modules are Express-free and unit-testable in isolation. Routes in server.js are thin wrappers that call into the modules and handle HTTP concerns (status codes, auth, rate limiting).
Key modules and what they own:
lib/files.js+lib/file-watcher.js+lib/session-files.js- file browser, live change events, terminal path previewslib/attention.js+lib/attach-tracker.js- hook state, notification dispatchlib/usage.js+lib/usage-limits.js+lib/pace.js- usage aggregation and rate-limit trackinglib/git-diff.js+lib/git-history.js- repo panel (changes, history, branches)lib/board.js- idea pipeline board, backed by~/.penates/board.jsonlib/brainstorm-spawn.js+lib/git-finish.js+lib/worktree.js- autonomous pipeline (spawn, merge, worktree isolation)lib/session-restore.js- boot-time auto-restore of dormant sessionslib/platform.js+lib/penates-home.js- OS abstraction and state directory resolutionlib/settings.js+lib/server-control.js- persistent settings and server restartlib/preview-proxy.js+lib/port-scan.js- browser preview reverse proxylib/mata.js- iOS Simulator (Mata) integrationlib/voice.js- local whisper.cpp transcriptionlib/moshi-hook.js+lib/antigravity-usage.js- moshi-hook and Antigravity interoplib/updates.js+lib/update-check.js- update systemlib/approvals.js,lib/audit-log.js,lib/cf-access.js,lib/rate-limit.js- security layer
No shell interpolation. All calls to tmux, git, and external tools use execFile or execFileSync with argv arrays, never template strings passed to a shell. Any new code that invokes external processes must follow the same pattern.
Frontend
The frontend is a single HTML file at public/index.html with all CSS and JS inlined. There is no bundler, no transpilation, and no frontend framework. xterm.js 6.0 and its addons are vendored under public/vendor/xterm/.
Each feature area is implemented as a self-contained IIFE (immediately-invoked function expression) inside index.html. IIFEs share state only through narrow interfaces, not global variables.
Shared code that is used by both the browser and node:test lives as separate ES Module files:
public/clis.js- CLI registry (claude, codex, antigravity) and command utilitiespublic/prefs.js- client-side preferences (theme, language)public/usage-format.js- shared usage formatting helpers
public/sw.js is the service worker for PWA and Web Push support.
Sessions and state
tmux is the source of truth for running sessions. The hub keeps no in-memory session state beyond what is needed for hook activity tracking. GET /api/sessions calls tmux list-sessions directly.
Persistent hub state (settings, known sessions, board cards) lives under ~/.penates by default. The path is overridable via PENATES_HOME, which is the primary isolation mechanism for unit tests and E2E tests.
The cc- session prefix is enforced on all sessions created through the hub. Foreign sessions (started outside the hub, e.g. via SSH or Moshi) can be adopted via POST /api/sessions/:name/adopt and are registered under their original name.
Auth and remote access
Local: every REST request must carry Authorization: Bearer <token>. WebSocket connections authenticate via the bearer.<token> subprotocol.
Remote via Cloudflare Tunnel: the hub can be exposed through a Cloudflare Tunnel to a custom domain. No inbound ports need to be opened on the host machine.
Cloudflare Access (optional): when CF_ACCESS_TEAM_DOMAIN and CF_ACCESS_AUD are set, lib/cf-access.js validates the Cf-Access-Jwt-Assertion JWT on all requests that arrive through the tunnel (identified by the presence of a Cf-Ray header). Direct local requests bypass JWT validation.
Rate limiting: lib/rate-limit.js enforces a fixed-window rate limit on write operations (shared writeLimiter used by upload, file mutation, board, and voice endpoints). All auth failures are appended to ~/.penates/audit.log.
Platform support
lib/platform.js is the single source of truth for OS detection. platform() returns macos or linux. All OS-specific branches in server.js and the lib modules go through this function rather than inline process.platform checks.
macOS behavior must remain byte-identical across changes. Linux differences are explicit OS branches. Apple-only features (Mata, voice input, moshi-hook) degrade gracefully on Linux rather than crashing.
Linux package management is handled by install.sh and scripts/lib.sh, which support apt, dnf, and pacman. Windows is supported only via WSL2, which follows the Linux code path.