Zum Hauptinhalt springen
D

dsh-monitor

daedalforge/dsh-monitor

System panel for DeepSeek Harness: machine, runtime, working repositories and Kubernetes cluster health

Installation

dsh plugin --profile web add github:daedalforge/dsh-monitor

README

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, a 24-minute CPU sparkline and the top processes. Memory follows the Activity Monitor definition (app + wired + compressed) instead of the naive total - 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/stat sample on Linux, the second top -l 2 -s 1 sample on macOS. Linux reads /proc/meminfo, /proc/stat, /proc/swaps and /sys/class/power_supply directly. When Colima is present, its VM disk is measured through colima ssh -- df -kP and 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 costLedger points 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 (git command), 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 .app bundle found in /Applications, its subfolders, /Applications/Utilities and ~/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, last brew update); À traiter groups what needs a decision by cause — a bundle that no longer opens, a cask whose Caskroom version and Info.plist version 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 and by reconciliation state; Homebrew shows the installed formulae and casks, their age and how far the tap is behind FETCH_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 for brew --version, brew list and brew outdated; with it off the view still reads the bundles. Everything is read-only: no brew upgrade, no install, no delete — a command is offered as a copy, not as a button.
  • Kubernetes · contexte — the header carries the context picker: it lists the contexts kubectl declares on the machine plus an automatic entry that follows the current context, and writes the choice straight to k8sContext. 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 projectsRoot does not exist, the cluster sections while kubectl is 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 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 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.

KeyDefaultMeaning
projectsRoot~/projectsDirectory holding the git repositories to watch
showRepostrueShow 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
showK8strueShow the Kubernetes sections
showAppstrueShow the Applications tab (macOS only)
showBrewtrueAsk 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.jsonLedger 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
refreshSeconds60Panel 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:

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

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.

FamilyActions
ProcessSIGTERM then SIGKILL after 5 s — PID 1, system processes, the DSH host and container runtimes are protected
DSHStop a job of the current session, empty ~/.dsh/cache
DiskDocker 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
Kubernetesrollout 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 in engines.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.platform and otherwise read procfs.
  • Optional tools. Each one enables a part of the panel; without it that part is hidden or reads —, never an error:
ToolWithout it
gh, authenticatedpull request columns show —
kubectl, plus docker or colima, plus kindthe Kubernetes tab is hidden
git, with checkouts under projectsRootthe 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. Requests must be local and same-origin; anything else gets a 403.
  • lib/apps.js — the macOS reading: the app folders scanned, each bundle's Info.plist and signature, the Homebrew formulae and casks, and the join that says whether a cask and its bundle agree. Bounded by one brew call batch and a per-bundle plutil, and skipped entirely when Homebrew is switched off.
  • lib/k8s.js — the Kubernetes reading: raw kubectl objects 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 its Machine, Kubernetes, Dépôts and Applications views — styles scoped under .dsh-systeme and .dsh-kube.
  • cordis.patch.yml — inserts the systeme entry.

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 tagVersion
one starts with feat:minor — y plus one, z back to zero
one carries ! before the colon (feat!:, fix!:) or a BREAKING CHANGE: footermajor — 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; ps and df are used on both. Kubernetes inspection only needs kubectl pointing 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 on PATH (docker, podman or nerdctl, with colima reported 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 only casks without an explicit version field — the version :latest ones — 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.

Ähnliche Plugins