Development
Getting Started
To set up PuTTrY for local development:
# Clone the repository
git clone <repo-url>
cd puttry
# Install dependencies
npm install
# Start the dev server
npm run dev
The dev server will start on http://localhost:5175 and display the session password in the terminal. Open the URL in your browser and authenticate with the password.
Project Structure
puttry/
├── src/
│ ├── client/ # React frontend
│ │ ├── components/ # UI components (sessions, terminal, auth, settings)
│ │ ├── hooks/ # React hooks (auth, WebSocket, terminal state)
│ │ ├── App.tsx # Main app component
│ │ └── main.tsx # Entry point
│ │
│ ├── server/ # Express backend
│ │ ├── server.ts # Express app setup
│ │ ├── cli.ts # CLI entry point (puttry command)
│ │ ├── vite-plugin.ts # Vite dev server integration
│ │ ├── pty-manager.ts # PTY session management
│ │ ├── terminal-routes.ts # Session API endpoints
│ │ ├── auth-state.ts # Password & 2FA persistence
│ │ ├── passkey-state.ts # Passkey storage
│ │ ├── sync-bus.ts # WebSocket broadcast for session sync
│ │ ├── rate-limit.ts # Rate limiting middleware
│ │ ├── settings-api.ts # Configuration management
│ │ └── logger.ts # Structured logging
│ │
│ ├── lib/ # Shared utilities
│ │ ├── utils.ts # Common helpers
│ │ └── password-gen.ts # Session password generation
│ │
│ └── vite-env.d.ts # Vite type definitions
│
├── vite.config.ts # Vite configuration
├── tsconfig.json # TypeScript configuration
├── package.json # Dependencies and scripts
└── README.md # This file
Development Scripts
npm run dev – Start Vite dev server with HMR (Hot Module Replacement)
- Frontend is served with live reloading
- Backend API and WebSockets are live via the custom Vite plugin
- Changes to React components reload instantly in the browser
- Changes to server code require a manual page refresh
npm run build – Build for production
- Compiles TypeScript with
tsc -b - Bundles frontend with Vite into
dist/ - Outputs to
dist/(frontend) anddist-server/(backend)
npm run build:server – Bundle backend with esbuild
- Produces
dist-server/server.js(Express app)
npm run build:cli – Bundle CLI with esbuild
- Produces
dist-server/cli.js(puttry command)
npm run lint – Run ESLint
- Checks TypeScript and React code for style and correctness
npm run preview – Preview production build locally
- Serves the built frontend from
dist/ - Requires the Express backend to be running separately
How Vite Dev Server Works
PuTTrY uses a custom Vite plugin (vite-plugin.ts) to integrate the Express backend with Vite’s dev server:
Frontend (Vite with HMR)
When you run npm run dev, Vite starts a dev server on port 5175. The frontend is served with Hot Module Replacement (HMR):
- React Fast Refresh: Changes to
.tsxfiles are reflected in the browser instantly without a full page reload. Component state is preserved. - CSS HMR: Tailwind CSS changes apply instantly via the
@tailwindcss/viteplugin. - Asset HMR: Images and static files are reloaded on change.
Backend (Express Integration)
The custom webTerminalPlugin() Vite plugin does two things:
1. Mounts Express as Vite Middleware
configureServer(server) {
server.middlewares.use(app) // Mount Express on Vite's middleware stack
}
This means:
- All API routes (
/api/*) are handled by Express - The Express app runs inside Vite’s dev server—no separate backend process needed
- Requests to Vite first check Express, then fall back to Vite’s frontend serving
2. Handles WebSocket Upgrades
server.httpServer?.on("upgrade", (req, socket, head) => {
// /sync WebSocket (session synchronization)
// /terminal/:sessionId WebSocket (PTY I/O)
})
The plugin intercepts HTTP upgrade requests for:
/sync– Broadcasts session creation/deletion/lock changes across browser tabs/terminal/:sessionId– Streams terminal output and receives user input
This allows real-time terminal I/O and multi-tab synchronization during development.
Development Flow
1. Browser connects to http://localhost:5175
↓
2. Vite serves index.html with React app
↓
3. React app loads and makes API calls:
- POST /api/auth/login
- GET /api/sessions
- WebSocket /sync
↓
4. Vite's middleware stack catches these requests
↓
5. Express handles them (auth, session management, etc.)
↓
6. PTY shell runs on the backend; output streams via /terminal/:sessionId WS
↓
7. React receives updates and renders the terminal in real-time
No Separate Backend Server Needed
Unlike some full-stack setups, you don’t need to run a separate backend server in dev. The Vite plugin ensures:
- API routes are available immediately
- WebSockets work across the dev server
- Changes to backend code are reflected without restarting (Node’s module system handles this for most changes)
If you modify backend code and it doesn’t reflect, refresh your browser. For full isolation, you can restart the Vite dev server.
Environment Variables
Dev mode loads .env.local from the project root (for development settings) and ~/.puttry/.env (for production-like configs):
# .env.local (development overrides)
PORT=5175
AUTH_DISABLED=0
LOG_SESSION_PASSWORD=1
TOTP_ENABLED=0
See the startup logs for which env file was loaded and current settings.
Debugging
Browser DevTools
- Open DevTools (F12) to inspect React components, network requests, and WebSocket messages
- Use React DevTools extension to inspect component state and props
Server Logging
- All API requests, authentication, and PTY events are logged to stdout
- Log format:
[component] message(e.g.,[auth] Session password rotated) - Set
LOG_SESSION_PASSWORD=0to hide the password in logs
WebSocket Debugging
- Network tab → WS connections → Messages
- Watch
/syncfor session state changes - Watch
/terminal/:sessionIdfor terminal data
Common Development Tasks
Run with Auth Disabled (for testing)
AUTH_DISABLED=1 npm run dev
Run with Custom Port
PORT=3000 npm run dev
Enable TOTP in Dev
TOTP_ENABLED=1 npm run dev
Build and Test Production Bundle
npm run build:all
npm start # Starts dist-server/server.js
Lint Code
npm run lint
Testing the Terminal
Once the dev server is running:
- Open
http://localhost:5175in your browser - Enter the session password (shown in the terminal output)
- Create a new session via the Web UI
- Type commands in the terminal (e.g.,
ls,echo "hello") - Try switching browsers/tabs to test write lock and synchronization
- Check browser DevTools Network tab to see WebSocket activity
Building for Production
To create a production build:
npm run build:all
This produces:
dist/– Frontend bundle (HTML, JS, CSS)dist-server/server.js– Express serverdist-server/cli.js– puttry CLI command
To test the production build locally:
npm run build:all
npm start
The server will start on port 3000 (or your configured PORT). Open http://localhost:3000 to verify.
Performance Considerations
- Frontend: React Fast Refresh only reloads changed components. Global styles are replaced without page reload.
- Backend: Changes to server code may require a page refresh to take effect, depending on Node’s module caching.
- WebSockets: Persistent connections mean HMR doesn’t reconnect—terminal sessions stay alive across frontend reloads.
- PTY Output: Each session buffers up to 10,000 lines of history by default. For long-running shells, memory usage grows over time.