Skip to content

CLI Reference

Monitor offers an interactive TUI (monitor studio), but every view is also a subcommand. Running bare monitor prints help. Most subcommands support --json for machine-readable output, so the CLI is the primary surface for scripts and agents.

bash
monitor --help          # list every subcommand
monitor <cmd> --help    # flags for one subcommand
monitor --version       # prints: monitor <version>

The subcommands group into four purposes:

Global flags

FlagEffect
--no-temperature-sourceSkip powermetrics; use the CPU-load estimation fallback (no sudo required). Persistent — available under every subcommand.
--jsonEmit JSON (or NDJSON for watch) to stdout. Supported by every subcommand except telemetry, which is always NDJSON, and run, reload, mcp, vault, and studio.
--pprof <addr>Expose Monitor's own net/http/pprof on addr (e.g. localhost:6060). Off by default.
--versionPrint the version and exit.

Profiling Monitor itself

--pprof <addr> starts an HTTP server serving Monitor's own /debug/pprof/ endpoints on the given address, then continues with whatever subcommand or mode you asked for. It is most useful with the long-running modes — the TUI, watch, history record, and mcp serve — where there's a live process to profile. Monitor logs the listen address to stderr:

bash
monitor --pprof localhost:6060 watch --json
# monitor: pprof listening on http://localhost:6060/debug/pprof/

Because the endpoint is a standard net/http/pprof server, you can point Monitor's own profiler at it to capture Monitor profiling Monitor:

bash
monitor profile <monitor-pid> --type heap --pprof-addr localhost:6060 --json

Observed-child environment

monitor run always sets MONITOR=1 and MONITOR_RUN_DIR in the glyphrun child's environment so the spec and the processes it launches can detect the observed run:

MONITOR=1
MONITOR_RUN_DIR=/private/tmp/monitor-run-...

An inherited MONITOR_RUN_DIR is reused. Otherwise monitor run creates a temporary run directory and removes it after glyphrun exits. In both cases the glyphrun child receives MONITOR=1 explicitly.

Inspect

snapshot

Print a single system snapshot — CPU, memory, network, and the process list. Default output is human-readable; --json emits the full lossless structure. --compact emits JSON using the stable monitor.compact_snapshot schema. It omits histories and per-core arrays, caps both top-process lists and the filesystem list, and is the recommended form for agent context windows.

FlagDefaultEffect
--interval1sWarm-up delay between counter samples; 0 skips warm-up and leaves first-sample rates unavailable.
--jsonfalseEmit JSON to stdout.
--compactfalseEmit bounded, schema-versioned JSON instead of human/full output.
--process-limit5Entries in each of top_cpu and top_memory; clamped to 25.
--process-filter""Case-insensitive process-name substring for the compact view.
--filesystem-limit10Filesystems in the compact view; clamped to 50.
--filesystem-filter""Case-insensitive device, mount-point, or filesystem substring.
bash
monitor snapshot
monitor snapshot --json | jq '.cpu.usage_percent'
monitor snapshot --compact --process-limit 3 --process-filter ollama
monitor snapshot --compact --filesystem-filter apfs | jq '.filesystems'

The compact payload starts with schema_version: 1 and kind: "monitor.compact_snapshot". processes.system_total, processes.matched, processes.truncated, filesystem_system_total, filesystem_total, filesystem_limit, and filesystems_truncated make every omission explicit. Empty lists serialize as [], never null. The existing --json shape is unchanged.

Per-process CPU in a warmed snapshot is an interval measurement derived from the delta of cumulative user+system CPU counters. 100% means one fully used core, so a multithreaded process can exceed it. A first observation, reused PID, backwards counter, or non-positive interval is marked unavailable rather than presented as a trustworthy zero. --interval 0 deliberately skips the second sample, so process CPU and other counter-derived rates can be unavailable.

json
{
  "cpu": {
    "usage_percent": 25.9,
    "per_core_usage": [52.1, 47.3, 41.9],
    "frequency_mhz": 4,
    "core_count": 10,
    "thread_count": 10,
    "load_avg_1": 5.51,
    "load_avg_5": 5.62,
    "load_avg_15": 5.07
  },
  "memory": {
    "total_bytes": 17179869184,
    "used_bytes": 11715772416,
    "usage_percent": 68.19
  }
}

watch

Stream metrics as NDJSON (newline-delimited JSON) to stdout. Each line is a self-describing object with a type discriminator (tick, alert, stash). Pipe into jq or any other line-oriented tool.

FlagDefaultEffect
-i, --interval1sTick interval.
--oncefalseEmit one event and exit.
--stashfalseOn every alert, capture the incident to fcheap via internal/incidents.
--stash-ttl7dTTL for incident stashes (passed to fcheap --ttl).
--webhook""POST each alert as JSON to this URL.
--notifyfalseShow a desktop notification on each alert (osascript / notify-send).
--alert-cooldown1mSuppress repeats of the same active finding across output and sinks; 0 emits every sample.
--delivery-limit8Maximum concurrent stash, webhook, and notification deliveries; excess work is dropped with a stderr warning.
--jsonfalseEmit NDJSON to stdout.

Alerts come from the built-in analyzer. Five rules always run, and watch feeds the analyzer the full event — including per-partition Disk and the Processes list — so the per-process rules fire too:

  • cpu_spike — current process CPU exceeds 3× its rolling median baseline and an absolute 50% floor.
  • rss_growth — process RSS keeps climbing faster than 50 KB/s (wall-clock-normalized regression with R² confidence).
  • disk_fill — any mounted partition's usage is >= 90%.
  • swap_pressure — swap used is >= 50% of swap total (real memory pressure).
  • zombie_process — the OS reports a process in zombie (Z) state.

In addition, the cpu_alert_threshold and memory_alert_threshold settings in config.json get teeth here: when either is set above 0, a threshold rule fires cpu_threshold / mem_threshold alerts whenever overall CPU or memory usage crosses the configured percentage.

With --stash, each alert captures an incident bundle in a background goroutine and emits a separate stash line reporting the outcome; the watch loop never blocks on fcheap I/O, and a stash failure shows up in a stash_error field rather than stalling stdout.

The same stash event also carries a grouped issue and occurrence when the local issue write succeeds. issue_error reports that independent failure. Issue persistence is tied to --stash; plain watch streams do not write the issue store.

--webhook and --notify are additional alert sinks — they fire alongside (not instead of) the NDJSON stream and --stash. Deliveries run asynchronously under the bounded --delivery-limit, so the watch loop never blocks or creates unbounded goroutines when a sink is slow. --webhook POSTs the raw collector.Alert JSON (the same object embedded in an alert line) to the given URL; a non-2xx response is logged to stderr. --notify shells out to osascript on macOS or notify-send on Linux; delivery failures are logged to stderr and never stall the stream.

Repeated alerts are gated by a stable identity (rule plus PID, filesystem, or system scope). The default one-minute cooldown prevents a full disk or sustained threshold from creating a stash, webhook, and notification every second. The first finding emits immediately and can emit again after the cooldown expires.

When the analyzer attaches a diagnosis to a per-process alert (see monitor_analyze in the MCP reference), it rides along on every sink: the webhook payload gains a diagnosis object, the desktop notification body swaps the terse rule detail for the diagnosis summary and appends the confidence level, and the fcheap bundle's manifest.json carries it too (tagged confidence:<level> for search). All additive — consumers that ignore the new key see no change.

bash
monitor watch --json | jq -c 'select(.type=="tick")'
monitor watch --json --stash --stash-ttl 24h
monitor watch --json --webhook https://hooks.example.com/alerts --notify
json
{"type":"tick","timestamp":"2026-06-27T00:02:38Z","cpu":{"usage_percent":25.9},"memory":{"usage_percent":68.1},"hostname":"host"}
{"type":"alert","timestamp":"2026-06-27T00:02:41Z","alert":{"rule":"cpu_spike","severity":"warning","pid":1133,"process":"node","detail":"cpu spike","diagnosis":{"summary":"node pinned a core for 45s while RSS stayed flat — consistent with a hot loop","evidence":["cpu 150% for 45s","rss flat"],"confidence":"medium","next_actions":["monitor profile 1133 --type cpu","monitor investigate 1133"]}}}
{"type":"alert","timestamp":"2026-06-27T00:02:44Z","alert":{"rule":"cpu_threshold","severity":"warning","detail":"CPU 91% >= threshold 90%"}}

telemetry

Stream bounded, versioned host-metric rollups as NDJSON. Unlike watch and snapshot, this is a privacy boundary for an external control-plane adapter: it excludes hostname, IP addresses, PIDs, process names, users, command arguments, environment variables, devices, mounts, paths, raw collector errors, alert details, and diagnoses.

Monitor only writes the stream to stdout. It makes no network request and does not persist a spool or credentials. The consuming adapter owns authentication, delivery, retry limits, deployment identity, and durable storage.

FlagDefaultEffect
-i, --interval5sInterval between source samples; minimum 1 second.
-w, --window30sRollup period; must be at least the interval and contain at most 3,600 samples.
--oncefalsePrewarm rate counters, wait one interval, emit one partial window, and exit.
bash
monitor telemetry
monitor telemetry --interval 1s --window 10s
monitor telemetry --once --interval 1s --window 1s | jq .

Every line uses schema_version: 1 and kind: "monitor.telemetry_window". session_id plus sequence is a stable idempotency identity for a consumer. Completed windows set partial: false; normal SIGINT/SIGTERM flushes a non-empty partial window and exits cleanly. Each line, including its newline, is at most 32 KiB.

The fixed metric IDs and their units are:

Metric IDUnit
system.cpu.usagepercent
system.memory.usedbytes
system.memory.availablebytes
system.memory.usagepercent
system.memory.pressurepercent
system.swap.usedbytes
system.network.receive_ratebytes_per_second
system.network.transmit_ratebytes_per_second
system.disk.read_ratebytes_per_second
system.disk.write_ratebytes_per_second
system.load.one_minuteload

Each observed metric carries count, min, avg, nearest-rank p95, max, and last. availability always describes all eleven metrics with observed, partial, unsupported, or unavailable, plus observed/missing sample counts. Alerts are reduced to allowlisted system rules (cpu_threshold, mem_threshold, disk_fill, swap_pressure), a safe severity, and the number of affected samples. The current strict collector emits only swap_pressure; it does not enumerate filesystems to calculate disk_fill.

See the Telemetry contract for the complete schema and compatibility policy.

process

Print detailed information for a single PID. Takes exactly one argument.

FlagDefaultEffect
--jsonfalseEmit JSON output.
--codebaseauto-detectOverride the project root used for runtime binding and later codemap/vecgrep correlation.
bash
monitor process 1133
monitor process 1133 --json
json
{
  "pid": 1133,
  "name": "node",
  "cpu_percent": 150.4,
  "memory": 339427328,
  "memory_percent": 1.98,
  "threads": 8,
  "user": "developer",
  "status": "S",
  "parent": 1,
  "is_system": false,
  "is_protected": false,
  "metric_states": {"cpu": {"state": "observed"}},
  "binding": {
    "pid": 1133,
    "name": "node",
    "runtime": "node",
    "main_script": "/workspace/dist/server.js",
    "codebase_root": "/workspace",
    "inspect_addr": "127.0.0.1:9229",
    "argv_redacted": true
  }
}

The original ProcessInfo fields remain at the top level; binding is added beside them rather than wrapping them in a nested process object.

Monitor inspects argv in memory to infer the runtime, entrypoint, and inspector address. It does not serialize argv into this JSON output, issue records, incident bundles, or telemetry.

processes

List a bounded process inventory without requesting the full system snapshot. ps is an alias. By default it uses max_processes and show_system_processes from Monitor's effective configuration; flags override those defaults. Human rows include OS status; JSON entries additionally expose parent, memory share, I/O counters when supported, and per-field metric_states when a value is unavailable.

FlagDefaultEffect
--sortcpuSort by cpu, memory, pid, or name.
-f, --filter""Case-insensitive substring across name, PID, user, and status.
-n, --limitconfig valueMaximum rows, capped at 1000.
--system, --allconfig valueInclude system-owned processes.
--jsonfalseEmit a bounded JSON envelope with match/truncation metadata.
bash
monitor processes --sort memory --limit 10
monitor ps --filter node --json

tree

Print the process forest by parent/child relationship. With no argument, every top-level process is shown (one whose parent isn't in the captured set, e.g. reparented to init), each followed by its descendants indented underneath. Pass a PID to print just that process's subtree. Children are sorted by PID for determinism.

FlagDefaultEffect
--jsonfalseEmit nested JSON output.
bash
monitor tree              # the whole forest
monitor tree 1234         # the subtree rooted at pid 1234
monitor tree --json | jq '.[0].children'

The text output indents each child under its parent and annotates CPU and memory:

launchd (pid 1)  cpu 0.0%  mem 12.1 MB
  loginwindow (pid 512)  cpu 0.1%  mem 48.0 MB
  node (pid 1133)  cpu 3.1%  mem 256.0 MB
    esbuild (pid 1190)  cpu 0.0%  mem 32.0 MB

With --json, each node is the full process object with its children nested under a children array (omitted when a process has none):

json
[
  {
    "pid": 1,
    "name": "launchd",
    "cpu_percent": 0.0,
    "memory": 12685312,
    "children": [
      {"pid": 1133, "name": "node", "cpu_percent": 3.1, "memory": 268435456,
       "children": [
         {"pid": 1190, "name": "esbuild", "cpu_percent": 0.0, "memory": 33554432}
       ]}
    ]
  }
]

Act

kill

Safely terminate one or more processes. Accepts one or more PIDs. Termination is gated by the same safety check used by the TUI and the MCP server: protected processes (launchd, kernel_task, WindowServer, Finder, Dock, …) and system (root-owned) processes are always refused. The compatibility --yes flag acknowledges intent but cannot bypass this invariant.

The kill is verified, not just dispatched: after sending the signal, kill polls for up to ~2 seconds to observe whether the process actually exited, and reports one of three outcomes — terminated, still_running, or unknown (state couldn't be checked) — alongside waited_ms. A process that survives SIGTERM is reported as still_running with a next_action suggesting --force; kill never escalates to SIGKILL on its own.

FlagDefaultEffect
--forcefalseSend SIGKILL instead of SIGTERM.
--yesfalseAcknowledge intent; never overrides protected/system safety.
--jsonfalseEmit JSON output.
bash
monitor kill 1234                 # SIGTERM, safety-checked
monitor kill 1234 5678 --force    # SIGKILL both
monitor kill 1 --yes --json       # still returns a structured refusal

When a human-output kill is refused, the command exits non-zero. With --json it returns a structured refusal instead of acting:

json
{
  "killed": false,
  "refused": true,
  "reason": "protected or system processes cannot be terminated by monitor",
  "protected": true,
  "safety_warnings": ["launchd (pid 1) is a protected system process"]
}

A successful kill reports per-PID results. killed at both the top level and per-PID reflects the verified outcome (terminated), not merely that a signal was sent:

json
{
  "killed": true,
  "results": [
    {"pid": 1234, "killed": true, "outcome": "terminated", "signal": "SIGTERM", "waited_ms": 84},
    {"pid": 5678, "killed": false, "error": "process not found"}
  ]
}

A process that ignores SIGTERM reports still_running with a suggested next action instead of a false killed:true:

json
{
  "killed": false,
  "results": [
    {"pid": 9012, "killed": false, "outcome": "still_running", "signal": "SIGTERM", "waited_ms": 2003,
     "next_action": "process ignored SIGTERM; if termination is required, retry with force (CLI: --force, MCP: force:true) to send SIGKILL"}
  ]
}

Diagnose

analyze

Sample a bounded window and run the same cross-signal process diagnosis engine as the read-only monitor_analyze MCP tool. Without --pid, Monitor considers every process present in the final sample; a PID focuses the report. A healthy JSON result always includes "diagnoses": [] rather than null.

FlagDefaultEffect
--window10sTotal sampling window; greater than zero and at most 60 seconds.
-i, --interval1sDelay between samples; at least 100ms and no longer than the window.
--pid0Focus on one positive PID (0 means all current processes).
--jsonfalseEmit {window, interval, pid?, samples, healthy, diagnoses}.
bash
monitor analyze --window 10s
monitor analyze --pid 1234 --window 15s --json

profile

Capture a process profile. heap, cpu, and goroutine are scraped from the target's net/http/pprof server; sample uses macOS sample. Heap and goroutine profiles are symbolicated; CPU profiles are returned as raw protobuf.

Before scraping the default pprof address, profile checks that the LISTEN socket at --pprof-addr actually belongs to the target pid (via connection enumeration) and refuses otherwise — an unrelated process could be listening on localhost:6060, and monitor won't silently profile the wrong thing. Passing --pprof-addr explicitly asserts you know the endpoint is correct and skips the check; -t sample needs no pprof endpoint at all. A capture that produces no usable data (empty file, or no text/symbols) is also refused rather than reported as a hollow success.

FlagDefaultEffect
-t, --typeheapProfile type: heap, cpu, goroutine, sample.
--pprof-addrlocalhost:6060host:port of the target's pprof server (heap/cpu/goroutine only). Passing this flag explicitly asserts the endpoint belongs to the target pid and skips the ownership check.
--jsonfalseEmit JSON output.
bash
monitor profile 1234 --type heap --json
monitor profile 1234 -t goroutine --pprof-addr localhost:7070 --json
json
{
  "pid": 1234,
  "type": "heap",
  "taken": "2026-06-27T00:02:38Z",
  "symbols": [
    {"func": "main.handler", "file": "main.go", "line": 42}
  ],
  "path": "/tmp/monitor-profile-1234-heap.pb.gz"
}

investigate

Run the seven-step diagnostic pipeline for a process: identify -> snapshot -> profile -> correlate -> semantic -> stash -> issue. It binds the process to a runtime and codebase, captures the best verified profile available, adds bounded code context, saves an integrity-hashed incident bundle with file.cheap when available, and groups the occurrence into a durable local issue.

The profile step is ownership-gated. Node/Bun/Deno processes with an owned --inspect endpoint prefer a bounded CDP CPU profile with file/line frames. For Go profiles, Monitor only trusts the default pprof listener when it is proven to belong to the target PID; macOS sample is the final fallback. Each stage reports {step, status, limitation, recovery} with status one of ok/failed/skipped. The top-level verdict is "complete" when no step failed and "partial" otherwise. Skipped optional correlation can therefore remain honest without making the entire investigation fail. An empty or unverified profile is omitted from the bundle.

FlagDefaultEffect
--ttl7dTTL for the stash (fcheap --ttl).
--no-savefalseSkip the file.cheap stash step; the issue occurrence is still recorded.
--codebaseauto-detectProject root for codemap/vecgrep.
--environmentenvironmentCorrelation environment.
--deployment-idenvironmentCorrelation deployment ID.
--run-idenvironmentCorrelation run ID.
--releaseenvironmentCorrelation release label.
--serviceprocess/environmentCorrelation service name.
--git-shaenvironmentCorrelation Git commit.
--jsonfalseEmit JSON output.
bash
monitor investigate 1234 --json
monitor investigate 1234 --no-save --json   # profile in JSON, nothing stashed
json
{
  "pid": 1234,
  "started_at": "2026-06-27T00:02:38Z",
  "steps": [
    {"step": "identify", "status": "ok"},
    {"step": "snapshot", "status": "ok"},
    {"step": "profile", "status": "ok"},
    {"step": "correlate", "status": "ok"},
    {"step": "semantic", "status": "ok"},
    {"step": "stash", "status": "ok"},
    {"step": "issue", "status": "ok"}
  ],
  "verdict": "complete",
  "profile_method": "inspector_cpu",
  "stash": {"stash_id": "abc123", "path": "fcheap://stash/abc123"},
  "issue": {"id": "ISS-0123456789ABCDEF", "status": "open"},
  "occurrence": {"id": "OCC-...", "evidence_refs": ["fcheap://stash/abc123"]},
  "note": "investigation pipeline complete (profile and stash verified)"
}

Codemap is best-effort: it receives -C <codebase> and only correlates frames or entrypoints with usable file/line positions. Vecgrep first reads its bounded JSON envelope and only trusts hits from an indexed, fresh project. Missing, stale, or warning-only results remain visible in the semantic step rather than being treated as successful evidence.

stash

Manually capture the current system snapshot as an integrity-hashed file.cheap stash and return the stash ID. Useful for "before" states ahead of risky operations, or manual incident triage. The trigger tag is manual. Monitor's tree hash verifies bundle bytes and supports correlation; it does not promise that repeated captures receive the same file.cheap stash ID.

FlagDefaultEffect
--note""Free-form note for downstream search.
--ttl7dTTL for the stash (fcheap --ttl).
--jsonfalseEmit JSON output.
bash
monitor stash --note "before deploy" --json
json
{
  "started_at": "2026-06-27T00:02:38Z",
  "stash": {"stash_id": "fcheap-def456", "path": "/tmp/monitor-incident-..."}
}

incidents

List recent monitor incident stashes (the bundles produced by watch --stash, investigate, and stash). Wraps fcheap list with the monitor-incident tag pre-applied. incident (singular) is an alias for the whole command tree.

FlagDefaultEffect
--jsonfalseEmit JSON output.
bash
monitor incidents
monitor incidents --json
json
[
  {"id": "fcheap-def456", "name": "manual snapshot", "created_at": "2026-06-27T00:02:38Z"}
]

When fcheap archival fails (fcheap missing, disk full, ...), the bundle isn't lost: it's persisted into a durable local registry under $XDG_STATE_HOME/monitor/incidents (falling back to ~/.local/state/monitor/incidents) instead of the ephemeral temp dir the capture started in, and the failed stash/investigate result carries a registry_id you can hand to resume-stash.

incidents pending

List bundles retained in the local registry — the ones still waiting to be archived.

bash
monitor incidents pending --json

incidents resume-stash

Re-attempt fcheap save for a bundle that failed to archive. Accepts a registry ID from incidents pending, a registry entry directory, or a path to a bare retained bundle directory. On success the local copy is removed; on failure the bundle is kept and the attempt (and error) is recorded for the next try.

bash
monitor incidents resume-stash abc123def456
monitor incident resume-stash abc123def456 --json

The registry is a recovery queue, not a permanent archive. It keeps the 20 newest entries, uses private directories (0700) and files (0600), rejects symlink/non-regular bundle entries, and verifies the recorded tree hash before archiving or deleting local evidence. Prefer a registry ID over a raw path.

issues

List and manage recurring observations grouped by Fingerprint V1. issue is an alias. Investigations always attempt to record an occurrence; alerts do so when watch --stash captures their evidence. The default private store is ~/.local/share/monitor/issues.veclite; issues --store <path> overrides it.

bash
monitor issues list --status open --project checkout --service api
monitor issues show ISS-0123456789ABCDEF --occurrences 50 --json
monitor issues resolve ISS-0123456789ABCDEF
monitor issues ignore ISS-0123456789ABCDEF
monitor issues reopen ISS-0123456789ABCDEF

list is newest-first and accepts repeatable --status values (open, resolved, ignored), --project, --service, and --limit (default 50, range 1–200). show returns the issue plus recent occurrences; its --occurrences default is 20 with the same 200 maximum. A later occurrence automatically reopens a resolved issue, while ignored issues keep accumulating occurrences until explicitly reopened. See Local Issues for the Run/Event/Issue/Evidence model and MCP tools.

logs

Manage captured process logs in the durable local veclite store at ~/.local/share/monitor/logs.veclite. --store overrides the location for either subcommand, and MONITOR_LOG_STORE provides a shared environment-wide override (explicit flag wins).

The default writer retains entries for 7 days and caps the collection at 100,000 records, evicting the oldest records first. Searches also hide expired legacy entries and clamp results to 1,000. These durable retention bounds are separate from logs capture --max-lines and --max-bytes, which stop one capture session. The default parent directory is restricted to mode 0700.

logs capture

Ingest log lines into the store. Two modes:

  1. Wrap a new command — everything after -- is passed directly to the executable as exact argv. Monitor does not join arguments or invoke an intermediate shell. Use an explicit sh -c '…' when shell syntax is intentional. stdout+stderr are captured until the process exits.
  2. Tail a running process--pid N shells out to lsof to find the process's open log files (.log, .out, paths under /var/log, …) and tails each from EOF until SIGINT.

Lines are auto-tagged by level: INFO: / [INFO] / WARN: / WARNING: / ERROR: / FATAL: / DEBUG: / TRACE: prefixes are detected; everything else defaults to info (or error for stderr lines without a level).

FlagDefaultEffect
--pid0Tail open log files for the running process (uses lsof).
--max-lines0Stop after N lines (0 = unlimited).
--max-bytes0Stop after N bytes (0 = unlimited).
--name""Override the captured process name in the store.
--process-name""Alias for --name.
--level""Default level for untagged lines; empty = info (or error for stderr).
--store~/.local/share/monitor/logs.vecliteOverride the shared capture/search database.
--jsonfalseEmit JSON output (final result).
bash
monitor logs capture -- sh -c 'echo INFO: hello; echo WARN: bad >&2'
monitor logs capture --pid 1234 --max-lines 500
json
{
  "name": "sh",
  "lines": 2,
  "bytes": 21,
  "duration": "3.2ms",
  "store": "/Users/me/.local/share/monitor/logs.veclite",
  "error": ""
}

Keyword-search the captured log store. The query is optional when filters are enough on their own; results are always newest-first. Exports can preserve the full structured entries (json/ndjson) or replay captured lines (raw).

FlagDefaultEffect
--limit50Max results; values above 1,000 are clamped.
--levelnoneFilter one or more levels; repeat or comma-separate.
--process""Filter by a case-insensitive process-name substring.
--pid0Filter by process ID.
--since0Include only entries this recent, such as 15m or 2h.
--formattextOutput text, json, ndjson, or raw.
--output, -ostdoutWrite an export to a file (- means stdout).
--store~/.local/share/monitor/logs.vecliteOverride the shared capture/search database.
--jsonfalseEmit JSON output.
bash
monitor logs search "error" --json
monitor logs search "timeout" --level error,warn --process api --since 2h
monitor logs search --since 15m --format ndjson -o recent.ndjson
json
[
  {"timestamp": "2026-06-27T00:02:38Z", "pid": 1234, "process": "sh", "message": "WARN: bad", "level": "warn"}
]

history

Record and query persistent metric history. While watch streams live ticks, history persists a small set of scalar series to a local veclite store so you can ask "what was CPU doing an hour ago?" after the fact. Three subcommands: record, query, and list. The store defaults to ~/.local/share/monitor/history.veclite (the directory is created on first use); override it with --db on any subcommand.

Recorded metrics: cpu.usage, mem.usage, mem.pressure, net.recv_bps, net.sent_bps, disk.read_bps, disk.write_bps, and load.1.

history record

Sample the system on an interval and append each tick to the store. Runs until SIGINT (Ctrl-C), then prints the number of ticks recorded to stderr.

FlagDefaultEffect
-i, --interval1sSampling interval.
--db~/.local/share/monitor/history.vecliteHistory store path.
bash
monitor history record                       # sample every second
monitor history record -i 5s --db /tmp/h.veclite

history query

Query a recorded metric over a look-back window. Takes exactly one argument — the metric name — and reports the matching samples plus summary stats (min/avg/p95/max, first/last, and trend = last − first).

FlagDefaultEffect
--since1hLook back this far.
--db~/.local/share/monitor/history.vecliteHistory store path.
--jsonfalseEmit JSON output.
bash
monitor history query cpu.usage --since 30m
monitor history query mem.usage --since 6h --json | jq '.summary.p95'
json
{
  "metric": "cpu.usage",
  "since": "1h0m0s",
  "summary": {
    "count": 3600,
    "min": 4.21,
    "max": 92.7,
    "avg": 27.43,
    "p95": 71.05,
    "first": 25.9,
    "last": 31.2,
    "trend": 5.3,
    "from": "2026-06-26T23:02:38Z",
    "to": "2026-06-27T00:02:38Z"
  },
  "points": [
    {"t": "2026-06-26T23:02:38Z", "v": 25.9},
    {"t": "2026-06-26T23:02:39Z", "v": 26.4}
  ]
}

history list

List the metrics that have been recorded into the store.

FlagDefaultEffect
--db~/.local/share/monitor/history.vecliteHistory store path.
--jsonfalseEmit JSON output.
bash
monitor history list
monitor history list --json
json
["cpu.usage", "disk.read_bps", "disk.write_bps", "load.1", "mem.pressure", "mem.usage", "net.recv_bps", "net.sent_bps"]

baseline

Capture and manage labeled system baselines. A baseline is an inspectable JSON snapshot — overall CPU / memory / load1, every process keyed by PID (name, memory, CPU percent), and the TCP listening sockets — saved to ~/.local/share/monitor/baselines/<name>.json. Pair baselines with diff to answer "what changed?" across a deploy, a restart, or an incident window. Three subcommands: save, list, and delete.

baseline save

Capture the current system as a named baseline. Takes exactly one argument — the baseline name. Listeners are gathered best-effort; some sockets are invisible without elevated privileges.

FlagDefaultEffect
--jsonfalseEmit JSON output.
bash
monitor baseline save pre-deploy
monitor baseline save pre-deploy --json
json
{"saved": "pre-deploy", "processes": 412, "listeners": 37}

baseline list

List the names of saved baselines (sorted).

FlagDefaultEffect
--jsonfalseEmit JSON output.
bash
monitor baseline list
monitor baseline list --json
json
["post-deploy", "pre-deploy"]

baseline delete

Delete a saved baseline. Takes exactly one argument — the baseline name.

bash
monitor baseline delete pre-deploy

diff

Compare a saved baseline against the live system, or against a second baseline. Takes one required argument (the "from" baseline) and an optional second argument (the "to" baseline); with one argument, the live system is captured as the "to" side. Reports new / gone / changed processes, new / gone listening ports, and the shift in CPU / memory / load1.

A process present in both sides is reported as changed only when its memory moved by at least --mem-threshold (in KB). Changed processes are sorted by the largest absolute memory movement first.

FlagDefaultEffect
--mem-threshold1024Minimum per-process memory change to report, in KB.
--jsonfalseEmit JSON output.
bash
monitor diff pre-deploy                 # pre-deploy -> live
monitor diff pre-deploy post-deploy     # pre-deploy -> post-deploy
monitor diff pre-deploy --mem-threshold 8192 --json

The --json output is the Diff struct: cpu_delta / mem_delta are percentage-point shifts, load1_delta is the load-average shift, and each process change carries old_mem / new_mem / mem_delta in bytes.

Deltas that clear both an absolute floor and a relative-change threshold (so a 10MB process doubling doesn't scream, but a 1GB process doubling does) get a verdict: a human- and agent-readable interpretation with a summary, supporting evidence, a confidence level (medium/high — a two-sample diff never earns low, since below-threshold means no verdict at all), and suggested next actions. Verdicts cover total RSS, memory, swap, CPU, load1, disk, process count, and the biggest individual process movers (capped at 3). Human output prints a verdicts: section after the process/listener changes; JSON output carries them under verdicts (omitted entirely when nothing was significant). Baselines saved before this feature carry no swap/disk data, so diffs against them silently skip swap and disk verdicts rather than reporting a false spike.

json
{
  "from": "pre-deploy",
  "to": "live",
  "cpu_delta": 12.4,
  "mem_delta": 3.1,
  "load1_delta": 0.87,
  "new_procs": [
    {"pid": 4821, "name": "myapp", "new_mem": 58720256}
  ],
  "gone_procs": [
    {"pid": 4099, "name": "myapp", "old_mem": 47185920}
  ],
  "changed_procs": [
    {"pid": 1133, "name": "node", "old_mem": 268435456, "new_mem": 402653184, "mem_delta": 134217728}
  ],
  "new_listeners": [
    {"proto": "tcp", "port": 8080, "pid": 4821, "process": "myapp"}
  ],
  "gone_listeners": [
    {"proto": "tcp", "port": 8079, "pid": 4099, "process": "myapp"}
  ]
}

When a delta clears its threshold, each verdicts entry has the same shape as an alert's diagnosis, plus the metric it interprets (and pid for a per-process proc_rss verdict):

json
{
  "verdicts": [
    {
      "metric": "proc_rss",
      "pid": 1133,
      "summary": "node (pid 1133) RSS +50% vs baseline (was 256.0 MB, now 384.0 MB) — investigate",
      "evidence": ["was 256.0 MB, now 384.0 MB (Δ +128.0 MB)", "significant: |Δ| >= 256.0 MB and >= 50% of this process's baseline RSS"],
      "confidence": "medium",
      "next_actions": ["monitor profile 1133 --type heap", "monitor investigate 1133"]
    }
  ]
}

Ecosystem & runtime

config

Inspect and update the settings shared by Studio and process inventory commands. Writes are validated and atomic; setting keys accept hyphens or underscores.

bash
monitor config show --json
monitor config get update-interval
monitor config set update-interval 500ms
monitor config set cpu-alert-threshold 85
monitor config path
monitor config reset --yes

See Configuration for fields and validation rules.

doctor

Print the availability and version of every sibling ecosystem tool. With --json, returns a structured status per tool (available, version, path).

FlagDefaultEffect
--jsonfalseEmit JSON output.
--requirenoneRequire named integrations; repeat or comma-separate values.
--strictfalseRequire every known integration.

Probed tools: codemap, fcheap, vecgrep, tinyvault, vidtrace, glyphrun, cairntrace, veclite, and tmux.

bash
monitor doctor
monitor doctor --json | jq '.fcheap.available'
monitor doctor --require fcheap,codemap
json
{
  "codemap": {
    "available": true,
    "version": "codemap version 0.17.0 (934813e) 2026-06-26T21:43:42Z",
    "path": "/opt/homebrew/bin/codemap"
  },
  "fcheap": {
    "available": true,
    "version": "fcheap version 0.26.2 ...",
    "path": "/opt/homebrew/bin/fcheap"
  }
}

run

Run a glyphrun behavioral spec against monitored services. Takes exactly one argument — the path to a spec — and prints glyphrun's output. The spawned spec sees MONITOR=1 and MONITOR_RUN_DIR.

bash
monitor run specs/version.yml

reload

POST to the /reload endpoint exposed by a running monitor TUI binary (the TUI embeds internal/reload on 127.0.0.1:7351). Useful after logs capture or any workflow that changes data the TUI caches. The endpoint is loopback-only; remote processes cannot reach it. If no TUI is running, reload exits non-zero with a clear error.

FlagDefaultEffect
--addr127.0.0.1:7351Address of the running TUI's /reload endpoint.
--timeout3sHTTP client timeout for the POST.
bash
monitor reload
monitor reload --addr 127.0.0.1:7351 --timeout 5s

mcp

Run an MCP stdio server exposing Monitor's data. The single subcommand, mcp serve, speaks MCP over stdio (newline-delimited JSON-RPC).

bash
monitor mcp serve

The server exposes ten tools — six read-only (monitor_snapshot, monitor_processes, monitor_doctor, monitor_analyze, monitor_issues, monitor_issue) and four mutating (monitor_kill, monitor_profile_capture, monitor_investigate, monitor_record). Every mutating tool requires confirm: true in its typed input; monitor_analyze is read-only and has no confirm gate. See the MCP Server guide for the full tool surface and confirmation model.

vault

Run a command with secrets injected via tinyvault. Wraps the command with tvault run --project <name> so secrets from the named project land in the child process's environment without appearing in the agent's context. Everything after -- is the command and its arguments. Requires the tvault binary on $PATH and a configured tinyvault project.

FlagDefaultEffect
--project""Tinyvault project name (required).
bash
monitor vault --project myapp -- /usr/local/bin/myapp --port 8080
monitor vault --project myapp -- env   # debug: show injected env

studio

Launch the interactive TUI (Bubble Tea v2). All nine tabs are rendered with full keyboard + mouse interactivity. tui is an alias.

bash
monitor studio                  # launch the TUI
monitor tui                     # alias
monitor studio --reload-server  # also expose POST /reload for agents/CI

See The TUI for the tab and keyboard reference.

Released under the MIT License.