Releases: 3leaps/sysprims
v0.1.13
v0.1.13 - macOS Command-Line Fidelity Fix & Binding Coverage
Release Date: 2026-02-13
Status: macOS Command-Line Fidelity Fix & Binding Coverage
Summary
This release fixes a high-severity bug where processList() returned truncated cmdline on macOS (just the process name instead of the full argument vector), breaking downstream consumers that filter by command-line arguments. It also exports v0.1.12 process tree capabilities to the FFI layer and Go/TypeScript bindings.
Highlights
- macOS cmdline fix:
cmdlinenow returns the full argument vector (e.g.["bun", "run", "scripts/dev.ts", "--root", "/path"]) instead of["bun"] - FFI coverage:
descendantsandkill-descendantsnow available through C-ABI FFI - Go binding:
Descendants()andKillDescendants()with option pattern - TypeScript binding:
descendants()andkillDescendants()via N-API
Changes
Bug Fix: macOS cmdline Truncation
Before (v0.1.12):
{"pid": 12345, "name": "bun", "cmdline": ["bun"]}After (v0.1.13):
{"pid": 12345, "name": "bun", "cmdline": ["bun", "run", "scripts/dev.ts", "--root", "/some/path"]}Root cause: The macOS implementation used proc_name() as a placeholder for cmdline, which only returns the process name (16 chars max). The fix uses sysctl(CTL_KERN, KERN_PROCARGS2) — the same kernel API that ps uses — to read the actual argv.
Impact: Any consumer filtering by cmdline arguments on macOS was affected. Known affected: kitfly discoverOrphans() which filters by p.cmdline.some(arg => arg.includes("scripts/dev.ts")).
Implementation details:
- Two-stage
sysctlcall: first with null buffer to query required size, then with allocated buffer to read data - Parses the
KERN_PROCARGS2buffer format:[argc: i32][exec_path\0][padding \0s][argv[0]\0][argv[1]\0]... - Uses
String::from_utf8_lossy()for non-UTF-8 safety (matches Linux/proc/[pid]/cmdlinepattern) - On any error (ESRCH, EPERM, EINVAL, buffer issues), returns empty
Vec— best-effort, consistent with theProcessInfo.cmdlinecontract: "May be empty if command line cannot be read"
Safety hardening (devrev):
- PID 0 and overflow-range PIDs (
> i32::MAX) rejected before sysctl call argccapped atMAX_ARGC = 4096to prevent pathological allocation from malformed kernel data- Empty argv entries filtered (consistent with Linux
/proc/[pid]/cmdlinebehavior)
FFI: descendants and kill-descendants Exports
v0.1.12 added descendants and kill-descendants to the CLI and Rust crates. This release makes them available through the C-ABI FFI layer:
int32_t sysprims_proc_descendants(const char *config_json, char **result_json_out);
int32_t sysprims_proc_kill_descendants(const char *config_json, char **result_json_out);Safety enforcement happens in the FFI layer — bindings get PID 1 protection, self-exclusion, and parent protection for free.
Go Binding: Descendants() and KillDescendants()
result, err := sysprims.Descendants(pid, &sysprims.DescendantsOptions{
MaxLevels: 2,
Filter: &sysprims.ProcessFilter{Name: "Helper"},
})
killResult, err := sysprims.KillDescendants(pid, &sysprims.KillDescendantsOptions{
Signal: sysprims.SIGTERM,
Filter: &sysprims.ProcessFilter{CpuAbove: 80},
Yes: true,
})TypeScript Binding: descendants() and killDescendants()
const result = descendants(pid, { maxLevels: 2, filter: { name: "Helper" } });
const killResult = killDescendants(pid, {
signal: "TERM",
filter: { cpuAbove: 80 },
yes: true,
});Validation
macOS cmdline Fix
Tested on macOS arm64 (Darwin 25.2.0):
# Start a background process with known arguments
$ sleep 999 &
$ cargo run -p sysprims-cli -- pstat --pid $! --json | jq '.processes[0].cmdline'
["sleep", "999"]
# Multi-argument process
$ python3 -c "import time; time.sleep(999)" &
$ cargo run -p sysprims-cli -- pstat --pid $! --json | jq '.processes[0].cmdline'
["python3", "-c", "import time; time.sleep(999)"]Automated test: test_own_process_has_cmdline — verifies the test runner's own process has non-empty cmdline via snapshot.
Binding Coverage
| Function | FFI | Go | TypeScript |
|---|---|---|---|
descendants() |
New | New | New |
killDescendants() |
New | New | New |
Platform Notes
macOS
sysctl(CTL_KERN, KERN_PROCARGS2)requires no special privileges for processes owned by the current user- For other users' processes, may return empty cmdline (EPERM) — this is expected and consistent with
psbehavior - The exec path in the KERN_PROCARGS2 buffer is intentionally skipped; only the actual argv entries are returned
Linux
- No changes —
/proc/[pid]/cmdlinealready worked correctly - The new
test_own_process_has_cmdlinetest is gated to#[cfg(target_os = "macos")]since Linux cmdline was never broken
Windows
- Not in scope for this fix — Windows uses
vec![name]placeholder, same as the old macOS behavior - Windows cmdline via
NtQueryInformationProcessis a separate effort
Safety Considerations
PID Validation (ADR-0011)
The read_cmdline() function enforces:
- PID 0 rejected (signals caller's process group)
- PID > i32::MAX rejected (would overflow
pid_tcast) - argc validation: Capped at 4096 to prevent memory exhaustion from malformed data
Error Handling
All errors in read_cmdline() result in an empty Vec<String> return — no panics, no error propagation. This is consistent with the ProcessInfo.cmdline field contract which documents that cmdline may be empty.
Upgrade Notes
- No breaking changes — all changes are additive
- macOS consumers will immediately see full
cmdlinedata where previously truncated - Consumers filtering by
cmdlinemay see more matches than before (this is correct behavior) - FFI shared library must be rebuilt for all platform targets to include new exports
Files Changed
crates/sysprims-proc/src/macos.rs— Addedread_cmdline()function usingsysctl(CTL_KERN, KERN_PROCARGS2), replaced placeholder at call sitecrates/sysprims-proc/src/lib.rs— Addedtest_own_process_has_cmdlinetestffi/sysprims-ffi/src/lib.rs— Addeddescendantsandkill-descendantsFFI exportsffi/sysprims-ffi/src/proc.rs— FFI implementation for new exportsbindings/go/sysprims/proc.go— Go binding functionsbindings/go/sysprims/include/sysprims.h— C header for new FFI functionsbindings/typescript/sysprims/native/src/lib.rs— N-API native modulebindings/typescript/sysprims/src/ffi.ts— TypeScript FFI declarationsbindings/typescript/sysprims/src/index.ts— TypeScript public API exportsbindings/typescript/sysprims/src/types.ts— TypeScript type definitions
References
- Bug report:
kitfly/.plans/memos/sysprims/processlist-cmdline-truncated.md - Fix plan:
.plans/active/v0.1.13/process-name-fidelity.md - Feature brief:
.plans/active/v0.1.13/feature-brief.md - ADR-0011: PID Validation Safety
v0.1.12
v0.1.12 - Process Tree Operations & Enhanced Discovery
Release Date: 2026-02-06
Status: Process Tree Operations & Enhanced Discovery Release
Summary
This release adds process tree traversal capabilities, ASCII tree visualization, and enhanced filtering for surgical process management. Operators can now inspect process hierarchies, identify runaway descendants, and terminate specific subtrees without affecting parent processes or critical system processes.
Highlights
- Process Tree Visibility: New
descendantscommand with ASCII art visualization shows instant, human-readable process trees - Targeted Cleanup:
kill-descendantsenables surgical subtree termination with filter support (--cpu-above, --running-for, --name) - Age-Based Filtering:
--running-foroption on all process commands helps distinguish long-running spinners from recent spikes - Parent PID Filtering:
--ppidoption onpstatandkillfor filtering by process parent - Safety by Design: Filter-based kills always preview unless
--yesprovided; never targets self, PID 1, parent, or root without--force - Depth-Controlled Traversal:
--max-levels Nlimits tree depth (default 1 = direct children only, accepts "all" for full subtree)
Changes
CLI: sysprims descendants Command
New subcommand to list child processes of a given root PID:
# Show direct children only (level 1)
sysprims descendants 7825 --table
# Show 2 levels deep (children + grandchildren)
sysprims descendants 7825 --max-levels 2 --table
# Show full subtree with ASCII art
sysprims descendants 7825 --max-levels all --tree
# Filter by CPU usage
sysprims descendants 7825 --cpu-above 80 --tree
# Filter by process age (long-running spinners)
sysprims descendants 7825 --running-for "1h" --treeOptions:
| Option | Description |
|---|---|
--max-levels <N> |
Maximum traversal depth (1 = direct children, "all" = full subtree) |
--json |
Output as JSON (default) |
--table |
Human-readable table format (flat, grouped by level) |
--tree |
ASCII art tree with hierarchy visualization |
--name <NAME> |
Filter by process name (substring match) |
--user <USER> |
Filter by username |
--cpu-above <PERCENT> |
Filter by minimum CPU usage |
--memory-above <KB> |
Filter by minimum memory usage |
--running-for <DURATION> |
Filter by minimum process age (e.g., "5s", "1m", "2h") |
Example output (ASCII tree):
7825 Electron [0.1% CPU, 160M, 7d18h]
├── 985 VSCodium Helper [0.0% CPU, 63M, 1d13h]
├── 986 VSCodium Helper (Plugin) [0.0% CPU, 84M, 1d13h]
│ ├── 1066 VSCodium Helper (Plugin) [0.0% CPU, 56M, 1d13h]
│ └── 5404 VSCodium Helper (Plugin) [0.0% CPU, 58M, 1d13h]
├── 5495 VSCodium Helper (Renderer) [0.0% CPU, 119M, 16h47m]
└── 5500 VSCodium Helper [0.0% CPU, 60M, 16h47m]
Total: 4 processes in subtree, 4 matched filter
CLI: sysprims kill-descendants Command
Send signals to descendants of a process without affecting the parent or root:
# Preview what would be killed
sysprims kill-descendants 7825 --cpu-above 80 --dry-run
# Kill all high-CPU descendants (requires --yes for filter-based selection)
sysprims kill-descendants 7825 --cpu-above 80 --yes
# Kill direct children only (level 1)
sysprims kill-descendants 7825 --max-levels 1 --yes
# Use SIGKILL for hung processes
sysprims kill-descendants 7825 --cpu-above 90 --signal KILL --yes
# Kill full subtree (all descendants)
sysprims kill-descendants 7825 --max-levels all --yesSafety behaviors:
- Preview mode: Filter-based selection defaults to
--dry-rununless--yesis explicitly provided - Self exclusion: Never targets CLI's own process
- Parent protection: Never targets the parent process of selected descendants (unless
--force) - PID 1 protection: Never targets init/launchd (unless
--force) - Root protection: Never targets system root without
--force
Options:
| Option | Description |
|---|---|
--max-levels <N> |
Maximum traversal depth (same as descendants) |
-s, --signal <SIGNAL> |
Signal name or number (default: TERM) |
--name <NAME> |
Filter by process name |
--user <USER> |
Filter by username |
--cpu-above <PERCENT> |
Filter by minimum CPU usage |
--memory-above <KB> |
Filter by minimum memory usage |
--running-for <DURATION> |
Filter by minimum process age |
--dry-run |
Print matched targets but do not send signals |
--yes |
Proceed with kill (required for filter-based selection) |
--force |
Proceed even if CLI safety checks would normally refuse |
--json |
Output as JSON |
CLI: Enhanced sysprims pstat Options
New filter options for process discovery:
# Filter by parent PID
sysprims pstat --ppid 7825 --table
# Filter by process age (long-running processes only)
sysprims pstat --cpu-above 80 --running-for "1h" --table
# Combine multiple filters
sysprims pstat --ppid 7825 --cpu-above 90 --running-for "10m" --tableNew filter options:
| Option | Available on |
|---|---|
--ppid <PID> |
pstat, kill |
--running-for <DURATION> |
pstat, kill, descendants, kill-descendants |
CLI: Enhanced sysprims kill Options
The kill command now accepts all filter options in addition to explicit PIDs:
# Kill by parent PID filter
sysprims kill --ppid 7825 --signal TERM
# Kill by combined filters (requires --yes for filter-based selection)
sysprims kill --ppid 7825 --cpu-above 80 --yes
# Preview before killing (dry-run mode)
sysprims kill --ppid 7825 --cpu-above 80 --dry-run
# Force override for protected targets
sysprims kill --ppid 7825 --yes --forceValidation
Process Tree Operations
Tested on macOS arm64 (Darwin 25.2.0):
Descendants command:
$ sysprims descendants 7825 --max-levels 1 --table
--- Level 1 ---
PID PPID CPU% MEM(KB) STATE USER NAME
--------------------------------------------------------------------------------
985 7825 0.0 62720 R davethompson VSCodium Helper
986 7825 0.0 83408 R davethompson VSCodium Helper (Plugin)
... (40 total descendants)ASCII tree visualization:
$ sysprims descendants 7825 --tree | head -15
7825 Electron [0.1% CPU, 160M, 7d18h]
├── 985 VSCodium Helper [0.0% CPU, 63M, 1d13h]
├── 986 VSCodium Helper (Plugin) [0.0% CPU, 84M, 1d13h]
├── 5495 VSCodium Helper (Renderer) [0.0% CPU, 119M, 16h47m]
└── ...Kill-descendants safety:
# Parent excluded by default
$ sysprims kill-descendants 7825 --dry-run
# Output: 40 descendants (no parent PID)
# With --force, parent included
$ sysprims kill-descendants 7825 --dry-run --force
# Output: 41 processes (includes parent PID)Filter Validation
Parent PID filter:
$ sysprims pstat --ppid 7825 --table
# Shows 32 direct children of VSCodium Electron processAge-based filtering:
# Find processes >90% CPU running >1 hour
$ sysprims pstat --cpu-above 90 --running-for "1h" --table
# Distinguishes long-running spinners from brief spikesReal-World Use Cases
Scenario: Identify and terminate runaway Electron helper processes
# 1. Find high CPU processes in tree
$ sysprims descendants 7825 --cpu-above 80 --tree
# 2. Preview what would be killed
$ sysprims kill-descendants 7825 --cpu-above 80 --dry-run --json
# 3. Terminate runaway descendants (parent VSCodium survives)
$ sysprims kill-descendants 7825 --cpu-above 80 --yesScenario: Chrome renderer runaway
# Chrome has many helper processes; find the one spinning
$ sysprims descendants 67566 --name "Helper (Renderer)" --cpu-above 100 --tree
# Kill just the runaway renderer (not entire browser)
$ sysprims kill-descendants 67566 --name "Helper (Renderer)" --cpu-above 100 --max-levels 2 --yesPlatform Notes
macOS
Process tree traversal works correctly with libproc:
- Uses
proc_pidinfo(PROC_PIDTBSDINFO)for parent-child relationships - BFS traversal respects
--max-levelsdepth limit - Parent process exclusion enforced for
kill-descendants
Age filtering availability:
- Process start time available via
proc_pidinfo() start_time_unix_msfield populated for all processes--running-forfilters work on macOS
ASCII tree visualization:
- Requires terminal supporting box-drawing characters (UTF-8)
- Falls back gracefully on terminals without tree line support
- Uses
├──,│,└──for tree structure
Linux
Full visibility: /proc/[pid]/stat provides complete process tree without restrictions.
All features work identically to macOS.
Windows
Process tree traversal: Supported via CreateToolhelp32Snapshot.
Depth limiting and filtering work as on Unix.
ASCII tree visualization: Box-drawing characters may not render correctly on some terminals.
Consider using --table or --json on Windows for reliability.
Schema Additions
process-filter.schema.json
New filter fields:
{
"properties": {
"ppid": {
"description": "Filter by parent process ID",
"type": "integer",
"minimum": 1
},
"running_for_at_least_secs": {
"description": "Filter by minimum process age in seconds",
"type": "number",
"minimum": 0
}
}
}descendants-result.schema.json (NEW)
New schema for descendants command output:
{
"schema_id": "https://schemas.3leaps.dev/sysprims/process/v1.0.0/descendants-result.schema.json",
"root_pid": "integer",
"max_levels": "integer",
"levels": [
{
"level": "integer",
"processes": [{ /* Process objects */ }]
}
],
"total_found": "integer",
"matched_by_filter": "integer"
}Safety Considerations
PID Validation (ADR-0011)
All tree traversal and kill operations enforce ADR-0011 PID safety:
- **PI...
v0.1.11
v0.1.11 - macOS Port Discovery & Bun Runtime Support
Release Date: 2026-02-04
Status: macOS Port Discovery & Bun Runtime Support Release
Summary
This release fixes listeningPorts() returning empty results on macOS, adds a new ports CLI command for listing listening port bindings, and enables Bun runtime support for TypeScript bindings.
Highlights
- macOS Port Discovery Fixed:
listeningPorts()now works on macOS - New CLI Command:
sysprims portsfor listing listening port bindings - Bun Runtime Support: TypeScript bindings now work under Bun
Changes
CLI: sysprims ports Command
New subcommand to list listening port bindings:
# Table output
sysprims ports --table
# Filter by protocol
sysprims ports --protocol tcp --table
# Filter by specific port (JSON output)
sysprims ports --protocol tcp --local-port 8080 --jsonOptions:
| Option | Description |
|---|---|
--json |
Output as JSON (default) |
--table |
Human-readable table format |
--protocol <tcp|udp> |
Filter by protocol |
--local-port <PORT> |
Filter by local port number |
Example output (table):
PROTO LOCAL STATE PID NAME
--------------------------------------------------------------------------------
tcp [::1]:9999 listen 54659 bun
tcp 0.0.0.0:9000 listen 41721 ssh
tcp 127.0.0.1:8080 listen 40672 namelens
...
Example output (JSON):
{
"schema_id": "https://schemas.3leaps.dev/sysprims/process/v1.0.0/port-bindings.schema.json",
"bindings": [
{
"protocol": "tcp",
"local_addr": "127.0.0.1",
"local_port": 8080,
"state": "listen",
"pid": 40672,
"process": {
"pid": 40672,
"name": "namelens",
"exe_path": "/Users/.../namelens",
"cmdline": ["namelens"]
}
}
],
"warnings": [...]
}macOS listeningPorts() Fix
The listeningPorts() function was returning empty results on macOS due to incorrect socket fdinfo parsing. This release fixes the underlying issues:
Problem: SDK struct layout mismatch caused proc_pidfdinfo(PROC_PIDFDSOCKETINFO) parsing to fail silently.
Solution:
-
UID Filtering: Scan current-user processes only
- Uses
proc_pidinfo(PROC_PIDTBSDINFO)to read UID cheaply - Skips other users' PIDs (reduces EPERM volume)
- Significantly improves performance and reduces noise
- Uses
-
Heuristic Layout Detection: Handle SDK variations
vinfo_statsize varies across macOS SDKs (136 vs 144 bytes)- New
select_socket_info_layout()tries common sizes and validates - Offset-based parsing instead of fixed struct assumptions
-
Strict TCP Listener Filtering: Only return actual listeners
- Checks
tcpsi_state == TSI_S_LISTENfor TCP sockets - UDP bindings included (UDP has no "listen" state)
- Checks
Before (v0.1.10):
const result = listeningPorts();
// result.bindings.length === 0 (empty!)After (v0.1.11):
const result = listeningPorts();
// result.bindings.length === 68 (working!)TypeScript Bindings: Bun Runtime Support
Removed the explicit Bun block from bindings/typescript/sysprims/src/native.ts:
// REMOVED in v0.1.11:
if ((process as unknown as { versions?: { bun?: string } }).versions?.bun) {
throw new Error(
"sysprims TypeScript bindings are not yet validated on Bun. " +
"Run under Node.js or add a fallback path for Bun.",
);
}Bun's N-API compatibility is mature enough for production use.
Validation
macOS Port Discovery
Tested on macOS arm64 (Darwin 25.2.0):
$ sysprims ports --protocol tcp --table
PROTO LOCAL STATE PID NAME
--------------------------------------------------------------------------------
tcp [::1]:9999 listen 54659 bun
tcp 0.0.0.0:9000 listen 41721 ssh
tcp 127.0.0.1:8080 listen 40672 namelens
... (31 TCP listeners found)
Self-listener test passes:
$ cargo test -p sysprims-proc test_listening_ports_self_listener_tcp
test test_listening_ports_self_listener_tcp ... ok
Bun Runtime
Validated by kitfly team:
| Feature | Status |
|---|---|
| Module loading | Works |
procGet() |
Works |
terminate() |
Works |
listeningPorts() |
Works (with macOS fix) |
Platform Notes
macOS Visibility
Port discovery on macOS is limited to current-user processes due to SIP/TCC restrictions:
- Visible: Processes owned by the current user
- Not visible: System processes, other users' processes
- Warnings: Indicate how many entries were filtered
This is inherent to macOS security model and cannot be bypassed without elevated privileges.
Linux
Full visibility via /proc/net/* - no restrictions for same-user processes.
Windows
Not yet implemented for listeningPorts().
Upgrade Notes
- No breaking changes
- macOS users will now see port bindings that were previously invisible
- Bun users can use sysprims directly without workarounds
- New
portsCLI command available
Files Changed
crates/sysprims-cli/src/main.rs- Addedportssubcommand (+138 lines)crates/sysprims-proc/src/macos.rs- Fixed socket fdinfo parsing (~200 lines changed)crates/sysprims-proc/tests/port_bindings.rs- Tightened macOS self-listener testbindings/typescript/sysprims/src/native.ts- Removed Bun guard (-6 lines)
References
- Feature briefs:
.plans/active/v0.1.11/01-macos-listening-ports-reliability.md.plans/active/v0.1.11/02-bun-runtime-support.md
- Commits:
b14b68a- fix(proc/macos): make listening port discovery reliable4ca6469- fix(proc/macos): harden socket fdinfo parsing96d8c11- feat(cli): add ports command
v0.1.10
v0.1.10 - 2026-02-03
Status: Go Shared Library Mode Polish Release
Fast-follow polish release improving Go shared-library mode developer experience and clarifying multi-Rust FFI collision guidance.
Highlights
sysprims_shared_localTag: New opt-in build tag for local development workflows- Cleaner Default Shared Mode:
sysprims_sharedno longer references non-existent local paths - Clearer Multi-Rust Guidance: README explicitly documents duplicate symbol
_rust_eh_personalityfailure mode
New Build Tag: sysprims_shared_local
For developers who need to link against locally-built shared libraries:
# Local development with custom shared libs
# (libs must be in bindings/go/sysprims/lib-shared/local/<platform>/)
go test -v -tags="sysprims_shared,sysprims_shared_local" ./...This tag re-enables the local override paths that were previously searched by default, which caused confusing linker warnings when the directory didn't exist.
Cleaner Default: sysprims_shared
The default shared mode now only searches shipped prebuilt libraries:
# Standard shared mode (no local paths searched)
# glibc/macOS/Windows
go test -v -tags=sysprims_shared ./...
# Alpine/musl
go test -v -tags="musl,sysprims_shared" ./...This eliminates the linker warnings that previously appeared when lib-shared/local/ didn't exist.
Multi-Rust FFI Collision Guidance
The README now explicitly documents the "multiple Rust FFI libs in one Go binary" failure mode:
Symptom: Link errors mentioning duplicate symbols like _rust_eh_personality
Cause: Linking multiple Rust static libraries (via cgo //#cgo LDFLAGS: -l...) in a single Go binary causes duplicate Rust runtime symbols.
Solution: Use sysprims as a shared library:
| Platform | Build Tags |
|---|---|
| glibc/macOS/Windows | -tags=sysprims_shared |
| Alpine/musl | -tags="musl,sysprims_shared" |
| Local dev override | -tags="sysprims_shared,sysprims_shared_local" |
When to Use Shared Mode
Default (static linking):
go test ./...- Recommended unless you hit Rust staticlib collisions
- Links against prebuilt
libsysprims_ffi.astatic library
Shared mode (avoids duplicate symbols):
# Standard platforms
go test -tags=sysprims_shared ./...
# Alpine/musl
go test -tags="musl,sysprims_shared" ./...- Required when linking multiple Rust staticlibs via cgo
- Links against prebuilt shared library (
.so/.dylib/.dll)
Local development override:
go test -tags="sysprims_shared,sysprims_shared_local" ./...- Links against locally-built shared library in
lib-shared/local/ - Useful for testing local changes to the FFI layer
Upgrade Notes
- No breaking changes for existing
sysprims_sharedworkflows using prebuilt libraries - If you were relying on
lib-shared/local/...implicitly, add thesysprims_shared_localtag explicitly - No changes needed for standard consumers using shipped prebuilt libs
References
- Commit:
3b004b7- addssysprims_shared_local, removes local-path warnings, updates docs - Go bindings:
bindings/go/sysprims/
v0.1.9
sysprims v0.1.9
GPL-free, cross-platform process utilities with group-by-default tree management.
Installation
Download the appropriate archive for your platform and extract.
Verification
Note: This release is unsigned. Signed checksums will be uploaded after manual signing.
Once signed artifacts are available:
# Verify checksum signature
minisign -Vm SHA256SUMS -p sysprims-minisign.pub
# Verify file checksums
shasum -a 256 -c SHA256SUMSPlatform Matrix
| Platform | CLI | FFI | Go Bindings |
|---|---|---|---|
| Linux x64 (glibc 2.17+) | sysprims-*-linux-amd64.tar.gz |
In FFI bundle | Supported |
| Linux x64 (musl) | sysprims-*-linux-amd64-musl.tar.gz |
In FFI bundle | Supported |
| Linux arm64 (glibc 2.17+) | sysprims-*-linux-arm64.tar.gz |
In FFI bundle | Supported |
| Linux arm64 (musl) | sysprims-*-linux-arm64-musl.tar.gz |
In FFI bundle | Supported |
| macOS x64 | sysprims-*-darwin-amd64.tar.gz |
In FFI bundle | Supported |
| macOS arm64 | sysprims-*-darwin-arm64.tar.gz |
In FFI bundle | Supported |
| Windows x64 | sysprims-*-windows-amd64.zip |
In FFI bundle | Supported |
| Windows arm64 | sysprims-*-windows-arm64.zip |
In FFI bundle | Not supported |
Note: Windows arm64 Go bindings are not supported because MinGW does not support the arm64 target.
What's Changed
- feat(bindings): update Go prebuilt libs for v0.1.8 by @github-actions[bot] in #8
- feat(bindings): update Go prebuilt libs for v0.1.9 by @github-actions[bot] in #9
- feat(bindings): update Go prebuilt libs for v0.1.9 by @github-actions[bot] in #10
Full Changelog: v0.1.7...v0.1.9
v0.1.8
sysprims v0.1.8
Release Date: 2026-01-29
Status: CLI Tree Termination Release
Summary
This release adds the terminate-tree CLI subcommand for safe, structured termination of existing process trees. Combined with enhanced pstat sampling modes, sysprims now provides a complete workflow for diagnosing and cleaning up runaway processes without GPL-licensed utilities.
Highlights
sysprims terminate-tree: Terminate process trees with graceful-then-kill escalation- PID Reuse Protection:
--require-start-time-msand--require-exe-pathidentity guards - CLI Safety: Refuses to terminate PID 1, self, or parent without
--force pstatSampling:--sampleand--topflags for CPU investigation- Documentation: Real-world guide for runaway process diagnosis
New CLI Commands
sysprims terminate-tree
Terminate an existing process tree by PID. This wraps the terminate_tree() library function (added in v0.1.6) with CLI-specific safety guards.
sysprims terminate-tree <PID> [OPTIONS]Basic Usage
# Terminate process tree rooted at PID 26021
sysprims terminate-tree 26021
# With JSON output for automation
sysprims terminate-tree 26021 --jsonPID Reuse Protection (Recommended)
When automating process termination, use identity guards to ensure you're killing the right process:
sysprims terminate-tree 26021 \
--require-exe-path "/Applications/VSCodium.app/Contents/MacOS/Electron" \
--require-start-time-ms 1769432792261 \
--jsonIf the current process at PID 26021 doesn't match the specified identity, the command fails safely.
Options Reference
| Option | Default | Description |
|---|---|---|
--grace <DURATION> |
5s | Grace period before escalation |
--kill-after <DURATION> |
10s | Time to wait before sending kill signal |
--signal <SIGNAL> |
TERM | Signal for grace period |
--kill-signal <SIGNAL> |
KILL | Signal for forced termination |
--require-start-time-ms <MS> |
- | Refuse if PID start time doesn't match |
--require-exe-path <PATH> |
- | Refuse if PID exe path doesn't match |
--force |
false | Override safety checks |
--json |
false | Output as JSON |
Output Schema
{
"schema_id": "https://schemas.3leaps.dev/sysprims/process/v1.0.0/terminate-tree-result.schema.json",
"timestamp": "2026-01-29T21:23:07.677021Z",
"platform": "macos",
"pid": 26021,
"pgid": 26021,
"signal_sent": 15,
"escalated": false,
"exited": true,
"timed_out": false,
"tree_kill_reliability": "guaranteed",
"warnings": []
}| Field | Description |
|---|---|
signal_sent |
Signal number used (15=SIGTERM, 9=SIGKILL) |
escalated |
Whether SIGKILL was needed after grace period |
exited |
Process terminated successfully |
tree_kill_reliability |
"guaranteed" if PGID kill was used |
warnings |
Any edge cases encountered |
CLI Safety Guards
The CLI includes interactive safety checks that refuse to proceed without --force:
- PID 1 (init/launchd): System stability risk
- Self: Would terminate the sysprims process
- Parent: Would terminate the calling shell/process
These are CLI-specific protections. The underlying library allows these operations for controlled automation scenarios that may legitimately need them.
pstat Sampling Enhancements
New flags for real-time CPU investigation:
# Sample CPU over 250ms interval, show top 5
sysprims pstat --sample 250ms --top 5 --sort cpu --table
# Find VSCodium helpers with >50% CPU (sampled)
sysprims pstat --name "VSCodium Helper" --sample 500ms --cpu-above 50 --json| New Option | Description |
|---|---|
--sample <DURATION> |
Compute CPU rate over sampling interval |
--top <N> |
Limit output to top N processes (after filtering) |
Without --sample, cpu_percent is a lifetime average which may not reflect current activity. With --sample, you get near-instantaneous CPU usage similar to Activity Monitor.
Surgical vs Tree Termination
This release documents two strategies for handling runaway processes:
Option A: Surgical Strike (Try First)
Kill individual runaway processes while preserving the parent application:
sysprims kill 8436 -s TERMIf SIGTERM is ignored (common with runaway Electron/Node processes):
sysprims kill 8436 -s KILLBenefits:
- Preserves parent application windows
- Minimal disruption
- No respawn observed in testing
Option B: Tree Termination (If Surgical Fails)
If processes respawn or too many are affected:
sysprims terminate-tree 26021 --require-exe-path "..."Trade-offs:
- Terminates parent and ALL descendants
- Closes all windows managed by that process
- Guaranteed cleanup
Documentation
New Guide: Runaway Process Diagnosis
docs/guides/runaway-process-diagnosis.md provides a real-world walkthrough:
- Finding high-CPU processes with
pstat --sample --cpu-above - Identifying process relationships via
ppidinspection - Deciding on termination scope (surgical vs tree)
- Verifying termination completed successfully
- TypeScript library examples for automation
The guide uses a real scenario: nine VSCodium Helper (Plugin) processes consuming 95%+ CPU each, caused by a runaway extension.
Library vs CLI Protections
| Protection | Library | CLI |
|---|---|---|
| PID 0 rejected | Yes | Yes (via library) |
| PID > MAX_SAFE_PID rejected | Yes | Yes (via library) |
| Don't group-kill own PGID | Yes | Yes (via library) |
| Refuse PID 1 | No | Yes (requires --force) |
| Refuse self | No | Yes (requires --force) |
| Refuse parent | No | Yes (requires --force) |
The library provides safety against dangerous POSIX semantics. The CLI adds interactive footgun protections for common mistakes.
Migration Guide
For CLI Users
No breaking changes. The terminate-tree subcommand is additive.
If you were using sysprims timeout solely for tree termination of external processes, you can now use terminate-tree directly:
# Before: awkward workaround
sysprims timeout --pid-file /tmp/pid 0s -- sleep infinity &
# ... then read PID and terminate
# After: direct tree termination
sysprims terminate-tree <PID>For Library Users
No changes required. The CLI wraps existing sysprims_timeout::terminate_tree().
Platform Support
| Platform | terminate-tree |
pstat --sample |
|---|---|---|
| Linux x64 (glibc) | Yes | Yes |
| Linux x64 (musl) | Yes | Yes |
| Linux arm64 (glibc) | Yes | Yes |
| Linux arm64 (musl) | Yes | Yes |
| macOS arm64 | Yes | Yes |
| Windows x64 | Yes | Yes |
| Windows arm64 | Yes | Yes |
Known Limitations
- Workspace identification: Determining which IDE window a child process belongs to requires OS tools (
lsofon Unix). Future releases will addsysprims fdsfor self-contained investigation. - SIGTERM may be ignored: Runaway Electron/Node processes sometimes ignore SIGTERM. The guide documents escalation to SIGKILL.
Coming in v0.1.9
sysprims fds: Open file descriptor inspection (Linux/macOS first; Windows NotSupported)- Multi-PID kill:
sysprims kill <PID> <PID> ...batch operations
Verification
Verify this release with the signed checksums:
# Download release and verification files
curl -LO https://github.com/3leaps/sysprims/releases/download/v0.1.8/SHA256SUMS
curl -LO https://github.com/3leaps/sysprims/releases/download/v0.1.8/SHA256SUMS.minisig
curl -LO https://github.com/3leaps/sysprims/releases/download/v0.1.8/sysprims-minisign.pub
# Verify signature
minisign -Vm SHA256SUMS -p sysprims-minisign.pub
# Verify checksums
shasum -a 256 -c SHA256SUMS --ignore-missingChanges
Added
-
CLI:
sysprims terminate-tree- tree termination for existing processes--require-start-time-ms,--require-exe-pathfor PID reuse protection--grace,--kill-after,--signal,--kill-signaltiming/signal options--forceto override CLI safety guards--jsonfor machine-readable output
-
CLI:
pstatsampling mode--sample <DURATION>for CPU sampling over interval--top <N>for limiting output
-
Documentation
docs/guides/runaway-process-diagnosis.md- real-world investigation guide
v0.1.7
sysprims v0.1.7
Release Date: 2026-01-26
Status: TypeScript Bindings Infrastructure Release
Summary
This release migrates TypeScript bindings from koffi FFI to a Node-API (N-API) native addon via napi-rs. The primary user-facing outcome: TypeScript bindings now work in Alpine/musl containers, removing a key blocker for container-based deployments.
Highlights
- Node-API Migration: TypeScript bindings use napi-rs instead of koffi + vendored shared libraries
- Alpine/musl Support: Linux musl containers (including Alpine) now supported
- No API Changes: Existing TypeScript imports and function calls remain unchanged
- npm Publishing Deferred: Prebuilt npm packages planned for future release
Architecture Change
Previous Model (v0.1.4-v0.1.6)
TypeScript code
↓
koffi.load() FFI
↓
Vendored C-ABI shared library (_lib/<platform>/libsysprims_ffi.*)
↓
Rust FFI exports (sysprims-ffi crate)
Limitations:
- koffi's native loading had glibc dependencies that prevented Alpine/musl usage
- Vendored shared libraries added distribution complexity
- Multiple failure modes when loading native code
New Model (v0.1.7+)
TypeScript code
↓
require() Node-API addon
↓
.node binary built with napi-rs (native/<platform>/)
↓
Rust implementation (direct, no C-ABI indirection)
Benefits:
- Node-API is designed for native addon portability
- napi-rs provides Rust-native Node-API bindings
- Single binary per platform (
.nodefile) - Works on glibc and musl
Migration Guide
For Application Code
No changes required. The JavaScript API surface is unchanged:
// These imports and calls work identically in v0.1.6 and v0.1.7
import { procGet, processList, terminateTree, spawnInGroup } from '@3leaps/sysprims';
const info = procGet(process.pid);
const procs = processList({ name_contains: 'node' });For Build/Deploy
Installing from Git Checkout (Current)
When installing from a git checkout or local path, the addon must be built from source:
# Ensure Rust toolchain is available
rustc --version
# Install and build
cd bindings/typescript/sysprims
npm install
npm run build:nativeBuild requirements:
- Rust toolchain (1.81+)
- C/C++ compiler (for napi-rs build)
- Node.js 18+
Installing from npm (Future)
When npm publishing is enabled, prebuilt platform packages will be installed automatically:
npm install @3leaps/sysprims
# Prebuilt .node binary selected based on platformNo build tools required for npm installs with prebuilds.
For Container Images
Alpine is now supported. Example Dockerfile:
FROM node:20-alpine
# For building from source (until npm prebuilds are available)
RUN apk add --no-cache rust cargo build-base
WORKDIR /app
COPY package*.json ./
RUN npm install
RUN npm run build:native
COPY . .
CMD ["node", "index.js"]Once npm prebuilds are available, the Rust/build-base dependencies will be unnecessary.
What Changed (Implementation Detail)
| Aspect | v0.1.6 (koffi) | v0.1.7 (N-API) |
|---|---|---|
| Native loading | koffi.load() C-ABI shared lib |
require() N-API .node addon |
| Library location | _lib/<platform>/libsysprims_ffi.* |
native/<platform>/sysprims.*.node |
| Build system | Prebuilt libs vendored in package | napi-rs build from Rust source |
| Error handling | FFI returns error code, JS checks | FFI returns { code, json?, message? } |
| Alpine support | No (glibc required) | Yes |
Error Handling Internals
The N-API layer returns structured results:
// Internal FFI result shape
interface NativeResult {
code: number; // 0 = success, >0 = error code
json?: string; // JSON payload on success
message?: string; // Error message on failure
}The TypeScript wrapper interprets these and throws SysprimsError with the same error codes as before, so error handling code is unchanged.
Platform Support
| Platform | CLI | Go Bindings | TypeScript Bindings |
|---|---|---|---|
| Linux x64 (glibc) | yes | yes | yes |
| Linux x64 (musl) | yes | yes | yes (new) |
| Linux arm64 (glibc) | yes | yes | yes |
| Linux arm64 (musl) | yes | yes | yes (new) |
| macOS x64 | no | no | no |
| macOS arm64 | yes | yes | yes |
| Windows x64 | yes | yes | yes |
CI Validation
The TypeScript bindings are validated by:
-
TypeScript Bindings workflow (
typescript-bindings.yml)- Runs on macOS, Windows, Linux (glibc)
- Includes Alpine/musl container lane
- Tests all exported functions
-
Validate Release workflow (
validate-release.yml)- Post-tag validation
- Includes TypeScript on all platforms including Alpine
Adoption Recommendations
Initial Rollout
- Pin to exact version:
"@3leaps/sysprims": "0.1.7" - Add smoke test: Verify addon loads before relying on it
import { procGet } from '@3leaps/sysprims'; // Smoke test at startup try { procGet(process.pid); } catch (e) { console.error('sysprims addon failed to load:', e); // Fall back to alternative implementation }
Fallback Strategy
Even with N-API, native addons can fail in edge cases (locked-down environments, unusual Node builds). Consider keeping fallback implementations:
let sysprims: typeof import('@3leaps/sysprims') | null = null;
try {
sysprims = require('@3leaps/sysprims');
} catch {
console.warn('sysprims unavailable, using fallbacks');
}
export function terminateProcess(pid: number): void {
if (sysprims) {
sysprims.terminate(pid);
} else {
process.kill(pid, 'SIGTERM');
}
}Bun Runtime
Bun has N-API compatibility, but behavior may differ from Node.js. Validate sysprims in Bun-specific test lanes before production use. If issues arise, gate sysprims to Node-only entrypoints.
Changes
Changed
- TypeScript Bindings Architecture (
bindings/typescript/sysprims/)- Migrated from koffi + vendored C-ABI shared libraries to Node-API (N-API) native addon via napi-rs
- Prebuilt
.nodebinaries loaded fromnative/directory - FFI returns
{ code, json?, message? }internally; JS layer throwsSysprimsErrorwith same error codes
Added
- Linux musl/Alpine Support (TypeScript)
- TypeScript bindings now work in Alpine-based containers
- Removes the "glibc-only" limitation from v0.1.4-v0.1.6
Notes
- No API Changes: Existing TypeScript imports and function calls remain unchanged
- Build from Source: Installing from git checkout requires Rust toolchain
- npm Prebuilds: Deferred to future release pending consumer validation
Verification
Verify this release with the signed checksums:
# Download release and verification files
curl -LO https://github.com/3leaps/sysprims/releases/download/v0.1.7/SHA256SUMS
curl -LO https://github.com/3leaps/sysprims/releases/download/v0.1.7/SHA256SUMS.minisig
curl -LO https://github.com/3leaps/sysprims/releases/download/v0.1.7/sysprims-minisign.pub
# Verify signature
minisign -Vm SHA256SUMS -p sysprims-minisign.pub
# Verify checksums
shasum -a 256 -c SHA256SUMS --ignore-missingNext Release
v0.1.8+ will continue toward:
- npm publishing with prebuilt platform packages
- Python bindings (cffi + wheel packaging)
- Timeout API for TypeScript (complex config struct)
v0.1.6
sysprims v0.1.6
Release Date: 2026-01-25
Status: Supervisor & Job Manager Primitives Release
Summary
This release delivers process management primitives for long-running supervisors and job managers. Teams building systems like gonimbus or rampart lifecycle can now spawn kill-tree-safe jobs, detect PID reuse, and cleanly terminate process trees—without coupling to the timeout API.
Highlights
- PID Reuse Guard: New
start_time_unix_msandexe_pathfields enable detection of PID reuse - Spawn In Group: Create processes in a new process group (Unix) or Job Object (Windows)
- Wait With Timeout: Poll for process exit with configurable timeout
- Terminate Tree: Graceful-then-kill tree termination as a standalone primitive
- Windows Documentation: Job Object registry behavior documented
New Primitives
| Primitive | Rust | FFI | Go | TypeScript |
|---|---|---|---|---|
| Process identity | ProcessInfo |
sysprims_proc_get |
ProcessGet |
procGet |
| Spawn in group | spawn_in_group() |
sysprims_spawn_in_group |
SpawnInGroup |
spawnInGroup |
| Wait PID with timeout | wait_pid() |
sysprims_proc_wait_pid |
WaitPID |
waitPID |
| Terminate tree | terminate_tree() |
sysprims_terminate_tree |
TerminateTree |
terminateTree |
Process Identity (PID Reuse Guard)
Problem
Long-running supervisors store PIDs and later attempt to signal them. PID reuse can cause supervisors to accidentally target the wrong process after the original exits.
Solution
The ProcessInfo struct now includes start_time_unix_ms and exe_path (when obtainable), allowing consumers to distinguish between "same PID, same process" and "same PID, different process" (PID reuse).
Rust Usage
use sysprims_proc::get_process;
// At job creation, store identity
let info = get_process(pid)?;
let job_record = JobRecord {
pid: info.pid,
start_time: info.start_time_unix_ms,
cmdline: info.cmdline.clone(),
};
// Later, before signaling
let current = get_process(job_record.pid)?;
if current.start_time_unix_ms != job_record.start_time {
log::warn!("PID {} was reused, not signaling", job_record.pid);
return Ok(JobStatus::Stale);
}Go Usage
import "github.com/3leaps/sysprims/bindings/go/sysprims"
// At job creation
info, _ := sysprims.ProcessGet(pid)
jobRecord := JobRecord{
PID: info.PID,
StartTime: info.StartTimeUnixMs,
}
// Later, before signaling
current, err := sysprims.ProcessGet(jobRecord.PID)
if err != nil || current.StartTimeUnixMs != jobRecord.StartTime {
log.Printf("PID %d was reused or gone", jobRecord.PID)
return
}TypeScript Usage
import { procGet } from '@3leaps/sysprims';
// At job creation
const info = procGet(pid);
const jobRecord = {
pid: info.pid,
startTime: info.start_time_unix_ms,
};
// Later, before signaling
const current = procGet(jobRecord.pid);
if (current.start_time_unix_ms !== jobRecord.startTime) {
console.warn(`PID ${jobRecord.pid} was reused, not signaling`);
return;
}Platform Notes
| Platform | Source | Reliability |
|---|---|---|
| Linux | /proc/<pid>/stat starttime + boot time |
High |
| macOS | proc_pidinfo / libproc APIs |
High |
| Windows | Process creation time via Win32 | High |
The field is optional and omitted when not obtainable due to permissions.
Spawn In Group
Problem
Supervisors want "kill-tree safe" jobs without using run_with_timeout. Today, reliable grouping is mainly exercised through the timeout API.
Solution
spawn_in_group spawns a child process in a new process group (Unix) or Job Object (Windows), returning identifiers needed for later tree termination.
Rust Usage
use sysprims_timeout::{spawn_in_group, SpawnInGroupConfig};
let outcome = spawn_in_group(SpawnInGroupConfig {
argv: vec!["./worker.sh".into(), "--id".into(), "42".into()],
cwd: Some("/app".into()),
env: Some(vec![("LOG_LEVEL".into(), "debug".into())]),
})?;
println!("Spawned PID {}", outcome.pid);
#[cfg(unix)]
println!("Process group: {}", outcome.pgid.unwrap());
println!("Tree kill reliability: {:?}", outcome.tree_kill_reliability);Go Usage
import "github.com/3leaps/sysprims/bindings/go/sysprims"
config := sysprims.SpawnInGroupConfig{
Argv: []string{"./worker.sh", "--id", "42"},
Cwd: "/app",
Env: map[string]string{"LOG_LEVEL": "debug"},
}
outcome, err := sysprims.SpawnInGroup(config)
if err != nil {
log.Fatal(err)
}
log.Printf("Spawned PID %d", outcome.PID)
// outcome.PGID is nil on WindowsTypeScript Usage
import { spawnInGroup } from '@3leaps/sysprims';
const outcome = spawnInGroup({
argv: ['./worker.sh', '--id', '42'],
cwd: '/app',
env: { LOG_LEVEL: 'debug' },
});
console.log(`Spawned PID ${outcome.pid}`);
// outcome.pgid is null on WindowsSpawnInGroupResult Fields
| Field | Type | Description |
|---|---|---|
schema_id |
string | Schema identifier |
timestamp |
string | RFC3339 timestamp |
platform |
string | Platform identifier |
pid |
u32 | Child process ID |
pgid |
u32? | Process group ID (Unix only; null on Windows) |
tree_kill_reliability |
string | "guaranteed" or "best_effort" |
warnings |
string[] | Platform-specific warnings |
Platform Notes
- Unix: Uses
setpgid(0, 0)to create new process group - Windows: Creates Job Object with
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE - Degradation: If Job Object creation fails (nested jobs, privilege limits), returns
tree_kill_reliability: best_effort
Wait PID With Timeout
Problem
Graceful stop flows need to wait for process exit without blocking forever:
- Send TERM
- Wait up to N seconds
- Send KILL if still running
Solution
wait_pid polls for process exit with configurable timeout.
Rust Usage
use sysprims_proc::wait_pid;
use std::time::Duration;
// Wait up to 10 seconds for exit
let outcome = wait_pid(pid, Duration::from_secs(10))?;
if outcome.timed_out {
println!("Process {} did not exit, escalating to SIGKILL", pid);
} else if let Some(code) = outcome.exit_code {
println!("Process {} exited with code {}", pid, code);
}Go Usage
import (
"time"
"github.com/3leaps/sysprims/bindings/go/sysprims"
)
outcome, err := sysprims.WaitPID(pid, 10*time.Second)
if err != nil {
log.Fatal(err)
}
if outcome.TimedOut {
log.Printf("Process %d did not exit, escalating", pid)
} else {
log.Printf("Process %d exited with code %d", pid, outcome.ExitCode)
}TypeScript Usage
import { waitPID } from '@3leaps/sysprims';
const outcome = waitPID(pid, 10000); // 10 seconds in ms
if (outcome.timed_out) {
console.log(`Process ${pid} did not exit, escalating`);
} else {
console.log(`Process ${pid} exited with code ${outcome.exit_code}`);
}WaitPidResult Fields
| Field | Type | Description |
|---|---|---|
schema_id |
string | Schema identifier |
timestamp |
string | RFC3339 timestamp |
platform |
string | Platform identifier |
pid |
u32 | PID waited on |
exited |
bool | True if process exited |
timed_out |
bool | True if timeout elapsed before exit |
exit_code |
i32? | Exit code (if exited and obtainable) |
warnings |
string[] | Platform-specific warnings |
Platform Notes
- Unix: Polling via
kill(pid, 0)(works for arbitrary PIDs; not parent-specific) - Windows:
OpenProcess+WaitForSingleObject(best-effort)
Terminate Tree
Problem
Consumers want a single "stop job" primitive that:
- Attempts graceful termination
- Waits for exit
- Escalates to kill
- Reports what happened
Today this is coupled to run_with_timeout's spawned command management.
Solution
terminate_tree provides one-call process tree termination, usable for arbitrary PIDs.
Rust Usage
use sysprims_timeout::{terminate_tree, TerminateTreeConfig};
let outcome = terminate_tree(pid, TerminateTreeConfig {
grace_timeout_ms: 5000,
kill_timeout_ms: 2000,
..Default::default()
})?;
println!("Grace sent: {}", outcome.grace_sent);
println!("Escalated to kill: {}", outcome.escalated);
println!("Tree kill reliability: {:?}", outcome.tree_kill_reliability);
for warning in &outcome.warnings {
eprintln!("Warning: {}", warning);
}Go Usage
import "github.com/3leaps/sysprims/bindings/go/sysprims"
config := sysprims.TerminateTreeConfig{
GraceTimeoutMs: 5000,
KillTimeoutMs: 2000,
}
outcome, err := sysprims.TerminateTree(pid, config)
if err != nil {
log.Fatal(err)
}
if outcome.Escalated {
log.Printf("Had to escalate to SIGKILL")
}TypeScript Usage
import { terminateTree } from '@3leaps/sysprims';
const outcome = terminateTree(pid, {
grace_timeout_ms: 5000,
});
if (outcome.escalated) {
console.log('Had to escalate to kill');
}
for (const warning of outcome.warnings) {
console.warn(warning);
}TerminateTreeConfig Fields
| Field | Type | Default | Description |
|---|---|---|---|
signal |
signal | SIGTERM | Initial termination signal |
grace_timeout_ms |
u64 | 10000 | Wait time before escalation (milliseconds) |
kill_signal |
signal | SIGKILL | Escalation signal |
kill_timeout_ms |
u64 | 2000 | Wait time after kill signal (milliseconds) |
TerminateTreeResult Fields
| Field | Type | Description |
|---|---|---|
schema_id |
string | Schema identifier |
timestamp |
string | RFC3339 timestamp |
platform |
string | Platform identifier |
pid |
u32 | PID that was terminated |
pgid |
u32? | Process group ID (Unix only) |
signal_sent |
i32 | Signal sent for graceful termination |
kill_signal |
i32? | Kill signal (if escalated) |
escalated |
bool ... |
v0.1.5
sysprims v0.1.5
Release Date: 2026-01-24
Status: TypeScript Bindings Parity Release
Summary
Node.js developers now have access to process inspection, port mapping, and signal APIs. This release achieves parity with Go bindings for these core surfaces (proc/ports/signals).
Highlights
- TypeScript Parity: Process listing, port inspection, and signal operations
- Full Type Definitions: All schemas have corresponding TypeScript interfaces
- Windows Stability: Signal tests no longer flaky on Windows CI
- CI Improvements: Separated binding validation from release validation
TypeScript API
Installation
npm install @3leaps/sysprimsNew Functions
| Function | Description |
|---|---|
processList(filter?) |
List running processes with optional filtering |
listeningPorts(filter?) |
Map listening ports to owning processes |
signalSend(pid, signal) |
Send signal to a process |
signalSendGroup(pgid, signal) |
Send signal to a process group (Unix only) |
terminate(pid) |
Graceful termination (SIGTERM on Unix; TerminateProcess on Windows - immediate, not graceful) |
forceKill(pid) |
Immediate kill (SIGKILL on Unix; TerminateProcess on Windows) |
Existing Functions (from v0.1.4)
| Function | Description |
|---|---|
procGet(pid) |
Get process info by PID |
selfPGID() |
Get current process group ID (Unix only) |
selfSID() |
Get current session ID (Unix only) |
Usage Examples
import {
processList,
listeningPorts,
terminate,
forceKill,
signalSend
} from '@3leaps/sysprims';
// List all processes
const all = processList();
console.log(`Found ${all.processes.length} processes`);
// Filter by name (case-insensitive substring match)
const nginx = processList({ name_contains: "nginx" });
for (const proc of nginx.processes) {
console.log(`${proc.pid}: ${proc.name} (${proc.cpu_percent}% CPU)`);
}
// Filter by multiple criteria (AND logic)
const heavy = processList({
cpu_above: 50,
memory_above_kb: 100000
});
// Find what's listening on port 8080
const http = listeningPorts({ local_port: 8080 });
for (const binding of http.bindings) {
console.log(`Port ${binding.local_port}: PID ${binding.pid} (${binding.process?.name})`);
}
// Filter by protocol
const tcpPorts = listeningPorts({ protocol: "tcp" });
// Gracefully terminate a process
terminate(1234);
// Force kill if graceful termination doesn't work
forceKill(1234);
// Send specific signal (Unix semantics; limited support on Windows)
signalSend(1234, 15); // SIGTERMFilter Options
ProcessFilter (for processList):
name_contains: Substring match (case-insensitive)name_equals: Exact name matchuser_equals: Filter by usernamepid_in: Array of PIDs to includestate_in: Array of process states ("running","sleeping","stopped","zombie","unknown")cpu_above: Minimum CPU percentage (0-100)memory_above_kb: Minimum memory in KB
PortFilter (for listeningPorts):
protocol:"tcp"or"udp"local_port: Specific port number
Requirements
- Node.js 18+
- glibc-based Linux (musl/Alpine not supported)
Changes
Added
-
TypeScript Bindings Parity (
bindings/typescript/sysprims/)processList(filter?)- list processes with optional filteringlisteningPorts(filter?)- port-to-PID mappingsignalSend(pid, signal)- send signal to processsignalSendGroup(pgid, signal)- send signal to process group (Unix)terminate(pid)- graceful terminationforceKill(pid)- immediate kill- Full TypeScript type definitions for
ProcessFilter,PortFilter,ProcessSnapshot,PortBindingsSnapshot
-
CI Improvements
- Separated binding validation from release validation (
validate-release.yml) - Clarified Go module tagging requirements in release validation
- Separated binding validation from release validation (
Changed
- Go Prebuilt Libraries
- Updated all 7 platform libraries for v0.1.5
Fixed
- Windows Signal Tests
- Signal tests now use deterministic patterns: reject pid=0, spawn-and-kill for terminate/forceKill
- Eliminates flakiness where arbitrary PIDs may exist on CI runners
Platforms
| Platform | CLI | Go Bindings | TypeScript Bindings |
|---|---|---|---|
| Linux x64 (glibc) | yes | yes | yes |
| Linux x64 (musl) | yes | yes | no |
| Linux arm64 (glibc) | yes | yes | yes |
| Linux arm64 (musl) | yes | yes | no |
| macOS x64 | yes | yes | yes |
| macOS arm64 | yes | yes | yes |
| Windows x64 | yes | yes | yes |
Note: TypeScript bindings require glibc. Linux musl (Alpine) is not supported.
Verification
Verify this release with the signed checksums:
# Download release and verification files
curl -LO https://github.com/3leaps/sysprims/releases/download/v0.1.5/SHA256SUMS
curl -LO https://github.com/3leaps/sysprims/releases/download/v0.1.5/SHA256SUMS.minisig
curl -LO https://github.com/3leaps/sysprims/releases/download/v0.1.5/sysprims-minisign.pub
# Verify signature
minisign -Vm SHA256SUMS -p sysprims-minisign.pub
# Verify checksums
shasum -a 256 -c SHA256SUMS --ignore-missingNext Release
v0.1.6+ will continue toward:
- Extended self-introspection surface (
self_infoAPI) - Python bindings (cffi + wheel packaging)
- Timeout API for TypeScript (complex config struct)
v0.1.4
sysprims v0.1.4
Release Date: 2026-01-22
Status: TypeScript Language Bindings Release
Summary
Node.js developers can now integrate sysprims directly. This release delivers koffi-based TypeScript bindings with cross-platform support, continuing the language bindings story after Go (v0.1.3).
Highlights
- TypeScript Bindings: First-class Node.js support via koffi FFI
- Cross-Platform: linux-amd64, linux-arm64, darwin-arm64, windows-amd64
- ABI Verification: Library loader validates ABI version at startup
- CI Coverage: Native ARM64 Linux testing added to CI matrix
TypeScript Bindings
Installation
npm install @3leaps/sysprimsUsage
import { procGet, selfPGID, selfSID } from '@3leaps/sysprims';
// Get process info by PID
const proc = procGet(process.pid);
console.log(`Process ${proc.pid}: ${proc.name}`);
// Get current process group/session IDs (Unix)
const pgid = selfPGID();
const sid = selfSID();API
| Function | Description |
|---|---|
procGet(pid) |
Get process info by PID (returns parsed JSON) |
selfPGID() |
Get current process group ID (Unix only) |
selfSID() |
Get current session ID (Unix only) |
Requirements
- Node.js 18+
- glibc-based Linux (musl/Alpine not supported)
How It Works
The TypeScript bindings use koffi to call the sysprims C-ABI shared library. At load time, the binding:
- Detects the current platform
- Loads the appropriate shared library from
_lib/<platform>/ - Verifies ABI version matches expected value
- Exposes typed functions to JavaScript
Changes
Added
-
TypeScript Bindings (
bindings/typescript/sysprims/)- koffi-based FFI for Node.js 18+
- Cross-platform: linux-amd64, linux-arm64, darwin-arm64, windows-amd64
- ABI version verification on library load
- CI validates TypeScript bindings on all supported platforms
-
Linux ARM64 CI Coverage
- Added linux-arm64 runner to CI matrix for native ARM64 testing
Changed
- CI Platform Matrix
- Replaced darwin-amd64 (Intel Mac) with linux-arm64
- Intel Mac runners deprecated by GitHub Actions
- TypeScript CI: linux-amd64, linux-arm64, darwin-arm64, windows-amd64
Fixed
-
Windows TypeScript Tests
- Cross-platform build scripts replace Unix-only shell commands
npm run test:cinow works on Windows runners
-
Parallel Test Flakiness
- Added atomic counter to
unique_marker()in tree_escape tests - Prevents collisions when tests run in parallel with same PID/timestamp
- Added atomic counter to
Platforms
| Platform | CLI | Go Bindings | TypeScript Bindings |
|---|---|---|---|
| Linux x64 (glibc) | yes | yes | yes |
| Linux x64 (musl) | yes | yes | no |
| Linux arm64 (glibc) | yes | yes | yes |
| Linux arm64 (musl) | yes | yes | no |
| macOS x64 | yes | yes | no (deprecated) |
| macOS arm64 | yes | yes | yes |
| Windows x64 | yes | yes | yes |
Note: TypeScript bindings require glibc. Linux musl (Alpine) is not supported.
Verification
Verify this release with the signed checksums:
# Download release and verification files
curl -LO https://github.com/3leaps/sysprims/releases/download/v0.1.4/SHA256SUMS
curl -LO https://github.com/3leaps/sysprims/releases/download/v0.1.4/SHA256SUMS.minisig
curl -LO https://github.com/3leaps/sysprims/releases/download/v0.1.4/sysprims-minisign.pub
# Verify signature
minisign -Vm SHA256SUMS -p sysprims-minisign.pub
# Verify checksums
shasum -a 256 -c SHA256SUMS --ignore-missingNext Release
v0.1.5+ will continue toward:
- Python bindings (cffi + wheel packaging)
- Additional TypeScript API surface