- Go 51.4%
- TypeScript 41.5%
- CSS 6.4%
- Dockerfile 0.4%
- HTML 0.3%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .forgejo/workflows | ||
| client | ||
| server | ||
| .gitignore | ||
| AGENTS.md | ||
| Dockerfile | ||
| Dockerfile.ci | ||
| 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.25+, 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_AUTH_PASSWORD |
(empty) | If set, requires HTTP Basic auth on every request |
PIPIT_CORS_ORIGIN |
(empty) | Allowed CORS origin. Empty means same-origin only |
PIPIT_MAX_FILE_SIZE |
32MB |
Largest file that may be opened or saved. 0 disables the limit |
Auth
When PIPIT_AUTH_PASSWORD is set, pipit uses HTTP Basic authentication. The browser prompts for credentials on first load and then sends them automatically with every request, including the live-reload event stream.
The username is ignored — there is one shared password, so enter anything you like as the user:
docker run -p 8080:8080 -e PIPIT_AUTH_PASSWORD=hunter2 \
-v /path/to/your/project:/workspace git.w33ble.com/w33ble/pipit:latest
curl -u pipit:hunter2 http://localhost:8080/api/tree?path=/
When PIPIT_AUTH_PASSWORD is empty (default), no authentication is required.
Everything is behind the password, including the web UI itself — an unauthenticated visitor gets a prompt and nothing else. GET /api/health is the one exception, so container healthchecks keep working.
Basic auth sends the password on every request, encoded but not encrypted. Over plain HTTP anyone who can watch the traffic can read it. Treat it as a way to keep a casual second party out of a file browser on a network you already trust, not as a security boundary — pipit is not built to face the public internet.
File size
Pipit is meant to stay small enough to leave running on a server, so a single
read or write is capped by PIPIT_MAX_FILE_SIZE (default 32MB, accepts a
plain byte count or a KB/MB/GB suffix). Opening or saving something
larger returns 413 with an explanation rather than loading it into memory.
Set it to 0 to remove the limit.
Saves stream to a temporary file next to the target and are renamed into place, so memory use does not scale with file size and an interrupted save leaves the previous contents intact.
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 routing, os.Root sandboxed file access) + 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