@harms-haus/pi-processes
Process definitions and utilities for the pi framework
Package details
Install @harms-haus/pi-processes from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@harms-haus/pi-processes- Package
@harms-haus/pi-processes- Version
0.1.0- Published
- May 25, 2026
- Downloads
- not available
- Author
- baharms
- License
- MIT
- Types
- extension, skill
- Size
- 88.7 KB
- Dependencies
- 0 dependencies · 3 peers
Pi manifest JSON
{
"extensions": [
"./src/index.ts"
],
"skills": [
"./skills/process-tools"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-processes
Process management extension for the pi Coding Agent. Provides tools to spawn, monitor, kill, and inspect long-running processes (dev servers, watchers, API backends) directly from a pi session.
Features
- Debounce-based startup detection — waits for process output to quiet down before declaring startup complete, giving you the actual boot logs.
- Output capture — stdout and stderr are captured in a single in-memory log buffer (max 10 000 lines, FIFO eviction).
- SIGTERM → SIGKILL escalation — graceful shutdown with automatic force-kill fallback.
- Log querying — head, tail, or arbitrary line-range queries against captured logs.
- Process Logs Dialog — interactive TUI overlay (
Ctrl+Alt+P) to browse, select, and insert log lines from managed processes. - Process lifecycle — start, list, kill, restart, and inspect up to 50 concurrent managed processes.
Installation
Recommended — via pi install
pi install git:github.com/harms-haus/pi-processes
That's it. The extension initializes automatically on every pi session start.
Manual — from source
git clone https://github.com/harms-haus/pi-processes.git
cd pi-processes
npm install
The extension is discovered automatically via the pi.extensions field in package.json:
// package.json
{
"pi": {
"extensions": ["./src/index.ts"]
}
}
No additional configuration is required — the extension initializes on every pi session start.
Available Tools
start_process
Spawn a long-running process and wait for its startup output to settle.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
name |
string | Yes | — | Unique identifier for the process. |
command |
string | Yes | — | Shell command to execute. |
start_delay |
number | No | 5 |
Seconds of output silence before declaring startup complete. |
Returns StartupResult:
{
name: string; // process name
pid: number; // OS process ID
startupTime: number; // ms from spawn to debounce completion
maxDelay: number; // max seconds between consecutive log lines during startup
logs: string; // all startup output, joined by newlines
}
Example:
start_process(name="api-server", command="npm run dev", start_delay=3)
Output:
Process 'api-server' started (PID 12345).
Startup time: 2.4s
Max log delay: 1s
> my-app@1.0.0 dev
> vite
VITE v5.2.0 ready in 240 ms
➜ Local: http://localhost:5173/
list_processes
List all active managed processes with their current status.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| (none) | — | — | — | — |
Returns ListProcessesResult:
{
processes: ProcessInfo[];
count: number;
}
Each ProcessInfo:
{
name: string; // unique process name
pid: number; // OS process ID
command: string; // spawn command
startTime: number; // Date.now() at spawn
running: boolean; // whether the process is still alive
uptimeSec: number; // seconds since start
logLines: number; // total captured log lines
startupComplete: boolean;
}
Example:
list_processes()
Output:
api-server (PID 12345) | npm run dev | uptime: 342.1s | 128 lines | ready
watcher (PID 12350) | npx tsc --watch | uptime: 340.5s | 42 lines | starting
kill_process
Terminate a managed process by name. Sends SIGTERM first, then SIGKILL after 5 seconds if the process hasn't exited. If the process has already exited naturally, calling kill_process simply removes its record from the manager (no signals are sent).
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
name |
string | Yes | — | Name of the process to kill. |
Returns KillResult:
{
name: string; // process name
pid: number; // OS process ID
totalRuntime: number; // total ms from start to kill
}
Example:
kill_process(name="api-server")
Output:
Process 'api-server' killed (PID 12345). Total runtime: 342.1s
process_logs
Read captured log output from a managed process. Supports three positional query modes (head, tail, or start/end range) plus optional grep filtering that can be combined with any positional mode.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
name |
string | Yes | — | Name of the process. |
head |
number | No | — | Return the first N lines. |
tail |
number | No | — | Return the last N lines. |
start |
number | No | — | Start line number (1-based, inclusive). |
end |
number | No | — | End line number (1-based, inclusive). |
grep |
string | No | — | Filter log lines by regex pattern. |
grepLiteral |
boolean | No | false |
Treat grep pattern as a literal string. |
grepIgnoreCase |
boolean | No | false |
Case-insensitive grep matching. |
Constraint: head, tail, and start/end are mutually exclusive positional modes — they cannot be combined with each other. The grep/grepLiteral/grepIgnoreCase parameters are independent and can be used with any positional mode or on their own.
Returns ProcessLogsResult:
{
logs: string; // formatted log lines
totalLines: number; // total lines in the log buffer
returnedLines: number; // lines returned by this query
}
Each log line is formatted as:
[lineNum] +Nms [stream] text
Where +Nms is milliseconds since process start and stream is stdout or stderr.
Examples:
// First 10 lines
process_logs(name="api-server", head=10)
// Last 5 lines
process_logs(name="api-server", tail=5)
// Lines 20 through 35
process_logs(name="api-server", start=20, end=35)
// All logs
process_logs(name="api-server")
Grep Examples
Filter logs by regex pattern:
// Find all lines containing "error" (case-insensitive)
process_logs(name="api-server", grep="error", grepIgnoreCase=true)
// Filter for a literal string containing regex metacharacters
process_logs(name="api-server", grep="[WARN]", grepLiteral=true)
// Combine grep with tail — last 5 lines matching "ECONNREFUSED"
process_logs(name="api-server", grep="ECONNREFUSED", tail=5)
// Grep across a line range
process_logs(name="api-server", grep="timeout", start=100, end=200)
restart_process
Kill a managed process (if running) and immediately start it again. Reuses the previous command unless a new one is provided.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
name |
string | Yes | — | Name of the process to restart. |
command |
string | No | previous command | New shell command to use. |
start_delay |
number | No | 5 |
Seconds of output silence before declaring ready. |
Returns StartupResult (same as start_process).
Example:
restart_process(name="api-server", command="npm run dev -- --host")
Output:
Process 'api-server' restarted (PID 12360).
Startup time: 2.1s
Max log delay: 1s
> my-app@1.0.0 dev
> vite --host
VITE v5.2.0 ready in 210 ms
➜ Local: http://localhost:5173/
➜ Network: http://192.168.1.10:5173/
Keyboard Shortcuts
| Shortcut | Description |
|---|---|
Ctrl+Alt+P |
Open the Process Logs Dialog |
The Process Logs Dialog is a TUI overlay that lets you visually browse, select, and insert log lines from managed processes.
Dialog Features
- Tab bar — Switch between processes with
Tab/Shift+Tab - Scrollable log viewport — Navigate with
↑/↓arrows - Multi-select — Hold
Shift+↑/↓to select multiple lines - Insert to compose — Press
Ctrl+Enterto insert selected logs into the compose box - Close — Press
Escto close without inserting - Timestamps — Logs display elapsed time as
+HH:MM:SS.mmm - Diagnostics — Footer shows process status (● running/exited), PID, uptime, log count
- Keybinding hints — Available shortcuts shown at the bottom of the dialog
The dialog requires a UI session (hasUI). In headless/RPC mode, the shortcut is a no-op.
Use Cases
Debug / Dev Servers
Start a dev server and get its boot logs immediately, including the listening port:
start_process(name="frontend", command="npm run dev", start_delay=3)
start_process(name="backend", command="npx nodemon src/index.ts", start_delay=4)
File Watchers
Run a TypeScript watcher alongside your dev server:
start_process(name="tsc-watch", command="npx tsc --watch --preserveWatchOutput")
Check logs after making edits:
process_logs(name="tsc-watch", tail=20)
API Servers
Start a backend, verify it's ready, then inspect its logs for connection errors:
start_process(name="api", command="node server.js")
list_processes() // confirm it's "ready"
process_logs(name="api", tail=50) // check for errors
restart_process(name="api") // restart after config change
Architecture
┌─────────────────────────────────────────────────────┐
│ pi Extension API │
│ (session_start / session_shutdown / │
│ registerTool / registerShortcut) │
└──────┬──────────────────────────┬───────────┬────────┘
│ │ │
▼ ▼ │
┌──────────────────────┐ ┌────────────┐ │
│ ProcessManager │ │ Tools │ │ registerShortcut
│ │ │ │ │ (Ctrl+Alt+P)
│ Map<string, │◄───│ start_… │ │
│ ProcessRecord> │ │ list_… │ │
│ │ │ kill_… │ │
│ start() ────────────┼───►│ logs │ │
│ kill() ─────────────┼───►│ restart_… │ │
│ list() │ └─────────────┘ │
│ getLogs() │ │
│ getLogOffset() │ ┌──────────────────┐│
│ restart() │ │ process-logs.ts ││
│ shutdown() │ │ (queryLogs) ││
│ │ │ (queryLogs) ││
└──────────┬───────────┘ └──────────────────┘│
│ │
│ getLogs() ▼
│ ┌──────────────────┐
└──────────────────────────►│ LogDialog │
│ (ui/log-dialog) │
│ │
│ ┌──────────────┐ │
│ │format-timestamp│ │
│ └──────────────┘ │
└──────────────────┘
Process Lifecycle
- Session start →
ProcessManageris instantiated. - Tool call (
start_process) →spawn()creates a child process with piped stdio. - Startup debounce → each stdout/stderr chunk resets a timer. When output goes quiet for
start_delayseconds, the tool resolves with boot logs. - Log capture → lines are appended to an in-memory log buffer (max 10 000 entries). Both stdout and stderr are captured identically; oldest lines are evicted via Array.shift() when the limit is reached.
Line numbers are stable across eviction. When the buffer exceeds
MAX_LOG_ENTRIES, the oldest entries are discarded, but displayed line numbers reflect the absolute position since process start. For example, after 15 000 lines have been produced,head=10would show lines[5001]–[5010]. - Kill →
SIGTERMis sent. If the process hasn't exited within 5 seconds,SIGKILLis sent. - Session shutdown → all managed processes are killed and the manager is cleared.
Configuration Constants
| Constant | Value | Description |
|---|---|---|
DEFAULT_START_DELAY |
5 seconds |
Default debounce silence period for startup. |
MAX_PROCESSES |
50 |
Maximum concurrent managed processes. |
MAX_LOG_ENTRIES |
10 000 |
Max log lines retained per process (FIFO eviction). |
SIGKILL_DELAY_MS |
5000 ms |
Grace period after SIGTERM before SIGKILL. |
MAX_LOG_LINE_BYTES |
8192 |
Maximum byte length for a single log line. Lines exceeding this are truncated with ... suffix. |
KILL_FORCE_RESOLVE_MS |
5000 ms |
Grace period after SIGKILL before force-resolving the kill promise for uninterruptible processes. |
Development
Prerequisites
- Node.js ≥ 20
- npm
Scripts
| Script | Description |
|---|---|
npm run typecheck |
Type-check with tsc --noEmit |
npm run lint |
Lint with ESLint |
npm run lint:fix |
Auto-fix ESLint issues |
npm run format |
Format code with Prettier |
npm run format:check |
Check formatting (CI) |
npm run test |
Run tests once (Vitest) |
npm run test:watch |
Run tests in watch mode |
npm run test:coverage |
Run tests with v8 coverage (90% thresholds) |
Project Structure
src/
├── index.ts # Extension entry point
├── types.ts # Shared types and schemas
├── process-manager.ts # Core process management
├── process-logs.ts # Log query helper
├── ui/ # TUI components
│ ├── format-timestamp.ts # Timestamp formatting
│ ├── log-dialog.ts # Process logs dialog
│ └── open-log-dialog.ts # Overlay factory & dialog opener
├── tools/ # Tool definitions
│ ├── start-process.ts
│ ├── list-processes.ts
│ ├── kill-process.ts
│ ├── process-logs.ts
│ ├── restart-process.ts
│ └── format-startup-result.ts
└── __tests__/
├── index.test.ts
├── process-manager.test.ts
├── process-logs.test.ts
├── helpers/
│ ├── index.ts
│ ├── mock-manager.ts
│ ├── mock-theme.ts
│ ├── execute-tool.ts
│ └── make-logs.ts
├── tools/
│ ├── error-propagation.test.ts
│ ├── format-startup-result.test.ts
│ ├── start-process.test.ts
│ ├── kill-process.test.ts
│ ├── list-processes.test.ts
│ ├── process-logs.test.ts
│ └── restart-process.test.ts
└── ui/
├── format-timestamp.test.ts
└── log-dialog.test.ts
Testing
Tests are written with Vitest and organized in src/__tests__/ and src/__tests__/tools/.
npm run test
Linting & Formatting
Linting is handled by ESLint with typescript-eslint and eslint-config-prettier. Formatting is handled by Prettier.
npm run lint # check for issues
npm run format # auto-format
npm run format:check # CI-friendly format check
Publishing
The package is published to npm via a tag-based dry-run workflow (.github/workflows/publish.yml). The engines field in package.json requires Node ≥ 20, and .nvmrc pins the development environment to Node 20.
Dependencies
The extension depends on @earendil-works/pi-coding-agent for the extension API and @earendil-works/pi-tui for TUI rendering components (Key, matchesKey, truncateToWidth, Container, Text, Spacer). Both are declared as peer dependencies.
Known Limitations
- Exited processes remain in memory: Processes that exit naturally are retained in the internal Map (with their logs) until explicitly killed via
kill_processorshutdown. This is intentional to allow log inspection after exit. The Map is capped at 50 processes. - Log eviction uses Array.shift(): The log ring buffer uses
Array.shift()for eviction when MAX_LOG_ENTRIES is reached, which is O(n) per eviction. For extremely high-throughput processes producing thousands of log lines, this may cause GC pressure. - Log line truncation: Individual log lines exceeding 8,192 bytes (
MAX_LOG_LINE_BYTES) are truncated to prevent unbounded memory growth. Truncated lines are suffixed with....
License
MIT