A process supervisor with queryable logs. Run your services in the background and query their output through a Unix socket API.
- Queryable logs: Filter, search, and paginate through output without log files
- Ring buffer: Keeps recent output in memory with configurable limits
- Unix socket API: Query logs programmatically from any language
- Readiness checks: Wait for a pattern in output before considering a service ready
- Zero dependencies at runtime: Pure Node.js with Unix sockets
npm install
npm run buildPlatform Support: tap requires Unix domain sockets and POSIX signals, so it runs on macOS and Linux. Windows users should use WSL (Windows Subsystem for Linux).
The tap command is a process supervisor with queryable logs. Use it to monitor server processes, search logs for errors, and restart services.
# List running processes
tap ls # Shows name, state, PID, uptime
# View process status
tap status website --format text
# View logs
tap observe website --last 50 # Last 50 lines
tap observe website --since 5m # Logs from last 5 minutes
tap observe website --stream stderr # Only stderr output
tap observe website --show-seq --show-stream # With sequence numbers and stream labels
# Search logs
tap observe website --grep "error" # Substring search
tap observe website --grep "error|warning" --regex # Regex search
tap observe website --grep "Error" --case-sensitive # Case-sensitive
# Restart a process (useful for workers that don't hot reload)
tap restart website
tap restart worker # Worker process requires restart after code changesWhen to restart processes:
- Worker processes: Don't hot reload. After modifying worker code, use
tap restart worker. - Next.js server: Restart after running
npx prisma generateto pick up the regenerated Prisma client. Usetap restart website.
# Start a service
tap run myapp -- node server.js
# View recent logs
tap observe myapp --last 50
# Check status
tap status myapp
# Restart the service
tap restart myapp
# Stop everything
tap stop myapptap automatically discovers services across subdirectories. This is useful for monorepos or projects with multiple components.
Services in subdirectories use a colon-separated prefix:
| Socket Location | Service Name |
|---|---|
.tap/api.sock |
api |
frontend/.tap/web.sock |
frontend:web |
backend/.tap/api.sock |
backend:api |
services/auth/.tap/main.sock |
services/auth:main |
# Start services in different directories
cd ~/myproject
tap run frontend:dev -- npm run dev # Creates frontend/.tap/dev.sock
tap run backend:api -- node server.js # Creates backend/.tap/api.sock
tap run worker -- node worker.js # Creates .tap/worker.sock
# List all services from project root
tap ls
# NAME STATE PID UPTIME
# ------------------------------------------------
# frontend:dev running 12345 5m 30s
# backend:api running 12346 5m 28s
# worker running 12347 5m 25s
# Query any service by name
tap observe frontend:dev --last 20
tap status backend:api
tap restart workerUse --tap-dir to target a specific directory (disables recursive search):
tap ls --tap-dir ./backend/.tap
tap observe api --tap-dir ./backend/.tapStart a runner server and child process.
tap run <name> [options] -- <command...>Arguments:
| Argument | Description |
|---|---|
<name> |
Service name (e.g., "api" or "frontend:api") |
Options:
| Option | Description | Default |
|---|---|---|
--tap-dir <path> |
Override .tap directory | ./.tap |
--cwd <path> |
Working directory for child | Current directory |
--env <KEY=VAL> |
Add/override env var (repeatable) | - |
--env-file <path> |
Load env vars from file | - |
--pty |
Use PTY for child process | Off |
--no-forward |
Don't forward output to stdout | Forward on |
--buffer-lines <N> |
Ring buffer max events | 5000 |
--buffer-bytes <N> |
Ring buffer max bytes | 10000000 |
--ready <pattern> |
Substring to wait for in output | - |
--ready-regex <regex> |
Regex to wait for in output | - |
--print-connection |
Print socket path and PID on startup | Off |
Examples:
# Basic usage
tap run api -- node server.js
# With environment variables
tap run api --env PORT=3000 --env NODE_ENV=production -- node server.js
# With env file and readiness check
tap run api --env-file .env --ready "listening on port" -- node server.js
# With PTY (for programs that need a terminal)
tap run app --pty -- npm run devFetch logs from a running service.
tap observe <name> [options]Arguments:
| Argument | Description |
|---|---|
<name> |
Service name (e.g., "api" or "frontend:api") |
Options:
| Option | Description | Default |
|---|---|---|
--tap-dir <path> |
Override .tap directory | - |
--since <duration> |
Events since duration ago (e.g., 5m, 1h) |
- |
--last <N> |
Last N events | 80 |
--since-cursor <seq> |
Events since cursor sequence | - |
--since-last |
Since last observed cursor | - |
--grep <pattern> |
Filter by pattern | - |
--regex |
Treat grep as regex | Off |
--case-sensitive |
Case-sensitive matching | Off |
--invert |
Invert match | Off |
--stream <type> |
Filter: combined, stdout, stderr |
combined |
--max-lines <N> |
Max lines to return | 80 |
--max-bytes <N> |
Max bytes to return | 32768 |
--format <type> |
Output format: text, json |
text |
--json |
Output JSON | Off |
--show-seq |
Prepend sequence number to each line | Off |
--show-ts |
Prepend relative timestamp to each line | Off |
--show-stream |
Prepend stream (stdout/stderr) to each line | Off |
Examples:
# Get last 100 lines (text format is default)
tap observe api --last 100
# Get logs from the last 5 minutes
tap observe api --since 5m
# Search for errors
tap observe api --grep "error" --case-sensitive
# Get only stderr
tap observe api --stream stderr
# Continuous polling (since last cursor)
tap observe api --since-last
# With sequence numbers and stream info
tap observe api --show-seq --show-stream
# JSON output
tap observe api --jsonSample output (text):
Server started on port 3000
Handling request GET /api/users
---
cursor=25 truncated=false dropped=false matches=2
The --- line separates log content from metadata for easy parsing.
Restart the child process without stopping the runner.
tap restart <name> [options]Arguments:
| Argument | Description |
|---|---|
<name> |
Service name (e.g., "api" or "frontend:api") |
Options:
| Option | Description | Default |
|---|---|---|
--tap-dir <path> |
Override .tap directory | ./.tap |
--timeout <duration> |
Readiness wait timeout | 20s |
--ready <pattern> |
Substring readiness pattern | - |
--ready-regex <regex> |
Regex readiness pattern | - |
--grace <duration> |
Grace period before SIGKILL | 2s |
--clear-logs |
Clear ring buffer on restart | Off |
--format <type> |
Output format: json, text |
json |
Examples:
# Simple restart
tap restart api
# Restart with readiness check
tap restart api --ready "listening on port" --timeout 30s
# Clear logs on restart
tap restart api --clear-logsStop the runner and child process.
tap stop <name> [options]Arguments:
| Argument | Description |
|---|---|
<name> |
Service name (e.g., "api" or "frontend:api") |
Options:
| Option | Description | Default |
|---|---|---|
--tap-dir <path> |
Override .tap directory | ./.tap |
--timeout <duration> |
Request timeout | 5s |
--grace <duration> |
Grace period before SIGKILL | 2s |
--format <type> |
Output format: json, text |
json |
Examples:
# Stop a service
tap stop api
# Stop with longer grace period
tap stop api --grace 10sGet runner and child status.
tap status <name> [options]Arguments:
| Argument | Description |
|---|---|
<name> |
Service name (e.g., "api" or "frontend:api") |
Options:
| Option | Description | Default |
|---|---|---|
--tap-dir <path> |
Override .tap directory | ./.tap |
--timeout <duration> |
Request timeout | 5s |
--format <type> |
Output format: json, text |
json |
Examples:
# Get status as JSON
tap status api
# Get human-readable status
tap status api --format textSample output (text):
Name: api
State: running
Runner PID: 12345
Child PID: 12346
Uptime: 2h 15m 30s
PTY: false
Forward: true
Buffer: 1234/5000 lines, 256KB/9765KB
List all known services. Recursively discovers services in subdirectories.
tap ls [options]Options:
| Option | Description | Default |
|---|---|---|
--tap-dir <path> |
Override .tap directory (disables recursive search) | - |
--format <type> |
Output format: json, text |
text |
Examples:
# List all services (recursive)
tap ls
# List as JSON
tap ls --json
# List only services in a specific directory
tap ls --tap-dir ./backend/.tapSample output:
NAME STATE PID UPTIME
----------------------------------------------------
api running 12345 2h 15m 30s
frontend:web running 12348 1h 20m 15s
backend:worker running 12350 1h 45m 12s
┌─────────────────────────────────────────────────────────┐
│ tap run │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Runner Server │ │
│ │ ┌──────────┐ ┌──────────────────────────┐ │ │
│ │ │ Child │───▶│ Ring Buffer │ │ │
│ │ │ Process │ │ (stdout/stderr lines) │ │ │
│ │ └──────────┘ └──────────────────────────┘ │ │
│ │ │ │ │ │
│ │ ▼ ▼ │ │
│ │ [forward] Unix Socket │ │
│ │ │ (.tap/name.sock) │ │
│ └───────│────────────────────────│────────────────┘ │
│ ▼ │ │
│ stdout │ │
└───────────────────────────────────│────────────────────┘
│
▼
┌───────────────────────────────┐
│ tap observe / status / ... │
│ (HTTP over UDS) │
└───────────────────────────────┘
-
Runner: The
tap runcommand starts a runner process that:- Spawns and manages a child process
- Captures stdout/stderr into a ring buffer
- Exposes a Unix socket HTTP API
-
Ring Buffer: Keeps the last N lines (default 5000) or N bytes (default 10MB) of output in memory. Old entries are evicted when limits are reached.
-
Unix Socket API: All commands except
runcommunicate with the runner over HTTP via Unix domain sockets at.tap/<name>.sock. -
Readiness: The runner can watch for a pattern in output to determine when the child is ready, useful for deployment scripts.
The trust boundary is filesystem access to the .tap/ directory.
Anyone who can read/write to the .tap directory can fully control all tap processes in that directory:
- Query logs from any service
- Restart any child process
- Stop any runner
- The
.tap/directory is created with mode0700(owner-only access) - Service names are validated to prevent path traversal attacks
- Regex patterns are validated to prevent ReDoS attacks
- Request body sizes are limited to prevent memory exhaustion
| Environment | Security |
|---|---|
| Single-user machine | Secure - only you can access your .tap/ directory |
| Multi-user, separate working dirs | Secure - each user has their own .tap/ |
Shared directory (e.g., /tmp) |
Not secure - anyone with directory access controls your processes |
- Root: By design, root can access anything
- Same-user processes: Other processes running as your user can access the socket
- Parent directory access: Users with write access to the parent of
.tap/could potentially manipulate it
This follows the standard Unix security model used by SSH agent sockets, Docker sockets, and tmux.
MIT