Metadata-Version: 2.4
Name: pidlens
Version: 0.1.2
Summary: Everything macOS knows about a running process, in one readable report.
Author-email: Sayed Tauseef Naqvi <tauseef.naqvi786@gmail.com>
License: MIT
Project-URL: Homepage, https://github.com/sthnaqvi/pidlens
Project-URL: Repository, https://github.com/sthnaqvi/pidlens
Project-URL: Issues, https://github.com/sthnaqvi/pidlens/issues
Project-URL: Changelog, https://github.com/sthnaqvi/pidlens/blob/main/CHANGELOG.md
Keywords: macos,process,ps,lsof,pid,cli,sysadmin,debugging,forensics,codesign,launchd
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: System :: Monitoring
Classifier: Topic :: System :: Systems Administration
Classifier: Topic :: Utilities
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: build>=1.0; extra == "dev"
Requires-Dist: twine>=5.0; extra == "dev"
Dynamic: license-file

# pidlens

**Everything macOS knows about a running process, in one readable report.**

[![CI](https://github.com/sthnaqvi/pidlens/actions/workflows/ci.yml/badge.svg)](https://github.com/sthnaqvi/pidlens/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/pidlens)](https://pypi.org/project/pidlens/)
[![npm](https://img.shields.io/npm/v/pidlens)](https://www.npmjs.com/package/pidlens)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

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

```console
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**

```sh
brew tap sthnaqvi/tap
brew install pidlens
```

**npm**

```sh
npm install -g pidlens
```

**pip** (or [pipx](https://pipx.pypa.io), which keeps it out of your system Python)

```sh
pipx install pidlens     # recommended
pip install pidlens
```

**From source**

```sh
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:

```console
sthnaqvi@macbook ~ % pidlens --version
pidlens 0.1.0 (installed via homebrew)
```

## Usage

```sh
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:

```console
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](docs/json-schema.md):

```sh
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 PID` shows them.
  Names that look like credentials (`*_TOKEN`, `*_SECRET`, `*PASSWORD*`, …) are
  masked before printing either way.
- **Gatekeeper "not assessed" is normal.** `spctl` only 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

```sh
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](CONTRIBUTING.md).

## License

MIT © Sayed Tauseef Naqvi
