Skip to the content.

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/
│   ├── components/             # React UI components
│   │   ├── auth/               # Authentication UI (login, passkeys, TOTP)
│   │   ├── ui/                 # Shadcn/ui reusable components (button, dialog, switch, etc.)
│   │   ├── PasswordDialog.tsx   # Password setup/change dialog
│   │   ├── SessionsSidebar.tsx  # Session list and management sidebar
│   │   ├── TerminalPane.tsx     # Terminal output and input component
│   │   ├── SettingsDialog.tsx   # Settings UI
│   │   └── TopBar.tsx           # Header and navigation
│   │
│   ├── hooks/                  # React custom hooks
│   │   ├── useAuth.ts          # Authentication state hook
│   │   ├── useSyncWebSocket.ts # Session sync WebSocket hook
│   │   └── useSidebarResize.ts # Sidebar resize management
│   │
│   ├── lib/                    # Shared utilities
│   │   ├── clientId.ts         # Client identification
│   │   ├── password-gen.ts     # Session password generation
│   │   └── utils.ts            # Common helpers
│   │
│   ├── server/                 # Express backend
│   │   ├── auth/               # Authentication logic
│   │   │   ├── middleware.ts   # Auth validation middleware
│   │   │   ├── routes.ts       # Auth API endpoints (/login, /register, etc.)
│   │   │   └── passkey-config.ts # WebAuthn/passkey configuration
│   │   ├── app.ts              # Express app factory
│   │   ├── server.ts           # Server startup and configuration
│   │   ├── cli.ts              # CLI entry point (puttry command)
│   │   ├── cli-configure.ts    # CLI configuration helpers
│   │   ├── vite-plugin.ts      # Vite dev server integration
│   │   ├── pty-manager.ts      # PTY session lifecycle management
│   │   ├── session-store.ts    # In-memory session storage
│   │   ├── terminal-routes.ts  # Terminal API endpoints (/api/sessions, /api/exec)
│   │   ├── auth-state.ts       # Password & passkey persistence
│   │   ├── passkey-state.ts    # Passkey credentials storage
│   │   ├── sync-bus.ts         # WebSocket broadcast for session sync
│   │   ├── settings-api.ts     # Configuration management API
│   │   ├── rate-limit.ts       # Rate limiting middleware
│   │   ├── password-hash.ts    # bcrypt password hashing
│   │   ├── totp-helper.ts      # TOTP 2FA generation and validation
│   │   ├── env-loader.ts       # Environment variable loading
│   │   └── logger.ts           # Structured logging
│   │
│   ├── __tests__/              # Test suites
│   │   ├── server/             # Server/backend tests (Node.js environment)
│   │   │   ├── unit/           # Unit tests for server modules
│   │   │   └── integration/    # Integration tests with real database/PTY
│   │   ├── browser/            # Browser/frontend tests (jsdom environment)
│   │   │   ├── components/     # React component tests
│   │   │   └── unit/           # Unit tests for hooks/utilities
│   │   ├── server/setup.ts     # Server test setup and fixtures
│   │   └── browser/setup.ts    # Browser test setup and fixtures
│   │
│   ├── App.tsx                 # Main React app component
│   ├── main.tsx                # Frontend entry point
│   └── vite-env.d.ts           # Vite type definitions
│
├── vitest.config.ts            # Vitest configuration (server + browser projects)
├── vite.config.ts              # Vite configuration
├── tsconfig.json               # TypeScript configuration
├── package.json                # Dependencies and scripts
├── .github/workflows/          # GitHub Actions workflows
│   ├── ci.yml                  # Automated tests on push/PR
│   ├── publish.yml             # npm publishing (with test gate)
│   └── docs.yml                # Deploy docs to GitHub Pages
└── README.md                   # User documentation

Development Scripts

npm run dev – Start Vite dev server with HMR (Hot Module Replacement)

npm run build – Build for production

npm run build:server – Bundle backend with esbuild

npm run build:cli – Bundle CLI with esbuild

npm run lint – Run ESLint

npm run preview – Preview production build locally

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):

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:

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:

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:

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
TOTP_ENABLED=0

See the startup logs for which env file was loaded and current settings.

Debugging

Browser DevTools

Server Logging

WebSocket Debugging

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

Running Tests

PuTTrY uses Vitest for testing with separate test projects for server and browser code.

Test Structure

Tests are organized into two projects:

Running Tests

Run all tests once:

npm run test

Run tests in watch mode (re-run on file change):

npm run test:watch

Run tests with coverage report:

npm run test:coverage

Coverage reports are generated in three formats:

Coverage thresholds are enforced:

Writing Tests

Server test example (src/__tests__/server/example.test.ts):

import { describe, it, expect } from 'vitest'
import { someFunction } from '@/server/module'

describe('someFunction', () => {
  it('should do something', () => {
    const result = someFunction()
    expect(result).toBe('expected')
  })
})

Browser test example (src/__tests__/browser/Component.test.tsx):

import { describe, it, expect } from 'vitest'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import MyComponent from '@/client/components/MyComponent'

describe('MyComponent', () => {
  it('should render and respond to click', async () => {
    const user = userEvent.setup()
    render(<MyComponent />)

    const button = screen.getByRole('button', { name: /click me/i })
    await user.click(button)

    expect(screen.getByText('Clicked!')).toBeInTheDocument()
  })
})

Test Configuration

Tests are configured in vitest.config.ts:

Debugging Tests

Run a single test file:

npx vitest src/__tests__/server/auth.test.ts

Run tests matching a pattern:

npx vitest -t "should authenticate"

Debug with Node DevTools (server tests):

node --inspect-brk node_modules/vitest/vitest.mjs run src/__tests__/server/example.test.ts

Then open chrome://inspect in Chrome to debug.

Enable verbose logging:

npx vitest --reporter=verbose

CI/CD Integration

PuTTrY has automated CI/CD workflows in GitHub Actions:

CI Workflow (.github/workflows/ci.yml)

Publish Workflow (.github/workflows/publish.yml)

Before pushing:

npm run lint
npm run test
npm run build:all

Ensure all checks pass locally before pushing. GitHub Actions will re-run tests automatically on push/PR.

Testing the Terminal

Once the dev server is running:

  1. Open http://localhost:5175 in your browser
  2. Enter the session password (shown in the terminal output)
  3. Create a new session via the Web UI
  4. Type commands in the terminal (e.g., ls, echo "hello")
  5. Try switching browsers/tabs to test write lock and synchronization
  6. Check browser DevTools Network tab to see WebSocket activity

Building for Production

To create a production build:

npm run build:all

This produces:

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.

Publishing to npm

PuTTrY supports two methods for publishing to the npm registry: manual publishing and automated publishing via GitHub Actions.

Option 1: Manual Publishing

Follow these steps to manually publish a new version:

1. Bump the Version

Use npm version to increment the version and create a git tag:

npm version patch    # 0.1.5 → 0.1.6 (bug fixes)
npm version minor    # 0.1.5 → 0.2.0 (new features)
npm version major    # 0.1.5 → 1.0.0 (breaking changes)

This automatically:

See semver for versioning guidelines.

2. Build and Test Locally

Ensure the build works before publishing:

npm run build:all

Test the CLI locally:

node dist-server/cli.js --help

3. Authenticate with npm

Log in to your npm account (if not already logged in):

npm login

You’ll be prompted for your username, password, and (if enabled) 2FA code.

4. Publish

Publish the new version:

npm publish

The prepublishOnly script in package.json will automatically run before publishing to ensure the server and CLI bundles are built:

npm run build:server && npm run build:cli

5. Push to Remote

Push the commit and tag as separate commands:

git push origin main
git push origin v0.1.6  # Replace with your actual version tag

Important: Do not use git push origin main --tags in a single command, as this may prevent the publish workflow from triggering.

6. Verify

Check that the new version is published:

npm view @chfischerx/puttry versions

Or visit https://www.npmjs.com/package/@chfischerx/puttry

Troubleshooting

Authentication failed: Ensure you’re logged in with npm login and your 2FA is enabled/disabled as configured.

Package already published: npm doesn’t allow re-publishing the same version. Bump the version number and try again.

Missing files: Ensure files in package.json includes the built directories (dist-server/, dist/). Run npm pack to preview what will be published.

Option 2: Automated Publishing via GitHub Actions

The publish workflow (.github/workflows/publish.yml) automatically publishes new versions to npm when you push a version tag to the repository.

1. Bump the Version

Use npm version locally (same as manual method):

npm version patch    # 0.1.5 → 0.1.6 (bug fixes)
npm version minor    # 0.1.5 → 0.2.0 (new features)
npm version major    # 0.1.5 → 1.0.0 (breaking changes)

This creates a git tag and commit locally.

2. Push to Remote

Push the commit and tag as separate commands to ensure GitHub Actions triggers correctly:

git push origin main
git push origin v0.1.6  # Replace with your actual version tag

Important: Do not use git push origin main --tags in a single command, as this may prevent the publish workflow from triggering.

3. Workflow Execution

GitHub Actions will automatically:

Benefits of Automated Publishing

Verification

After the workflow completes successfully:

npm view @chfischerx/puttry versions

Or visit https://www.npmjs.com/package/@chfischerx/puttry

Choosing a Method

Performance Considerations