dsh-monitorv0.9.0
daedalforge/dsh-monitor
dsh plugin --profile web add github:daedalforge/dsh-monitor#f90e190b40e77636ca68e46d54680a26abecad00
介绍
A System panel for DeepSeek Harness: machine, runtime, working repositories and Kubernetes cluster health.
标签
概述
dsh-monitor
A monitoring plugin for DeepSeek Harness: machine health, runtime state, the git repositories you work in, and the Kubernetes cluster you develop on — from one sidebar entry, without leaving the app.
The Système entry carries four sub-views, switched from the tabs at the top of the panel: Machine answers « is this machine healthy? » in one scroll, Kubernetes answers « what runs in the cluster, what is degraded, and why? », Dépôts answers « what is the state of the git workspace, and what is left to push? », and Applications answers « what is installed on this Mac, and what is out of date? ». The four interfaces are written in French.
What it shows
- Machine — CPU, memory, disk, load average, battery, swap, the top processes and a Processeur card that draws the processor over time. A sample is appended to
cpu.jsonlevery 60 s, whether the panel is open or not, and kept for 24 h; the card reads that file back in one of three windows (1 h, 6 h, 24 h — it opens on 1 h), draws the average of each bucket as one curve, says for every point what the average and the peak were over its measures, sums the window up (current, minimum, average, maximum), and explains itself instead of showing a blank plot when the file holds nothing yet. Memory follows the Activity Monitor definition (app + wired + compressed) instead of the naivetotal - free, which sits at ~99 % as soon as the OS keeps inactive pages. CPU is a delta over a real time window on both platforms: a/proc/statsample on Linux, the secondtop -l 2 -s 1sample on macOS. Linux reads/proc/meminfo,/proc/stat,/proc/swapsand/sys/class/power_supplydirectly. When Colima is present, its VM disk is measured throughcolima ssh -- df -kPand reported separately from the host disk. - DSH — active sessions, running jobs, startup errors over 24 h, cache size, recent sessions, and today's cost when
costLedgerpoints to a JSON ledger. - Dépôts — the git workspace read as a mission board: a banner states the workspace in one sentence (how many repositories are watched, how many carry a remainder, whether a PR is failing); six headline figures (repositories, repositories to watch, dirty repositories and files, behind, mission worktrees, mission PRs); À traiter groups the remainder by cause — a stale clone, uncommitted files, a mission that never left the machine — one card per cause with its evidence (
gitcommand), its scope and the repository it touches; the Dépôts table lists every repository with its branch, state, sync, upstream, last commit and PR, filterable by watch state and by worktrees; Où vit le travail shows the clone and its linked worktrees, one line per checkout with its PR or its « not pushed » flag; Missions en cours joins the mission branches with their PRs; Activité récente sums the last 24 h of commits per checkout; Comprendre les chiffres explains the figures and opens a twelve-term glossary. A repository opens a drawer (branch and upstream, checkouts, pull requests, last commits, changed files, working-tree state, last raw read) and a cause opens its proof. Everything is read-only: the panel never runs a git write, and a command is offered as a copy, not as a button. - Applications (macOS) — the Mac read as an installed estate: every
.appbundle found in/Applications, its subfolders,/Applications/Utilitiesand~/Applications, joined with Homebrew. A banner states the estate in one sentence; six headline figures (applications, bundles managed by Homebrew, known updates, casks out of sync, invalid bundles, lastbrew update); À traiter groups what needs a decision by cause — a bundle that no longer opens, a cask whose Caskroom version andInfo.plistversion disagree, applications outside Homebrew — one card per cause with its evidence; the Applications table lists every bundle with its version, origin (Homebrew cask, direct install, macOS system link), install date and state, filterable by origin, by reconciliation state and by invalid bundles — the Bundles invalides figure is itself the filter: a click narrows the table to the bundles that no longer open, its chip and the pressed figure show the filter is on, and Toutes or a second click brings the whole estate back; with no invalid bundle the figure takes no focus and says why, and a filtered table that holds no line names the absence instead of showing an empty body; the Gérées par Homebrew figure filters the same way on the bundles a cask manages, and its chip and pressed state say so; Homebrew shows the installed formulae and casks, their age and how far the tap is behindFETCH_HEAD; Les casks compares Caskroom with the disk, line by line; Comprendre les chiffres explains each figure and opens a ten-term glossary. A bundle opens a drawer (identifier, signature and team, architecture, size, quarantine, versions on both sides, the two commands the panel ran) and a cause opens its proof. A Homebrew switch in the settings asks forbrew --version,brew listandbrew outdated(the formulae and the casks); with it off the view still reads the bundles and the synchronisation stays shut. Reading itself never writes — the panel installs nothing — and two gestures write. The first is Synchroniser…, in the header of the Homebrew section: it opens a modal that names the commands it will run —brew update,brew upgrade --formula, then onebrew upgrade --cask --greedy <token>per cask — states what it will not do, and runs them only on confirmation. A cask whose bundle on disk is newer than its published recipe is left out of the plan and named, because replacing it would make the bundle go backwards; no cask is ever reinstalled, none is downgraded. The steps run in order and each one gets its verdict: anupdatethat fails stops the upgrades instead of letting them work on a stale tap; an upgrade that fails does not stop the next one. Each brew call runs with its stdin closed and a bounded timeout, so a cask that asks for an administrator password — Cloudflare WARP, for one — fails on its own line in seconds instead of freezing the whole gesture on an invisible prompt. The panel never asks for that password: the receipt names the failed command and offers it to copy, so the cask is finished at the terminal. The modal ends on the receipt — one line per step with its duration, the end of the output it wrote, what is still out of date afterwards — and the whole gesture is onehomebrew/synchroniserentry in the journal. The second gesture is removal — a bundle can now be removed: the bin at the end of its row and « Supprimer… » in the drawer open the same modal, which first states what is about to go. It shows the bundle, the data the panel attributes to the application (caches, HTTP storages, containers, application scripts, logs, saved state, and~/Library/Application Support/<name>when a folder there is named after the app — those lines are tagged « reconnu par le nom »), and the preference plists, one checkbox per group, the size of every line, the destination and the three things the panel will not touch: shared group containers, your personal folders, and/Library/Application Support. The bundle and the checked entries go to the Trash through the Finder, so they can be brought back until the Trash is emptied; macOS asks for the Automation permission on the first removal, and a refusal stops the gesture instead of falling back torm. A Homebrew cask is removed withbrew uninstall --cask, which takes the bundle and the Caskroom entry in the same move. Nothing is removed while the application runs: the modal names the PID and keeps its button shut until the app quits. Two cases stay closed — a bundle macOS owns (the/Applicationssystem links) and a bundle a package receipt claims (pkgutil --file-infonames thepkgid), because removing the bundle would leave the receipt, its daemons and its helpers behind, and a management profile would put the app back at the next check-in. Every removal is written to the journal asapps/supprimer, with the elements, the size and the destination. - Kubernetes · contexte — the header carries the context picker: it lists the contexts
kubectldeclares on the machine plus an automatic entry that follows the current context, and writes the choice straight tok8sContext. Next to it, a Ressources window lists deployments, pods, jobs, statefulsets, daemonsets, cronjobs and unbound volumes with per-namespace filtering and remediation gestures. - Kubernetes · écran — the cluster read as a place to work, not as an inventory. A banner states the cluster in one sentence (how many pods run, on how many nodes, how many secrets are stuck, whether a service is down); six headline figures; À traiter groups what is degraded by cause — one card per cause, carrying its evidence, its scope and the namespaces it touches; Où tourne quoi draws machine → node → four domains (Applicatif, Données, Observabilité, Plateforme) → HTTP entries, one dot per pod; the Namespaces table joins workloads, pods, restarts, secrets and state per namespace; Capacité du nœud compares reservations with allocatable; Composants checks context, the container runtime, kubectl, ingress, CoreDNS, metrics-server and external-secrets; Activité récente retells the last 24 h in sentences; Comprendre les chiffres explains each figure and opens a twelve-term glossary. A namespace opens a drawer (what to watch, pods, workloads, Azure secrets, network) and a cause opens its explanation. Every number is read from Kubernetes; a figure without a source says so instead of showing zero.
- Empty sections hide themselves — the repositories block disappears while
projectsRootdoes not exist, the cluster sections whilekubectlis absent, and the Applications tab on anything but macOS. They come back on their own.
The panel refreshes on open, on window focus, and every 60 s while visible — no background polling. The last snapshot is cached in localStorage and painted immediately on the next open.
/systeme/api/etat always answers with the latest snapshot (stale-while-revalidate) and rebuilds in the background, so the panel never blocks on a slow probe. A snapshot is rebuilt at most every 15 s, with per-section caching for the expensive parts (repositories 3 min, pull requests 60 s, runtime 90 s, cluster 20 s, applications 60 s, machine and Colima disk 15 s). Pull requests are read with gh pr list when gh is installed and authenticated; without it the column stays at « — » and the rest of the view still renders. ?refresh=1 forces a rebuild after a remediation.
The free-space modal reads GET /systeme/api/disque when it opens — docker system df, the Colima filesystems, and the size of the node_modules and artefact folders of the repositories with no commit in 30 days. That reading takes ten seconds and more (docker system df alone costs about seven on a full Docker store), so the modal waits on a loader that names it: the spinner, « Analyse des cibles… », the time it started and the three families it measures, with aria-busy on the dialog; the loader goes as soon as the reading lands, and an error takes the plain banner. Fermer, the close button and the overlay stay live throughout — the analysis is a read, there is nothing to lock.
The machine view also exposes GET /systeme/api/cpu?fenetre=1h|6h|24h: the stored CPU history reduced to about 120 points over the requested window, each point carrying its average, its peak and how many samples it joins, plus the current, minimum, average and maximum of the window and the path of the file that was read. Any other fenetre falls back to 1h. The file is written by the panel's own sampler — one {"at","cpu"} line every 60 s, from the moment the panel starts and whether or not the tab is open — and is rewritten without its expired lines when it is loaded; the snapshot itself no longer carries a CPU history.
The applications view also exposes /systeme/api/app?name=<bundle>, which returns the identifiers, versions on both sides, signature, architecture, size and quarantine of one bundle, each of its sub-bundles, the two commands the panel ran, and a guard against names that leave the scanned folders: the name is resolved against the last snapshot, never against the disk, and an optional path picks one of two bundles that share a name. The snapshot is reused, so a drawer costs a handful of plutil and codesign calls and never a brew call.
The applications view also exposes GET /systeme/api/app/footprint?name=<bundle>&path=<path>, the removal inventory: the bundle, the data and configuration the panel attributes to it — each line with its size, and the nom tag for a folder named after the app —, the shared group containers and system-wide folders it will not touch, whether the application is running, and whether removal is refused (system for a bundle macOS owns, pkg for a bundle a package receipt claims, with the pkgid in the reason). It is read-only and costs one du, one pkgutil and one pgrep. The removal itself is POST /systeme/api/action with {famille: 'apps', action: 'supprimer', name, path, data, config, confirm: true}: the server recomputes the inventory and never takes a path from the client, answers 409 for a running application, 422 for a refused bundle or a Finder that was not granted the Automation permission, and returns the journal entry plus a receipt (elements, bytes, what the Finder and brew handled, what stayed behind).
The applications view exposes GET /systeme/api/homebrew/plan, the synchronisation plan as the panel would run it: the formulae and the cask tokens that are out of date, the casks set aside with their bundle and recipe versions, and the commands in order. It costs no brew call — it reads the snapshot. The gesture itself is POST /systeme/api/action with {famille: 'homebrew', action: 'synchroniser', confirm: true}: the server rebuilds the plan from its own snapshot, never takes a token from the client, answers 409 when there is nothing to upgrade, and returns the journal entry plus a receipt (the plan, one verdict per step with its duration and the tail of its output, the steps that never ran, and the counts left after a fresh reading).
The repositories view also exposes /systeme/api/depot?name=<repository>, which returns the last four commits, the raw git status --porcelain=v2 --branch output and the stashes of one repository; the name is restricted to a direct child of projectsRoot.
Configuration
The plugin is configured on its cordis.patch.yml entry, from a page the package brings to Plugins → Système. Saving the page writes the values into the profile patch (the YAML below), so the configuration stays in the profile's own file.
The page is split in four sections: Général first, with the panel refresh cadence, then Dépôts git, Kubernetes and Applications. The Applications section is only shown on macOS; its first switch hides the tab, its second asks Homebrew (without it the bundle reading still runs). The context picker sits in the header of the Kubernetes view: it lists the contexts kubectl declares on the machine, plus an automatic entry, the default, that follows the current context, and writes the choice straight to k8sContext. The cluster check asks that context for its version and makes no assumption about the cluster's flavour. Each of those last two opens with a switch that hides the matching tab — the switch only changes what the panel shows, the collection keeps running in the background so the view comes back with its current values when switched on again.
| Key | Default | Meaning |
|---|---|---|
projectsRoot | ~/projects | Directory holding the git repositories to watch |
showRepos | true | Show the Dépôts tab |
k8sContext | '' | Watched kubectl context, chosen from the Kubernetes card of the panel among the contexts the machine detects; empty follows the current context |
showK8s | true | Show the Kubernetes sections |
showApps | true | Show the Applications tab (macOS only) |
showBrew | true | Ask Homebrew when the Applications tab is read — brew --version, brew list, brew outdated; off limits the tab to the bundles |
costLedger | ~/.dsh/storages/cost-meter/ledger.json | Ledger written by dsh-cost-meter (days.<YYYY-MM-DD>.cost, .calls, config.currency/config.symbol); empty hides the cost row. Not shown on the page — override it in the YAML only when the ledger lives elsewhere |
refreshSeconds | 60 | Panel refresh cadence, in seconds, clamped between 15 and 900 |
- insert:
- id: systeme
name: '@daedalforge/dsh-monitor'
config:
refreshSeconds: 60
projectsRoot: '~/projects'
showRepos: true
k8sContext: ''
showK8s: true
An empty field clears its key and falls back to the schema default. The panel follows refreshSeconds and the two card switches without a restart: they are volatile values, read at each build of the state.
Thresholds are edited from the panel and stored in ~/.dsh/storages/dsh-monitor/config.json:
| Measure | Attention | Alert |
|---|---|---|
| Free disk (host and Colima VM) | < 20 % | < 10 % |
| Memory used | > 80 % | > 90 % |
| Load | > 1 × cores | > 2 × cores |
| Battery | < 20 % | < 10 % |
| Dirty repository | > 3 days | > 7 days |
| Pod restarts | > 2 | > 5 |
Journal: ~/.dsh/storages/dsh-monitor/journal.jsonl. Last Kubernetes state: ~/.dsh/storages/dsh-monitor/k8s-last.json. CPU history: ~/.dsh/storages/dsh-monitor/cpu.jsonl, one sample per 60 s and 24 h of retention.
Remediations
Every action requires a confirmation naming the target and the measured impact. Nothing runs without confirm: true; the outcome is written to the journal and the panel refreshes.
| Family | Actions |
|---|---|
| Process | SIGTERM then SIGKILL after 5 s — PID 1, system processes, the DSH host and container runtimes are protected |
| DSH | Stop a job of the current session, empty ~/.dsh/cache |
| Disk | Docker prune without volumes, DSH cache, node_modules of repositories with no commit in 30 days, dist/build/.next/target artifacts of those repositories — never a git-tracked file |
| Kubernetes | rollout restart of a deployment, delete a pod, purge a finished job |
No Docker volume is ever removed, no secret is read, no git write is performed, no automatic remediation runs.
Prerequisites
- DeepSeek Harness
>=0.2.1-rc.5 <0.3.0-0. The range sits inengines.dsh, so a host outside it refuses the plugin rather than loading a panel it was never tested against. - The app's own Node runtime, 20 or later. Nothing else is installed for the panel to work.
- macOS or Linux. Windows is not supported: the machine collectors branch on
process.platformand otherwise read procfs. - Optional tools. Each one enables a part of the panel; without it that part is hidden or reads
—, never an error:
| Tool | Without it |
|---|---|
gh, authenticated | pull request columns show — |
kubectl, plus docker or colima, plus kind | the Kubernetes tab is hidden |
git, with checkouts under projectsRoot | the Dépôts tab is hidden |
- No configuration file is required: the defaults (
~/projects, a 60 s refresh, local same-origin requests only) apply as is.
Install
dsh plugin --profile desktop add @daedalforge/dsh-monitor
Restart the application completely (Cmd+Q) — a new server route is not picked up by a client reload.
From a tarball or a git checkout:
dsh plugin --profile desktop add file:/path/to/daedalforge-dsh-monitor-<version>.tgz
Uninstall:
dsh plugin --profile desktop remove @daedalforge/dsh-monitor
Files under ~/.dsh/storages/dsh-monitor/ stay in place; delete them by hand if you want a clean slate.
Architecture
lib/index.js— server side: collectors, thresholds, actions, journal. Routes/systeme/api/etat,/systeme/api/disque,/systeme/api/action,/systeme/api/journal,/systeme/api/reglages,/systeme/api/app,/systeme/api/app/footprint,/systeme/api/cpu. Requests must be local and same-origin; anything else gets a 403.lib/apps.js— the macOS reading: the app folders scanned, each bundle'sInfo.plistand signature, the Homebrew formulae and casks, and the join that says whether a cask and its bundle agree. Bounded by onebrewcall batch and a per-bundleplutil, and skipped entirely when Homebrew is switched off.lib/k8s.js— the Kubernetes reading: rawkubectlobjects in, one model out (counters, capacity, causes, namespace rows, topology, components, activity). Pure and unit-tested, so the panel logic is verified without a live cluster.lib/client.js— the React panel loaded by the DSH client —Système, with itsMachine,Kubernetes,DépôtsandApplicationsviews — styles scoped under.dsh-systemeand.dsh-kube.cordis.patch.yml— inserts thesystemeentry.
Releases
The version is never set by hand. Every push to main reads the commits since the last vX.Y.Z tag and takes the highest bump their messages ask for:
| Commit messages since the last tag | Version |
|---|---|
one starts with feat: | minor — y plus one, z back to zero |
one carries ! before the colon (feat!:, fix!:) or a BREAKING CHANGE: footer | major — x plus one, y and z back to zero |
anything else (fix:, chore:, docs:, a plain sentence) | patch — z plus one |
It writes that version into package.json with a commit created through the GitHub API, so GitHub signs it; tags the commit; and opens a GitHub release. The release commit is skipped by the same trigger, so the workflow never loops. With no tag yet, the version sitting in package.json is released as is — that is the first release.
Opening the release is what publishes: a second workflow runs on release: published, checks the tag against package.json, runs the tests, publishes to npm with provenance, and attaches the tarball to the release. Publishing a release by hand from the GitHub interface therefore works the same way.
The release workflow writes to main, which a branch ruleset guards with a pull request and a verified signature. It does so under a dedicated GitHub App, whose installation token it mints with actions/create-github-app-token; that app sits on the bypass list of both rulesets, so the version commit, the tag and the release land without weakening the rules. Two secrets carry the identity: APP_ID, and APP_PRIVATE_KEY holding the app's private key. The release must be opened with that app token and not with GITHUB_TOKEN, or the Publish workflow would never run.
Trusted publishing only adds a version to a package that already exists; it cannot create the package. The very first version therefore has to be pushed by a person holding the npm account, with two-factor authentication enabled — a security key or an authenticator app, which npm asks for in the browser. From the tagged commit:
npm login
npm publish --access public
That first version is the only one without provenance. Everything after it goes through the workflow, which authenticates through npm trusted publishing: no token is stored in the repository, the workflow file is the credential.
npm trust github @daedalforge/dsh-monitor --repo daedalforge/dsh-monitor --file publish.yml --allow-publish
Run that once the package exists. The trust configuration lives inside the package, so deleting the package deletes it too — including the placeholder npm creates on its own while a staged publish is pending, and removes again when that staged publish is rejected.
A publish that fails can be replayed from the Actions tab, with the tag to publish as input.
Limits
- Machine metrics use macOS tools (
vm_stat,sysctl,pmset,top) or procfs/sysfs on Linux;psanddfare used on both. Kubernetes inspection only needskubectlpointing at a reachable context: liveness is the API's own version endpoint, so no local runtime and no particular cluster flavour is required. The runtime drawn in the topology is the first one found onPATH(docker,podmanornerdctl, withcolimareported when present); the cluster itself is never created, started or stopped by the panel. The plugin reads nodes, pods, deployments, statefulsets, daemonsets, jobs, cronjobs, namespaces, PVCs, services, ingresses and external secrets, plus Warning events, and needs no metrics-server: reservations come from the pod specs, not from measured usage. - The Applications view reads what macOS and Homebrew expose. App Store applications are out of scope, a bundle pushed by an MDM is read like any other bundle — the panel does not know a management console's version and never compares against one, and it refuses to remove a bundle a package receipt claims rather than pretending to know which console owns it — and only casks without an explicit
versionfield — theversion :latestones — are waited on: the others update through their own updater. - The panel reads the disk, the repositories and the installed applications; it never modifies a git-tracked file and never runs a package manager.
- Stopping a job is only available for the current session.
License
MIT — see LICENSE.
星级评价
基本信息
插件信息来自公开 GitHub 仓库与 SkillHub,版权归原作者所有;安装前请自行审查仓库代码。