Skip to content

Repository files navigation

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 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).

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 changes

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

# 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 myapp

Multi-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 worker

Disabling Discovery

Use --tap-dir to target a specific directory (disables recursive search):

tap ls --tap-dir ./backend/.tap
tap observe api --tap-dir ./backend/.tap

Commands

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 dev

tap 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 --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.

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-logs

tap 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 10s

tap 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 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

tap 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/.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 API
  2. 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/<name>.sock.

  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

Protections

  • 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

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

About

CLI tool for wrapping and supervising long-running processes and allowing an agent to query their output and restart

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages