- TypeScript 45.9%
- Go 44.3%
- CSS 9.2%
- Dockerfile 0.4%
- HTML 0.2%
| client | ||
| server | ||
| .gitignore | ||
| AGENTS.md | ||
| Dockerfile | ||
| mise.toml | ||
| README.md | ||
Pipit
A lightweight web-based file browser and code editor. Browse and edit files through a familiar split-pane IDE interface — file tree on the left, editor on the right.
Go backend, React frontend. Monaco editor with syntax highlighting. Live-reloads when files change on disk.
Quick Start
Docker (recommended)
docker pull git.w33ble.com/w33ble/pipit:latest
docker run -p 8080:8080 -v /path/to/your/project:/workspace git.w33ble.com/w33ble/pipit:latest
Open http://localhost:8080.
Development
Prerequisites: Go 1.23+, Bun
# Terminal 1 — backend
cd server
go run .
# Terminal 2 — frontend (with hot reload)
cd client
bun install
bun run dev
The Vite dev server proxies /api requests to the Go backend at http://localhost:8080, so open http://localhost:5173.
Configuration
All settings via environment variables with a PIPIT_ prefix. Everything has a default.
| Variable | Default | Description |
|---|---|---|
PIPIT_PORT |
8080 |
Server listen port |
PIPIT_ROOT_DIR |
. (current directory) |
Root directory to serve files from |
PIPIT_BEARER_TOKEN |
(empty) | If set, requires Authorization: Bearer <token> on all API requests |
PIPIT_CORS_ORIGIN |
* |
Allowed CORS origin |
Auth
When PIPIT_BEARER_TOKEN is set, the UI shows a login screen. Enter the token to connect. The token is stored in localStorage and persists across tab closes.
When PIPIT_BEARER_TOKEN is empty (default), no authentication is required.
API
All paths are relative to PIPIT_ROOT_DIR. Path traversal (../) is rejected.
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/health |
Health check |
GET |
/api/tree?path=/ |
List directory contents |
GET |
/api/file?path=/foo.ts |
Read file contents |
PUT |
/api/file?path=/foo.ts |
Save file (body = raw content) |
POST |
/api/file?path=/new.ts |
Create empty file |
DELETE |
/api/file?path=/foo.ts |
Delete file |
POST |
/api/dir?path=/newdir |
Create directory |
DELETE |
/api/dir?path=/olddir |
Delete directory (must be empty) |
POST |
/api/rename |
Rename file/directory (JSON: {"oldPath","newPath"}) |
GET |
/api/events |
SSE stream of file change events |
SSE Events
The /api/events endpoint streams real-time file changes. Events are JSON:
{"type":"created","path":"src/new.ts"}
{"type":"modified","path":"src/app.tsx"}
{"type":"deleted","path":"src/old.ts"}
Tech Stack
Backend: Go standard library (net/http 1.22+ routing) + fsnotify for file watching
Frontend:
- React 19 + TypeScript
- TanStack Router (typed context)
- TanStack Query v5 (server state)
- Monaco Editor (
@monaco-editor/react) react-resizable-panels(split pane layout)
Everything: Tailwind-free, no CSS framework. Dark theme using CSS custom properties.
Project Structure
server/ Go backend
main.go Entry point, middleware stack, routes
internal/
config/ Env var parsing (PIPIT_ prefix)
handler/ REST + SSE endpoints
middleware/ CORS, auth, logging, path safety
watcher/ fsnotify with debounce dedup
client/ React frontend (Vite)
src/
components/ FileTree, EditorTabs, MonacoEditor, Toolbar, LoginGate, Toast
hooks/ useFileTree, useFileContent, useFileOps, useOpenTabs, useSSE
lib/ API client, language detection
Testing
# Backend
cd server && go test ./... -v -count=1
# Frontend
cd client && bun run test