Process supervisor with queryable logs
npm install @cerebralutopia/tapA 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
``bash`
npm install
npm run build
Platform 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.
`bashList running processes
tap ls # Shows name, state, PID, uptime
When 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 generate to pick up the regenerated Prisma client. Use tap restart website.Quick Start
`bash
Start a service
tap run myapp -- node server.jsView recent logs
tap observe myapp --last 50Check status
tap status myappRestart the service
tap restart myappStop everything
tap stop myapp
`Multi-Directory Support
tap automatically discovers services across subdirectories. This is useful for monorepos or projects with multiple components.
$3
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 |$3
`bash
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.sockList 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 worker
`$3
Use
--tap-dir to target a specific directory (disables recursive search):`bash
tap ls --tap-dir ./backend/.tap
tap observe api --tap-dir ./backend/.tap
`Commands
$3
Start a runner server and child process.
`bash
tap run [options] --
`Arguments:
| Argument | Description |
|----------|-------------|
|
| Service name (e.g., "api" or "frontend:api") |Options:
| Option | Description | Default |
|--------|-------------|---------|
|
--tap-dir | Override .tap directory | ./.tap |
| --cwd | Working directory for child | Current directory |
| --env | Add/override env var (repeatable) | - |
| --env-file | Load env vars from file | - |
| --pty | Use PTY for child process | Off |
| --no-forward | Don't forward output to stdout | Forward on |
| --buffer-lines | Ring buffer max events | 5000 |
| --buffer-bytes | Ring buffer max bytes | 10000000 |
| --ready | Substring to wait for in output | - |
| --ready-regex | Regex to wait for in output | - |
| --print-connection | Print socket path and PID on startup | Off |Examples:
`bash
Basic usage
tap run api -- node server.jsWith environment variables
tap run api --env PORT=3000 --env NODE_ENV=production -- node server.jsWith env file and readiness check
tap run api --env-file .env --ready "listening on port" -- node server.jsWith PTY (for programs that need a terminal)
tap run app --pty -- npm run dev
`$3
Fetch logs from a running service.
`bash
tap observe [options]
`Arguments:
| Argument | Description |
|----------|-------------|
|
| Service name (e.g., "api" or "frontend:api") |Options:
| Option | Description | Default |
|--------|-------------|---------|
|
--tap-dir | Override .tap directory | - |
| --since | Events since duration ago (e.g., 5m, 1h) | - |
| --last | Last N events | 80 |
| --since-cursor | Events since cursor sequence | - |
| --since-last | Since last observed cursor | - |
| --grep | Filter by pattern | - |
| --regex | Treat grep as regex | Off |
| --case-sensitive | Case-sensitive matching | Off |
| --invert | Invert match | Off |
| --stream | Filter: combined, stdout, stderr | combined |
| --max-lines | Max lines to return | 80 |
| --max-bytes | Max bytes to return | 32768 |
| --format | 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:
`bash
Get last 100 lines (text format is default)
tap observe api --last 100Get logs from the last 5 minutes
tap observe api --since 5mSearch for errors
tap observe api --grep "error" --case-sensitiveGet only stderr
tap observe api --stream stderrContinuous polling (since last cursor)
tap observe api --since-lastWith sequence numbers and stream info
tap observe api --show-seq --show-streamJSON output
tap observe api --json
`Sample 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.$3
Restart the child process without stopping the runner.
`bash
tap restart [options]
`Arguments:
| Argument | Description |
|----------|-------------|
|
| Service name (e.g., "api" or "frontend:api") |Options:
| Option | Description | Default |
|--------|-------------|---------|
|
--tap-dir | Override .tap directory | ./.tap |
| --timeout | Readiness wait timeout | 20s |
| --ready | Substring readiness pattern | - |
| --ready-regex | Regex readiness pattern | - |
| --grace | Grace period before SIGKILL | 2s |
| --clear-logs | Clear ring buffer on restart | Off |
| --format | Output format: json, text | json |Examples:
`bash
Simple restart
tap restart apiRestart with readiness check
tap restart api --ready "listening on port" --timeout 30sClear logs on restart
tap restart api --clear-logs
`$3
Stop the runner and child process.
`bash
tap stop [options]
`Arguments:
| Argument | Description |
|----------|-------------|
|
| Service name (e.g., "api" or "frontend:api") |Options:
| Option | Description | Default |
|--------|-------------|---------|
|
--tap-dir | Override .tap directory | ./.tap |
| --timeout | Request timeout | 5s |
| --grace | Grace period before SIGKILL | 2s |
| --format | Output format: json, text | json |Examples:
`bash
Stop a service
tap stop apiStop with longer grace period
tap stop api --grace 10s
`$3
Get runner and child status.
`bash
tap status [options]
`Arguments:
| Argument | Description |
|----------|-------------|
|
| Service name (e.g., "api" or "frontend:api") |Options:
| Option | Description | Default |
|--------|-------------|---------|
|
--tap-dir | Override .tap directory | ./.tap |
| --timeout | Request timeout | 5s |
| --format | Output format: json, text | json |Examples:
`bash
Get status as JSON
tap status apiGet human-readable status
tap status api --format text
`Sample 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
`$3
List all known services. Recursively discovers services in subdirectories.
`bash
tap ls [options]
`Options:
| Option | Description | Default |
|--------|-------------|---------|
|
--tap-dir | Override .tap directory (disables recursive search) | - |
| --format | Output format: json, text | text |Examples:
`bash
List all services (recursive)
tap lsList as JSON
tap ls --jsonList only services in a specific directory
tap ls --tap-dir ./backend/.tap
`Sample 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
`How It Works
`
┌─────────────────────────────────────────────────────────┐
│ tap run │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Runner Server │ │
│ │ ┌──────────┐ ┌──────────────────────────┐ │ │
│ │ │ Child │───▶│ Ring Buffer │ │ │
│ │ │ Process │ │ (stdout/stderr lines) │ │ │
│ │ └──────────┘ └──────────────────────────┘ │ │
│ │ │ │ │ │
│ │ ▼ ▼ │ │
│ │ [forward] Unix Socket │ │
│ │ │ (.tap/name.sock) │ │
│ └───────│────────────────────────│────────────────┘ │
│ ▼ │ │
│ stdout │ │
└───────────────────────────────────│────────────────────┘
│
▼
┌───────────────────────────────┐
│ tap observe / status / ... │
│ (HTTP over UDS) │
└───────────────────────────────┘
`1. Runner: The
tap run command starts a runner process that:
- Spawns and manages a child process
- Captures stdout/stderr into a ring buffer
- Exposes a Unix socket HTTP API2. 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.
3. Unix Socket API: All commands except
run communicate with the runner over HTTP via Unix domain sockets at .tap/.4. Readiness: The runner can watch for a pattern in output to determine when the child is ready, useful for deployment scripts.
Security Model
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$3
- The
.tap/ directory is created with mode 0700 (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$3
| 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 |$3
- 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 itThis follows the standard Unix security model used by SSH agent sockets, Docker sockets, and tmux.
MIT