pidlens
Everything macOS knows about a running process, in one readable report.
ps tells you a process exists. lsof tells you what it has open. codesign
tells you who signed it. launchctl tells you who keeps it alive. pidlens
asks all of them and prints one report you can actually read — decoded into
English, wrapped to your terminal, no flags to memorise.
sthnaqvi@macbook ~ % pidlens 594
╭──────────────────────────────────────────────────────────────────────────╮
│ Finder (pid 594) │
╰──────────────────────────────────────────────────────────────────────────╯
── IDENTITY ────────────────────────────────────────────────────────────────
Name : Finder
PID : 594
Parent PID : 1
Owner : sthnaqvi (uid 501)
Terminal : none — not attached to a terminal
State : sleeping (waiting, idle under 20s)
Scheduling : priority 46, nice 0 (normal)
Process group : 594
Session : 0
── WHEN IT STARTED ─────────────────────────────────────────────────────────
Started at : Wednesday, 5 August 2026 at 9:38:15 PM
Running for : 23 days, 18 hours, 31 min
CPU time used : 38 min, 26 sec (0.1% of one core over its lifetime)
Booted at : Wednesday, 5 August 2026 at 9:37:59 PM
Vs. boot : started 16 sec after boot → a startup service, not
something you launched
Unix epoch : 1785946095
── WHAT IS RUNNING ─────────────────────────────────────────────────────────
Executable : /System/Library/CoreServices/Finder.app/Contents/MacOS
/Finder
Arguments : none
Working dir : /
── WHO STARTED IT ──────────────────────────────────────────────────────────
[1] root
/sbin/launchd
└─ [594] sthnaqvi
/System/Library/CoreServices/Finder.app/Contents/MacOS/Finder
── RESOURCE USAGE ──────────────────────────────────────────────────────────
CPU now : 0.0%
Memory : 26.6 MB resident (0.1% of RAM)
Virtual size : 398.8 GB
virtual size is address space reserved, not memory in use
Threads : 10
Open handles : 249
── NETWORK ─────────────────────────────────────────────────────────────────
no network sockets open
plus 1 unix-domain socket(s) for local IPC
── THE BINARY ON DISK ──────────────────────────────────────────────────────
Path : /System/Library/CoreServices/Finder.app/Contents/MacOS
/Finder
Size : 23.6 MB
Modified : 17 August 2025 at 12:14 AM
Owner : root, mode 755
Architectures : arm64e, x86_64
Signed by : Software Signing
Chain : Apple Code Signing Certification Authority → Apple Root CA
Bundle ID : com.apple.finder
Team ID : —
Signed on : 12 Jul 2025 at 2:00:36 PM
Entitlements : 88 granted
· com.apple.accounts.appleaccount.fullaccess
· com.apple.amp.devices.client
· com.apple.application-identifier
── SERVICE / LAUNCHD ───────────────────────────────────────────────────────
Managed job : com.apple.Finder
Last exit : 0
launchd owns this — it is restarted automatically when it exits
…
Install
Homebrew
brew tap sthnaqvi/tap
brew install pidlens
npm
npm install -g pidlens
pip (or pipx, which keeps it out of your system Python)
pipx install pidlens # recommended
pip install pidlens
From source
git clone https://github.com/sthnaqvi/pidlens.git
cd pidlens && make venv && make test && pip install .
The npm package bundles the same Python program and only needs Python 3.9+ on
your PATH — nothing is compiled or downloaded at install time. Set
PIDLENS_PYTHON to pick a specific interpreter.
However you installed it, pidlens --version says which channel that copy came
from, so a bug report can name it:
sthnaqvi@macbook ~ % pidlens --version
pidlens 0.1.0 (installed via homebrew)
Usage
pidlens 594 # by pid
pidlens Finder # by name — exact first, then loose, then argv
pidlens node --all # don't truncate long lists
pidlens 594 --json | jq . # same data, machine-readable
pidlens 594 -s timing,binary # only the sections you want
sudo pidlens 594 # also reveals environment variables
| Flag | What it does |
|---|---|
-a, --all |
Print every open file, environment variable and entitlement instead of the first few |
-j, --json |
Emit the collected data as JSON — the same values the report renders from |
-s, --sections LIST |
Render only these sections, comma separated |
-w, --width N |
Wrap to N columns instead of detecting the terminal |
--no-color |
Disable ANSI colour (also honours NO_COLOR and non-tty output) |
-V, --version |
Print the version |
Exit codes: 0 report produced · 2 no target given · 3 no such process ·
4 not macOS.
What each section answers
| Section | The question it answers |
|---|---|
identity |
Who owns it, what state it's in, and what that state actually means |
timing |
When did it start, how long has it run, how much CPU has it burned, and did it start at boot (so: a system service, not something you launched) |
command |
Which binary, which arguments, from which working directory |
environment |
Its environment variables, with credential-shaped values masked |
ancestry |
The chain of parents up to launchd, plus its children |
resources |
CPU, resident memory, virtual size, threads, open handles |
network |
Every socket in English — listening ports, established peers, unbound sockets |
files |
Open files grouped into libraries, real files, pipes and devices |
binary |
Size, architectures, signing authority and chain, Team ID, signing date, entitlements, Gatekeeper verdict, quarantine flag, installing package |
launchd |
Whether launchd owns it and will restart it |
commands |
Copy-paste fs_usage, nettop, log stream, sample, spindump, lldb lines with the pid already filled in |
Answering "when did this start?"
The timing section is the one most people come for:
Started at : Wednesday, 5 August 2026 at 9:38:15 PM
Running for : 23 days, 18 hours, 31 min
CPU time used : 38 min, 26 sec (0.1% of one core over its lifetime)
Booted at : Wednesday, 5 August 2026 at 9:37:59 PM
Vs. boot : started 16 sec after boot → a startup service, not
something you launched
Unix epoch : 1785946095
ps -o lstart gives you the first line. The value is in the rest: elapsed time
in units you can hold in your head, CPU time as a share of one core, and the
comparison against boot that tells you whether anyone ever chose to run this.
JSON
Every value in the report comes from one dict, and --json prints it:
sthnaqvi@macbook ~ % pidlens 594 --json | jq '{started: .timing.started_human,
age: .timing.elapsed_human, signed_by: .binary.signature.authority,
launchd: .launchd.label}'
{
"started": "Wednesday, 5 August 2026 at 9:38:15 PM",
"age": "23 days, 18 hours, 31 min",
"signed_by": "Software Signing",
"launchd": "com.apple.Finder"
}
More recipes — every field is documented in docs/json-schema.md:
pidlens node --json | jq -r '.sockets[].description'
pidlens Safari --json | jq -r '.binary.entitlements[]'
pidlens 594 --json | jq '.timing.started_at_boot'
How it works
pidlens is read-only. It never attaches to, signals, or modifies the process it describes — it shells out to tools already on your Mac and parses what they print:
| Tool | Used for |
|---|---|
ps |
identity, state, priority, start time, elapsed and CPU time, argv, environment |
lsof |
open files, sockets, working directory, handle count |
pgrep |
name matching and children |
sysctl |
boot time, to place the start time in context |
codesign |
signing authority, Team ID, signing date, entitlements |
spctl |
the Gatekeeper verdict (assessed on the app bundle, not the inner executable) |
xattr, pkgutil, file |
quarantine flag, installing package, architectures |
launchctl |
whether it's a managed job |
Two consequences worth knowing:
- Environment variables need root. macOS only exposes another process's
environment to root, even your own processes.
sudo pidlens PIDshows them. Names that look like credentials (*_TOKEN,*_SECRET,*PASSWORD*, …) are masked before printing either way. - Gatekeeper "not assessed" is normal.
spctlonly rates app bundles, so a plain executable comes back unassessable even when its signature is perfectly valid. pidlens says that, instead of reporting a scary "rejected".
Requirements
macOS (tested on Sonoma and Sequoia) and Python 3.9 or newer — the version macOS itself ships. No third-party dependencies — the whole tool is the standard library plus the system tools above.
Development
make venv # local .venv with dev dependencies
make test # 550+ tests, no live processes required for most of them
make demo # run pidlens against your shell
make build # sdist + wheel
make npm-pack # build the npm tarball
The code is split so that almost all of it is testable without a running process:
src/pidlens/
humanize.py bytes, durations, ps state codes → English (pure)
layout.py width-aware wrapping and the Renderer (pure)
parse.py one parser per tool's output format (pure)
system.py the only module that shells out
collect.py builds the data model for one pid
report.py turns the data model into sections
cli.py arguments, exit codes, JSON
Tests use fixtures captured from real lsof, codesign, spctl and
launchctl output, plus a FakeRunner that answers commands from a dict — so
the parsers and the entire report are verified without touching a live process.
The layout suite asserts that no line ever exceeds the terminal width and
that wrapping loses no characters, across widths from 38 to 110 columns.
Contributing
Issues and pull requests are welcome — see CONTRIBUTING.md.
License
MIT © Sayed Tauseef Naqvi
Release files for pidlens 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pidlens-0.1.2.tar.gz | 34.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pidlens-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 59.5 kB
Release files / pidlens-0.1.2.tar.gz
| Download URL | pidlens-0.1.2.tar.gz |
|---|---|
| Size | 34.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
44539f86b24d188347a8721eb56741ca11699b0f7c01da105be230686cf33a73
|
|
BLAKE2b-256 checksum How to use checksums |
631077bd831caa56c5ff2437df92e46d047ef112098c19dea92108e2d6c50980
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 29, 2026.
Transparency logRelease files / pidlens-0.1.2-py3-none-any.whl
| Download URL | pidlens-0.1.2-py3-none-any.whl |
|---|---|
| Size | 24.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7cc876d4ecf847657167e2641edfa339d60049664c824a834e4144f90e67a5ad
|
|
BLAKE2b-256 checksum How to use checksums |
24276381b2eb207d6db9d0174e529222fdc56a8b71d8dff6dccd28fc9e44bda2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 29, 2026.
Transparency log