paseo-plugin-machine-status 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dutchakdev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,367 @@
1
+ <h1 align="center">paseo-plugin-machine-status</h1>
2
+
3
+ <p align="center">
4
+ What the machine behind your agents is actually doing, inside <a href="https://paseo.sh">Paseo</a>.<br>
5
+ Load, memory, disk and battery; what is listening on the network and which agent started it;<br>
6
+ Docker; and the caches and runaway processes you can clear without leaving the app.
7
+ </p>
8
+
9
+ <p align="center">
10
+ <img src="https://img.shields.io/badge/Paseo-0.8.x%20%E2%80%93%200.11.x-3987e5" alt="Supports Paseo 0.8.x through 0.11.x, including betas">
11
+ <img src="https://img.shields.io/badge/macOS-only-9aa1a6" alt="macOS only">
12
+ <img src="https://img.shields.io/badge/tests-237-199e70" alt="237 tests">
13
+ <img src="https://img.shields.io/badge/dependencies-none-c98500" alt="No runtime dependencies">
14
+ <img src="https://img.shields.io/badge/license-MIT-9aa1a6" alt="MIT licensed">
15
+ </p>
16
+
17
+ <p align="center">
18
+ <img src="docs/preview.svg" alt="The dashboard: KPI tiles, a load history chart, memory breakdown and disk usage" width="920">
19
+ </p>
20
+
21
+ <p align="center"><sub>Generated by <code>npm run preview</code> from the plugin&apos;s own palette, thresholds and chart geometry, so the picture cannot drift from the code.</sub></p>
22
+
23
+ One sidebar item, three tabs, and a pill beside the message box.
24
+
25
+ > Requires macOS. Metrics come from `vm_stat`, `sysctl`, `df`, `pmset` and
26
+ > `memory_pressure`. On any other platform the panel reports that it cannot
27
+ > collect metrics rather than showing wrong numbers.
28
+
29
+ ## Install
30
+
31
+ ```bash
32
+ paseo plugin install npm:paseo-plugin-machine-status # Paseo 0.9 and newer
33
+ paseo plugin ls # expect: running
34
+ ```
35
+
36
+ Paseo 0.8 has no npm sources, so install from Git there:
37
+ `paseo plugin add dutchakdev/paseo-plugin-machine-status`. Either way,
38
+ `paseo plugin update machine-status` pulls later releases. The plugin supports **Paseo 0.8.x through 0.11.x, including betas**,
39
+ on both the daemon, which runs its collectors, and the app, which runs its screens.
40
+
41
+ The manifest remains `>=0.8.0`, which already admits 0.9.x, 0.10.x and 0.11.x. Paseo also checks a
42
+ prerelease's stable version core, so `0.11.0-beta.3` satisfies this range; see
43
+ [version requirements](https://paseo.sh/docs/plugins/reference#requirements).
44
+
45
+ To work on it locally instead:
46
+
47
+ ```bash
48
+ git clone https://github.com/dutchakdev/paseo-plugin-machine-status.git
49
+ cd paseo-plugin-machine-status
50
+ npm install
51
+ npm run typecheck && npm test
52
+ paseo plugin install "$PWD"
53
+ paseo plugin ls # expect: running
54
+ ```
55
+
56
+ Plugins must be enabled daemon-wide first, under **Settings → Plugins → Enable
57
+ plugins** (the root `pluginsEnabled` field in `~/.paseo/config.json`).
58
+
59
+ Open it from the **Machine** sidebar item, or ⌘K → "Machine status".
60
+
61
+ ## The screen
62
+
63
+ ```mermaid
64
+ flowchart LR
65
+ subgraph client["Paseo app — client/"]
66
+ S["status: KPI tiles · charts · cleanup · processes"]
67
+ V["charts: bars, stacks, meters (Views only)"]
68
+ end
69
+ subgraph shared["shared/"]
70
+ Z["Zod contracts"]
71
+ P["validated palette + geometry"]
72
+ end
73
+ subgraph server["Daemon subprocess — server/"]
74
+ M["metrics + rolling history"]
75
+ C["cleanup"]
76
+ R["processes"]
77
+ end
78
+ OS["vm_stat · df · pmset · ps · du · fs.rm · kill"]
79
+
80
+ S --> V --> P
81
+ S -- "useRpc(contract, input)" --> Z
82
+ Z -- "validated both ways" --> M & C & R
83
+ M & C & R --> OS
84
+ ```
85
+
86
+ **Overview** — top to bottom: a header with a live indicator and a **Pause**
87
+ control, a row of four stat tiles (CPU, memory, swap, disk) that expand into
88
+ detail when tapped, CPU-over-time and memory-over-time charts, a disk/swap/power
89
+ card, the dev-cache scanner, and the process table.
90
+
91
+ **Ports & services** — every listening socket on the left, and what is running on
92
+ the right. Only the visible tab is mounted, so the port collection does not run
93
+ while you are looking at the overview.
94
+
95
+ ## The pill above the composer
96
+
97
+ The dashboard answers "what is this machine doing" when you go and ask. The pill
98
+ answers it without being asked: two percentages sitting beside the message box,
99
+ CPU load and memory pressure, its icon coloured by whichever of the two is worse.
100
+
101
+ Those are the two numbers that move while an agent works, and they fail in
102
+ different ways — one saturates, the other swaps — so both are shown. The colour
103
+ follows the worse one, because a pill has room for a single colour and you need
104
+ to know something is wrong before you need to know which one. A number that
105
+ cannot be read shows as `—` rather than as zero.
106
+
107
+ One poll every ten seconds feeds every open agent's pill at once. Paseo draws
108
+ the label, so the numbers reach each pill through its `update`; the icon is the
109
+ plugin's own and takes the warning colour when either reading turns serious.
110
+ Pressing the pill opens a compact card instead of leaving the chat: both
111
+ readings with their recent trend and their severity in words, the three
112
+ processes using the most CPU right now, and any development ports held by an
113
+ agent or by a process an agent started. **Open Machine** at the bottom leads to
114
+ the full tab. On a phone the card is a bottom sheet.
115
+
116
+ ## Reading the dashboard
117
+
118
+ **CPU** shows load average, not utilisation. The bar treats one runnable thread
119
+ per core as 100%, so a sustained bar above full width means work is queueing.
120
+
121
+ **Memory** follows Activity Monitor's split rather than "free vs used":
122
+
123
+ | Row | Meaning |
124
+ | --- | ------- |
125
+ | App | Anonymous pages minus purgeable — what your programs actually hold |
126
+ | Wired | Kernel memory that cannot be paged out |
127
+ | Compressed | RAM reclaimed by compressing inactive pages |
128
+ | Cached files | File-backed pages, reclaimed automatically when needed |
129
+
130
+ The pressure figure is macOS's own, read from `memory_pressure -Q`, not a value
131
+ this plugin invents. Cached files are deliberately excluded from "Used": the
132
+ kernel gives them back on demand, so counting them as consumed is misleading.
133
+
134
+ **Thermal** reads "Not reported" on a healthy machine — macOS records a level only
135
+ once it has throttled something. That is honest rather than a failure.
136
+
137
+ **Per-core utilisation is absent** on purpose. It needs `host_processor_info`, a
138
+ native call macOS does not expose through any unprivileged shell tool, so the CPU
139
+ detail says so instead of estimating.
140
+
141
+ ## History and the charts
142
+
143
+ History accumulates **as a side effect of being read** — there is no background
144
+ timer. A plugin that sampled forever would keep the machine awake in order to
145
+ report on how busy the machine is. The cost is that history only covers the time
146
+ someone was watching, which is why every chart caption reports the real span
147
+ between its first and last sample ("last 4 min") instead of claiming a fixed
148
+ window. The buffer holds 90 samples and lives in the daemon subprocess, so
149
+ closing and reopening the tab does not lose it.
150
+
151
+ React Native has no SVG, and a Paseo client bundle may import only `react`,
152
+ `react-native`, `@tanstack/react-query`, `zod` and `@getpaseo/plugin`. Every chart
153
+ here is therefore built from `View`s: lines and areas are a column per sample, and
154
+ the disk ring is a **meter**. The meter is not a consolation prize — for a single
155
+ ratio against a limit it is the better form, and a donut comparing close values is
156
+ a documented anti-pattern.
157
+
158
+ ### Colour
159
+
160
+ Chrome (backgrounds, text, borders) comes from Paseo's six theme tokens. Series
161
+ colour does not: a categorical hue has to stay the same hue across themes or it
162
+ stops identifying anything. Paseo never says which theme is active, so the plugin
163
+ measures the relative luminance of `surface0` and picks the matching column.
164
+
165
+ Both columns are the reference categorical order and were checked with the
166
+ validator against their own surface — not chosen by eye:
167
+
168
+ | Check | Dark | Light |
169
+ | ----- | ---- | ----- |
170
+ | Worst adjacent CVD ΔE | 8.4 | 9.1 |
171
+ | Worst adjacent normal-vision ΔE | 19.8 | 22.9 |
172
+ | Contrast vs surface | all ≥ 3:1 | aqua and yellow under 3:1 |
173
+
174
+ The light-mode shortfall is covered by the relief rule: every series carries a
175
+ visible label and value, so nothing is identified by colour alone. Status colours
176
+ (warning, critical) are a separate fixed set and always ship beside a word —
177
+ `High`, `Critical` — never as hue on its own.
178
+
179
+ ## Ports & services
180
+
181
+ ### Where the port list comes from
182
+
183
+ `netstat` sees every listener on the machine; `lsof` sees only what this user may
184
+ inspect. On this machine that is 56 sockets against 39. So `netstat` defines the
185
+ list and `lsof` fills in the process — a root-owned port is reported as belonging
186
+ to another user rather than quietly dropped, and the count of those appears as a
187
+ note under the service list.
188
+
189
+ The dot beside each port is computed from the bind address, not guessed from the
190
+ port number: a wildcard bind (`*`, `0.0.0.0`, `::`) is reachable from the network,
191
+ a loopback bind is not, and a bind to one interface address is neither.
192
+
193
+ One process bound to the same port on IPv4 and IPv6 is shown once — that is one
194
+ service, and `kubectl port-forward` produces the pair every time. Sockets with no
195
+ visible owner are never merged, because without a PID there is no evidence they
196
+ belong together.
197
+
198
+ ### Development ports only, by default
199
+
200
+ Most of what a Mac listens on has nothing to do with development. On this machine
201
+ the split is 30 development ports against 44 system ones: AirPlay on 5000 and
202
+ 7000, Remote Desktop on 3283, Handoff, SMB, Kerberos, and a long tail of
203
+ ephemeral UDP. The list shows development ports and keeps the rest behind a
204
+ **Show system** toggle with its count — classified, never discarded.
205
+
206
+ Relevance is decided by evidence, because port ranges cannot separate these
207
+ cases: 7000 here is AirPlay, while 49165 is a coding agent.
208
+
209
+ | Rule | Result |
210
+ | ---- | ------ |
211
+ | Attributed to an agent, an agent's child, or a container | development |
212
+ | Owner not inspectable (root, another account) | system |
213
+ | Executable under `/System/`, `/usr/libexec/`, `/usr/sbin/`, `/Library/Apple/` | system |
214
+ | A known runtime — node, bun, python, kubectl, next-server, postgres, … | development |
215
+ | Anything else under `$HOME`, except `~/Library/` | development |
216
+ | Otherwise | system |
217
+
218
+ The executable path comes from `ps`, not from `lsof`, whose command column is a
219
+ bare name: the path is what separates `/System/Library/…/ControlCenter` from
220
+ `~/dev/analytics-tool/.venv/bin/python`.
221
+
222
+ ### Attributing background processes to agents
223
+
224
+ Coding agents leave things running. A single `claude` agent on this machine had
225
+ three MCP servers alive under it — `@playwright/mcp`, `chrome-devtools-mcp` and
226
+ `prisma mcp` — none of which is obvious from a process list.
227
+
228
+ Matching agents by name does not work. A substring search for the providers
229
+ Paseo supports finds `AMPDeviceDiscoveryAgent`, `Campo` and `CursorUIViewService`
230
+ on a stock macOS install. The signal used instead is structural:
231
+
232
+ 1. Paseo starts each agent as a **direct child of the daemon**, and the plugin's
233
+ own parent *is* the daemon — so `process.ppid` gives the root for free.
234
+ 2. Each daemon child is confirmed against `paseo.agents.list()`: the binary must
235
+ match the provider **as a whole word**, and the process's `cwd` (read through
236
+ `lsof -d cwd`) must match the agent's. Anything else under the daemon —
237
+ `esbuild`, Paseo's helpers, this plugin — is left out.
238
+ 3. `cwd` also tells two agents of the same provider apart, which is the ordinary
239
+ case when the same model runs in two workspaces.
240
+ 4. Everything below a confirmed agent is its background process.
241
+ 5. A listening socket is attributed by walking its owner's parents until an agent
242
+ root appears — so a dev server three levels down still names the agent that
243
+ started it.
244
+
245
+ If `paseo.agents.list()` is unavailable or changes shape, the panel says
246
+ attribution is off and still lists the ports. It never falls back to guessing by
247
+ name.
248
+
249
+ ## Docker
250
+
251
+ The third tab manages the container engine and what runs on it.
252
+
253
+ **The engine itself.** The card at the top separates three states, because they
254
+ need different offers: running shows the server version and a guarded `Quit`,
255
+ stopped shows a **Start** button, and missing says which app to install and shows
256
+ no button at all. Starting is a launch rather than a daemon command — Docker
257
+ Desktop brings up its own VM over tens of seconds — so the panel polls every 3
258
+ seconds instead of 10 until the daemon answers.
259
+
260
+ **Containers.** Right-click any row for logs, `Open http://127.0.0.1:<port>` for
261
+ each published port, restart, stop, start and remove. Destructive actions confirm
262
+ in a second menu.
263
+
264
+ **Disk.** `docker system df` broken out by images, containers, volumes and build
265
+ cache, each with what it reclaims. Two prune actions are offered and both are
266
+ narrow on purpose:
267
+
268
+ - stopped containers
269
+ - **dangling** images only, never `-a`, which would delete every image without a
270
+ running container and cost gigabytes of re-pulling
271
+
272
+ Volumes are never offered. They are usually the largest reclaimable number on a
273
+ development machine and they hold container data, so the panel labels that number
274
+ `but this is container data` and gives it no button.
275
+
276
+ ## Quick actions
277
+
278
+ Both actions are two-step. The first press states what will happen and what it
279
+ costs; the second carries it out. An armed button disarms itself after six
280
+ seconds so it cannot be triggered later by a stray tap.
281
+
282
+ ### Dev caches
283
+
284
+ Scanning walks the directories with `du`, so it runs only when asked. Every
285
+ candidate is a cache its own tool recreates; clearing one costs a re-download or a
286
+ rebuild, never data.
287
+
288
+ | Target | Path | Cost of clearing |
289
+ | ------ | ---- | ---------------- |
290
+ | npm cache | `~/.npm/_cacache` | Re-download on next install |
291
+ | pnpm store | `~/Library/pnpm/store` | Rebuilt on next install |
292
+ | Yarn cache | `~/Library/Caches/Yarn` | Re-download on next install |
293
+ | Bun install cache | `~/.bun/install/cache` | Re-download on next install |
294
+ | Turbo cache | `~/.cache/turbo` | Next run rebuilds instead of replaying |
295
+ | Xcode DerivedData | `~/Library/Developer/Xcode/DerivedData` | Next build is a full rebuild |
296
+ | Cargo registry cache | `~/.cargo/registry/cache` | Re-download on next build |
297
+ | Go build cache | `~/Library/Caches/go-build` | Next build is a full rebuild |
298
+ | pip cache | `~/Library/Caches/pip` | Re-download on next install |
299
+ | uv cache | `~/.cache/uv` | Re-download on next sync |
300
+
301
+ `~/Library/Caches` as a whole is deliberately absent: it also holds application
302
+ state that does not regenerate.
303
+
304
+ Three guards stand between the plugin and `fs.rm`:
305
+
306
+ 1. Only paths in the table above can ever be touched — the plugin takes a target
307
+ id, never a path from the client.
308
+ 2. Each path is resolved through `realpath` and must land at least two levels
309
+ below the real home directory. A symlink planted inside a cache cannot
310
+ redirect the deletion outside it.
311
+ 3. Only the directory's contents are removed; the directory itself stays, because
312
+ several tools expect it to exist.
313
+
314
+ ### Top processes
315
+
316
+ The list refreshes every five seconds and pauses while a process is selected — a
317
+ list that reorders under your finger is how the wrong PID gets terminated. Each
318
+ row carries a CPU bar scaled to the heaviest process on screen.
319
+
320
+ Paseo and its parent processes are greyed out and marked `protected`. The plugin
321
+ walks its own parent chain to build that set and recomputes it from a fresh `ps`
322
+ on every kill, so a stale or forged PID from the client cannot get through.
323
+
324
+ SIGTERM is sent first and the result is reported honestly: a process that ignores
325
+ it is reported as still running, and force kill becomes a separate, separately
326
+ confirmed step.
327
+
328
+ ## Development
329
+
330
+ ```bash
331
+ npm run typecheck
332
+ npm test # 237 tests
333
+ paseo plugin reload machine-status # source edits need an explicit reload
334
+ paseo plugin logs machine-status
335
+ npm run preview # regenerates docs/preview.svg from the real palette
336
+ ```
337
+
338
+ Tests cover the command-output parsers against captured real output, the path and
339
+ PID guards, the rolling history buffer, the palette and chart geometry, the
340
+ port parsers against captured `netstat`/`lsof`/`docker` output, the agent-matching
341
+ rules including the real false positives above, and a
342
+ round-trip of every handler result through its own Zod contract — the drift that
343
+ would otherwise surface as an RPC rejection at runtime. The live tests never
344
+ mutate anything: cleanup is only planned, and the kill tests assert refusals,
345
+ including a refusal to terminate the test runner itself.
346
+
347
+ The SDK remains pinned to 0.8.0 as the development baseline for typechecking and
348
+ tests. Paseo supplies the runtime instances on every supported release line, so
349
+ none of it ships with the plugin.
350
+
351
+ ## Troubleshooting
352
+
353
+ | Symptom | Check |
354
+ | ------- | ----- |
355
+ | Sidebar item missing | `paseo plugin ls` reports `running`, and the client is on this host |
356
+ | "Metrics unavailable" | The daemon is not on macOS, or a `/usr/bin` tool is missing |
357
+ | Charts look empty | History starts empty; it fills as the tab stays open |
358
+ | A port shows "another user" | It belongs to root or another account; `lsof` cannot inspect it unprivileged |
359
+ | No agent attribution | `paseo.agents.list()` failed — the note under the service list says so |
360
+ | A port you expected is missing | It was classified as a system port; use **Show system** |
361
+ | Docker containers missing | Docker is not installed or not running; the note says so |
362
+ | A cache shows "not present" | The tool is not installed, or it moved its cache |
363
+ | Edits do not appear | `npm run typecheck`, then `paseo plugin reload machine-status` |
364
+
365
+ ## License
366
+
367
+ MIT