{"id":"k8s","repo":"jeroenfrenken/paseo-k8s","url":"https://github.com/jeroenfrenken/paseo-k8s","name":"k8s","description":"Kubernetes workloads, logs, GitOps and a kubectl command bar, as a Paseo plugin","categories":["monitoring","automation"],"platforms":[],"caveats":[],"images":[],"themes":[],"health":{"manifestValid":true,"hasReadme":true,"hasLicense":false,"hasTests":true,"hasTypecheckScript":true,"updatedRecently":true},"scannedAt":"2026-10-15T12:30:09.608Z","addedAt":"2026-09-10T18:09:18+03:00","version":"0.1.0","security":{"status":"passed","blockingFindings":0,"advisoryFindings":0,"scannedAt":"2026-10-15T12:14:24.405Z","commit":"f4c7cf5cee700dd8ad4080aeb43b9952c8cddb6d"},"paseoVersionRequirement":">=0.8.0","descriptionNodes":[{"type":"text","text":"Kubernetes workloads, logs, GitOps and a kubectl command bar, as a Paseo plugin"}],"caveatNodes":[],"manifest":{"id":"k8s","requirements":{"paseo":">=0.8.0"}},"repoMeta":{"stars":1,"defaultBranch":"main","pushedAt":"2026-09-24T07:25:20Z"},"owner":{"login":"jeroenfrenken","avatarUrl":"https://avatars.githubusercontent.com/u/16974549?v=4"},"installNotesHtml":"<pre><code class=\"language-sh\">paseo plugin add jeroenfrenken/paseo-k8s\n</code></pre>\n<blockquote>\n<p><strong>Requires Paseo >= 0.8.0.</strong> v0.8 split plugins into separate client and server\nruntime entries; this plugin is migrated to that layout and declares\n<code>requirements.paseo</code> in its manifest, so an older Paseo will refuse to load it.\nThe last version that ran on 0.7.x is tagged <code>v0.7-final</code>.</p>\n</blockquote>\n<p>Or from a local clone:</p>\n<pre><code class=\"language-sh\">git clone git@github.com:jeroenfrenken/paseo-k8s.git\ncd paseo-k8s &#x26;&#x26; npm install\npaseo plugin install \"$PWD\"\n</code></pre>\n<p>Once installed, pick it up later with:</p>\n<pre><code class=\"language-sh\">paseo plugin status k8s     # is there a newer commit?\npaseo plugin update k8s     # pull it in\npaseo plugin logs k8s       # what the plugin printed\n</code></pre>\n<p>Nothing is configured out of the box: on first run the panel offers any kubeconfig\nit discovers, checks which command-line tools are present, and takes you to\nSettings to add a cluster.</p>","readmeText":"# Kubernetes panel (Paseo plugin)\n\nAdds a **Kubernetes** item to the Paseo sidebar — its own global surface, below the\nbuilt-in items, not attached to a workspace or agent chat. Add as many clusters as\nyou like and switch between them from the header.\n\n## Install\n\n```sh\npaseo plugin add jeroenfrenken/paseo-k8s\n```\n\n> **Requires Paseo >= 0.8.0.** v0.8 split plugins into separate client and server\n> runtime entries; this plugin is migrated to that layout and declares\n> `requirements.paseo` in its manifest, so an older Paseo will refuse to load it.\n> The last version that ran on 0.7.x is tagged `v0.7-final`.\n\nOr from a local clone:\n\n```sh\ngit clone git@github.com:jeroenfrenken/paseo-k8s.git\ncd paseo-k8s && npm install\npaseo plugin install \"$PWD\"\n```\n\nOnce installed, pick it up later with:\n\n```sh\npaseo plugin status k8s     # is there a newer commit?\npaseo plugin update k8s     # pull it in\npaseo plugin logs k8s       # what the plugin printed\n```\n\nNothing is configured out of the box: on first run the panel offers any kubeconfig\nit discovers, checks which command-line tools are present, and takes you to\nSettings to add a cluster.\n\n## What it shows\n\nA Lens-style workspace built inside the one surface the host gives a plugin.\n\n**Header** — a cluster dropdown naming the current cluster and its context, then\nrefresh, auto-refresh, Shell and Settings. Production clusters carry a red\n`PRODUCTION` badge. Below it, the live context and API server.\n\n**Tabs** — open as many resource views as you like. `+` opens a full picker showing\nevery view with its live count; `×` closes a tab. The **namespace dropdown** sits on\nthe same line as the tabs, on the right, and is searchable.\n\n**First run** — with no cluster configured yet, the panel opens on a setup screen\nthat lists any kubeconfig it discovered, checks which command-line tools are\ninstalled, and links straight into Settings.\n\n## Command-line tools\n\nNothing needs to be installed to browse a cluster: that path talks to the API\nserver over HTTPS directly. The setup screen and **Settings → Command bar** both\nshow what is present and what each one unlocks:\n\n| Tool | | Unlocks |\n|---|---|---|\n| `kubectl` | recommended | The command bar, and the Flux reconcile/suspend buttons |\n| `git` | optional | Comparing the deployed revision to a local Flux checkout |\n| `helm` | optional | Running helm from the command bar |\n| `flux` | optional | Nothing the panel needs — Flux state is read from the API. Only for running `flux` in the command bar |\n\nMissing tools show an install hint rather than blocking anything.\n\n**Settings** — its own screen with a sidebar:\n\n- **Clusters** — add and remove as many as you like. There is no fixed set of\n  environments: each cluster is a name, a kubeconfig, a context, a default\n  namespace, and a *production* flag that turns it red everywhere. Ids are slugged\n  from the name and kept unique. Each has a connection test.\n- **Command bar** — how commands run, the allowed programs, and the timeout.\n- **GitOps** — the Flux repo path.\n- **Config file** — where all of this is stored, and how to point at another file.\n\n**List view** — a dense sortable table per tab. Click any column header to sort\n(again to reverse). A search box filters as you type across name, namespace, status,\nnode and image. Live **CPU and memory** columns come from metrics-server; they are\nhidden automatically when it is not installed. The footer shows matched/total counts.\n\n**Detail drawer** — click a row. Opens on the right (stacked below on narrow\nwindows) with stat tiles, a replica or capacity meter, full properties, the owning\nworkload's pods, and one-click actions that push a prefilled command into the shell\n(`describe`, `get -o yaml`, `rollout status`, `top`, `events`).\n\n**Bottom dock** — resizable by dragging its handle, collapsible, with its own tabs:\n\n- **Log tabs** — one per pod, several open at once. 100/500/2000 line tail, container\n  picker on multi-container pods, a **Previous** toggle on pods that have restarted\n  (reads the terminated container), **Follow** for 5s polling, and a search box that\n  filters the lines. Timestamps render as wall-clock time.\n- **Shell tab** — type kubectl commands with `KUBECONFIG` already pointed at the\n  selected environment. Up/down arrows walk history. One status line shows the\n  cluster, namespace and what may run; everything configurable lives in Settings,\n  reachable from the ⚙ in the tab's own header. See *Command bar* below.\n\n**Overview tab** — replica availability as the headline figure, stat tiles, a\npods-by-phase bar, and per-node CPU/memory pressure meters.\n\n**Flux tab** — see *GitOps* below.\n\nHealth is always carried by a glyph and a word as well as a colour\n(`● Healthy`, `▲ Progressing`, `◆ Degraded`, `■ Down`, `○ Idle`), so nothing depends\non hue alone.\n\n## GitOps (Flux)\n\nThe **Flux** tab reads the cluster's Flux state directly — it is cluster-wide and\ndeliberately ignores the namespace filter.\n\n- **Deployed revision** as the headline: the git revision every Kustomization has\n  applied, e.g. `main@1a2b3c4`.\n- **Local comparison.** If a checkout of the GitOps repo is found, the panel says\n  whether your branch is in sync or lists the commits the cluster has not applied\n  yet. It is strictly read-only: it never fetches and never touches the working\n  tree. If the deployed commit is not in your clone it says so and suggests\n  `git fetch` rather than running one.\n- **Sources, Kustomizations, Helm releases** with ready / failed / suspended state,\n  the path or chart each came from, applied revision, and the condition message.\n- **Image automation** — ImagePolicies with the newest image they resolve to. When\n  that differs from what the workload is actually running, the row is flagged\n  **update pending** with `running … → latest …`.\n- **Actions** on any row: **Reconcile**, and **Suspend**/**Resume** for the kinds\n  that support it.\n\nActions run through `kubectl` on the command-bar path rather than a raw API PATCH,\nso they inherit the allowlist and show up as a normal command. `Reconcile` only sets\nthe standard `reconcile.fluxcd.io/requestedAt` annotation; suspend/resume patch\n`spec.suspend`. Nothing else is ever written.\n\nThe repo is found at `PASEO_K8S_FLUX_REPO`, then `fluxRepoPath` in clusters.json,\nthen `~/flux`. Saving clusters from the UI preserves `fluxRepoPath`.\n\n## Attaching cluster context to an agent\n\nThe plugin registers a **Kubernetes** composer attachment source. In an agent's\ncomposer, attach → Kubernetes → search, and the picked resource arrives as context:\n\n- **A pod** — phase and reason, container readiness, restarts, node, CPU/memory,\n  its recent warning events, and its last 80 log lines.\n- **A workload** — replica counts, health, condition message, images, every pod with\n  its status, the warning events across them, **and a log tail from each of up to four\n  of its pods**, so asking about \"the api pods\" gets all of them rather\n  than one. Running pods are preferred over finished ones (completed Jobs and\n  migrations sort last).\n\nBoth environments are searched at once and each result is labelled Staging or\nProduction. Unhealthy resources are ranked first, since a broken pod is usually the\none you meant to ask about. Cluster listings are cached for 20s so typing in the\npicker does not hammer the API; logs are fetched live for the results actually shown.\n\n## Launching an agent from a resource\n\nSelecting a pod, workload or node puts an **✦ Ask an agent** button at the top of\nthe detail drawer. It opens an inline panel with an editable instruction, a project\npicker and a model picker, and starts a new Paseo agent whose opening prompt is your\ninstruction followed by the same live context bundle the attachment picker builds —\nstatus, events and log tail for a pod; replicas, images, pods and events for a\nworkload; pressure and scheduled pods for a node.\n\n**A fresh workspace is always created**, never reused. Pick the project it belongs\nto and whether it is a **Worktree** (branch-off, the default) or a plain **Local**\ndirectory. Non-git projects only offer Local, and the panel says why.\n\nThe agent is created inside that new workspace and tagged with labels\n`source=k8s-plugin`, `environment` and `resource`, so agents started this way are\neasy to find later.\n\nThe panel reads the daemon's provider catalogue, so **Agent** lists every provider\nthat is enabled and available (Claude, Codex, OpenCode, Cursor, …) and **Model**\nlists that provider's models, with its default preselected. Providers that report\nno models are left out rather than offered as dead entries.\n\n## Command bar\n\nThe shell tab runs commands on the daemon with `KUBECONFIG` set to the selected\nenvironment's kubeconfig. It is **not** a PTY — the host's plugin API exposes no\nterminal, so `kubectl exec -it` and anything else needing a TTY will not work. One\ncommand in, its output back.\n\nThe shell tab is only a shell: you type, it runs. *How* it runs is decided once in\n**Settings → Command bar**, which offers two clearly described modes:\n\n- **Allowlist only** (default, recommended) — the first word must be one of the\n  programs you listed (`kubectl`, `helm`, `kustomize` out of the box). The line is\n  split into arguments directly, without a shell, so quoting tricks and injection\n  cannot happen. Pipes, redirects and wildcards are refused, with a message saying\n  which setting to change.\n- **Full bash** — the whole line goes to `bash -lc`. Pipes, redirects and wildcards\n  work and anything on your PATH can run, including things that delete. Marked\n  *less safe* in Settings, and the shell tab's header says **▲ Full bash** while it\n  is active.\n\nThe shell header always states the current mode and links straight to the setting.\nOutput is capped at 400 KB per stream.\n\nFlux actions ignore this setting entirely: they are fixed `kubectl` calls, pinned to\nallowlist mode with `kubectl` as the only permitted program.\n\n## Configuration\n\nThe panel talks to the Kubernetes API directly over HTTPS using a kubeconfig per\nenvironment. Open **Clusters** in the header to set them.\n\nConfig lives at `~/.config/paseo-k8s/clusters.json`:\n\n```json\n{\n  \"environments\": [\n    {\n      \"id\": \"staging\",\n      \"label\": \"Staging\",\n      \"kubeconfig\": \"/home/you/.config/kubernetes-mcp/staging.kubeconfig\",\n      \"context\": null,\n      \"namespace\": null\n    },\n    {\n      \"id\": \"prod\",\n      \"label\": \"Production\",\n      \"kubeconfig\": \"/home/you/.config/kubernetes-mcp/prod.kubeconfig\",\n      \"context\": null,\n      \"namespace\": \"app\"\n    }\n  ]\n}\n```\n\n- `kubeconfig` — path to a kubeconfig file. `~` is expanded.\n- `context` — which context inside that file; `null` uses `current-context`.\n  The Clusters editor lists the contexts it finds in the file so you can pick one.\n- `namespace` — the namespace the panel opens on; `null` means all namespaces.\n\n**Pointing at a file elsewhere.** The Clusters section has a *Use a config file\nelsewhere* field: give it a path to another `clusters.json` (or a directory\ncontaining one) and the panel reads and writes that file from then on. The pointer\nis stored in `~/.config/paseo-k8s/pointer.json`; *Use default* clears it.\n`PASEO_K8S_CONFIG` overrides both.\n\n**Before any config exists** the panel discovers kubeconfigs on its own: it reads\n`kubeconfig = \"...\"` out of `~/.config/kubernetes-mcp/{staging,prod}.toml` (the\n`kubernetes-mcp-server` configs behind the `k8s-staging` / `k8s-prod` MCP servers),\nthen falls back to `~/.config/kubernetes-mcp/<env>.kubeconfig` and conventionally\nnamed files under `~/.kube`. So on a machine that already has the k8s MCP servers\nset up, it works with no configuration at all.\n\n### Credentials\n\nSupported: bearer `token`, `tokenFile`, client certificate (`client-certificate-data`\n/ `client-key-data` or their file forms), basic auth, and `exec` credential plugins\n(`client.authentication.k8s.io`, e.g. `aws eks get-token`). Exec credentials are\nresolved before requests, with concurrent requests sharing one plugin invocation.\nCredentials are cached until a minute before their stated expiry or until an API\nrequest returns HTTP 401; the next request then runs the plugin again. Without an\nexpiry, credentials remain cached until HTTP 401 or a daemon restart.\nThe plugin receives `KUBERNETES_EXEC_INFO` using its configured `v1` or `v1beta1`\nAPI version, with `interactive: false` and cluster information when requested.\nPlugins requiring `interactiveMode: Always` cannot run in the daemon. `certificate-authority-data`, `tls-server-name` and\n`insecure-skip-tls-verify` are honoured.\n\n`auth-provider` (OIDC and friends) is **not** supported — the panel cannot drive\nthose exchanges. Use a kubeconfig with a token, client certificate, or exec\ncredential plugin instead.\n\nEverything the panel does is a read (`GET` only), including `pods/log`. Kubernetes RBAC on the credential\nremains the authorization boundary; if the credential cannot list a resource the\npanel reports it under *Partial data* and shows the rest.\n\n## Development\n\n```sh\nnpm install\nnpm run check          # typecheck + client/server boundary + regression tests\npaseo plugin reload k8s\npaseo plugin logs k8s\n```\n\n`reload` picks up edits when the plugin was installed from a directory. If it was\ninstalled with `paseo plugin add`, it runs from a checkout under `~/.paseo/plugins`\ninstead — commit, push, then `paseo plugin update k8s`.\n\nLayout:\n\n| File | Runs on | Purpose |\n|---|---|---|\n| `index.client.tsx` | app | client entry: surface, sidebar, attachment source, command-center items |\n| `index.server.ts` | daemon | server entry: every RPC handler |\n| `shared/contracts.ts` | both | zod RPC contracts and shared types |\n| `server/config.ts` | daemon | clusters.json read/write, pointer file, kubeconfig discovery |\n| `server/kubeconfig.ts` | daemon | kubeconfig → connection (server, TLS material, credentials) |\n| `server/yaml.ts` | daemon | minimal YAML reader for the kubeconfig subset |\n| `server/k8s-api.ts` | daemon | HTTPS GET against the API server, resource types |\n| `server/collect.ts` | daemon | fetches and shapes the snapshot: workloads, pods, nodes, metrics, events |\n| `server/flux.ts` | daemon | Flux resources, the git comparison, and reconcile/suspend actions |\n| `server/attach.ts` | daemon | composer attachment search and the context bundles it returns |\n| `server/agent.ts` | daemon | workspace/model discovery and agent creation via PaseoApi |\n| `server/node-context.ts` | daemon | node context bundle |\n| `server/exec.ts` | daemon | the command runner behind the shell tab |\n| `client/main.tsx` | app | surface shell: header, tabs, split layout, dock |\n| `client/list.tsx` | app | sortable, searchable resource table |\n| `client/detail.tsx` | app | the detail drawer |\n| `client/flux.tsx` | app | the Flux tab |\n| `client/launch.tsx` | app | the \"Ask an agent\" panel |\n| `client/dock.tsx` | app | resizable dock, log tabs, shell tab |\n| `client/settings.tsx` | app | the Settings screen and its sidebar |\n| `client/chooser.tsx` | app | the `+` tab picker and the first-run screen |\n| `client/ui.tsx` | app | shared primitives (chips, buttons, tiles, meters, tabs) |\n| `client/theme.ts` | app | status palette, tokens, formatters |\n\n**The directories are load-bearing.** Paseo compiles the two entries separately and\nrefuses to pull a `server/` module into the app bundle, a `client/` module into the\ndaemon bundle, or any `node:` builtin into the app bundle. `shared/` lands in both,\nso it must never import `node:` builtins. `npm run check` enforces all of that\nlocally, before the plugin is loaded.\n"}