@cerebralutopia/tap
v0.1.1
Published
Process supervisor with queryable logs
Maintainers
Readme
tap
A process supervisor with queryable logs. Run your services in the background and query their output through a Unix socket API.
Why tap?
- 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
Installation
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).
Example Instructions for Coding Agents
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.
Quick Start
# 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 myappMulti-Directory Support
tap automatically discovers services across subdirectories. This is useful for monorepos or projects with multiple components.
Service Naming
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 |
Example: Monorepo Setup
# 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 workerDisabling Discovery
Use --tap-dir to target a specific directory (disables recursive search):
tap ls --tap-dir ./backend/.tap
tap observe api --tap-dir ./backend/.tapCommands
tap run
Start 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 devtap observe
Fetch 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=2The --- line separates log content from metadata for easy parsing.
tap restart
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-logstap stop
Stop 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 10stap status
Get 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/9765KBtap ls
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 12sHow 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) │
└───────────────────────────────┘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.
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
Protections
- 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
Implications
| 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 |
Not Protected Against
- 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.
License
MIT
